]> git.sesse.net Git - kdenlive/commitdiff
Merge branch 'master' into audioAlign
authorJean-Baptiste Mardelle <jb@kdenlive.org>
Thu, 16 Feb 2012 19:52:42 +0000 (20:52 +0100)
committerJean-Baptiste Mardelle <jb@kdenlive.org>
Thu, 16 Feb 2012 19:52:42 +0000 (20:52 +0100)
35 files changed:
.gitignore
CMakeLists.txt
mindmap.svg
src/CMakeLists.txt
src/clipitem.h
src/clipmanager.cpp
src/commands/moveclipcommand.cpp
src/commands/moveclipcommand.h
src/customtrackview.cpp
src/customtrackview.h
src/definitions.cpp [new file with mode: 0644]
src/definitions.h
src/gentime.cpp
src/gentime.h
src/lib/CMakeLists.txt [new file with mode: 0644]
src/lib/audio/CMakeLists.txt [new file with mode: 0644]
src/lib/audio/audioCorrelation.cpp [new file with mode: 0644]
src/lib/audio/audioCorrelation.h [new file with mode: 0644]
src/lib/audio/audioCorrelationInfo.cpp [new file with mode: 0644]
src/lib/audio/audioCorrelationInfo.h [new file with mode: 0644]
src/lib/audio/audioEnvelope.cpp [new file with mode: 0644]
src/lib/audio/audioEnvelope.h [new file with mode: 0644]
src/lib/audio/audioInfo.cpp [new file with mode: 0644]
src/lib/audio/audioInfo.h [new file with mode: 0644]
src/lib/audio/audioStreamInfo.cpp [new file with mode: 0644]
src/lib/audio/audioStreamInfo.h [new file with mode: 0644]
src/lib/qtimerWithTime.cpp [new file with mode: 0644]
src/lib/qtimerWithTime.h [new file with mode: 0644]
src/mainwindow.cpp
src/mainwindow.h
src/renderer.cpp
src/statusbarmessagelabel.cpp
src/statusbarmessagelabel.h
testingArea/CMakeLists.txt [new file with mode: 0644]
testingArea/audioOffset.cpp [new file with mode: 0644]

index 378eac25d311703f3f2cd456d8036da525cd0366..f444b5458a8bc2ea69a5b4b8cdb74fba293687e4 100644 (file)
@@ -1 +1,3 @@
+*~
 build
+CMakeLists.txt.user
index 45aeff186491e71aa8851783b2b980aa3d265d18..d2a161b4240a57b7f2b831000c382e4ea44f8ee5 100644 (file)
@@ -88,6 +88,7 @@ add_subdirectory(renderer)
 add_subdirectory(src)
 add_subdirectory(thumbnailer)
 add_subdirectory(titles)
+add_subdirectory(testingArea)
 
 macro_display_feature_log()
 
index 87792da8eb3a62fe3c0fbe38f75624215f9b6626..72c00c3def6ca8380a46bf2ead3d5c3d7fcdeb69 100644 (file)
@@ -14,8 +14,8 @@
    height="6765.7144"
    id="svg2"
    version="1.1"
-   inkscape:version="0.48+devel r"
-   sodipodi:docname="kdenlive-mindmap.svg"
+   inkscape:version="0.48.1 r9760"
+   sodipodi:docname="mindmap.svg"
    inkscape:export-filename="/data/cworkspace/kdenlive.git/kdenlive/kdenlive-mindmap.png"
    inkscape:export-xdpi="90.003799"
    inkscape:export-ydpi="90.003799">
      borderopacity="1.0"
      inkscape:pageopacity="0"
      inkscape:pageshadow="2"
-     inkscape:zoom="0.0875"
-     inkscape:cx="9315.434"
-     inkscape:cy="2634.062"
+     inkscape:zoom="0.49497474"
+     inkscape:cx="5281.8526"
+     inkscape:cy="535.67899"
      inkscape:document-units="px"
      inkscape:current-layer="layer3"
      showgrid="false"
-     inkscape:window-width="1680"
-     inkscape:window-height="999"
-     inkscape:window-x="-2"
-     inkscape:window-y="0"
+     inkscape:window-width="1366"
+     inkscape:window-height="709"
+     inkscape:window-x="-3"
+     inkscape:window-y="-3"
      inkscape:window-maximized="1"
      inkscape:showpageshadow="false"
      showborder="true" />
          x="-3640.9109"
          y="2973.2024"
          id="tspan529">http://en.wikipedia.org/wiki/Safe_area</tspan></text>
+    <text
+       sodipodi:linespacing="125%"
+       id="text3486"
+       y="-176.01756"
+       x="-1375.8613"
+       style="font-size:24px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-indent:0;text-align:start;text-decoration:none;line-height:125%;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:start;color:#000000;fill:#eeeeee;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;font-family:DejaVu Sans;-inkscape-font-specification:DejaVu Sans"
+       xml:space="preserve"><tspan
+         id="tspan3490"
+         y="-176.01756"
+         x="-1375.8613"
+         sodipodi:role="line">Reversed videos</tspan></text>
+    <use
+       x="0"
+       y="0"
+       xlink:href="#path3261"
+       id="use3494"
+       transform="translate(4657.1529,-4583.293)"
+       width="14148.571"
+       height="6765.7144" />
+    <text
+       sodipodi:linespacing="125%"
+       id="text3489"
+       y="4072.3269"
+       x="-1186.6422"
+       style="font-size:24px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-indent:0;text-align:start;text-decoration:none;line-height:125%;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:start;color:#000000;fill:#eeeeee;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;font-family:DejaVu Sans;-inkscape-font-specification:DejaVu Sans"
+       xml:space="preserve"><tspan
+         id="tspan3493"
+         y="4072.3269"
+         x="-1186.6422"
+         sodipodi:role="line">Automatic synchronisation</tspan><tspan
+         y="4102.3271"
+         x="-1186.6422"
+         sodipodi:role="line"
+         id="tspan3497">for additional audio (portable recorders etc.)</tspan><tspan
+         y="4132.3267"
+         x="-1186.6422"
+         sodipodi:role="line"
+         id="tspan3499">http://jeff.ecchi.ca/blog/2011/07/25/automated-multicamera-clip-syncing/</tspan></text>
   </g>
 </svg>
index 6b3197b83d038f5d0fed003b56274688bb9f77f8..d26352fca094c8ac30c6724102951272f0be43b6 100644 (file)
@@ -83,10 +83,12 @@ add_subdirectory(beziercurve)
 add_subdirectory(colorcorrection)
 add_subdirectory(colorscopes)
 add_subdirectory(commands)
+add_subdirectory(lib)
 add_subdirectory(projecttree)
 add_subdirectory(utils)
 add_subdirectory(databackup)
 add_subdirectory(kiss_fft)
+add_subdirectory(lib)
 add_subdirectory(mimetypes)
 add_subdirectory(onmonitoritems)
 add_subdirectory(simplekeyframes)
