aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJean-Baptiste Mardelle <[email protected]>2016-05-24 01:25:42 +0200
committerJean-Baptiste Mardelle <[email protected]>2016-05-24 01:25:42 +0200
commit2c51113e238044f36d587749e868af0f79b4c7c9 (patch)
tree1246fc9ba3c31da50c3609c171e469c1ecbd1a04
parentd9b73c74a0b6725d1455558d5a3cb244b7e812cf (diff)
Timeline preview: refactor and move all functions into a new previewmanager class
-rw-r--r--src/doc/kdenlivedoc.cpp140
-rw-r--r--src/doc/kdenlivedoc.h13
-rw-r--r--src/project/dialogs/projectsettings.cpp4
-rw-r--r--src/renderer.cpp70
-rw-r--r--src/renderer.h8
-rw-r--r--src/timeline/CMakeLists.txt1
-rw-r--r--src/timeline/customruler.cpp19
-rw-r--r--src/timeline/customruler.h4
-rw-r--r--src/timeline/managers/previewmanager.cpp307
-rw-r--r--src/timeline/managers/previewmanager.h82
-rw-r--r--src/timeline/timeline.cpp131
-rw-r--r--src/timeline/timeline.h12
12 files changed, 486 insertions, 305 deletions
diff --git a/src/doc/kdenlivedoc.cpp b/src/doc/kdenlivedoc.cpp
index 68bdddb..0579f73 100644
--- a/src/doc/kdenlivedoc.cpp
+++ b/src/doc/kdenlivedoc.cpp
@@ -294,7 +294,6 @@ KdenliveDoc::KdenliveDoc(const QUrl &url, const QUrl &projectFolder, QUndoGroup
QDir dir2(QStandardPaths::writableLocation(QStandardPaths::CacheLocation));
dir2.mkdir(documentId);
updateProjectFolderPlacesEntry();
- connect(this, &KdenliveDoc::cleanupOldPreviews, this, &KdenliveDoc::doCleanupOldPreviews);
}
void KdenliveDoc::slotSetDocumentNotes(const QString &notes)
@@ -313,23 +312,6 @@ KdenliveDoc::~KdenliveDoc()
if (!m_autosave->fileName().isEmpty()) m_autosave->remove();
delete m_autosave;
}
- // Remove all timeline preview undo data
- QString id = m_documentProperties.value(QStringLiteral("documentid"));
- if (id.isEmpty() || id.toLong() == 0) {
- // Something is wrong, make sure we don't trash valuable data
- // id should be a number (ms since epoch)
- return;
- }
- QDir dir = getCacheDir();
- if (m_url.isEmpty()) {
- // Doc was not saved, double check path and delete folder
- if (dir.dirName() == id)
- dir.removeRecursively();
- } else {
- if (dir.cd("undo") && dir.absolutePath().contains(id)) {
- dir.removeRecursively();
- }
- }
}
int KdenliveDoc::setSceneList()
@@ -1638,91 +1620,6 @@ void KdenliveDoc::doAddAction(const QString &name, QAction *a, QKeySequence shor
pCore->window()->actionCollection()->setDefaultShortcut(a, shortcut);
}
-QDir KdenliveDoc::getCacheDir()
-{
- QString documentId = m_documentProperties.value(QStringLiteral("documentid"));
- return QDir(QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/"+ documentId);
-}
-
-void KdenliveDoc::invalidatePreviews(QList <int> chunks)
-{
- // We are not at the bottom of undo stack, chunks have already been archived previously
- QMutexLocker lock(&m_previewMutex);
- QDir dir = getCacheDir();
- QString ext = m_documentProperties.value(QStringLiteral("previewextension"));
- if (m_commandStack->index() == m_commandStack->count() && !dir.exists(QString("undo/%1").arg(m_commandStack->index() - 1))) {
- // Archive just created chunks
- int ix = m_commandStack->index() - 1;
- if (!dir.exists("undo"))
- dir.mkdir("undo");
- if (!dir.exists("undo")) {
- // Cannot create undo dir, abort
- return;
- }
- dir.mkdir(QString("undo/%1").arg(ix));
- bool foundPreviews = false;
- foreach(int i, chunks) {
- QString current = QString("%1.%2").arg(i).arg(ext);
- if (dir.rename(current, QString("undo/%1/%2").arg(ix).arg(current))) {
- foundPreviews = true;
- }
- }
- if (!foundPreviews) {
- if (dir.cd(QString("undo/%1").arg(ix)) && dir.absolutePath().contains("/undo/")) {
- dir.removeRecursively();
- }
- }
- else emit cleanupOldPreviews(ix);
- } else {
- // Restore existing chunks, delete others
- QDir srcdir(dir);
- QStringList filters;
- filters << QString("*.%1").arg(ext);
- QList <int> foundChunks;
- // Check if we just undo the last stack action, then backup, otherwise delete
- bool lastUndo = false;
- int max = m_commandStack->count();
- if (m_commandStack->index() == max - 1) {
- if (!srcdir.exists(QString("undo/%1").arg(max))) {
- lastUndo = true;
- bool foundPreviews = false;
- dir.mkdir(QString("undo/%1").arg(max));
- foreach(int i, chunks) {
- QString current = QString("%1.%2").arg(i).arg(ext);
- if (dir.rename(current, QString("undo/%1/%2").arg(max).arg(current))) {
- foundPreviews = true;
- }
- }
- if (!foundPreviews) {
- QDir tmpDir = dir;
- if (tmpDir.cd(QString("undo/%1").arg(max)) && tmpDir.absolutePath().contains("/undo/")) {
- tmpDir.removeRecursively();
- }
- }
- }
- }
- if (!lastUndo) {
- foreach(int i, chunks) {
- srcdir.remove(QString("%1.%2").arg(i).arg(ext));
- }
- }
- if (!dir.cd(QString("undo/%1").arg(m_commandStack->index())))
- return;
- QStringList filesnames = dir.entryList(filters, QDir::Files);
- foreach(const QString & fname, filesnames) {
- int existingChunk = fname.section(".", 0, 0).toInt();
- if (chunks.contains(existingChunk)) {
- // Restore chunks
- foundChunks << existingChunk;
- QFile::copy(dir.absoluteFilePath(fname), srcdir.absoluteFilePath(fname));
- }
- }
- qSort(foundChunks);
- emit reloadChunks(foundChunks);
- }
- setModified(true);
-}
-
void KdenliveDoc::previewProgress(int p)
{
pCore->window()->setPreviewProgress(p);
@@ -1783,40 +1680,13 @@ void KdenliveDoc::selectPreviewProfile()
}
}
-void KdenliveDoc::doCleanupOldPreviews(int ix)
+void KdenliveDoc::checkPreviewStack()
{
- QDir dir = getCacheDir();
- if (!dir.cd("undo"))
- return;
- QStringList dirs = dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
- bool ok;
- foreach (const QString &num, dirs) {
- int nb = num.toInt(&ok);
- if (ok && nb < ix - 5) {
- QDir tmp = dir;
- if (tmp.cd(num) && tmp.absolutePath().contains("/undo/")) {
- tmp.removeRecursively();
- }
- }
- }
+ // A command was pushed in the middle of the stack, remove all cached data from last undos
+ emit removeInvalidUndo(m_commandStack->count());
}
-void KdenliveDoc::checkPreviewStack()
+void KdenliveDoc::saveMltPlaylist(const QString fileName)
{
- // A command was pushed in the middle of the stack, remove all cached data from last undos
- int max = m_commandStack->count();
- QDir dir = getCacheDir();
- if (!dir.cd("undo"))
- return;
- QStringList dirs = dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
- bool ok;
- foreach (const QString &num, dirs) {
- int nb = num.toInt(&ok);
- if (ok && nb >= max) {
- QDir tmp = dir;
- if (tmp.cd(num) && tmp.absolutePath().contains("/undo/")) {
- tmp.removeRecursively();
- }
- }
- }
+ m_render->preparePreviewRendering(fileName);
}
diff --git a/src/doc/kdenlivedoc.h b/src/doc/kdenlivedoc.h
index 3b3dca1..eb8afa3 100644
--- a/src/doc/kdenlivedoc.h
+++ b/src/doc/kdenlivedoc.h
@@ -32,7 +32,6 @@
#include <QObject>
#include <QTimer>
#include <QUrl>
-#include <QMutex>
#include <kautosavefile.h>
#include <KDirWatch>
@@ -132,6 +131,8 @@ public:
QDomDocument xmlSceneList(const QString &scene);
/** @brief Saves the project file xml to a file. */
bool saveSceneList(const QString &path, const QString &scene);
+ /** @brief Saves only the MLT xml to a file for preview rendering. */
+ void saveMltPlaylist(const QString fileName);
void cacheImage(const QString &fileId, const QImage &img) const;
void setProjectFolder(QUrl url);
void setZone(int start, int end);
@@ -173,8 +174,6 @@ public:
void previewProgress(int p);
/** @brief Select most appropriate rendering profile for timeline preview based on fps / size. */
void selectPreviewProfile();
- /** @brief Get the directory to store timeline previews */
- QDir getCacheDir();
private:
QUrl m_url;
@@ -193,7 +192,6 @@ private:
ClipManager *m_clipManager;
MltVideoProfile m_profile;
QString m_searchFolder;
- QMutex m_previewMutex;
/** @brief Tells whether the current document has been changed after being saved. */
bool m_modified;
@@ -241,7 +239,6 @@ private slots:
void slotSetDocumentNotes(const QString &notes);
void switchProfile(MltVideoProfile profile, const QString &id, const QDomElement &xml);
void slotSwitchProfile();
- void doCleanupOldPreviews(int ix);
/** @brief Check if we did a new action invalidating more recent undo items. */
void checkPreviewStack();
@@ -263,10 +260,8 @@ signals:
void reloadEffects();
/** @brief Fps was changed, update timeline */
void updateFps(bool changed);
- /** @brief Some timeline preview chunks restored, reload them */
- void reloadChunks(QList <int> chunks);
- /** @brief Only keep 5 undo levels of timeline previews, ask for cleanup */
- void cleanupOldPreviews(int ix);
+ /** @brief If a command is pushed when we are in the middle of undo stack, invalidate further undo history */
+ void removeInvalidUndo(int ix);
};
diff --git a/src/project/dialogs/projectsettings.cpp b/src/project/dialogs/projectsettings.cpp
index a4d43ee..21d6bcc 100644
--- a/src/project/dialogs/projectsettings.cpp
+++ b/src/project/dialogs/projectsettings.cpp
@@ -79,7 +79,7 @@ ProjectSettings::ProjectSettings(KdenliveDoc *doc, QMap <QString, QString> metad
m_proxyextension = doc->getDocumentProperty(QStringLiteral("proxyextension"));
m_previewparams = doc->getDocumentProperty(QStringLiteral("previewparameters"));
m_previewextension = doc->getDocumentProperty(QStringLiteral("previewextension"));
- m_previewDir = doc->getCacheDir();
+ m_previewDir = doc->getDocumentProperty(QStringLiteral("cachedir"));
}
else {
currentProf = KdenliveSettings::default_profile();
@@ -288,7 +288,7 @@ void ProjectSettings::slotDeleteProxies()
void ProjectSettings::slotDeletePreviews()
{
- if (KMessageBox::warningContinueCancel(this, i18n("Deleting these preview files will invalidate all timeline previews for this project.")) != KMessageBox::Continue) return;
+ if (KMessageBox::warningContinueCancel(this, i18n("Deleting the project preview files in this folder will invalidate all timeline previews:\n%1", m_previewDir.absolutePath())) != KMessageBox::Continue) return;
buttonBox->setEnabled(false);
//TODO
emit disablePreviews();
diff --git a/src/renderer.cpp b/src/renderer.cpp
index 05c2e17..3db3167 100644
--- a/src/renderer.cpp
+++ b/src/renderer.cpp
@@ -72,8 +72,7 @@ Render::Render(Kdenlive::MonitorId rendererName, BinController *binController, G
m_isLoopMode(false),
m_blackClip(NULL),
m_isActive(false),
- m_isRefreshing(false),
- m_abortPreview(false)
+ m_isRefreshing(false)
{
qRegisterMetaType<stringMap> ("stringMap");
analyseAudio = KdenliveSettings::monitor_audio();
@@ -102,7 +101,6 @@ Render::Render(Kdenlive::MonitorId rendererName, BinController *binController, G
Render::~Render()
{
- m_abortPreview = true;
closeMlt();
}
@@ -113,7 +111,6 @@ void Render::closeMlt()
delete m_mltConsumer;
delete m_mltProducer;
delete m_blackClip;
- m_previewThread.waitForFinished();
}
void Render::slotSwitchFullscreen()
@@ -1618,23 +1615,9 @@ void Render::updateSlowMotionProducers(const QString &id, QMap <QString, QString
}
}
-void Render::abortPreview()
+void Render::preparePreviewRendering(const QString sceneListFile)
{
- if (m_previewThread.isRunning()) {
- m_abortPreview = true;
- m_previewThread.waitForFinished();
- }
-}
-
-void Render::previewRendering(QList <int> frames, const QString &cacheDir, QStringList consumerParams, const QString extension)
-{
- abortPreview();
- m_previewChunks << frames;
- qSort(m_previewChunks);
- QDir dir(cacheDir);
- dir.mkpath(QStringLiteral("."));
// Save temporary scenelist
- QString sceneListFile = dir.absoluteFilePath("preview.mlt");
Mlt::Consumer xmlConsumer(*m_qmlView->profile(), "xml", sceneListFile.toUtf8().constData());
if (!xmlConsumer.is_valid())
return;
@@ -1645,52 +1628,5 @@ void Render::previewRendering(QList <int> frames, const QString &cacheDir, QStri
return;
xmlConsumer.connect(prod);
xmlConsumer.run();
- m_previewThread = QtConcurrent::run(this, &Render::doPreviewRender, dir, sceneListFile, consumerParams, extension);
-}
-
-void Render::doPreviewRender(QDir folder, QString scene, QStringList consumerParams, const QString &extension)
-{
- int progress;
- int chunkSize = KdenliveSettings::timelinechunks();
- consumerParams << "an=1";
- emit previewRender(0, QString(), 0);
- int ct = 0;
- while (!m_previewChunks.isEmpty()) {
- int i = m_previewChunks.takeFirst();
- ct++;
- if (m_abortPreview) {
- m_previewChunks.prepend(i);
- emit previewRender(0, QString(), 1000);
- break;
- }
- QString fileName = QString("%1.%2").arg(i).arg(extension);
- if (m_previewChunks.isEmpty()) {
- progress = 1000;
- } else {
- progress = (double) (ct) / (ct + m_previewChunks.count()) * 1000;
- }
- if (folder.exists(fileName)) {
- // This chunk already exists
- emit previewRender(i, folder.absoluteFilePath(fileName), progress);
- continue;
- }
- // Build rendering process
- QStringList args;
- args << scene;
- args << "in=" + QString::number(i);
- args << "out=" + QString::number(i + chunkSize - 1);
- args << "-consumer" << "avformat:" + folder.absoluteFilePath(fileName);
- args << consumerParams;
- int result = QProcess::execute(KdenliveSettings::rendererpath(), args);
- if (result != 0) {
- // Something is wrong, abort
- qDebug()<<"+++++++++\n++ ERROR ++\n++++++";
- emit previewRender(i, QString(), -1);
- QFile::remove(folder.absoluteFilePath(fileName));
- break;
- }
- emit previewRender(i, folder.absoluteFilePath(fileName), progress);
- }
- QFile::remove(scene);
- m_abortPreview = false;
}
+
diff --git a/src/renderer.h b/src/renderer.h
index d880706..0daa425 100644
--- a/src/renderer.h
+++ b/src/renderer.h
@@ -251,8 +251,7 @@ class Render: public AbstractRender
void prepareProfileReset(double fps);
void finishProfileReset();
void updateSlowMotionProducers(const QString &id, QMap <QString, QString> passProperties);
- void previewRendering(QList <int> frames, const QString &cacheDir, QStringList consumerParams, const QString extension);
- void abortPreview();
+ void preparePreviewRendering(const QString sceneListFile);
private:
@@ -265,7 +264,6 @@ private:
Mlt::Producer * m_mltProducer;
Mlt::Event *m_showFrameEvent;
Mlt::Event *m_pauseEvent;
- QFuture <void> m_previewThread;
BinController *m_binController;
GLWidget *m_qmlView;
double m_fps;
@@ -289,10 +287,8 @@ private:
bool m_isActive;
/** @brief True if the consumer is currently refreshing itself. */
bool m_isRefreshing;
- bool m_abortPreview;
void closeMlt();
QMap<QString, Mlt::Producer *> m_slowmotionProducers;
- QList <int> m_previewChunks;
/** @brief Build the MLT Consumer object with initial settings.
* @param profileName The MLT profile to use for the consumer */
@@ -312,7 +308,6 @@ private slots:
/** @brief Refreshes the monitor display. */
void refresh();
void slotCheckSeeking();
- void doPreviewRender(QDir folder, QString scene, QStringList consumerParams, const QString &extension);
signals:
/** @brief The renderer stopped, either playing or rendering. */
@@ -349,7 +344,6 @@ signals:
void mltFrameReceived(Mlt::Frame *);
/** @brief We want to replace a clip with another, but before we need to change clip producer id so that there is no interference*/
void prepareTimelineReplacement(const QString &);
- void previewRender(int frame, const QString &file, int progress);
public slots:
diff --git a/src/timeline/CMakeLists.txt b/src/timeline/CMakeLists.txt
index 2bd7952..1fd7a80 100644
--- a/src/timeline/CMakeLists.txt
+++ b/src/timeline/CMakeLists.txt
@@ -25,5 +25,6 @@ set(kdenlive_SRCS
timeline/managers/guidemanager.cpp
timeline/managers/razormanager.cpp
timeline/managers/selectmanager.cpp
+ timeline/managers/previewmanager.cpp
PARENT_SCOPE)
diff --git a/src/timeline/customruler.cpp b/src/timeline/customruler.cpp
index 8d3d1f9..c0c86c0 100644
--- a/src/timeline/customruler.cpp
+++ b/src/timeline/customruler.cpp
@@ -510,17 +510,21 @@ void CustomRuler::activateZone()
update();
}
-void CustomRuler::updatePreview(int frame, bool rendered, bool refresh)
+bool CustomRuler::updatePreview(int frame, bool rendered, bool refresh)
{
+ bool result = false;
if (rendered) {
m_renderingPreviews << frame;
m_dirtyRenderingPreviews.removeAll(frame);
} else {
- m_renderingPreviews.removeAll(frame);
- m_dirtyRenderingPreviews << frame;
+ if (m_renderingPreviews.removeAll(frame) > 0) {
+ m_dirtyRenderingPreviews << frame;
+ result = true;
+ }
}
if (refresh)
update(frame * m_factor - offset(), MAX_HEIGHT - 3, KdenliveSettings::timelinechunks() * m_factor + 1, 3);
+ return result;
}
void CustomRuler::updatePreviewDisplay(int start, int end)
@@ -553,9 +557,10 @@ bool CustomRuler::hasPreviewRange() const
return (!m_dirtyRenderingPreviews.isEmpty() || !m_renderingPreviews.isEmpty());
}
-void CustomRuler::addChunks(QList <int> chunks, bool add)
+QList <int> CustomRuler::addChunks(QList <int> chunks, bool add)
{
qSort(chunks);
+ QList <int> toProcess;
if (add) {
foreach(int frame, chunks) {
if (m_renderingPreviews.contains(frame)) {
@@ -569,10 +574,14 @@ void CustomRuler::addChunks(QList <int> chunks, bool add)
}
} else {
foreach(int frame, chunks) {
- m_renderingPreviews.removeAll(frame);
+ if (m_renderingPreviews.removeAll(frame) > 0) {
+ // A preview file existed for this chunk, ask deletion
+ toProcess << frame;
+ }
m_dirtyRenderingPreviews.removeAll(frame);
}
}
update(chunks.first() * m_factor - offset(), MAX_HEIGHT - 3, (chunks.last() - chunks.first()) * KdenliveSettings::timelinechunks() * m_factor + 1, 3);
+ return toProcess;
}
diff --git a/src/timeline/customruler.h b/src/timeline/customruler.h
index 3c609fd..5602c91 100644
--- a/src/timeline/customruler.h
+++ b/src/timeline/customruler.h
@@ -50,12 +50,12 @@ public:
void updateProjectFps(const Timecode &t);
void updateFrameSize();
void activateZone();
- void updatePreview(int frame, bool rendered = true, bool refresh = false);
+ bool updatePreview(int frame, bool rendered = true, bool refresh = false);
/** @brief Returns a list of rendered timeline preview chunks */
const QStringList previewChunks() const;
/** @brief Returns a list of dirty timeline preview chunks (that need to be generated) */
const QList <int> getDirtyChunks() const;
- void addChunks(QList <int> chunks, bool add);
+ QList <int> addChunks(QList <int> chunks, bool add);
/** @brief Returns true if a timeline preview zone has already be defined */
bool hasPreviewRange() const;
/** @brief Refresh timeline preview range */
diff --git a/src/timeline/managers/previewmanager.cpp b/src/timeline/managers/previewmanager.cpp
new file mode 100644
index 0000000..ed19147
--- /dev/null
+++ b/src/timeline/managers/previewmanager.cpp
@@ -0,0 +1,307 @@
+/***************************************************************************
+ * Copyright (C) 2016 by Jean-Baptiste Mardelle ([email protected]) *
+ * *
+ * 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. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program; if not, write to the *
+ * Free Software Foundation, Inc., *
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *
+ ***************************************************************************/
+
+
+#include "previewmanager.h"
+#include "../customruler.h"
+#include "kdenlivesettings.h"
+#include "doc/kdenlivedoc.h"
+
+#include <QtConcurrent>
+#include <QStandardPaths>
+#include <QProcess>
+
+PreviewManager::PreviewManager(KdenliveDoc *doc, CustomRuler *ruler) : QObject()
+ , m_doc(doc)
+ , m_ruler(ruler)
+ , m_initialized(false)
+ , m_abortPreview(false)
+{
+}
+
+PreviewManager::~PreviewManager()
+{
+ if (m_initialized) {
+ abortRendering();
+ m_undoDir.removeRecursively();
+ if (m_cacheDir.entryList(QDir::NoDotAndDotDot).count() == 0) {
+ m_cacheDir.removeRecursively();
+ }
+ }
+}
+
+bool PreviewManager::initialize()
+{
+ QString documentId = m_doc->getDocumentProperty(QStringLiteral("documentid"));
+ m_initialized = true;
+ if (documentId.isEmpty() || documentId.toLong() == 0) {
+ // Something is wrong, documentId should be a number (ms since epoch), abort
+ return false;
+ }
+ QString cacheDir = m_doc->getDocumentProperty(QStringLiteral("cachedir"));
+ if (!cacheDir.isEmpty() && QFile::exists(cacheDir)) {
+ m_cacheDir = QDir(cacheDir);
+ } else {
+ m_cacheDir = QDir(QStandardPaths::writableLocation(QStandardPaths::CacheLocation));
+ m_cacheDir.mkdir(documentId);
+ if (!m_cacheDir.cd(documentId)) {
+ return false;
+ }
+ }
+ if (m_cacheDir.dirName() != documentId || (!m_cacheDir.exists("undo") && !m_cacheDir.mkdir("undo"))) {
+ // TODO: cannot create undo folder, abort
+ return false;
+ }
+ if (!loadParams()) {
+ return false;
+ }
+ m_doc->setDocumentProperty(QStringLiteral("cachedir"), m_cacheDir.absolutePath());
+ m_undoDir = QDir(m_cacheDir.absoluteFilePath("undo"));
+ connect(this, &PreviewManager::cleanupOldPreviews, this, &PreviewManager::doCleanupOldPreviews);
+ connect(m_doc, &KdenliveDoc::removeInvalidUndo, this, &PreviewManager::slotRemoveInvalidUndo);
+ m_previewTimer.setSingleShot(true);
+ m_previewTimer.setInterval(3000);
+ connect(&m_previewTimer, &QTimer::timeout, this, &PreviewManager::startPreviewRender);
+ m_initialized = true;
+ return true;
+}
+
+bool PreviewManager::loadParams()
+{
+ m_extension= m_doc->getDocumentProperty(QStringLiteral("previewextension"));
+ m_consumerParams = m_doc->getDocumentProperty(QStringLiteral("previewparameters")).split(" ");
+
+ if (m_consumerParams.isEmpty() || m_extension.isEmpty()) {
+ m_doc->selectPreviewProfile();
+ m_consumerParams = m_doc->getDocumentProperty(QStringLiteral("previewparameters")).split(" ");
+ m_extension= m_doc->getDocumentProperty(QStringLiteral("previewextension"));
+ }
+ if (m_consumerParams.isEmpty() || m_extension.isEmpty()) {
+ return false;
+ }
+ m_consumerParams << "an=1";
+ return true;
+}
+
+void PreviewManager::invalidatePreviews(QList <int> chunks)
+{
+ // We are not at the bottom of undo stack, chunks have already been archived previously
+ QMutexLocker lock(&m_previewMutex);
+ m_previewTimer.stop();
+ int stackIx = m_doc->commandStack()->index();
+ int stackMax = m_doc->commandStack()->count();
+ abortRendering();
+ if (stackIx == stackMax && !m_undoDir.exists(QString::number(stackIx - 1))) {
+ // We just added a new command in stack, archive existing chunks
+ int ix = stackIx - 1;
+ m_undoDir.mkdir(QString::number(ix));
+ bool foundPreviews = false;
+ foreach(int i, chunks) {
+ QString current = QString("%1.%2").arg(i).arg(m_extension);
+ if (m_cacheDir.rename(current, QString("undo/%1/%2").arg(ix).arg(current))) {
+ foundPreviews = true;
+ }
+ }
+ if (!foundPreviews) {
+ // No preview files found, remove undo folder
+ m_undoDir.rmdir(QString::number(ix));
+ } else {
+ // new chunks archived, cleanup old ones
+ emit cleanupOldPreviews();
+ }
+ } else {
+ // Restore existing chunks, delete others
+ // Check if we just undo the last stack action, then backup, otherwise delete
+ bool lastUndo = false;
+ if (stackIx == stackMax - 1) {
+ if (!m_undoDir.exists(QString::number(stackMax))) {
+ lastUndo = true;
+ bool foundPreviews = false;
+ m_undoDir.mkdir(QString::number(stackMax));
+ foreach(int i, chunks) {
+ QString current = QString("%1.%2").arg(i).arg(m_extension);
+ if (m_cacheDir.rename(current, QString("undo/%1/%2").arg(stackMax).arg(current))) {
+ foundPreviews = true;
+ }
+ }
+ if (!foundPreviews) {
+ m_undoDir.rmdir(QString::number(stackMax));
+ }
+ }
+ }
+ bool moveFile = true;
+ QDir tmpDir = m_undoDir;
+ if (!tmpDir.cd(QString::number(stackIx))) {
+ moveFile = false;
+ }
+ QList <int> foundChunks;
+ foreach(int i, chunks) {
+ QString cacheFileName = QString("%1.%2").arg(i).arg(m_extension);
+ if (!lastUndo) {
+ m_cacheDir.remove(cacheFileName);
+ }
+ if (moveFile) {
+ if (QFile::copy(tmpDir.absoluteFilePath(cacheFileName), m_cacheDir.absoluteFilePath(cacheFileName))) {
+ foundChunks << i;
+ }
+ }
+ }
+ qSort(foundChunks);
+ emit reloadChunks(m_cacheDir, foundChunks, m_extension);
+ }
+ m_doc->setModified(true);
+}
+
+void PreviewManager::doCleanupOldPreviews()
+{
+ QStringList dirs = m_undoDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
+ qSort(dirs);
+ while (dirs.count() > 5) {
+ QString dir = dirs.takeFirst();
+ QDir tmp = m_undoDir;
+ if (tmp.cd(dir)) {
+ tmp.removeRecursively();
+ }
+ }
+}
+
+void PreviewManager::addPreviewRange(bool add)
+{
+ QPoint p = m_doc->zone();
+ int chunkSize = KdenliveSettings::timelinechunks();
+ int startChunk = p.x() / chunkSize;
+ int endChunk = rintl(p.y() / chunkSize);
+ QList <int> frames;
+ for (int i = startChunk; i <= endChunk; i++) {
+ frames << i * chunkSize;
+ }
+ QList <int> toProcess = m_ruler->addChunks(frames, add);
+ if (toProcess.isEmpty())
+ return;
+ if (add) {
+ if (KdenliveSettings::autopreview())
+ m_previewTimer.start();
+ } else {
+ // Remove processed chunks
+ foreach(int ix, toProcess) {
+ m_cacheDir.remove(QString("%1.%2").arg(ix).arg(m_extension));
+ }
+ }
+}
+
+void PreviewManager::abortRendering()
+{
+ if (!m_previewThread.isRunning())
+ return;
+ m_abortPreview = true;
+ emit abortPreview();
+ m_previewThread.waitForFinished();
+}
+
+void PreviewManager::startPreviewRender()
+{
+ if (!m_ruler->hasPreviewRange() && !KdenliveSettings::autopreview()) {
+ addPreviewRange(true);
+ }
+ QList <int> chunks = m_ruler->getDirtyChunks();
+ if (!chunks.isEmpty()) {
+ // Abort any rendering
+ abortRendering();
+ const QString sceneList = m_cacheDir.absoluteFilePath(QStringLiteral("preview.mlt"));
+ m_doc->saveMltPlaylist(sceneList);
+ m_previewThread = QtConcurrent::run(this, &PreviewManager::doPreviewRender, sceneList, chunks);
+ }
+}
+
+void PreviewManager::doPreviewRender(QString scene, QList <int> chunks)
+{
+ int progress;
+ int chunkSize = KdenliveSettings::timelinechunks();
+ // initialize progress bar
+ emit previewRender(0, QString(), 0);
+ int ct = 0;
+ qSort(chunks);
+ while (!chunks.isEmpty()) {
+ int i = chunks.takeFirst();
+ ct++;
+ QString fileName = QString("%1.%2").arg(i).arg(m_extension);
+ if (chunks.isEmpty()) {
+ progress = 1000;
+ } else {
+ progress = (double) (ct) / (ct + chunks.count()) * 1000;
+ }
+ if (m_cacheDir.exists(fileName)) {
+ // This chunk already exists
+ emit previewRender(i, m_cacheDir.absoluteFilePath(fileName), progress);
+ continue;
+ }
+ // Build rendering process
+ QStringList args;
+ args << scene;
+ args << "in=" + QString::number(i);
+ args << "out=" + QString::number(i + chunkSize - 1);
+ args << "-consumer" << "avformat:" + m_cacheDir.absoluteFilePath(fileName);
+ args << m_consumerParams;
+ QProcess previewProcess;
+ connect(this, SIGNAL(abortPreview()), &previewProcess, SLOT(kill()), Qt::DirectConnection);
+ previewProcess.start(KdenliveSettings::rendererpath(), args);
+ if (previewProcess.waitForStarted()) {
+ previewProcess.waitForFinished(-1);
+ if (previewProcess.exitStatus() != QProcess::NormalExit) {
+ // Something went wrong
+ if (m_abortPreview) {
+ emit previewRender(0, QString(), 1000);
+ } else {
+ qDebug()<<"+++++++++\n++ ERROR ++\n++++++";
+ emit previewRender(i, QString(), -1);
+ }
+ QFile::remove(m_cacheDir.absoluteFilePath(fileName));
+ break;
+ } else {
+ emit previewRender(i, m_cacheDir.absoluteFilePath(fileName), progress);
+ }
+ }
+ }
+ QFile::remove(scene);
+ m_abortPreview = false;
+}
+
+void PreviewManager::slotProcessDirtyChunks()
+{
+ QList <int> chunks = m_ruler->getDirtyChunks();
+ invalidatePreviews(chunks);
+ m_ruler->updatePreviewDisplay(chunks.first(), chunks.last());
+ if (KdenliveSettings::autopreview())
+ m_previewTimer.start();
+}
+
+void PreviewManager::slotRemoveInvalidUndo(int ix)
+{
+ QStringList dirs = m_undoDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
+ qSort(dirs);
+ foreach(const QString dir, dirs) {
+ if (dir.toInt() >= ix) {
+ QDir tmp = m_undoDir;
+ if (tmp.cd(dir)) {
+ tmp.removeRecursively();
+ }
+ }
+ }
+}
+
diff --git a/src/timeline/managers/previewmanager.h b/src/timeline/managers/previewmanager.h
new file mode 100644
index 0000000..47f0ada
--- /dev/null
+++ b/src/timeline/managers/previewmanager.h
@@ -0,0 +1,82 @@
+/***************************************************************************
+ * Copyright (C) 2016 by Jean-Baptiste Mardelle ([email protected]) *
+ * *
+ * 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. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program; if not, write to the *
+ * Free Software Foundation, Inc., *
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *
+ ***************************************************************************/
+
+#ifndef PREVIEWMANAGER_H
+#define PREVIEWMANAGER_H
+
+#include "definitions.h"
+
+#include <QDir>
+#include <QMutex>
+#include <QTimer>
+#include <QFuture>
+
+class KdenliveDoc;
+class CustomRuler;
+
+/**
+ * @namespace PreviewManager
+ * @brief Handles timeline preview.
+ */
+
+class PreviewManager : public QObject
+{
+ Q_OBJECT
+
+public:
+ explicit PreviewManager(KdenliveDoc *doc, CustomRuler *ruler);
+ virtual ~PreviewManager();
+ /** @brief: initialize base variables, return false if error. */
+ bool initialize();
+ void invalidatePreviews(QList <int> chunks);
+ void addPreviewRange(bool add);
+ void abortRendering();
+ bool loadParams();
+
+private:
+ KdenliveDoc *m_doc;
+ CustomRuler *m_ruler;
+ QDir m_cacheDir;
+ QDir m_undoDir;
+ QMutex m_previewMutex;
+ QStringList m_consumerParams;
+ QString m_extension;
+ QTimer m_previewTimer;
+ bool m_initialized;
+ bool m_abortPreview;
+ QFuture <void> m_previewThread;
+
+private slots:
+ void doCleanupOldPreviews();
+ void doPreviewRender(QString scene, QList <int> chunks);
+ void slotRemoveInvalidUndo(int ix);
+
+public slots:
+ void slotProcessDirtyChunks();
+ void startPreviewRender();
+
+signals:
+ void abortPreview();
+ void cleanupOldPreviews();
+ void previewRender(int frame, const QString &file, int progress);
+ void reloadChunks(QDir, QList <int>, const QString ext);
+};
+
+#endif
+
diff --git a/src/timeline/timeline.cpp b/src/timeline/timeline.cpp
index e16a954..f209775 100644
--- a/src/timeline/timeline.cpp
+++ b/src/timeline/timeline.cpp
@@ -40,6 +40,7 @@
#include "project/clipmanager.h"
#include "effectslist/initeffects.h"
#include "mltcontroller/effectscontroller.h"
+#include "managers/previewmanager.h"
#include <QScrollBar>
#include <QLocale>
@@ -60,6 +61,7 @@ Timeline::Timeline(KdenliveDoc *doc, const QList<QAction *> &actions, const QLis
, m_scale(1.0)
, m_doc(doc)
, m_verticalZoom(1)
+ , m_timelinePreview(NULL)
{
m_trackActions << actions;
setupUi(this);
@@ -142,18 +144,17 @@ Timeline::Timeline(KdenliveDoc *doc, const QList<QAction *> &actions, const QLis
connect(m_trackview->horizontalScrollBar(), SIGNAL(valueChanged(int)), m_ruler, SLOT(slotMoveRuler(int)));
connect(m_trackview->horizontalScrollBar(), SIGNAL(rangeChanged(int,int)), this, SLOT(slotUpdateVerticalScroll(int,int)));
connect(m_trackview, SIGNAL(mousePosition(int)), this, SIGNAL(mousePosition(int)));
- connect(m_doc->renderer(), &Render::previewRender, this, &Timeline::gotPreviewRender);
- connect(m_doc, &KdenliveDoc::reloadChunks, this, &Timeline::slotReloadChunks);
- m_previewTimer.setSingleShot(true);
- m_previewTimer.setInterval(3000);
- connect(&m_previewTimer, &QTimer::timeout, this, &Timeline::startPreviewRender);
+
+ // Timeline preview stuff
+ initializePreview();
m_previewGatherTimer.setSingleShot(true);
m_previewGatherTimer.setInterval(200);
- connect(&m_previewGatherTimer, &QTimer::timeout, this, &Timeline::slotProcessDirtyChunks);
}
Timeline::~Timeline()
{
+ if (m_timelinePreview)
+ delete m_timelinePreview;
delete m_ruler;
delete m_trackview;
delete m_scene;
@@ -1787,46 +1788,12 @@ void Timeline::gotPreviewRender(int frame, const QString &file, int progress)
m_doc->setModified(true);
}
-void Timeline::addPreviewRange(bool add)
-{
- QPoint p = m_doc->zone();
- int chunkSize = KdenliveSettings::timelinechunks();
- int startChunk = p.x() / chunkSize;
- int endChunk = rintl(p.y() / chunkSize);
- QList <int> frames;
- for (int i = startChunk; i <= endChunk; i++) {
- frames << i * chunkSize;
- }
- m_ruler->addChunks(frames, add);
- if (add && KdenliveSettings::autopreview())
- m_previewTimer.start();
-}
-void Timeline::startPreviewRender()
-{
- if (!m_ruler->hasPreviewRange() && !KdenliveSettings::autopreview()) {
- addPreviewRange(true);
- }
- QList <int> chunks = m_ruler->getDirtyChunks();
- if (!chunks.isEmpty()) {
- QDir dir(QStandardPaths::writableLocation(QStandardPaths::CacheLocation));
- QString documentId = m_doc->getDocumentProperty(QStringLiteral("documentid"));
- QString docParams = m_doc->getDocumentProperty(QStringLiteral("previewparameters"));
- if (docParams.isEmpty()) {
- m_doc->selectPreviewProfile();
- docParams = m_doc->getDocumentProperty(QStringLiteral("previewparameters"));
- }
- if (docParams.isEmpty()) {
- KMessageBox::sorry(this, i18n("No available preview profile found, please check the Timeline Settings"));
- return;
- }
- m_doc->renderer()->previewRendering(chunks, dir.absoluteFilePath(documentId), docParams.split(" "), m_doc->getDocumentProperty(QStringLiteral("previewextension")));
- }
-}
void Timeline::stopPreviewRender()
{
- m_doc->renderer()->abortPreview();
+ if (m_timelinePreview)
+ m_timelinePreview->abortRendering();
}
void Timeline::invalidateRange(ItemInfo info)
@@ -1847,34 +1814,27 @@ void Timeline::invalidatePreview(int startFrame, int endFrame)
int chunkSize = KdenliveSettings::timelinechunks();
int start = startFrame / chunkSize;
int end = lrintf(endFrame / chunkSize);
+ m_timelinePreview->abortPreview();
Mlt::Producer *overlayTrack = m_tractor->track(tracksCount());
m_tractor->lock();
Mlt::Playlist trackPlaylist((mlt_playlist) overlayTrack->get_service());
delete overlayTrack;
- QList <int> list;
- for (int i = start; i <=end; i++) {
- int ix = trackPlaylist.get_clip_index_at(chunkSize * i);
- if (trackPlaylist.is_blank(ix))
- continue;
- list << i * chunkSize;
- Mlt::Producer *prod = trackPlaylist.replace_with_blank(ix);
- delete prod;
- m_ruler->updatePreview(i * chunkSize, false);
+ start *= chunkSize;
+ end *= chunkSize;
+ for (int i = start; i <=end; i+= chunkSize) {
+ if (m_ruler->updatePreview(i, false)) {
+ int ix = trackPlaylist.get_clip_index_at(i);
+ if (trackPlaylist.is_blank(ix))
+ continue;
+ Mlt::Producer *prod = trackPlaylist.replace_with_blank(ix);
+ delete prod;
+ }
}
trackPlaylist.consolidate_blanks();
m_tractor->unlock();
m_previewGatherTimer.start();
}
-void Timeline::slotProcessDirtyChunks()
-{
- QList <int> chunks = m_ruler->getDirtyChunks();
- m_doc->invalidatePreviews(chunks);
- m_ruler->updatePreviewDisplay(chunks.first(), chunks.last());
- if (KdenliveSettings::autopreview())
- m_previewTimer.start();
-}
-
void Timeline::loadPreviewRender()
{
QString documentId = m_doc->getDocumentProperty(QStringLiteral("documentid"));
@@ -1929,27 +1889,26 @@ void Timeline::updatePreviewSettings(const QString &profile)
invalidateRange(ItemInfo());
m_doc->setDocumentProperty(QStringLiteral("previewparameters"), params);
m_doc->setDocumentProperty(QStringLiteral("previewextension"), ext);
+ if (m_timelinePreview) {
+ if (!m_timelinePreview->loadParams()) {
+ delete m_timelinePreview;
+ m_timelinePreview = NULL;
+ }
+ } else {
+ initializePreview();
+ }
}
}
-void Timeline::slotReloadChunks(QList <int> chunks)
+void Timeline::slotReloadChunks(QDir cacheDir, QList <int> chunks, const QString ext)
{
- bool timer = false;
- if (m_previewTimer.isActive()) {
- m_previewTimer.stop();
- timer = true;
- }
- QString documentId = m_doc->getDocumentProperty(QStringLiteral("documentid"));
- QString ext = m_doc->getDocumentProperty(QStringLiteral("previewextension"));
- QDir dir(QStandardPaths::writableLocation(QStandardPaths::CacheLocation));
- dir.cd(documentId);
Mlt::Producer *overlayTrack = m_tractor->track(tracksCount());
m_tractor->lock();
Mlt::Playlist trackPlaylist((mlt_playlist) overlayTrack->get_service());
delete overlayTrack;
foreach(int ix, chunks) {
if (trackPlaylist.is_blank_at(ix)) {
- const QString fileName = dir.absoluteFilePath(QString("%1.%2").arg(ix).arg(ext));
+ const QString fileName = cacheDir.absoluteFilePath(QString("%1.%2").arg(ix).arg(ext));
Mlt::Producer prod(*m_tractor->profile(), 0, fileName.toUtf8().constData());
if (prod.is_valid()) {
m_ruler->updatePreview(ix, true);
@@ -1961,8 +1920,6 @@ void Timeline::slotReloadChunks(QList <int> chunks)
m_ruler->updatePreviewDisplay(chunks.first(), chunks.last());
trackPlaylist.consolidate_blanks();
m_tractor->unlock();
- if (timer)
- m_previewTimer.start();
}
void Timeline::invalidateTrack(int ix)
@@ -1975,3 +1932,31 @@ void Timeline::invalidateTrack(int ix)
invalidatePreview(p.x(), p.y());
}
}
+
+void Timeline::initializePreview()
+{
+ m_timelinePreview = new PreviewManager(m_doc, m_ruler);
+ if (!m_timelinePreview->initialize()) {
+ //TODO warn user
+ delete m_timelinePreview;
+ m_timelinePreview = NULL;
+ qDebug()<<" * * * *TL PREVIEW NOT INITIALIZED!!!";
+ } else {
+ connect(&m_previewGatherTimer, &QTimer::timeout, m_timelinePreview, &PreviewManager::slotProcessDirtyChunks);
+ connect(m_timelinePreview, &PreviewManager::previewRender, this, &Timeline::gotPreviewRender);
+ connect(m_timelinePreview, &PreviewManager::reloadChunks, this, &Timeline::slotReloadChunks, Qt::DirectConnection);
+ }
+
+}
+
+void Timeline::startPreviewRender()
+{
+ if (m_timelinePreview)
+ m_timelinePreview->startPreviewRender();
+}
+
+void Timeline::addPreviewRange(bool add)
+{
+ if (m_timelinePreview)
+ m_timelinePreview->addPreviewRange(add);
+}
diff --git a/src/timeline/timeline.h b/src/timeline/timeline.h
index e4bfef1..b10b629 100644
--- a/src/timeline/timeline.h
+++ b/src/timeline/timeline.h
@@ -35,6 +35,7 @@
#include <QGraphicsLineItem>
#include <QDomElement>
#include <QTimer>
+#include <QDir>
#include <mlt++/Mlt.h>
@@ -45,6 +46,7 @@ class KdenliveDoc;
class TransitionHandler;
class CustomRuler;
class QUndoCommand;
+class PreviewManager;
class Timeline : public QWidget, public Ui::TimeLine_UI
{
@@ -171,6 +173,8 @@ public:
void updatePreviewSettings(const QString &profile);
/** @brief invalidate timeline preview for visible clips in a track */
void invalidateTrack(int ix);
+ /** @brief Start rendering preview rendering range. */
+ void startPreviewRender();
protected:
void keyPressEvent(QKeyEvent * event);
@@ -187,8 +191,6 @@ public slots:
void updateProfile(bool fpsChanged);
/** @brief Enable/disable multitrack view (split monitor in 4) */
void slotMultitrackView(bool enable);
- /** @brief Start rendering preview rendering range. */
- void startPreviewRender();
/** @brief Stop rendering preview. */
void stopPreviewRender();
@@ -213,7 +215,7 @@ private:
QList <QAction *> m_trackActions;
/** @brief sometimes grouped commands quickly send invalidate commands, so wait a little bit before processing*/
QTimer m_previewGatherTimer;
- QTimer m_previewTimer;
+ PreviewManager *m_timelinePreview;
void adjustTrackHeaders();
@@ -228,6 +230,7 @@ private:
void refreshTrackActions();
/** @brief load existing timeline previews */
void loadPreviewRender();
+ void initializePreview();
private slots:
void slotSwitchTrackComposite(int trackIndex, bool enable);
@@ -255,8 +258,7 @@ private slots:
void slotEnableZone(bool enable);
void gotPreviewRender(int frame, const QString &file, int progress);
void invalidatePreview(int startFrame, int endFrame);
- void slotReloadChunks(QList <int> chunks);
- void slotProcessDirtyChunks();
+ void slotReloadChunks(QDir cacheDir, QList <int> chunks, const QString ext);
signals:
void mousePosition(int);