+*~
build
+CMakeLists.txt.user
add_subdirectory(src)
add_subdirectory(thumbnailer)
add_subdirectory(titles)
+add_subdirectory(testingArea)
macro_display_feature_log()
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>
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)
customruler.cpp
customtrackscene.cpp
customtrackview.cpp
+ definitions.cpp
docclipbase.cpp
documentchecker.cpp
documentvalidator.cpp
namespace Mlt
{
class Producer;
-};
+}
class ClipItem : public AbstractClipItem
{
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;
/***************************************************************************
* 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) {
}
-// 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;
}
/***************************************************************************
* 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 *
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
#include "commands/razorgroupcommand.h"
#include "profilesdialog.h"
+#include "lib/audio/audioEnvelope.h"
+#include "lib/audio/audioCorrelation.h"
+
#include <KDebug>
#include <KLocale>
#include <KUrl>
// 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);
}
event->setDropAction(Qt::MoveAction);
event->accept();
+
+ /// \todo enable when really working
+// alignAudio();
+
} else QGraphicsView::dropEvent(event);
setFocus();
}
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);
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)
}
}
+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);
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);
class AbstractClipItem;
class AbstractGroupItem;
class Transition;
+class AudioCorrelation;
class CustomTrackView : public QGraphicsView
{
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);
/** @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
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;
--- /dev/null
+#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();
+}
#include <QTreeWidgetItem>
#include <KLocale>
+#include <QDebug>
const int MAXCLIPDURATION = 15000;
};
+QDebug operator << (QDebug qd, const ItemInfo &info);
+
#endif
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);
+}
#ifndef GENTIME_H
#define GENTIME_H
+#include <QString>
#include <cmath>
/**
* @param framesPerSecond Number of frames per second */
GenTime & roundNearestFrame(double framesPerSecond);
+ QString toString() const;
+
/*
* Operators.
--- /dev/null
+
+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
+)
+
--- /dev/null
+
+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
+)
--- /dev/null
+/***************************************************************************
+ * 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);
+}
--- /dev/null
+/***************************************************************************
+ * 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
--- /dev/null
+/***************************************************************************
+ * 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;
+}
--- /dev/null
+/***************************************************************************
+ * 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
--- /dev/null
+/***************************************************************************
+ * 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;
+ }
+ }
+}
--- /dev/null
+/***************************************************************************
+ * 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
--- /dev/null
+/***************************************************************************
+ * 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();
+ }
+}
--- /dev/null
+/***************************************************************************
+ * 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
--- /dev/null
+/***************************************************************************
+ * 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
+ ;
+
+}
--- /dev/null
+/***************************************************************************
+ * 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
--- /dev/null
+#include "qtimerWithTime.h"
+
+void QTimerWithTime::start(int msec)
+{
+ QTimer::start(msec);
+ m_time.start();
+}
+
+int QTimerWithTime::elapsed() const
+{
+ return m_time.elapsed();
+}
--- /dev/null
+
+#include <QTimer>
+#include <QTime>
+
+class QTimerWithTime : public QTimer
+{
+ Q_OBJECT
+public:
+ virtual void start(int msec);
+ int elapsed() const;
+ private:
+ QTime m_time;
+};
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)));
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);
+ KAction* addMarkerGuideQuickly = 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()));
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");
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) {
void slotClipInProjectTree();
//void slotClipToProjectTree();
void slotSplitAudio();
+ void slotSetAudioAlignReference();
+ void slotAlignAudio();
void slotUpdateClipType(QAction *action);
void slotShowTimeline(bool show);
void slotMaximizeCurrent(bool show);
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 {
/***************************************************************************
* 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()
}
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:
m_timer.start(100);
m_state = Illuminate;
m_closeButton->hide();
- KNotification::event("ErrorMessage", m_text);
- m_hidetimer.stop();
break;
case MltError:
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)
}
}
-void StatusBarMessageLabel::paintEvent(QPaintEvent* /* event */)
+void StatusBarMessageLabel::paintEvent(QPaintEvent*)
{
QPainter painter(this);
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();
}
{
QWidget::resizeEvent(event);
updateCloseButtonPosition();
- //QTimer::singleShot(GeometryTimeout, this, SLOT(assureVisibleText()));
}
void StatusBarMessageLabel::timerDone()
case Illuminated: {
// start desaturation
- if (m_type != MltError) {
+ if (m_currentMessage.type != MltError) {
m_state = Desaturate;
m_timer.start(80);
}
}
}
-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) ?
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"
/***************************************************************************
* 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.
*
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;
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.
*/
* 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 {
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
--- /dev/null
+
+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}
+)
--- /dev/null
+/***************************************************************************
+ * 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;
+
+}
+
+
+