@@ -131,6 +133,7 @@ list(APPEND kdenlive_SRCS
   customruler.cpp
   customtrackscene.cpp
   customtrackview.cpp
+  definitions.cpp
   docclipbase.cpp
   documentchecker.cpp
   documentvalidator.cpp
index 5629743fee8b328384b946f714a5a8d157c63a15..c28709938f444eb41f03de68cb51930b9f0d31e4 100644 (file)
@@ -39,7 +39,7 @@ class Transition;
 namespace Mlt
 {
 class Producer;
-};
+}
 
 class ClipItem : public AbstractClipItem
 {
index af4522a6cdeb76c9dfc48d460195e920ee85a7ea..608087f4470c1c2d11186786051f4921324c4af3 100644 (file)
@@ -305,7 +305,7 @@ void ClipManager::slotGetAudioThumbs()
             producer.seek(z);
             mlt_frame = producer.get_frame();
             if (mlt_frame && mlt_frame->is_valid()) {
-                int samples = mlt_sample_calculator(framesPerSecond, frequency, mlt_frame_get_position(mlt_frame->get_frame()));
+                int samples = mlt_sample_calculator(framesPerSecond, frequency, mlt_frame->get_position());
                 qint16* pcm = static_cast<qint16*>(mlt_frame->get_audio(audioFormat, frequency, channels, samples));
                 for (int c = 0; c < channels; c++) {
                     QByteArray audioArray;
index 659c09faad669e31687fc9f1ad0a0f6dbeb88828..b590b97dcb4f69239df46fe507c174a2f14d04ce 100644 (file)
@@ -1,5 +1,6 @@
 /***************************************************************************
  *   Copyright (C) 2007 by Jean-Baptiste Mardelle (jb@kdenlive.org)        *
+ *                 2012    Simon A. Eugster <simon.eu@gmail.com>           *
  *                                                                         *
  *   This program is free software; you can redistribute it and/or modify  *
  *   it under the terms of the GNU General Public License as published by  *
 #include <KLocale>
 
 MoveClipCommand::MoveClipCommand(CustomTrackView *view, const ItemInfo start, const ItemInfo end, bool doIt, QUndoCommand * parent) :
-        QUndoCommand(parent),
-        m_view(view),
-        m_startPos(start),
-        m_endPos(end),
-        m_doIt(doIt)
+    QUndoCommand(parent),
+    m_view(view),
+    m_startPos(start),
+    m_endPos(end),
+    m_doIt(doIt),
+    m_success(true)
 {
     setText(i18n("Move clip"));
     if (parent) {
@@ -38,19 +40,22 @@ MoveClipCommand::MoveClipCommand(CustomTrackView *view, const ItemInfo start, co
 }
 
 
-// virtual
 void MoveClipCommand::undo()
 {
-// kDebug()<<"----  undoing action";
     m_doIt = true;
-    m_view->moveClip(m_endPos, m_startPos, m_refresh);
+    // We can only undo what was done;
+    // if moveClip() failed in redo() the document does (or should) not change.
+    if (m_success) {
+        m_view->moveClip(m_endPos, m_startPos, m_refresh);
+    }
 }
-// virtual
 void MoveClipCommand::redo()
 {
-    //kDebug() << "----  redoing action";
-    if (m_doIt)
-        m_view->moveClip(m_startPos, m_endPos, m_refresh);
+    if (m_doIt) {
+        qDebug() << "Executing move clip command. End now:" << m_endPos;
+        m_success = m_view->moveClip(m_startPos, m_endPos, m_refresh, &m_endPos);
+        qDebug() << "Move clip command executed. End now: " << m_endPos;
+    }
     m_doIt = true;
 }
 
index 5b7d99ffecac95d1495be7a3dba530be8fd19886..3ab20cb22dd3e5b6f33f014580e234abbac23a33 100644 (file)
@@ -1,5 +1,6 @@
 /***************************************************************************
  *   Copyright (C) 2007 by Jean-Baptiste Mardelle (jb@kdenlive.org)        *
+ *                 2012    Simon A. Eugster <simon.eu@gmail.com>           *
  *                                                                         *
  *   This program is free software; you can redistribute it and/or modify  *
  *   it under the terms of the GNU General Public License as published by  *
@@ -37,9 +38,10 @@ public:
 private:
     CustomTrackView *m_view;
     const ItemInfo m_startPos;
-    const ItemInfo m_endPos;
+    ItemInfo m_endPos;
     bool m_doIt;
     bool m_refresh;
+    bool m_success;
 };
 
 #endif
index cc75b20f515da8153416e123f9eac89b9ccfe2e0..1a0a8ebb1acdc8c70459fae14ea6e5877b12ad7b 100644 (file)
@@ -62,6 +62,9 @@
 #include "commands/razorgroupcommand.h"
 #include "profilesdialog.h"
 
+#include "lib/audio/audioEnvelope.h"
+#include "lib/audio/audioCorrelation.h"
+
 #include <KDebug>
 #include <KLocale>
 #include <KUrl>
@@ -98,38 +101,41 @@ bool sortGuidesList(const Guide *g1 , const Guide *g2)
 // const int duration = animate ? 1500 : 1;
 
 CustomTrackView::CustomTrackView(KdenliveDoc *doc, CustomTrackScene* projectscene, QWidget *parent) :
-        QGraphicsView(projectscene, parent),
-        m_tracksHeight(KdenliveSettings::trackheight()),
-        m_projectDuration(0),
-        m_cursorPos(0),
-        m_document(doc),
-        m_scene(projectscene),
-        m_cursorLine(NULL),
-        m_operationMode(NONE),
-        m_moveOpMode(NONE),
-        m_dragItem(NULL),
-        m_dragGuide(NULL),
-        m_visualTip(NULL),
-        m_animation(NULL),
-        m_clickPoint(),
-        m_autoScroll(KdenliveSettings::autoscroll()),
-        m_pasteEffectsAction(NULL),
-        m_ungroupAction(NULL),
-        m_scrollOffset(0),
-        m_clipDrag(false),
-        m_findIndex(0),
-        m_tool(SELECTTOOL),
-        m_copiedItems(),
-        m_menuPosition(),
-        m_blockRefresh(false),
-        m_selectionGroup(NULL),
-        m_selectedTrack(0),
-        m_controlModifier(false)
-{
-    if (doc)
+    QGraphicsView(projectscene, parent),
+    m_tracksHeight(KdenliveSettings::trackheight()),
+    m_projectDuration(0),
+    m_cursorPos(0),
+    m_document(doc),
+    m_scene(projectscene),
+    m_cursorLine(NULL),
+    m_operationMode(NONE),
+    m_moveOpMode(NONE),
+    m_dragItem(NULL),
+    m_dragGuide(NULL),
+    m_visualTip(NULL),
+    m_animation(NULL),
+    m_clickPoint(),
+    m_autoScroll(KdenliveSettings::autoscroll()),
+    m_pasteEffectsAction(NULL),
+    m_ungroupAction(NULL),
+    m_scrollOffset(0),
+    m_clipDrag(false),
+    m_findIndex(0),
+    m_tool(SELECTTOOL),
+    m_copiedItems(),
+    m_menuPosition(),
+    m_blockRefresh(false),
+    m_selectionGroup(NULL),
+    m_selectedTrack(0),
+    m_audioCorrelator(NULL),
+    m_audioAlignmentReference(NULL),
+    m_controlModifier(false)
+{
+    if (doc) {
         m_commandStack = doc->commandStack();
-    else
+    } else {
         m_commandStack = NULL;
+    }
     m_ct = 0;
     setMouseTracking(true);
     setAcceptDrops(true);
@@ -2475,6 +2481,10 @@ void CustomTrackView::dropEvent(QDropEvent * event)
         }
         event->setDropAction(Qt::MoveAction);
         event->accept();
+
+        /// \todo enable when really working
+//        alignAudio();
+
     } else QGraphicsView::dropEvent(event);
     setFocus();
 }
@@ -4282,18 +4292,24 @@ Transition *CustomTrackView::getTransitionItemAtStart(GenTime pos, int track)
     return clip;
 }
 
-void CustomTrackView::moveClip(const ItemInfo &start, const ItemInfo &end, bool refresh)
+bool CustomTrackView::moveClip(const ItemInfo &start, const ItemInfo &end, bool refresh, ItemInfo *out_actualEnd)
 {
     if (m_selectionGroup) resetSelectionGroup(false);
     ClipItem *item = getClipItemAt((int) start.startPos.frames(m_document->fps()), start.track);
     if (!item) {
         emit displayMessage(i18n("Cannot move clip at time: %1 on track %2", m_document->timecode().getTimecodeFromFrames(start.startPos.frames(m_document->fps())), start.track), ErrorMessage);
         kDebug() << "----------------  ERROR, CANNOT find clip to move at.. ";
-        return;
+        return false;
     }
     Mlt::Producer *prod = item->getProducer(end.track);
 
-    bool success = m_document->renderer()->mltMoveClip((int)(m_document->tracksCount() - start.track), (int)(m_document->tracksCount() - end.track), (int) start.startPos.frames(m_document->fps()), (int)end.startPos.frames(m_document->fps()), prod);
+    qDebug() << "Moving item " << (long)item << " from .. to:";
+    qDebug().maybeSpace() << item->info();
+    qDebug() << start;
+    qDebug() << end;
+    bool success = m_document->renderer()->mltMoveClip((int)(m_document->tracksCount() - start.track), (int)(m_document->tracksCount() - end.track),
+                                                       (int) start.startPos.frames(m_document->fps()), (int)end.startPos.frames(m_document->fps()),
+                                                       prod);
     if (success) {
         bool snap = KdenliveSettings::snaptopoints();
         KdenliveSettings::setSnaptopoints(false);
@@ -4323,6 +4339,12 @@ void CustomTrackView::moveClip(const ItemInfo &start, const ItemInfo &end, bool
         emit displayMessage(i18n("Cannot move clip to position %1", m_document->timecode().getTimecodeFromFrames(end.startPos.frames(m_document->fps()))), ErrorMessage);
     }
     if (refresh) m_document->renderer()->doRefresh();
+    if (out_actualEnd != NULL) {
+        *out_actualEnd = item->info();
+        qDebug() << "Actual end position updated:" << *out_actualEnd;
+    }
+    qDebug() << item->info();
+    return success;
 }
 
 void CustomTrackView::moveGroup(QList <ItemInfo> startClip, QList <ItemInfo> startTransition, const GenTime &offset, const int trackOffset, bool reverseMove)
@@ -5943,6 +5965,97 @@ void CustomTrackView::splitAudio()
     }
 }
 
+void CustomTrackView::setAudioAlignReference()
+{
+    QList<QGraphicsItem *> selection = scene()->selectedItems();
+    if (selection.isEmpty() || selection.size() > 1) {
+        emit displayMessage(i18n("You must select exactly one clip for the audio reference."), ErrorMessage);
+        return;
+    }
+    if (m_audioCorrelator != NULL) {
+        delete m_audioCorrelator;
+    }
+    if (selection.at(0)->type() == AVWIDGET) {
+        ClipItem *clip = static_cast<ClipItem*>(selection.at(0));
+        if (clip->clipType() == AV || clip->clipType() == AUDIO) {
+            m_audioAlignmentReference = clip;
+
+            AudioEnvelope *envelope = new AudioEnvelope(clip->getProducer(clip->track()));
+            m_audioCorrelator = new AudioCorrelation(envelope);
+
+            envelope->drawEnvelope().save("kdenlive-audio-reference-envelope.png");
+            envelope->dumpInfo();
+
+            emit displayMessage(i18n("Audio align reference set."), InformationMessage);
+        }
+        return;
+    }
+    emit displayMessage(i18n("Reference for audio alignment must contain audio data."), ErrorMessage);
+}
+
+void CustomTrackView::alignAudio()
+{
+    bool referenceOK = true;
+    if (m_audioCorrelator == NULL) {
+        referenceOK = false;
+    }
+    if (referenceOK) {
+        if (!scene()->items().contains(m_audioAlignmentReference)) {
+            // The reference item has been deleted from the timeline (or so)
+            referenceOK = false;
+        }
+    }
+    if (!referenceOK) {
+        emit displayMessage(i18n("Audio alignment reference not yet set."), DefaultMessage);
+        return;
+    }
+
+    int counter = 0;
+    QList<QGraphicsItem *> selection = scene()->selectedItems();
+    foreach (QGraphicsItem *item, selection) {
+        if (item->type() == AVWIDGET) {
+            ClipItem *clip = static_cast<ClipItem*>(item);
+            if (clip->clipType() == AV || clip->clipType() == AUDIO) {
+                AudioEnvelope *envelope = new AudioEnvelope(clip->getProducer(clip->track()));
+                int index = m_audioCorrelator->addChild(envelope);
+                int shift = m_audioCorrelator->getShift(index);
+                counter++;
+
+                m_audioCorrelator->info(index)->toImage().save("kdenlive-audio-align-cross-correlation.png");
+                envelope->drawEnvelope().save("kdenlive-audio-align-envelope.png");
+                envelope->dumpInfo();
+
+                int targetPos = m_audioAlignmentReference->startPos().frames(m_document->fps()) + shift;
+                qDebug() << "Reference starts at " << m_audioAlignmentReference->startPos().frames(m_document->fps());
+                qDebug() << "We will start at " << targetPos;
+                qDebug() << "to shift by " << shift;
+                qDebug() << "(eventually)";
+                qDebug() << "(maybe)";
+
+                GenTime add(shift, m_document->fps());
+                ItemInfo start = clip->info();
+                ItemInfo end = clip->info();
+                end.startPos = m_audioAlignmentReference->info().startPos + add;
+                end.endPos = m_audioAlignmentReference->info().startPos + add + clip->info().cropDuration;
+
+                QUndoCommand *moveCommand = new QUndoCommand();
+                moveCommand->setText(i18n("Auto-align clip"));
+                new MoveClipCommand(this, start, end, true, moveCommand);
+//                moveClip(start, end, true);
+                updateTrackDuration(clip->track(), moveCommand);
+                m_commandStack->push(moveCommand);
+
+            }
+        }
+    }
+
+    if (counter == 0) {
+        emit displayMessage(i18n("No audio clips selected."), ErrorMessage);
+    } else {
+        emit displayMessage(i18n("Auto-aligned %1 clips.").arg(counter), InformationMessage);
+    }
+}
+
 void CustomTrackView::doSplitAudio(const GenTime &pos, int track, EffectsList effects, bool split)
 {
     ClipItem *clip = getClipItemAt(pos, track);
@@ -6026,8 +6139,15 @@ void CustomTrackView::doSplitAudio(const GenTime &pos, int track, EffectsList ef
                 deleteClip(clp->info());
                 clip->setVideoOnly(false);
                 Mlt::Tractor *tractor = m_document->renderer()->lockService();
-                if (!m_document->renderer()->mltUpdateClipProducer(tractor, m_document->tracksCount() - info.track, info.startPos.frames(m_document->fps()), clip->baseClip()->getProducer(info.track))) {
-                    emit displayMessage(i18n("Cannot update clip (time: %1, track: %2)", info.startPos.frames(m_document->fps()), info.track), ErrorMessage);
+                if (!m_document->renderer()->mltUpdateClipProducer(
+                            tractor,
+                            m_document->tracksCount() - info.track,
+                            info.startPos.frames(m_document->fps()),
+                            clip->baseClip()->getProducer(info.track))) {
+
+                    emit displayMessage(i18n("Cannot update clip (time: %1, track: %2)",
+                                             info.startPos.frames(m_document->fps()), info.track),
+                                        ErrorMessage);
                 }
                 m_document->renderer()->unlockService(tractor);
 
index c5c62f9a0624b3fbb6c6403cb71ea31e52027abb..66ee744b8a791d0a67854e7e5a2d6b7145933cee 100644 (file)
@@ -42,6 +42,7 @@ class ClipItem;
 class AbstractClipItem;
 class AbstractGroupItem;
 class Transition;
+class AudioCorrelation;
 
 class CustomTrackView : public QGraphicsView
 {
@@ -60,7 +61,13 @@ public:
     void configTracks(QList <TrackInfo> trackInfos);
     int cursorPos();
     void checkAutoScroll();
-    void moveClip(const ItemInfo &start, const ItemInfo &end, bool refresh);
+    /**
+      Move the clip at \c start to \c end.
+
+      If \c out_actualEnd is not NULL, it will be set to the position the clip really ended up at.
+      For example, attempting to move a clip to t = -1 s will actually move it to t = 0 s.
+      */
+    bool moveClip(const ItemInfo &start, const ItemInfo &end, bool refresh, ItemInfo *out_actualEnd = NULL);
     void moveGroup(QList <ItemInfo> startClip, QList <ItemInfo> startTransition, const GenTime &offset, const int trackOffset, bool reverseMove = false);
     /** move transition, startPos = (old start, old end), endPos = (new start, new end) */
     void moveTransition(const ItemInfo &start, const ItemInfo &end, bool refresh);
@@ -138,6 +145,12 @@ public:
     /** @brief Creates SplitAudioCommands for selected clips. */
     void splitAudio();
 
+    /// Define which clip to take as reference for automatic audio alignment
+    void setAudioAlignReference();
+
+    /// Automatically align the currently selected clips to synchronize their audio with the reference's audio
+    void alignAudio();
+
     /** @brief Seperates the audio of a clip to a audio track.
     * @param pos Position of the clip to split
     * @param track Track of the clip
@@ -356,6 +369,9 @@ private:
     QWaitCondition m_producerNotReady;
     KStatefulBrush m_activeTrackBrush;
 
+    AudioCorrelation *m_audioCorrelator;
+    ClipItem *m_audioAlignmentReference;
+
     /** stores the state of the control modifier during mouse press.
      * Will then be used to identify whether we resize a group or only one item of it. */
     bool m_controlModifier;
diff --git a/src/definitions.cpp b/src/definitions.cpp
new file mode 100644 (file)
index 0000000..2112faf
--- /dev/null
@@ -0,0 +1,12 @@
+#include "definitions.h"
+
+QDebug operator << (QDebug qd, const ItemInfo &info)
+{
+    qd << "ItemInfo " << &info;
+    qd << "\tTrack" << info.track;
+    qd << "\tStart pos: " << info.startPos.toString();
+    qd << "\tEnd pos: " << info.endPos.toString();
+    qd << "\tCrop start: " << info.cropStart.toString();
+    qd << "\tCrop duration: " << info.cropDuration.toString();
+    return qd.maybeSpace();
+}
index 9c69a485806da5b53252f000ed7db63b1b0c8cdc..a9c55c85aad0105b6a2e5ce471952c493725497d 100644 (file)
@@ -26,6 +26,7 @@
 
 #include <QTreeWidgetItem>
 #include <KLocale>
+#include <QDebug>
 
 const int MAXCLIPDURATION = 15000;
 
@@ -215,5 +216,7 @@ private:
 
 };
 
+QDebug operator << (QDebug qd, const ItemInfo &info);
+
 
 #endif
index 21eb7b70af76466539db9460a1688a296003f0e1..82e0d9c7a61839795d273abea24aa9c102af7d8a 100644 (file)
@@ -54,3 +54,8 @@ GenTime& GenTime::roundNearestFrame(double framesPerSecond)
     m_time = floor((m_time * framesPerSecond) + 0.5) / framesPerSecond;
     return *this;
 }
+
+QString GenTime::toString() const
+{
+    return QString("%1 s").arg(m_time, 0, 'f', 2);
+}
index 34f7bc59a2fc158bea78ab68a9303a2481928648..e927eabbc9f54a2f8f6cd4a633463ce0e6c33dbf 100644 (file)
@@ -18,6 +18,7 @@
 #ifndef GENTIME_H
 #define GENTIME_H
 
+#include <QString>
 #include <cmath>
 
 /**
@@ -52,6 +53,8 @@ public:
     * @param framesPerSecond Number of frames per second */
     GenTime & roundNearestFrame(double framesPerSecond);
 
+    QString toString() const;
+
 
     /*
      * Operators.
diff --git a/src/lib/CMakeLists.txt b/src/lib/CMakeLists.txt
new file mode 100644 (file)
index 0000000..fe191f3
--- /dev/null
@@ -0,0 +1,12 @@
+
+add_subdirectory(audio)
+
+# Hack. kdenlive_SRCS is defined in the sub-scope and added to this scope;
+# now we again need to add it to the parent scope.
+
+set(kdenlive_SRCS
+  ${kdenlive_SRCS}
+  lib/qtimerWithTime.cpp
+  PARENT_SCOPE
+)
+
diff --git a/src/lib/audio/CMakeLists.txt b/src/lib/audio/CMakeLists.txt
new file mode 100644 (file)
index 0000000..bf5cd2b
--- /dev/null
@@ -0,0 +1,10 @@
+
+set(kdenlive_SRCS
+    ${kdenlive_SRCS}
+    lib/audio/audioCorrelation.cpp
+    lib/audio/audioCorrelationInfo.cpp
+    lib/audio/audioEnvelope.cpp
+    lib/audio/audioInfo.cpp
+    lib/audio/audioStreamInfo.cpp
+    PARENT_SCOPE
+)
diff --git a/src/lib/audio/audioCorrelation.cpp b/src/lib/audio/audioCorrelation.cpp
new file mode 100644 (file)
index 0000000..7198344
--- /dev/null
@@ -0,0 +1,124 @@
+/***************************************************************************
+ *   Copyright (C) 2012 by Simon Andreas Eugster (simon.eu@gmail.com)      *
+ *   This file is part of kdenlive. See www.kdenlive.org.                  *
+ *                                                                         *
+ *   This program is free software; you can redistribute it and/or modify  *
+ *   it under the terms of the GNU General Public License as published by  *
+ *   the Free Software Foundation; either version 2 of the License, or     *
+ *   (at your option) any later version.                                   *
+ ***************************************************************************/
+
+#include "audioCorrelation.h"
+
+#include <QTime>
+#include <cmath>
+#include <iostream>
+
+AudioCorrelation::AudioCorrelation(AudioEnvelope *mainTrackEnvelope) :
+    m_mainTrackEnvelope(mainTrackEnvelope)
+{
+    m_mainTrackEnvelope->normalizeEnvelope();
+}
+
+AudioCorrelation::~AudioCorrelation()
+{
+    delete m_mainTrackEnvelope;
+    foreach (AudioEnvelope *envelope, m_children) {
+        delete envelope;
+    }
+    std::cout << "Envelope deleted." << std::endl;
+}
+
+int AudioCorrelation::addChild(AudioEnvelope *envelope)
+{
+    envelope->normalizeEnvelope();
+
+    const int sizeMain = m_mainTrackEnvelope->envelopeSize();
+    const int sizeSub = envelope->envelopeSize();
+
+
+    AudioCorrelationInfo *info = new AudioCorrelationInfo(sizeMain, sizeSub);
+    int64_t *correlation = info->correlationVector();
+
+    const int64_t *envMain = m_mainTrackEnvelope->envelope();
+    const int64_t *envSub = envelope->envelope();
+    int64_t const* left;
+    int64_t const* right;
+    int size;
+    int64_t sum;
+    int64_t max = 0;
+
+
+    /*
+      Correlation:
+
+      SHIFT \in [-sS..sM]
+
+      <--sS----
+      [  sub  ]----sM--->[ sub ]
+               [  main  ]
+
+            ^ correlation vector index = SHIFT + sS
+
+      main is fixed, sub is shifted along main.
+
+    */
+
+
+    QTime t;
+    t.start();
+    for (int shift = -sizeSub; shift <= sizeMain; shift++) {
+
+        if (shift <= 0) {
+            left = envSub-shift;
+            right = envMain;
+            size = std::min(sizeSub+shift, sizeMain);
+        } else {
+            left = envSub;
+            right = envMain+shift;
+            size = std::min(sizeSub, sizeMain-shift);
+        }
+
+        sum = 0;
+        for (int i = 0; i < size; i++) {
+            sum += (*left) * (*right);
+            left++;
+            right++;
+        }
+        correlation[sizeSub+shift] = std::abs(sum);
+
+        if (sum > max) {
+            max = sum;
+        }
+
+    }
+    info->setMax(max);
+    std::cout << "Correlation calculated. Time taken: " << t.elapsed() << " ms." << std::endl;
+
+
+    m_children.append(envelope);
+    m_correlations.append(info);
+
+    Q_ASSERT(m_correlations.size() == m_children.size());
+
+    return m_children.indexOf(envelope);
+}
+
+int AudioCorrelation::getShift(int childIndex) const
+{
+    Q_ASSERT(childIndex >= 0);
+    Q_ASSERT(childIndex < m_correlations.size());
+
+    int indexOffset = m_correlations.at(childIndex)->maxIndex();
+    indexOffset -= m_children.at(childIndex)->envelopeSize();
+
+    return indexOffset;
+}
+
+AudioCorrelationInfo const* AudioCorrelation::info(int childIndex) const
+{
+    Q_ASSERT(childIndex >= 0);
+    Q_ASSERT(childIndex < m_correlations.size());
+
+    return m_correlations.at(childIndex);
+}
diff --git a/src/lib/audio/audioCorrelation.h b/src/lib/audio/audioCorrelation.h
new file mode 100644 (file)
index 0000000..d5b0402
--- /dev/null
@@ -0,0 +1,51 @@
+/***************************************************************************
+ *   Copyright (C) 2012 by Simon Andreas Eugster (simon.eu@gmail.com)      *
+ *   This file is part of kdenlive. See www.kdenlive.org.                  *
+ *                                                                         *
+ *   This program is free software; you can redistribute it and/or modify  *
+ *   it under the terms of the GNU General Public License as published by  *
+ *   the Free Software Foundation; either version 2 of the License, or     *
+ *   (at your option) any later version.                                   *
+ ***************************************************************************/
+
+#ifndef AUDIOCORRELATION_H
+#define AUDIOCORRELATION_H
+
+#include "audioCorrelationInfo.h"
+#include "audioEnvelope.h"
+#include <QList>
+
+class AudioCorrelationInfo;
+
+/**
+  This class does the correlation between two tracks
+  in order to synchronize (align) them.
+
+  It uses one main track (used in the initializer); further tracks will be
+  aligned relative to this main track.
+  */
+class AudioCorrelation
+{
+public:
+    /// AudioCorrelation will take ownership of mainTrackEnvelope
+    AudioCorrelation(AudioEnvelope *mainTrackEnvelope);
+    ~AudioCorrelation();
+
+    /**
+      This object will take ownership of the passed envelope.
+      \return The child's index
+      */
+    int addChild(AudioEnvelope *envelope);
+
+    const AudioCorrelationInfo *info(int childIndex) const;
+    int getShift(int childIndex) const;
+
+
+private:
+    AudioEnvelope *m_mainTrackEnvelope;
+
+    QList<AudioEnvelope*> m_children;
+    QList<AudioCorrelationInfo*> m_correlations;
+};
+
+#endif // AUDIOCORRELATION_H
diff --git a/src/lib/audio/audioCorrelationInfo.cpp b/src/lib/audio/audioCorrelationInfo.cpp
new file mode 100644 (file)
index 0000000..97f4a20
--- /dev/null
@@ -0,0 +1,93 @@
+/***************************************************************************
+ *   Copyright (C) 2012 by Simon Andreas Eugster (simon.eu@gmail.com)      *
+ *   This file is part of kdenlive. See www.kdenlive.org.                  *
+ *                                                                         *
+ *   This program is free software; you can redistribute it and/or modify  *
+ *   it under the terms of the GNU General Public License as published by  *
+ *   the Free Software Foundation; either version 2 of the License, or     *
+ *   (at your option) any later version.                                   *
+ ***************************************************************************/
+
+#include "audioCorrelationInfo.h"
+#include <iostream>
+
+AudioCorrelationInfo::AudioCorrelationInfo(int mainSize, int subSize) :
+    m_mainSize(mainSize),
+    m_subSize(subSize),
+    m_max(-1)
+{
+    m_correlationVector = new int64_t[m_mainSize+m_subSize+1];
+}
+
+AudioCorrelationInfo::~AudioCorrelationInfo()
+{
+    delete[] m_correlationVector;
+}
+
+int AudioCorrelationInfo::size() const
+{
+    return m_mainSize+m_subSize+1;
+}
+
+void AudioCorrelationInfo::setMax(int64_t max)
+{
+    m_max = max;
+}
+
+int64_t AudioCorrelationInfo::max() const
+{
+    Q_ASSERT(m_max > 0);
+    if (m_max <= 0) {
+        int width = size();
+        int64_t max = 0;
+        for (int i = 0; i < width; i++) {
+            if (m_correlationVector[i] > max) {
+                max = m_correlationVector[i];
+            }
+        }
+        Q_ASSERT(max > 0);
+        return max;
+    }
+    return m_max;
+}
+
+int AudioCorrelationInfo::maxIndex() const
+{
+    int64_t max = 0;
+    int index = 0;
+    int width = size();
+
+    for (int i = 0; i < width; i++) {
+        if (m_correlationVector[i] > max) {
+            max = m_correlationVector[i];
+            index = i;
+        }
+    }
+
+    return index;
+}
+
+int64_t* AudioCorrelationInfo::correlationVector()
+{
+    return m_correlationVector;
+}
+
+QImage AudioCorrelationInfo::toImage(int height) const
+{
+    int width = size();
+    int64_t maxVal = max();
+
+    QImage img(width, height, QImage::Format_ARGB32);
+    img.fill(qRgb(255,255,255));
+
+    int val;
+
+    for (int x = 0; x < width; x++) {
+        val = m_correlationVector[x]/double(maxVal)*img.height();
+        for (int y = img.height()-1; y > img.height() - val - 1; y--) {
+            img.setPixel(x, y, qRgb(50, 50, 50));
+        }
+    }
+
+    return img;
+}
diff --git a/src/lib/audio/audioCorrelationInfo.h b/src/lib/audio/audioCorrelationInfo.h
new file mode 100644 (file)
index 0000000..6bb5b8b
--- /dev/null
@@ -0,0 +1,47 @@
+/***************************************************************************
+ *   Copyright (C) 2012 by Simon Andreas Eugster (simon.eu@gmail.com)      *
+ *   This file is part of kdenlive. See www.kdenlive.org.                  *
+ *                                                                         *
+ *   This program is free software; you can redistribute it and/or modify  *
+ *   it under the terms of the GNU General Public License as published by  *
+ *   the Free Software Foundation; either version 2 of the License, or     *
+ *   (at your option) any later version.                                   *
+ ***************************************************************************/
+
+#ifndef AUDIOCORRELATIONINFO_H
+#define AUDIOCORRELATIONINFO_H
+
+#include <QImage>
+
+/**
+  This class holds the correlation of two audio samples.
+  It is mainly a container for data, the correlation itself is calculated
+  in the class AudioCorrelation.
+  */
+class AudioCorrelationInfo
+{
+public:
+    AudioCorrelationInfo(int mainSize, int subSize);
+    ~AudioCorrelationInfo();
+
+    int size() const;
+    int64_t* correlationVector();
+    int64_t const* correlationVector() const;
+
+    int64_t max() const;
+    void setMax(int64_t max); ///< Can be set to avoid calculating the max again in this function
+
+    int maxIndex() const;
+
+    QImage toImage(int height = 400) const;
+
+private:
+    int m_mainSize;
+    int m_subSize;
+
+    int64_t *m_correlationVector;
+    int64_t m_max;
+
+};
+
+#endif // AUDIOCORRELATIONINFO_H
diff --git a/src/lib/audio/audioEnvelope.cpp b/src/lib/audio/audioEnvelope.cpp
new file mode 100644 (file)
index 0000000..7849372
--- /dev/null
@@ -0,0 +1,188 @@
+/***************************************************************************
+ *   Copyright (C) 2012 by Simon Andreas Eugster (simon.eu@gmail.com)      *
+ *   This file is part of kdenlive. See www.kdenlive.org.                  *
+ *                                                                         *
+ *   This program is free software; you can redistribute it and/or modify  *
+ *   it under the terms of the GNU General Public License as published by  *
+ *   the Free Software Foundation; either version 2 of the License, or     *
+ *   (at your option) any later version.                                   *
+ ***************************************************************************/
+
+#include "audioEnvelope.h"
+
+#include "audioStreamInfo.h"
+#include <QImage>
+#include <QTime>
+#include <cmath>
+#include <iostream>
+
+AudioEnvelope::AudioEnvelope(Mlt::Producer *producer) :
+    m_envelope(NULL),
+    m_producer(producer),
+    m_envelopeSize(producer->get_length()),
+    m_envelopeStdDevCalculated(false),
+    m_envelopeIsNormalized(false)
+{
+    m_info = new AudioInfo(m_producer);
+}
+
+AudioEnvelope::~AudioEnvelope()
+{
+    if (m_envelope != NULL) {
+        delete[] m_envelope;
+    }
+    delete m_info;
+}
+
+
+
+const int64_t *AudioEnvelope::envelope()
+{
+    if (m_envelope == NULL) {
+        loadEnvelope();
+    }
+    return m_envelope;
+}
+int AudioEnvelope::envelopeSize() const
+{
+    return m_envelopeSize;
+}
+
+
+
+
+void AudioEnvelope::loadEnvelope()
+{
+    Q_ASSERT(m_envelope == NULL);
+
+    std::cout << "Loading envelope ..." << std::endl;
+
+    int samplingRate = m_info->info(0)->samplingRate();
+    mlt_audio_format format_s16 = mlt_audio_s16;
+    int channels = 1;
+
+    Mlt::Frame *frame;
+    int64_t position;
+    int samples;
+
+    m_envelope = new int64_t[m_envelopeSize];
+    m_envelopeMax = 0;
+    m_envelopeMean = 0;
+
+    QTime t;
+    t.start();
+    m_producer->seek(0);
+    m_producer->set_speed(1.0); // This is necessary, otherwise we don't get any new frames in the 2nd run.
+    for (int i = 0; i < m_envelopeSize; i++) {
+
+        frame = m_producer->get_frame(i);
+        position = mlt_frame_get_position(frame->get_frame());
+        samples = mlt_sample_calculator(m_producer->get_fps(), samplingRate, position);
+
+        int16_t *data = static_cast<int16_t*>(frame->get_audio(format_s16, samplingRate, channels, samples));
+
+        int64_t sum = 0;
+        for (int k = 0; k < samples; k++) {
+            sum += fabs(data[k]);
+        }
+        m_envelope[i] = sum;
+
+        m_envelopeMean += sum;
+        if (sum > m_envelopeMax) {
+            m_envelopeMax = sum;
+        }
+
+//        std::cout << position << "|" << m_producer->get_playtime()
+//                  << "-" << m_producer->get_in() << "+" << m_producer->get_out() << " ";
+
+        delete frame;
+    }
+    m_envelopeMean /= m_envelopeSize;
+    std::cout << "Calculating the envelope (" << m_envelopeSize << " frames) took "
+              << t.elapsed() << " ms." << std::endl;
+}
+
+int64_t AudioEnvelope::loadStdDev()
+{
+    if (m_envelopeStdDevCalculated) {
+        std::cout << "Standard deviation already calculated, not re-calculating." << std::endl;
+    } else {
+
+        if (m_envelope == NULL) {
+            loadEnvelope();
+        }
+
+        m_envelopeStdDev = 0;
+        for (int i = 0; i < m_envelopeSize; i++) {
+            m_envelopeStdDev += sqrt((m_envelope[i]-m_envelopeMean)*(m_envelope[i]-m_envelopeMean)/m_envelopeSize);
+        }
+        m_envelopeStdDevCalculated = true;
+
+    }
+    return m_envelopeStdDev;
+}
+
+void AudioEnvelope::normalizeEnvelope(bool clampTo0)
+{
+    if (m_envelope == NULL) {
+        loadEnvelope();
+    }
+
+    if (!m_envelopeIsNormalized) {
+
+        m_envelopeMax = 0;
+        int64_t newMean = 0;
+        for (int i = 0; i < m_envelopeSize; i++) {
+
+            m_envelope[i] -= m_envelopeMean;
+
+            if (clampTo0) {
+                if (m_envelope[i] < 0) { m_envelope[i] = 0; }
+            }
+
+            if (m_envelope[i] > m_envelopeMax) {
+                m_envelopeMax = m_envelope[i];
+            }
+
+            newMean += m_envelope[i];
+        }
+        m_envelopeMean = newMean / m_envelopeSize;
+
+        m_envelopeIsNormalized = true;
+    }
+
+}
+
+QImage AudioEnvelope::drawEnvelope()
+{
+    if (m_envelope == NULL) {
+        loadEnvelope();
+    }
+
+    QImage img(m_envelopeSize, 400, QImage::Format_ARGB32);
+    img.fill(qRgb(255,255,255));
+    double fy;
+    for (int x = 0; x < img.width(); x++) {
+        fy = m_envelope[x]/double(m_envelopeMax) * img.height();
+        for (int y = img.height()-1; y > img.height()-1-fy; y--) {
+            img.setPixel(x,y, qRgb(50, 50, 50));
+        }
+    }
+    return img;
+}
+
+void AudioEnvelope::dumpInfo() const
+{
+    if (m_envelope == NULL) {
+        std::cout << "Envelope not generated, no information available." << std::endl;
+    } else {
+        std::cout << "Envelope info" << std::endl
+                  << "* size = " << m_envelopeSize << std::endl
+                  << "* max = " << m_envelopeMax << std::endl
+                  << "* Âµ = " << m_envelopeMean << std::endl
+                     ;
+        if (m_envelopeStdDevCalculated) {
+            std::cout << "* s = " << m_envelopeStdDev << std::endl;
+        }
+    }
+}
diff --git a/src/lib/audio/audioEnvelope.h b/src/lib/audio/audioEnvelope.h
new file mode 100644 (file)
index 0000000..c039033
--- /dev/null
@@ -0,0 +1,58 @@
+/***************************************************************************
+ *   Copyright (C) 2012 by Simon Andreas Eugster (simon.eu@gmail.com)      *
+ *   This file is part of kdenlive. See www.kdenlive.org.                  *
+ *                                                                         *
+ *   This program is free software; you can redistribute it and/or modify  *
+ *   it under the terms of the GNU General Public License as published by  *
+ *   the Free Software Foundation; either version 2 of the License, or     *
+ *   (at your option) any later version.                                   *
+ ***************************************************************************/
+
+#ifndef AUDIOENVELOPE_H
+#define AUDIOENVELOPE_H
+
+#include "audioInfo.h"
+#include <mlt++/Mlt.h>
+
+class QImage;
+
+/**
+  The audio envelope is a simplified version of an audio track
+  with frame resolution. One entry is calculated by the sum
+  of the absolute values of all samples in the current frame.
+
+  See also: http://bemasc.net/wordpress/2011/07/26/an-auto-aligner-for-pitivi/
+  */
+class AudioEnvelope
+{
+public:
+    AudioEnvelope(Mlt::Producer *producer);
+    ~AudioEnvelope();
+
+    /// Returns the envelope, calculates it if necessary.
+    int64_t const* envelope();
+    int envelopeSize() const;
+
+    void loadEnvelope();
+    int64_t loadStdDev();
+    void normalizeEnvelope(bool clampTo0 = false);
+
+    QImage drawEnvelope();
+
+    void dumpInfo() const;
+
+private:
+    int64_t *m_envelope;
+    Mlt::Producer *m_producer;
+    AudioInfo *m_info;
+
+    int m_envelopeSize;
+    int64_t m_envelopeMax;
+    int64_t m_envelopeMean;
+    int64_t m_envelopeStdDev;
+
+    bool m_envelopeStdDevCalculated;
+    bool m_envelopeIsNormalized;
+};
+
+#endif // AUDIOENVELOPE_H
diff --git a/src/lib/audio/audioInfo.cpp b/src/lib/audio/audioInfo.cpp
new file mode 100644 (file)
index 0000000..c467a96
--- /dev/null
@@ -0,0 +1,60 @@
+/***************************************************************************
+ *   Copyright (C) 2012 by Simon Andreas Eugster (simon.eu@gmail.com)      *
+ *   This file is part of kdenlive. See www.kdenlive.org.                  *
+ *                                                                         *
+ *   This program is free software; you can redistribute it and/or modify  *
+ *   it under the terms of the GNU General Public License as published by  *
+ *   the Free Software Foundation; either version 2 of the License, or     *
+ *   (at your option) any later version.                                   *
+ ***************************************************************************/
+
+#include "audioInfo.h"
+
+#include "audioStreamInfo.h"
+#include <QString>
+#include <iostream>
+#include <cstdlib>
+
+AudioInfo::AudioInfo(Mlt::Producer *producer)
+{
+    // Since we already receive an MLT producer, we do not need to initialize MLT:
+    // Mlt::Factory::init(NULL);
+
+    // Get the number of streams and add the information of each of them if it is an audio stream.
+    int streams = atoi(producer->get("meta.media.nb_streams"));
+    for (int i = 0; i < streams; i++) {
+        QByteArray propertyName = QString("meta.media.%1.stream.type").arg(i).toLocal8Bit();
+
+        if (strcmp("audio", producer->get(propertyName.data())) == 0) {
+            m_list << new AudioStreamInfo(producer, i);
+        }
+
+    }
+}
+
+AudioInfo::~AudioInfo()
+{
+    foreach (AudioStreamInfo *info, m_list) {
+        delete info;
+    }
+}
+
+int AudioInfo::size() const
+{
+    return m_list.size();
+}
+
+AudioStreamInfo const* AudioInfo::info(int pos) const
+{
+    Q_ASSERT(pos >= 0);
+    Q_ASSERT(pos <= m_list.size());
+
+    return m_list.at(pos);
+}
+
+void AudioInfo::dumpInfo() const
+{
+    foreach (AudioStreamInfo *info, m_list) {
+        info->dumpInfo();
+    }
+}
diff --git a/src/lib/audio/audioInfo.h b/src/lib/audio/audioInfo.h
new file mode 100644 (file)
index 0000000..5a30fcd
--- /dev/null
@@ -0,0 +1,35 @@
+/***************************************************************************
+ *   Copyright (C) 2012 by Simon Andreas Eugster (simon.eu@gmail.com)      *
+ *   This file is part of kdenlive. See www.kdenlive.org.                  *
+ *                                                                         *
+ *   This program is free software; you can redistribute it and/or modify  *
+ *   it under the terms of the GNU General Public License as published by  *
+ *   the Free Software Foundation; either version 2 of the License, or     *
+ *   (at your option) any later version.                                   *
+ ***************************************************************************/
+
+#ifndef AUDIOINFO_H
+#define AUDIOINFO_H
+
+#include <QList>
+#include <mlt++/Mlt.h>
+
+class AudioStreamInfo;
+class AudioInfo
+{
+public:
+    AudioInfo(Mlt::Producer *producer);
+    ~AudioInfo();
+
+    int size() const;
+    AudioStreamInfo const* info(int pos) const;
+
+    void dumpInfo() const;
+
+private:
+    Mlt::Producer *m_producer;
+    QList<AudioStreamInfo*> m_list;
+
+};
+
+#endif // AUDIOINFO_H
diff --git a/src/lib/audio/audioStreamInfo.cpp b/src/lib/audio/audioStreamInfo.cpp
new file mode 100644 (file)
index 0000000..c470652
--- /dev/null
@@ -0,0 +1,67 @@
+/***************************************************************************
+ *   Copyright (C) 2012 by Simon Andreas Eugster (simon.eu@gmail.com)      *
+ *   This file is part of kdenlive. See www.kdenlive.org.                  *
+ *                                                                         *
+ *   This program is free software; you can redistribute it and/or modify  *
+ *   it under the terms of the GNU General Public License as published by  *
+ *   the Free Software Foundation; either version 2 of the License, or     *
+ *   (at your option) any later version.                                   *
+ ***************************************************************************/
+
+#include "audioStreamInfo.h"
+
+#include <QString>
+#include <iostream>
+#include <cstdlib>
+
+AudioStreamInfo::AudioStreamInfo(Mlt::Producer *producer, int audioStreamIndex) :
+    m_audioStreamIndex(audioStreamIndex)
+{
+
+    QByteArray key;
+
+    key = QString("meta.media.%1.codec.sample_fmt").arg(audioStreamIndex).toLocal8Bit();
+    m_samplingFormat = QString(producer->get(key.data()));
+
+    key = QString("meta.media.%1.codec.sample_rate").arg(audioStreamIndex).toLocal8Bit();
+    m_samplingRate = atoi(producer->get(key.data()));
+
+    key = QString("meta.media.%1.codec.bit_rate").arg(audioStreamIndex).toLocal8Bit();
+    m_bitRate = atoi(producer->get(key.data()));
+
+    key = QString("meta.media.%1.codec.channels").arg(audioStreamIndex).toLocal8Bit();
+    m_channels = atoi(producer->get(key.data()));
+
+    key = QString("meta.media.%1.codec.name").arg(audioStreamIndex).toLocal8Bit();
+    m_codecName = QString(producer->get(key.data()));
+
+    key = QString("meta.media.%1.codec.long_name").arg(audioStreamIndex).toLocal8Bit();
+    m_codecLongName = QString(producer->get(key.data()));
+}
+AudioStreamInfo::~AudioStreamInfo()
+{
+}
+
+int AudioStreamInfo::streamIndex() const { return m_audioStreamIndex; }
+int AudioStreamInfo::samplingRate() const { return m_samplingRate; }
+int AudioStreamInfo::channels() const { return m_channels; }
+int AudioStreamInfo::bitrate() const { return m_bitRate; }
+const QString& AudioStreamInfo::codecName(bool longName) const
+{
+    if (longName) {
+        return m_codecLongName;
+    } else {
+        return m_codecName;
+    }
+}
+
+void AudioStreamInfo::dumpInfo() const
+{
+    std::cout << "Info for audio stream " << m_audioStreamIndex << std::endl
+              << "\tCodec: " << m_codecLongName.toLocal8Bit().data() << " (" << m_codecName.toLocal8Bit().data() << ")" << std::endl
+              << "\tChannels: " << m_channels << std::endl
+              << "\tSampling rate: " << m_samplingRate << std::endl
+              << "\tBit rate: " << m_bitRate << std::endl
+                 ;
+
+}
diff --git a/src/lib/audio/audioStreamInfo.h b/src/lib/audio/audioStreamInfo.h
new file mode 100644 (file)
index 0000000..868bb6c
--- /dev/null
@@ -0,0 +1,47 @@
+/***************************************************************************
+ *   Copyright (C) 2012 by Simon Andreas Eugster (simon.eu@gmail.com)      *
+ *   This file is part of kdenlive. See www.kdenlive.org.                  *
+ *                                                                         *
+ *   This program is free software; you can redistribute it and/or modify  *
+ *   it under the terms of the GNU General Public License as published by  *
+ *   the Free Software Foundation; either version 2 of the License, or     *
+ *   (at your option) any later version.                                   *
+ ***************************************************************************/
+
+#ifndef AUDIOSTREAMINFO_H
+#define AUDIOSTREAMINFO_H
+
+#include <mlt++/Mlt.h>
+#include <QString>
+
+/**
+  Provides easy access to properties of an audio stream.
+  */
+class AudioStreamInfo
+{
+public:
+    AudioStreamInfo(Mlt::Producer *producer, int audioStreamIndex);
+    ~AudioStreamInfo();
+
+    int streamIndex() const;
+    int samplingRate() const;
+    int channels() const;
+    int bitrate() const;
+    const QString& codecName(bool longName = false) const;
+    const QString& samplingFormat() const;
+
+    void dumpInfo() const;
+
+private:
+    int m_audioStreamIndex;
+
+    int m_samplingRate;
+    int m_channels;
+    int m_bitRate;
+    QString m_codecName;
+    QString m_codecLongName;
+    QString m_samplingFormat;
+
+};
+
+#endif // AUDIOSTREAMINFO_H
diff --git a/src/lib/qtimerWithTime.cpp b/src/lib/qtimerWithTime.cpp
new file mode 100644 (file)
index 0000000..cd40710
--- /dev/null
@@ -0,0 +1,12 @@
+#include "qtimerWithTime.h"
+
+void QTimerWithTime::start(int msec)
+{
+    QTimer::start(msec);
+    m_time.start();
+}
+
+int QTimerWithTime::elapsed() const
+{
+    return m_time.elapsed();
+}
diff --git a/src/lib/qtimerWithTime.h b/src/lib/qtimerWithTime.h
new file mode 100644 (file)
index 0000000..1a6e53b
--- /dev/null
@@ -0,0 +1,13 @@
+
+#include <QTimer>
+#include <QTime>
+
+class QTimerWithTime : public QTimer
+{
+    Q_OBJECT
+public:
+    virtual void start(int msec);
+    int elapsed() const;
+ private:
+    QTime m_time;
+};
index bfeab9c585ed333ecbbca7b466146a3b320ccf94..6ed7c1ecc54f3b902f9440bd70b8ef4e2e704722 100644 (file)
@@ -587,6 +587,8 @@ MainWindow::MainWindow(const QString &MltPath, const KUrl & Url, const QString &
     m_timelineContextClipMenu->addAction(actionCollection()->action("group_clip"));
     m_timelineContextClipMenu->addAction(actionCollection()->action("ungroup_clip"));
     m_timelineContextClipMenu->addAction(actionCollection()->action("split_audio"));
+    m_timelineContextClipMenu->addAction(actionCollection()->action("set_audio_align_ref"));
+    m_timelineContextClipMenu->addAction(actionCollection()->action("align_audio"));
     m_timelineContextClipMenu->addSeparator();
     m_timelineContextClipMenu->addAction(actionCollection()->action("cut_timeline_clip"));
     m_timelineContextClipMenu->addAction(actionCollection()->action(KStandardAction::name(KStandardAction::Copy)));
@@ -1505,7 +1507,7 @@ void MainWindow::setupActions()
     collection.addAction("edit_clip_marker", editClipMarker);
     connect(editClipMarker, SIGNAL(triggered(bool)), this, SLOT(slotEditClipMarker()));
 
-    KAction *addMarkerGuideQuickly = new KAction(KIcon("bookmark-new"), i18n("Add Marker/Guide quickly"), this);
+    KActionaddMarkerGuideQuickly = new KAction(KIcon("bookmark-new"), i18n("Add Marker/Guide quickly"), this);
     addMarkerGuideQuickly->setShortcut(Qt::Key_Asterisk);
     collection.addAction("add_marker_guide_quickly", addMarkerGuideQuickly);
     connect(addMarkerGuideQuickly, SIGNAL(triggered(bool)), this, SLOT(slotAddMarkerGuideQuickly()));
@@ -1514,6 +1516,14 @@ void MainWindow::setupActions()
     collection.addAction("split_audio", splitAudio);
     connect(splitAudio, SIGNAL(triggered(bool)), this, SLOT(slotSplitAudio()));
 
+    KAction* setAudioAlignReference = new KAction(i18n("Set reference for audio alignment"), this);
+    collection.addAction("set_audio_align_ref", setAudioAlignReference);
+    connect(setAudioAlignReference, SIGNAL(triggered()), this, SLOT(slotSetAudioAlignReference()));
+
+    KAction* alignAudio = new KAction(i18n("Align audio to reference"), this);
+    collection.addAction("align_audio", alignAudio);
+    connect(alignAudio, SIGNAL(triggered()), this, SLOT(slotAlignAudio()));
+
     KAction* audioOnly = new KAction(KIcon("document-new"), i18n("Audio Only"), this);
     collection.addAction("clip_audio_only", audioOnly);
     audioOnly->setData("clip_audio_only");
@@ -3797,6 +3807,20 @@ void MainWindow::slotSplitAudio()
         m_activeTimeline->projectView()->splitAudio();
 }
 
+void MainWindow::slotSetAudioAlignReference()
+{
+    if (m_activeTimeline) {
+        m_activeTimeline->projectView()->setAudioAlignReference();
+    }
+}
+
+void MainWindow::slotAlignAudio()
+{
+    if (m_activeTimeline) {
+        m_activeTimeline->projectView()->alignAudio();
+    }
+}
+
 void MainWindow::slotUpdateClipType(QAction *action)
 {
     if (m_activeTimeline) {
index 4444ef341b6a1f5b85f2a526eaa142f01f7d4d68..0dd27859a85004af72e1c03d0ff732e1549e22a6 100644 (file)
@@ -478,6 +478,8 @@ private slots:
     void slotClipInProjectTree();
     //void slotClipToProjectTree();
     void slotSplitAudio();
+    void slotSetAudioAlignReference();
+    void slotAlignAudio();
     void slotUpdateClipType(QAction *action);
     void slotShowTimeline(bool show);
     void slotMaximizeCurrent(bool show);
index 3dd02443f01c6d1c5783c37d01bed8a249109e9d..9bb3c8a88d8f29f39ea5c1816ac4e9d6b01f9c38 100644 (file)
@@ -3344,6 +3344,7 @@ bool Render::mltMoveClip(int startTrack, int endTrack, int moveStart, int moveEn
         Mlt::Playlist destTrackPlaylist((mlt_playlist) destTrackProducer.get_service());
         if (!overwrite && !destTrackPlaylist.is_blank_at(moveEnd)) {
             // error, destination is not empty
+            kDebug() << "Cannot move: Destination is not empty";
             service.unlock();
             return false;
         } else {
index 42eef766691fb9ad5cc08f75b4a83faa8bc2c0c1..a50b5db6ba01e03d8a902355e6ffedf856a1752b 100644 (file)
@@ -1,5 +1,6 @@
 /***************************************************************************
  *   Copyright (C) 2006 by Peter Penz                                      *
+ *                 2012    Simon A. Eugster <simon.eu@gmail.com>           *
  *   peter.penz@gmx.at                                                     *
  *   Code borrowed from Dolphin, adapted (2008) to Kdenlive by             *
  *   Jean-Baptiste Mardelle, jb@kdenlive.org                               *
 
 
 StatusBarMessageLabel::StatusBarMessageLabel(QWidget* parent) :
-        QWidget(parent),
-        m_type(DefaultMessage),
-        m_state(Default),
-        m_illumination(-64),
-        m_minTextHeight(-1),
-        m_closeButton(0)
+    QWidget(parent),
+    m_state(Default),
+    m_illumination(-64),
+    m_minTextHeight(-1),
+    m_queueSemaphore(1),
+    m_closeButton(0)
 {
     setMinimumHeight(KIconLoader::SizeSmall);
     QPalette palette;
     palette.setColor(QPalette::Background, Qt::transparent);
     setPalette(palette);
-    m_hidetimer.setSingleShot(true);
-    m_hidetimer.setInterval(5000);
-    connect(&m_timer, SIGNAL(timeout()), this, SLOT(timerDone()));
-    connect(&m_hidetimer, SIGNAL(timeout()), this, SLOT(closeErrorMessage()));
 
-    m_closeButton = new QPushButton(i18nc("@action:button", "Close"), this);
+    m_closeButton = new QPushButton(i18nc("@action:button", "Confirm"), this);
     m_closeButton->hide();
-    connect(m_closeButton, SIGNAL(clicked()), this, SLOT(closeErrorMessage()));
+
+    m_queueTimer.setSingleShot(true);
+
+    bool b = true;
+    b &= connect(&m_queueTimer, SIGNAL(timeout()), this, SLOT(slotMessageTimeout()));
+
+    b &= connect(m_closeButton, SIGNAL(clicked()), this, SLOT(confirmErrorMessage()));
+    b &= connect(&m_timer, SIGNAL(timeout()), this, SLOT(timerDone()));
+    Q_ASSERT(b);
 }
 
 StatusBarMessageLabel::~StatusBarMessageLabel()
@@ -63,44 +68,93 @@ StatusBarMessageLabel::~StatusBarMessageLabel()
 }
 
 void StatusBarMessageLabel::setMessage(const QString& text,
-                                       MessageType type)
+                                       MessageType type, int timeoutMS)
+{
+    StatusBarMessageItem item(text, type, timeoutMS);
+
+    if (item.type == ErrorMessage || item.type == MltError) {
+        KNotification::event("ErrorMessage", item.text);
+    }
+
+    m_queueSemaphore.acquire();
+    if (!m_messageQueue.contains(item)) {
+        if (item.type == ErrorMessage || item.type == MltError) {
+            qDebug() << item.text;
+
+            // Put the new errror message at first place and immediately show it
+            if (item.timeoutMillis < 2000) {
+                item.timeoutMillis = 2000;
+            }
+            m_messageQueue.push_front(item);
+
+            // In case we are already displaying an error message, add a little delay
+            int delay = 800 * (m_currentMessage.type == ErrorMessage || m_currentMessage.type == MltError);
+            m_queueTimer.start(delay);
+
+        } else {
+
+            // Message with normal priority
+            m_messageQueue.push_back(item);
+            if (!m_queueTimer.elapsed() >= m_currentMessage.timeoutMillis) {
+                m_queueTimer.start(0);
+            }
+
+        }
+    }
+
+    m_queueSemaphore.release();
+}
+
+bool StatusBarMessageLabel::slotMessageTimeout()
 {
-    if ((text == m_text) && (type == m_type)) {
-        if (type == ErrorMessage) KNotification::event("ErrorMessage", m_text);
-        return;
+    m_queueSemaphore.acquire();
+
+    bool newMessage = false;
+
+    // Get the next message from the queue, unless the current one needs to be confirmed
+    if (m_messageQueue.size() > 0) {
+
+        if (!m_currentMessage.needsConfirmation()) {
+
+            m_currentMessage = m_messageQueue.at(0);
+            m_messageQueue.removeFirst();
+            newMessage = true;
+
+        }
+    }
+
+    // If the queue is empty, add a default (empty) message
+    if (m_messageQueue.size() == 0 && m_currentMessage.type != DefaultMessage) {
+        m_messageQueue.push_back(StatusBarMessageItem());
     }
 
-    /*if (m_type == ErrorMessage) {
-        if (type == ErrorMessage) {
-            m_pendingMessages.insert(0, m_text);
-        } else if ((m_state != Default) || !m_pendingMessages.isEmpty()) {
-            // a non-error message should not be shown, as there
-            // are other pending error messages in the queue
-            return;
+    // Start a new timer, unless the current message still needs to be confirmed
+    if (m_messageQueue.size() > 0) {
+
+        if (!m_currentMessage.needsConfirmation()) {
+
+            // If we only have the default message left to show in the queue,
+            // keep the current one for a little longer.
+            m_queueTimer.start(m_currentMessage.timeoutMillis + 2000*(m_messageQueue.at(0).type == DefaultMessage));
+
         }
-    }*/
+    }
 
-    m_text = text;
-    m_type = type;
 
     m_illumination = -64;
     m_state = Default;
     m_timer.stop();
 
     const char* iconName = 0;
-    QPixmap pixmap;
-    switch (type) {
+    switch (m_currentMessage.type) {
     case OperationCompletedMessage:
         iconName = "dialog-ok";
-        // "ok" icon should probably be "dialog-success", but we don't have that icon in KDE 4.0
         m_closeButton->hide();
-        m_hidetimer.stop();
         break;
 
     case InformationMessage:
         iconName = "dialog-information";
         m_closeButton->hide();
-        m_hidetimer.start();
         break;
 
     case ErrorMessage:
@@ -108,8 +162,6 @@ void StatusBarMessageLabel::setMessage(const QString& text,
         m_timer.start(100);
         m_state = Illuminate;
         m_closeButton->hide();
-        KNotification::event("ErrorMessage", m_text);
-        m_hidetimer.stop();
         break;
 
     case MltError:
@@ -118,24 +170,26 @@ void StatusBarMessageLabel::setMessage(const QString& text,
         m_state = Illuminate;
         updateCloseButtonPosition();
         m_closeButton->show();
-        m_hidetimer.stop();
         break;
 
     case DefaultMessage:
     default:
         m_closeButton->hide();
-        m_hidetimer.stop();
         break;
     }
 
     m_pixmap = (iconName == 0) ? QPixmap() : SmallIcon(iconName);
 
-    /*QFontMetrics fontMetrics(font());
-    setMaximumWidth(fontMetrics.boundingRect(m_text).width() + m_pixmap.width() + (BorderGap * 4));
-    updateGeometry();*/
+    m_queueSemaphore.release();
 
-    //QTimer::singleShot(GeometryTimeout, this, SLOT(assureVisibleText()));
     update();
+    return newMessage;
+}
+
+void StatusBarMessageLabel::confirmErrorMessage()
+{
+    m_currentMessage.confirmed = true;
+    m_queueTimer.start(0);
 }
 
 void StatusBarMessageLabel::setMinimumTextHeight(int min)
@@ -149,7 +203,7 @@ void StatusBarMessageLabel::setMinimumTextHeight(int min)
     }
 }
 
-void StatusBarMessageLabel::paintEvent(QPaintEvent* /* event */)
+void StatusBarMessageLabel::paintEvent(QPaintEvent*)
 {
     QPainter painter(this);
 
@@ -179,7 +233,7 @@ void StatusBarMessageLabel::paintEvent(QPaintEvent* /* event */)
     if (height() > m_minTextHeight) {
         flags = flags | Qt::TextWordWrap;
     }
-    painter.drawText(QRect(x, 0, availableTextWidth(), height()), flags, m_text);
+    painter.drawText(QRect(x, 0, availableTextWidth(), height()), flags, m_currentMessage.text);
     painter.end();
 }
 
@@ -187,7 +241,6 @@ void StatusBarMessageLabel::resizeEvent(QResizeEvent* event)
 {
     QWidget::resizeEvent(event);
     updateCloseButtonPosition();
-    //QTimer::singleShot(GeometryTimeout, this, SLOT(assureVisibleText()));
 }
 
 void StatusBarMessageLabel::timerDone()
@@ -211,7 +264,7 @@ void StatusBarMessageLabel::timerDone()
 
     case Illuminated: {
         // start desaturation
-        if (m_type != MltError) {
+        if (m_currentMessage.type != MltError) {
             m_state = Desaturate;
             m_timer.start(80);
         }
@@ -237,53 +290,6 @@ void StatusBarMessageLabel::timerDone()
     }
 }
 
-void StatusBarMessageLabel::assureVisibleText()
-{
-    if (m_text.isEmpty()) {
-        return;
-    }
-
-    int requiredHeight = m_minTextHeight;
-    if (m_type != DefaultMessage) {
-        // Calculate the required height of the widget thats
-        // needed for having a fully visible text. Note that for the default
-        // statusbar type (e. g. hover information) increasing the text height
-        // is not wanted, as this might rearrange the layout of items.
-
-        QFontMetrics fontMetrics(font());
-        const QRect bounds(fontMetrics.boundingRect(0, 0, availableTextWidth(), height(),
-                           Qt::AlignVCenter | Qt::TextWordWrap, m_text));
-        requiredHeight = bounds.height();
-        if (requiredHeight < m_minTextHeight) {
-            requiredHeight = m_minTextHeight;
-        }
-    }
-
-    // Increase/decrease the current height of the widget to the
-    // required height. The increasing/decreasing is done in several
-    // steps to have an animation if the height is modified
-    // (see StatusBarMessageLabel::resizeEvent())
-    const int gap = m_minTextHeight / 2;
-    int minHeight = minimumHeight();
-    if (minHeight < requiredHeight) {
-        minHeight += gap;
-        if (minHeight > requiredHeight) {
-            minHeight = requiredHeight;
-        }
-        setMinimumHeight(minHeight);
-        updateGeometry();
-    } else if (minHeight > requiredHeight) {
-        minHeight -= gap;
-        if (minHeight < requiredHeight) {
-            minHeight = requiredHeight;
-        }
-        setMinimumHeight(minHeight);
-        updateGeometry();
-    }
-
-    updateCloseButtonPosition();
-}
-
 int StatusBarMessageLabel::availableTextWidth() const
 {
     const int buttonWidth = 0; /*(m_type == ErrorMessage) ?
@@ -298,21 +304,5 @@ void StatusBarMessageLabel::updateCloseButtonPosition()
     m_closeButton->move(x, y);
 }
 
-void StatusBarMessageLabel::closeErrorMessage()
-{
-    if (!showPendingMessage()) {
-        setMessage(QString(), DefaultMessage);
-    }
-}
-
-bool StatusBarMessageLabel::showPendingMessage()
-{
-    if (!m_pendingMessages.isEmpty()) {
-        setMessage(m_pendingMessages.takeFirst(), ErrorMessage);
-        return true;
-    }
-    return false;
-}
-
 
 #include "statusbarmessagelabel.moc"
index a0f491df6994f8e7d912f02455713b9d116a46f3..2b7f7c5d156d0eb178b8af3a0dc80a20346b71dd 100644 (file)
@@ -1,5 +1,6 @@
 /***************************************************************************
  *   Copyright (C) 2006 by Peter Penz                                      *
+ *                 2012    Simon A. Eugster <simon.eu@gmail.com>           *
  *   peter.penz@gmx.at                                                     *
  *   Code borrowed from Dolphin, adapted (2008) to Kdenlive by             *
  *   Jean-Baptiste Mardelle, jb@kdenlive.org                               *
 #include <QPixmap>
 #include <QWidget>
 #include <QTimer>
+#include <QSemaphore>
 
 #include <definitions.h>
 
+#include "lib/qtimerWithTime.h"
+
 class QPaintEvent;
 class QResizeEvent;
 class QPushButton;
 
+
+/**
+  Queue-able message item holding all important information
+  */
+struct StatusBarMessageItem {
+
+    QString text;
+    MessageType type;
+    int timeoutMillis;
+    bool confirmed; ///< MLT errors need to be confirmed.
+
+    /// \return true if the error still needs to be confirmed
+    bool needsConfirmation() const
+    {
+        return type == MltError && !confirmed;
+    }
+
+    StatusBarMessageItem(const QString& text = QString(), MessageType type = DefaultMessage, int timeoutMS = 0) :
+        text(text), type(type), timeoutMillis(timeoutMS), confirmed(false) {}
+
+    bool operator ==(const StatusBarMessageItem &other)
+    {
+        return type == other.type && text == other.text;
+    }
+};
+
 /**
  * @brief Represents a message text label as part of the status bar.
  *
@@ -51,10 +81,6 @@ public:
     explicit StatusBarMessageLabel(QWidget* parent);
     virtual ~StatusBarMessageLabel();
 
-    MessageType type() const;
-
-    const QString& text() const;
-
     // TODO: maybe a better approach is possible with the size hint
     void setMinimumTextHeight(int min);
     int minimumTextHeight() const;
@@ -67,17 +93,11 @@ protected:
     virtual void resizeEvent(QResizeEvent* event);
 
 public slots:
-    void setMessage(const QString& text, MessageType type);
+    void setMessage(const QString& text, MessageType type, int timeoutMS = 0);
 
 private slots:
     void timerDone();
 
-    /**
-     * Increases the height of the message label so that
-     * the given text fits into given area.
-     */
-    void assureVisibleText();
-
     /**
      * Returns the available width in pixels for the text.
      */
@@ -93,21 +113,13 @@ private slots:
      * Closes the currently shown error message and replaces it
      * by the next pending message.
      */
-    void closeErrorMessage();
+    void confirmErrorMessage();
 
-private:
     /**
      * Shows the next pending error message. If no pending message
      * was in the queue, false is returned.
      */
-    bool showPendingMessage();
-
-    /**
-     * Resets the message label properties. This is useful when the
-     * result of invoking StatusBarMessageLabel::setMessage() should
-     * not rely on previous states.
-     */
-    void reset();
+    bool slotMessageTimeout();
 
 private:
     enum State {
@@ -120,31 +132,24 @@ private:
     enum { GeometryTimeout = 100 };
     enum { BorderGap = 2 };
 
-    MessageType m_type;
     State m_state;
     int m_illumination;
     int m_minTextHeight;
     QTimer m_timer;
-    QTimer m_hidetimer;
-    QString m_text;
-    QList<QString> m_pendingMessages;
+
+    QTimerWithTime m_queueTimer;
+    QSemaphore m_queueSemaphore;
+    QList<StatusBarMessageItem> m_messageQueue;
+    StatusBarMessageItem m_currentMessage;
+
     QPixmap m_pixmap;
     QPushButton* m_closeButton;
 };
 
-inline MessageType StatusBarMessageLabel::type() const
-{
-    return m_type;
-}
-
-inline const QString& StatusBarMessageLabel::text() const
-{
-    return m_text;
-}
-
 inline int StatusBarMessageLabel::minimumTextHeight() const
 {
     return m_minTextHeight;
 }
 
+
 #endif
diff --git a/testingArea/CMakeLists.txt b/testingArea/CMakeLists.txt
new file mode 100644 (file)
index 0000000..3e561dc
--- /dev/null
@@ -0,0 +1,22 @@
+
+message(STATUS "Building experimental executables")
+
+include_directories(
+  ${LIBMLT_INCLUDE_DIR}
+  ${LIBMLTPLUS_INCLUDE_DIR}
+)
+include(${QT_USE_FILE})
+
+add_executable(audioOffset
+    audioOffset.cpp
+    ../src/lib/audio/audioInfo.cpp
+    ../src/lib/audio/audioStreamInfo.cpp
+    ../src/lib/audio/audioEnvelope.cpp
+    ../src/lib/audio/audioCorrelation.cpp
+    ../src/lib/audio/audioCorrelationInfo.cpp
+)
+target_link_libraries(audioOffset 
+  ${QT_LIBRARIES}
+  ${LIBMLT_LIBRARY}
+  ${LIBMLTPLUS_LIBRARY}
+)
diff --git a/testingArea/audioOffset.cpp b/testingArea/audioOffset.cpp
new file mode 100644 (file)
index 0000000..0de3b70
--- /dev/null
@@ -0,0 +1,174 @@
+/***************************************************************************
+ *   Copyright (C) 2012 by Simon Andreas Eugster (simon.eu@gmail.com)      *
+ *   This file is part of kdenlive. See www.kdenlive.org.                  *
+ *                                                                         *
+ *   This program is free software; you can redistribute it and/or modify  *
+ *   it under the terms of the GNU General Public License as published by  *
+ *   the Free Software Foundation; either version 2 of the License, or     *
+ *   (at your option) any later version.                                   *
+ ***************************************************************************/
+
+
+#include <QMap>
+#include <QFile>
+#include <QTime>
+#include <QImage>
+#include <QDebug>
+#include <QFileInfo>
+#include <QDateTime>
+#include <QStringList>
+#include <QCoreApplication>
+#include <mlt++/Mlt.h>
+#include <iostream>
+#include <cstdlib>
+#include <cmath>
+
+#include "../src/lib/audio/audioInfo.h"
+#include "../src/lib/audio/audioStreamInfo.h"
+#include "../src/lib/audio/audioEnvelope.h"
+#include "../src/lib/audio/audioCorrelation.h"
+
+void printUsage(const char *path)
+{
+    std::cout << "This executable takes two audio/video files A and B and determines " << std::endl
+              << "how much B needs to be shifted in order to be synchronized with A." << std::endl << std::endl
+              << path << " <main audio file> <second audio file>" << std::endl
+              << "\t-h, --help\n\t\tDisplay this help" << std::endl
+              << "\t--profile=<profile>\n\t\tUse the given profile for calculation (run: melt -query profiles)" << std::endl
+              << "\t--no-images\n\t\tDo not save envelope and correlation images" << std::endl
+                 ;
+}
+
+int main(int argc, char *argv[])
+{
+    QCoreApplication app(argc, argv);
+    QStringList args = app.arguments();
+    args.removeAt(0);
+
+    std::string profile = "atsc_1080p_24";
+    bool saveImages = true;
+
+    // Load arguments
+    foreach (QString str, args) {
+
+        if (str.startsWith("--profile=")) {
+            QString s = str;
+            s.remove(0, QString("--profile=").length());
+            profile = s.toStdString();
+            args.removeOne(str);
+
+        } else if (str == "-h" || str == "--help") {
+            printUsage(argv[0]);
+            return 0;
+
+        } else if (str == "--no-images") {
+            saveImages = false;
+            args.removeOne(str);
+        }
+
+    }
+
+    if (args.length() < 2) {
+        printUsage(argv[0]);
+        return 1;
+    }
+
+
+
+    std::string fileMain(args.at(0).toStdString());
+    args.removeFirst();
+    std::string fileSub = args.at(0).toStdString();
+    args.removeFirst();
+
+
+    qDebug() << "Unused arguments: " << args;
+
+
+    if (argc > 2) {
+        fileMain = argv[1];
+        fileSub = argv[2];
+    } else {
+        std::cout << "Usage: " << argv[0] << " <main audio file> <second audio file>" << std::endl;
+        return 0;
+    }
+    std::cout << "Trying to align (2)\n\t" << fileSub << "\nto fit on (1)\n\t" << fileMain
+              << "\n, result will indicate by how much (2) has to be moved." << std::endl
+              << "Profile used: " << profile << std::endl
+                 ;
+
+
+    // Initialize MLT
+    Mlt::Factory::init(NULL);
+
+    // Load an arbitrary profile
+    Mlt::Profile prof(profile.c_str());
+
+    // Load the MLT producers
+    Mlt::Producer prodMain(prof, fileMain.c_str());
+    if (!prodMain.is_valid()) {
+        std::cout << fileMain << " is invalid." << std::endl;
+        return 2;
+    }
+    Mlt::Producer prodSub(prof, fileSub.c_str());
+    if (!prodSub.is_valid()) {
+        std::cout << fileSub << " is invalid." << std::endl;
+        return 2;
+    }
+
+
+    // Build the audio envelopes for the correlation
+    AudioEnvelope *envelopeMain = new AudioEnvelope(&prodMain);
+    envelopeMain->loadEnvelope();
+    envelopeMain->loadStdDev();
+    envelopeMain->dumpInfo();
+
+    AudioEnvelope *envelopeSub = new AudioEnvelope(&prodSub);
+    envelopeSub->loadEnvelope();
+    envelopeSub->loadStdDev();
+    envelopeSub->dumpInfo();
+
+
+
+
+
+
+    // Calculate the correlation and hereby the audio shift
+    AudioCorrelation corr(envelopeMain);
+    int index = corr.addChild(envelopeSub);
+
+    int shift = corr.getShift(index);
+    std::cout << fileSub << " should be shifted by " << shift << " frames" << std::endl
+              << "\trelative to " << fileMain << std::endl
+              << "\tin a " << prodMain.get_fps() << " fps profile (" << profile << ")." << std::endl
+                 ;
+
+
+    if (saveImages) {
+        QString outImg;
+        outImg = QString("envelope-main-%1.png")
+                .arg(QDateTime::currentDateTime().toString("yyyy-MM-dd-hh:mm:ss"));
+        envelopeMain->drawEnvelope().save(outImg);
+        std::cout << "Saved volume envelope as "
+                  << QFileInfo(outImg).absoluteFilePath().toStdString()
+                  << std::endl;
+        outImg = QString("envelope-sub-%1.png")
+                .arg(QDateTime::currentDateTime().toString("yyyy-MM-dd-hh:mm:ss"));
+        envelopeSub->drawEnvelope().save(outImg);
+        std::cout << "Saved volume envelope as "
+                  << QFileInfo(outImg).absoluteFilePath().toStdString()
+                  << std::endl;
+        outImg = QString("correlation-%1.png")
+                .arg(QDateTime::currentDateTime().toString("yyyy-MM-dd-hh:mm:ss"));
+        corr.info(index)->toImage().save(outImg);
+        std::cout << "Saved correlation image as "
+                  << QFileInfo(outImg).absoluteFilePath().toStdString()
+                  << std::endl;
+    }
+
+
+    return 0;
+
+}
+
+
+