]> git.sesse.net Git - kdenlive/blobdiff - src/customtrackview.cpp
Merge branch 'audioAlign'
[kdenlive] / src / customtrackview.cpp
index f450fc1598dae81fc166c1aca9947a8a31519aad..76e4bb1cecb3f927b12eab7e8fce2ebc91b5ab5b 100644 (file)
@@ -63,6 +63,9 @@
 #include "commands/razorgroupcommand.h"
 #include "profilesdialog.h"
 
+#include "lib/audio/audioEnvelope.h"
+#include "lib/audio/audioCorrelation.h"
+
 #include <KDebug>
 #include <KLocale>
 #include <KUrl>
@@ -84,6 +87,8 @@
 #include <QGraphicsDropShadowEffect>
 #endif
 
+//#define DEBUG
+
 bool sortGuidesList(const Guide *g1 , const Guide *g2)
 {
     return (*g1).position() < (*g2).position();
@@ -99,38 +104,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);
@@ -2590,6 +2598,10 @@ void CustomTrackView::dropEvent(QDropEvent * event)
         }
         event->setDropAction(Qt::MoveAction);
         event->accept();
+
+        /// \todo enable when really working
+//        alignAudio();
+
     } else QGraphicsView::dropEvent(event);
     setFocus();
 }
@@ -4416,18 +4428,26 @@ 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);
+#ifdef DEBUG
+    qDebug() << "Moving item " << (long)item << " from .. to:";
+    qDebug() << item->info();
+    qDebug() << start;
+    qDebug() << end;
+#endif
+    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);
@@ -4457,6 +4477,16 @@ 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();
+#ifdef DEBUG
+        qDebug() << "Actual end position updated:" << *out_actualEnd;
+#endif
+    }
+#ifdef DEBUG
+    qDebug() << item->info();
+#endif
+    return success;
 }
 
 void CustomTrackView::moveGroup(QList <ItemInfo> startClip, QList <ItemInfo> startTransition, const GenTime &offset, const int trackOffset, bool reverseMove)
@@ -6078,6 +6108,141 @@ 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."), InformationMessage);
+        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 == m_audioAlignmentReference) {
+                continue;
+            }
+
+            if (clip->clipType() == AV || clip->clipType() == AUDIO) {
+                AudioEnvelope *envelope = new AudioEnvelope(clip->getProducer(clip->track()),
+                                                            clip->info().cropStart.frames(m_document->fps()),
+                                                            clip->info().cropDuration.frames(m_document->fps()));
+
+                // FFT only for larger vectors. We could use it all time, but for small vectors
+                // the (anyway not noticeable) overhead is smaller with a nested for loop correlation.
+                int index = m_audioCorrelator->addChild(envelope, envelope->envelopeSize() > 200);
+                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();
+
+#ifdef DEBUG
+                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)";
+#endif
+
+
+                QUndoCommand *moveCommand = new QUndoCommand();
+
+                GenTime add(shift, m_document->fps());
+                ItemInfo start = clip->info();
+
+                ItemInfo end = start;
+                end.startPos = m_audioAlignmentReference->startPos() + add - m_audioAlignmentReference->cropStart();
+                end.endPos = end.startPos + start.cropDuration;
+
+                if ( end.startPos.seconds() < 0 ) {
+                    // Clip would start before 0, so crop it first
+                    GenTime cropBy = -end.startPos;
+
+#ifdef DEBUG
+                    qDebug() << "Need to crop clip. " << start;
+                    qDebug() << "end.startPos: " << end.startPos.toString() << ", cropBy: " << cropBy.toString();
+#endif
+
+                    ItemInfo resized = start;
+                    resized.startPos += cropBy;
+
+                    resizeClip(start, resized);
+                    new ResizeClipCommand(this, start, resized, false, false, moveCommand);
+
+                    start = clip->info();
+                    end.startPos += cropBy;
+
+#ifdef DEBUG
+                    qDebug() << "Clip cropped. " << start;
+                    qDebug() << "Moving to: " << end;
+#endif
+                }
+
+                if (itemCollision(clip, end)) {
+                    delete moveCommand;
+                    emit displayMessage(i18n("Unable to move clip due to collision."), ErrorMessage);
+                    return;
+                }
+
+                moveCommand->setText(i18n("Auto-align clip"));
+                new MoveClipCommand(this, start, end, true, moveCommand);
+                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);
@@ -6161,8 +6326,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);