Timeline zone fine tuning

Ref: T1949
parent 410f363e
......@@ -92,7 +92,6 @@ KdenliveDoc::KdenliveDoc(const QUrl &url, const QUrl &projectFolder, QUndoGroup
m_height(0),
m_render(render),
m_notesWidget(notes->widget()),
// m_commandStack(new DocUndoStack(undoGroup)),
m_modified(false),
m_projectFolder(projectFolder)
{
......
......@@ -510,6 +510,19 @@ void CustomRuler::activateZone()
update();
}
bool CustomRuler::isUnderPreview(int start, int end)
{
QList <int> allPreviews;
allPreviews << m_renderingPreviews << m_dirtyRenderingPreviews;
qSort(allPreviews);
foreach (int ix, allPreviews) {
if (ix >= start && ix <= end) {
return true;
}
}
return false;
}
bool CustomRuler::updatePreview(int frame, bool rendered, bool refresh)
{
bool result = false;
......@@ -522,6 +535,8 @@ bool CustomRuler::updatePreview(int frame, bool rendered, bool refresh)
result = true;
}
}
std::sort(m_renderingPreviews.begin(), m_renderingPreviews.end());
std::sort(m_dirtyRenderingPreviews.begin(), m_dirtyRenderingPreviews.end());
if (refresh)
update(frame * m_factor - offset(), MAX_HEIGHT - 3, KdenliveSettings::timelinechunks() * m_factor + 1, 3);
return result;
......@@ -583,6 +598,8 @@ QList <int> CustomRuler::addChunks(QList <int> chunks, bool add)
m_dirtyRenderingPreviews.removeAll(frame);
}
}
std::sort(m_renderingPreviews.begin(), m_renderingPreviews.end());
std::sort(m_dirtyRenderingPreviews.begin(), m_dirtyRenderingPreviews.end());
update(chunks.first() * m_factor - offset(), MAX_HEIGHT - 3, (chunks.last() - chunks.first()) * KdenliveSettings::timelinechunks() * m_factor + 1, 3);
return toProcess;
}
......
......@@ -60,6 +60,7 @@ public:
bool hasPreviewRange() const;
/** @brief Refresh timeline preview range */
void updatePreviewDisplay(int start, int end);
bool isUnderPreview(int start, int end);
protected:
void paintEvent(QPaintEvent * /*e*/);
......
......@@ -27,12 +27,16 @@
#include <QStandardPaths>
#include <QProcess>
PreviewManager::PreviewManager(KdenliveDoc *doc, CustomRuler *ruler) : QObject()
PreviewManager::PreviewManager(KdenliveDoc *doc, CustomRuler *ruler, Mlt::Tractor *tractor) : QObject()
, m_doc(doc)
, m_ruler(ruler)
, m_tractor(tractor)
, m_previewTrack(NULL)
, m_initialized(false)
, m_abortPreview(false)
{
m_previewGatherTimer.setSingleShot(true);
m_previewGatherTimer.setInterval(200);
}
PreviewManager::~PreviewManager()
......@@ -44,6 +48,7 @@ PreviewManager::~PreviewManager()
m_cacheDir.removeRecursively();
}
}
delete m_previewTrack;
}
bool PreviewManager::initialize()
......@@ -74,14 +79,46 @@ bool PreviewManager::initialize()
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);
connect(m_doc, &KdenliveDoc::removeInvalidUndo, this, &PreviewManager::slotRemoveInvalidUndo, Qt::DirectConnection);
m_previewTimer.setSingleShot(true);
m_previewTimer.setInterval(3000);
connect(&m_previewTimer, &QTimer::timeout, this, &PreviewManager::startPreviewRender);
connect(this, &PreviewManager::previewRender, this, &PreviewManager::gotPreviewRender);
connect(&m_previewGatherTimer, &QTimer::timeout, this, &PreviewManager::slotProcessDirtyChunks);
m_initialized = true;
return true;
}
bool PreviewManager::buildPreviewTrack()
{
if (m_previewTrack)
return false;
// Create overlay track
m_previewTrack = new Mlt::Playlist(*m_tractor->profile());
int trackIndex = m_tractor->count();
m_tractor->lock();
m_tractor->insert_track(*m_previewTrack, trackIndex);
Mlt::Producer *tk = m_tractor->track(trackIndex);
tk->set("hide", 2);
delete tk;
m_tractor->unlock();
return true;
}
void PreviewManager::reconnectTrack()
{
if (m_previewTrack) {
m_tractor->insert_track(*m_previewTrack, m_tractor->count());
}
}
void PreviewManager::disconnectTrack()
{
if (m_previewTrack) {
m_tractor->remove_track(m_tractor->count() - 1);
}
}
bool PreviewManager::loadParams()
{
m_extension= m_doc->getDocumentProperty(QStringLiteral("previewextension"));
......@@ -110,7 +147,6 @@ void PreviewManager::invalidatePreviews(QList <int> chunks)
}
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;
......@@ -167,7 +203,7 @@ void PreviewManager::invalidatePreviews(QList <int> chunks)
}
}
qSort(foundChunks);
emit reloadChunks(m_cacheDir, foundChunks, m_extension);
slotReloadChunks(m_cacheDir, foundChunks, m_extension);
}
m_doc->setModified(true);
if (timer)
......@@ -201,8 +237,10 @@ void PreviewManager::addPreviewRange(bool add)
if (toProcess.isEmpty())
return;
if (add) {
//TODO: optimize, don't abort rendering process, just add required frames
if (KdenliveSettings::autopreview())
if (m_previewThread.isRunning()) {
// just add required frames to current rendering job
m_waitingThumbs << toProcess;
} else if (KdenliveSettings::autopreview())
m_previewTimer.start();
} else {
// Remove processed chunks
......@@ -230,28 +268,30 @@ void PreviewManager::startPreviewRender()
if (!chunks.isEmpty()) {
// Abort any rendering
abortRendering();
m_waitingThumbs.clear();
const QString sceneList = m_cacheDir.absoluteFilePath(QStringLiteral("preview.mlt"));
m_doc->saveMltPlaylist(sceneList);
m_previewThread = QtConcurrent::run(this, &PreviewManager::doPreviewRender, sceneList, chunks);
m_waitingThumbs = chunks;
m_previewThread = QtConcurrent::run(this, &PreviewManager::doPreviewRender, sceneList);
}
}
void PreviewManager::doPreviewRender(QString scene, QList <int> chunks)
void PreviewManager::doPreviewRender(QString scene)
{
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();
qSort(m_waitingThumbs);
while (!m_waitingThumbs.isEmpty()) {
int i = m_waitingThumbs.takeFirst();
ct++;
QString fileName = QString("%1.%2").arg(i).arg(m_extension);
if (chunks.isEmpty()) {
if (m_waitingThumbs.isEmpty()) {
progress = 1000;
} else {
progress = (double) (ct) / (ct + chunks.count()) * 1000;
progress = (double) (ct) / (ct + m_waitingThumbs.count()) * 1000;
}
if (m_cacheDir.exists(fileName)) {
// This chunk already exists
......@@ -292,6 +332,8 @@ void PreviewManager::doPreviewRender(QString scene, QList <int> chunks)
void PreviewManager::slotProcessDirtyChunks()
{
QList <int> chunks = m_ruler->getDirtyChunks();
if (chunks.isEmpty())
return;
invalidatePreviews(chunks);
m_ruler->updatePreviewDisplay(chunks.first(), chunks.last());
if (KdenliveSettings::autopreview())
......@@ -300,6 +342,7 @@ void PreviewManager::slotProcessDirtyChunks()
void PreviewManager::slotRemoveInvalidUndo(int ix)
{
QMutexLocker lock(&m_previewMutex);
QStringList dirs = m_undoDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
qSort(dirs);
foreach(const QString dir, dirs) {
......@@ -312,3 +355,69 @@ void PreviewManager::slotRemoveInvalidUndo(int ix)
}
}
void PreviewManager::invalidatePreview(int startFrame, int endFrame)
{
int chunkSize = KdenliveSettings::timelinechunks();
int start = startFrame / chunkSize;
int end = lrintf(endFrame / chunkSize);
start *= chunkSize;
end *= chunkSize;
if (!m_ruler->isUnderPreview(start, end)) {
return;
}
m_previewGatherTimer.stop();
abortPreview();
m_tractor->lock();
for (int i = start; i <=end; i+= chunkSize) {
if (m_ruler->updatePreview(i, false)) {
int ix = m_previewTrack->get_clip_index_at(i);
if (m_previewTrack->is_blank(ix))
continue;
Mlt::Producer *prod = m_previewTrack->replace_with_blank(ix);
delete prod;
}
}
m_previewTrack->consolidate_blanks();
m_tractor->unlock();
m_previewGatherTimer.start();
}
void PreviewManager::slotReloadChunks(QDir cacheDir, QList <int> chunks, const QString ext)
{
m_tractor->lock();
foreach(int ix, chunks) {
if (m_previewTrack->is_blank_at(ix)) {
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);
prod.set("mlt_service", "avformat-novalidate");
m_previewTrack->insert_at(ix, &prod, 1);
}
}
}
m_ruler->updatePreviewDisplay(chunks.first(), chunks.last());
m_previewTrack->consolidate_blanks();
m_tractor->unlock();
}
void PreviewManager::gotPreviewRender(int frame, const QString &file, int progress)
{
if (file.isEmpty()) {
m_doc->previewProgress(progress);
return;
}
m_tractor->lock();
if (m_previewTrack->is_blank_at(frame)) {
Mlt::Producer prod(*m_tractor->profile(), 0, file.toUtf8().constData());
if (prod.is_valid()) {
m_ruler->updatePreview(frame, true, true);
prod.set("mlt_service", "avformat-novalidate");
m_previewTrack->insert_at(frame, &prod, 1);
}
}
m_previewTrack->consolidate_blanks();
m_tractor->unlock();
m_doc->previewProgress(progress);
m_doc->setModified(true);
}
......@@ -30,6 +30,11 @@
class KdenliveDoc;
class CustomRuler;
namespace Mlt {
class Tractor;
class Playlist;
}
/**
* @namespace PreviewManager
* @brief Handles timeline preview.
......@@ -40,7 +45,7 @@ class PreviewManager : public QObject
Q_OBJECT
public:
explicit PreviewManager(KdenliveDoc *doc, CustomRuler *ruler);
explicit PreviewManager(KdenliveDoc *doc, CustomRuler *ruler, Mlt::Tractor *tractor);
virtual ~PreviewManager();
/** @brief: initialize base variables, return false if error. */
bool initialize();
......@@ -48,34 +53,43 @@ public:
void addPreviewRange(bool add);
void abortRendering();
bool loadParams();
void invalidatePreview(int startFrame, int endFrame);
bool buildPreviewTrack();
void reconnectTrack();
void disconnectTrack();
private:
KdenliveDoc *m_doc;
CustomRuler *m_ruler;
Mlt::Tractor *m_tractor;
Mlt::Playlist *m_previewTrack;
QDir m_cacheDir;
QDir m_undoDir;
QMutex m_previewMutex;
QStringList m_consumerParams;
QString m_extension;
QTimer m_previewTimer;
QTimer m_previewGatherTimer;
bool m_initialized;
bool m_abortPreview;
QList <int> m_waitingThumbs;
QFuture <void> m_previewThread;
private slots:
void doCleanupOldPreviews();
void doPreviewRender(QString scene, QList <int> chunks);
void doPreviewRender(QString scene);
void slotRemoveInvalidUndo(int ix);
void slotReloadChunks(QDir cacheDir, QList <int> chunks, const QString ext);
void slotProcessDirtyChunks();
public slots:
void slotProcessDirtyChunks();
void startPreviewRender();
void gotPreviewRender(int frame, const QString &file, int progress);
signals:
void abortPreview();
void cleanupOldPreviews();
void previewRender(int frame, const QString &file, int progress);
void reloadChunks(QDir, QList <int>, const QString ext);
};
#endif
......
......@@ -62,6 +62,7 @@ Timeline::Timeline(KdenliveDoc *doc, const QList<QAction *> &actions, const QLis
, m_doc(doc)
, m_verticalZoom(1)
, m_timelinePreview(NULL)
, m_usePreview(false)
{
m_trackActions << actions;
setupUi(this);
......@@ -147,8 +148,6 @@ Timeline::Timeline(KdenliveDoc *doc, const QList<QAction *> &actions, const QLis
// Timeline preview stuff
initializePreview();
m_previewGatherTimer.setSingleShot(true);
m_previewGatherTimer.setInterval(200);
}
Timeline::~Timeline()
......@@ -192,12 +191,12 @@ Track* Timeline::track(int i)
int Timeline::tracksCount() const
{
return m_tractor->count() - (m_hasOverlayTrack ? 1 : 0);
return m_tractor->count() - m_hasOverlayTrack - m_usePreview;
}
int Timeline::visibleTracksCount() const
{
return m_tractor->count() - 1 - (m_hasOverlayTrack ? 1 : 0);
return m_tractor->count() - 1 - m_hasOverlayTrack - m_usePreview;
}
//virtual
......@@ -1657,16 +1656,24 @@ void Timeline::slotMultitrackView(bool enable)
void Timeline::connectOverlayTrack(bool enable)
{
if (!m_hasOverlayTrack) return;
if (!m_hasOverlayTrack && !m_usePreview) return;
m_tractor->lock();
if (enable) {
// Re-add overlaytrack
m_tractor->insert_track(*m_overlayTrack, tracksCount() + 1);
delete m_overlayTrack;
m_overlayTrack = NULL;
if (m_usePreview)
m_timelinePreview->reconnectTrack();
if (m_hasOverlayTrack) {
m_tractor->insert_track(*m_overlayTrack, tracksCount() + 1);
delete m_overlayTrack;
m_overlayTrack = NULL;
}
} else {
m_overlayTrack = m_tractor->track(tracksCount());
m_tractor->remove_track(tracksCount());
if (m_usePreview)
m_timelinePreview->disconnectTrack();
if (m_hasOverlayTrack) {
m_overlayTrack = m_tractor->track(tracksCount());
m_tractor->remove_track(tracksCount());
}
}
m_tractor->unlock();
}
......@@ -1755,41 +1762,6 @@ void Timeline::slotEnableZone(bool enable)
m_ruler->activateZone();
}
void Timeline::gotPreviewRender(int frame, const QString &file, int progress)
{
if (!m_hasOverlayTrack) {
// Create overlay track
Mlt::Playlist overlay(*m_tractor->profile());
int trackIndex = tracksCount();
m_tractor->lock();
m_tractor->insert_track(overlay, trackIndex);
m_tractor->unlock();
m_hasOverlayTrack = true;
}
if (file.isEmpty()) {
m_doc->previewProgress(progress);
return;
}
Mlt::Producer *overlayTrack = m_tractor->track(tracksCount());
m_tractor->lock();
Mlt::Playlist trackPlaylist((mlt_playlist) overlayTrack->get_service());
delete overlayTrack;
if (trackPlaylist.is_blank_at(frame)) {
Mlt::Producer prod(*m_tractor->profile(), 0, file.toUtf8().constData());
if (prod.is_valid()) {
m_ruler->updatePreview(frame, true, true);
prod.set("mlt_service", "avformat-novalidate");
trackPlaylist.insert_at(frame, &prod, 1);
}
}
trackPlaylist.consolidate_blanks();
m_tractor->unlock();
m_doc->previewProgress(progress);
m_doc->setModified(true);
}
void Timeline::stopPreviewRender()
{
if (m_timelinePreview)
......@@ -1798,60 +1770,26 @@ void Timeline::stopPreviewRender()
void Timeline::invalidateRange(ItemInfo info)
{
if (!m_hasOverlayTrack)
if (!m_usePreview)
return;
if (info.isValid())
invalidatePreview(info.startPos.frames(m_doc->fps()), info.endPos.frames(m_doc->fps()));
m_timelinePreview->invalidatePreview(info.startPos.frames(m_doc->fps()), info.endPos.frames(m_doc->fps()));
else {
invalidatePreview(0, m_trackview->duration());
}
}
void Timeline::invalidatePreview(int startFrame, int endFrame)
{
if (m_previewGatherTimer.isActive())
m_previewGatherTimer.stop();
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;
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;
}
m_timelinePreview->invalidatePreview(0, m_trackview->duration());
}
trackPlaylist.consolidate_blanks();
m_tractor->unlock();
m_previewGatherTimer.start();
}
void Timeline::loadPreviewRender()
{
if (!m_timelinePreview)
return;
QString documentId = m_doc->getDocumentProperty(QStringLiteral("documentid"));
QString chunks = m_doc->getDocumentProperty(QStringLiteral("previewchunks"));
QString dirty = m_doc->getDocumentProperty(QStringLiteral("dirtypreviewchunks"));
QString ext = m_doc->getDocumentProperty(QStringLiteral("previewextension"));
QDateTime documentDate = QFileInfo(m_doc->url().path()).lastModified();
if (!chunks.isEmpty() || !dirty.isEmpty()) {
if (!m_hasOverlayTrack) {
// Create overlay track
Mlt::Playlist overlay(*m_tractor->profile());
int trackIndex = tracksCount();
m_tractor->lock();
m_tractor->insert_track(overlay, trackIndex);
m_tractor->unlock();
m_hasOverlayTrack = true;
}
m_timelinePreview->buildPreviewTrack();
QDir dir(QStandardPaths::writableLocation(QStandardPaths::CacheLocation));
dir.cd(documentId);
QStringList previewChunks = chunks.split(",", QString::SkipEmptyParts);
......@@ -1866,7 +1804,7 @@ void Timeline::loadPreviewRender()
file.remove();
dirtyChunks << frame;
} else {
gotPreviewRender(pos, fileName, 1000);
m_timelinePreview->gotPreviewRender(pos, fileName, 1000);
}
} else dirtyChunks << frame;
}
......@@ -1876,6 +1814,7 @@ void Timeline::loadPreviewRender()
}
m_ruler->update();
}
m_usePreview = true;
}
}
......@@ -1893,36 +1832,14 @@ void Timeline::updatePreviewSettings(const QString &profile)
}
}
void Timeline::slotReloadChunks(QDir cacheDir, QList <int> chunks, const QString ext)
{
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 = 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);
prod.set("mlt_service", "avformat-novalidate");
trackPlaylist.insert_at(ix, &prod, 1);
}
}
}
m_ruler->updatePreviewDisplay(chunks.first(), chunks.last());
trackPlaylist.consolidate_blanks();
m_tractor->unlock();
}
void Timeline::invalidateTrack(int ix)
{
if (!m_hasOverlayTrack)
if (!m_usePreview)
return;
Track* tk = track(ix);
QList <QPoint> visibleRange = tk->visibleClips();
foreach(const QPoint p, visibleRange) {
invalidatePreview(p.x(), p.y());
m_timelinePreview->invalidatePreview(p.x(), p.y());
}
}
......@@ -1935,27 +1852,40 @@ void Timeline::initializePreview()
m_timelinePreview = NULL;
}
} else {
m_timelinePreview = new PreviewManager(m_doc, m_ruler);
m_timelinePreview = new PreviewManager(m_doc, m_ruler, m_tractor);
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);
}
}
QAction *previewRender = m_doc->getAction(QStringLiteral("prerender_timeline_zone"));
if (previewRender)
if (previewRender) {
previewRender->setEnabled(m_timelinePreview != NULL);
}
/*if (m_timelinePreview) {
if (!m_hasOverlayTrack) {
// Create overlay track
Mlt::Playlist overlay(*m_tractor->profile());
int trackIndex = tracksCount();
m_tractor->lock();
m_tractor->insert_track(overlay, trackIndex);
m_tractor->unlock();
m_hasOverlayTrack = true;
}
}*/
}
void Timeline::startPreviewRender()
{
if (m_timelinePreview)
if (m_timelinePreview) {
if (!m_usePreview) {
m_timelinePreview->buildPreviewTrack();
m_usePreview = true;
}
m_timelinePreview->startPreviewRender();
}
}
void Timeline::addPreviewRange(bool add)
......
......@@ -34,7 +34,6 @@
#include <QGraphicsScene>
#include <QGraphicsLineItem>
#include <QDomElement>
#include <QTimer>
#include <QDir>
#include <mlt++/Mlt.h>
......@@ -214,8 +213,8 @@ private:
QString m_documentErrors;
QList <QAction *> m_trackActions;
/** @brief sometimes grouped commands quickly send invalidate commands, so wait a little bit before processing*/
QTimer m_previewGatherTimer;
PreviewManager *m_timelinePreview;