Preliminary implementation of timeline preview rendering

Available in Timeline menu > Timeline Preview Render, it renders chunks of 200 frames.
Ref: T1949
parent e6523907
......@@ -1606,3 +1606,20 @@ void KdenliveDoc::doAddAction(const QString &name, QAction *a)
{
pCore->window()->actionCollection()->addAction(name, a);
}
void KdenliveDoc::previewRender()
{
QString cacheDir = QStandardPaths::writableLocation(QStandardPaths::CacheLocation);
QString documentId = m_documentProperties.value(QStringLiteral("documentid"));
m_render->previewRendering(zone(), cacheDir, documentId);
emit progressInfo(i18n("Rendering preview"), 0);
}
void KdenliveDoc::invalidatePreviews(QList <int> chunks)
{
QDir dir(QStandardPaths::writableLocation(QStandardPaths::CacheLocation));
QString documentId = m_documentProperties.value(QStringLiteral("documentid"));
foreach(int i, chunks) {
QFile::remove(dir.absoluteFilePath(documentId + QString("-%1.mp4").arg(i)));
}
}
......@@ -159,6 +159,8 @@ public:
/** @brief Returns true if the profile file has changed. */
bool profileChanged(const QString &profile) const;
void doAddAction(const QString &name, QAction *a);
void previewRender();
void invalidatePreviews(QList <int> chunks);
private:
QUrl m_url;
......
<!DOCTYPE kpartgui SYSTEM "kpartgui.dtd">
<kpartgui name="kdenlive" version="117" translationDomain="kdenlive">
<kpartgui name="kdenlive" version="119" translationDomain="kdenlive">
<ToolBar name="extraToolBar" >
<text>Extra Toolbar</text>
<Action name="project_render" />
......@@ -76,7 +76,7 @@
<Action name="razor_tool" />
<Action name="spacer_tool" />
</Menu>
<Menu name="clip" ><text>Clip</text>
<Menu name="marker_menu" ><text>Markers</text>
<Action name="add_clip_marker" />
......@@ -111,6 +111,7 @@
<Action name="remove_extract" />
<Action name="remove_lift" />
</Menu>
<Action name="prerender_timeline_zone" />
<Action name="resize_timeline_clip_start" />
<Action name="resize_timeline_clip_end" />
<Menu name="current_clip" ><text>Current clip</text>
......
......@@ -831,7 +831,7 @@ void MainWindow::setupActions()
setStatusBarStyleSheet(palette());
QString styleBorderless = QStringLiteral("QToolButton { border-width: 0px;margin: 1px 3px 0px;padding: 0px;}");
//create edit mode buttons
m_normalEditTool = new QAction(KoIconUtils::themedIcon(QStringLiteral("kdenlive-normal-edit")), i18n("Normal mode"), this);
m_normalEditTool->setShortcut(i18nc("Normal editing", "n"));
......@@ -1014,6 +1014,9 @@ void MainWindow::setupActions()
statusBar()->addWidget(m_messageLabel, 10);
statusBar()->addWidget(m_statusProgressBar, 0);
QWidget *spacer = new QWidget(this);
spacer->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred);
statusBar()->addWidget(spacer, 10);
statusBar()->addPermanentWidget(toolbar);
m_timeFormatButton = new KSelectAction(QStringLiteral("00:00:00:00 / 00:00:00:00"), this);
......@@ -1152,6 +1155,8 @@ void MainWindow::setupActions()
addAction(QStringLiteral("insert_to_in_point"), i18n("Insert Clip Zone in Timeline (Insert)"), this, SLOT(slotInsertClipInsert()), QIcon(), Qt::Key_V);
addAction(QStringLiteral("remove_extract"), i18n("Extract Timeline Zone"), this, SLOT(slotExtractZone()), QIcon(), Qt::SHIFT + Qt::Key_X);
addAction(QStringLiteral("remove_lift"), i18n("Lift Timeline Zone"), this, SLOT(slotLiftZone()), QIcon(), Qt::Key_Z);
addAction(QStringLiteral("prerender_timeline_zone"), i18n("Timeline Preview Render"), this, SLOT(slotPreviewRender()), QIcon());
addAction(QStringLiteral("select_timeline_clip"), i18n("Select Clip"), this, SLOT(slotSelectTimelineClip()), KoIconUtils::themedIcon(QStringLiteral("edit-select")), Qt::Key_Plus);
addAction(QStringLiteral("deselect_timeline_clip"), i18n("Deselect Clip"), this, SLOT(slotDeselectTimelineClip()), KoIconUtils::themedIcon(QStringLiteral("edit-select")), Qt::Key_Minus);
addAction(QStringLiteral("select_add_timeline_clip"), i18n("Add Clip To Selection"), this, SLOT(slotSelectAddTimelineClip()),
......@@ -2168,6 +2173,13 @@ void MainWindow::slotLiftZone()
}
}
void MainWindow::slotPreviewRender()
{
if (pCore->projectManager()->current()) {
pCore->projectManager()->current()->previewRender();
}
}
void MainWindow::slotSelectTimelineClip()
{
if (pCore->projectManager()->currentTimeline())
......@@ -2333,10 +2345,10 @@ void MainWindow::slotGotProgressInfo(const QString &message, int progress, Messa
if (type == DefaultMessage) {
m_statusProgressBar->setValue(progress);
}
m_messageLabel->setMessage(message, type);
m_messageLabel->setMessage(progress < 100 ? message : QString(), type);
if (progress >= 0) {
if (type == DefaultMessage) {
m_statusProgressBar->setVisible(true);
m_statusProgressBar->setVisible(progress < 100);
}
} else {
m_statusProgressBar->setVisible(false);
......
......@@ -319,6 +319,7 @@ private slots:
void slotInsertClipInsert();
void slotExtractZone();
void slotLiftZone();
void slotPreviewRender();
void slotSelectTimelineClip();
void slotSelectTimelineTransition();
void slotDeselectTimelineClip();
......
......@@ -167,7 +167,6 @@ void ProxyJob::startJob()
}
m_jobProcess->waitForFinished(400);
}
if (m_jobStatus != JobAborted) {
int result = m_jobProcess->exitStatus();
if (result == QProcess::NormalExit) {
......
......@@ -72,7 +72,8 @@ Render::Render(Kdenlive::MonitorId rendererName, BinController *binController, G
m_isLoopMode(false),
m_blackClip(NULL),
m_isActive(false),
m_isRefreshing(false)
m_isRefreshing(false),
m_abortPreview(false)
{
qRegisterMetaType<stringMap> ("stringMap");
analyseAudio = KdenliveSettings::monitor_audio();
......@@ -101,6 +102,7 @@ Render::Render(Kdenlive::MonitorId rendererName, BinController *binController, G
Render::~Render()
{
m_abortPreview = true;
closeMlt();
}
......@@ -111,6 +113,7 @@ void Render::closeMlt()
delete m_mltConsumer;
delete m_mltProducer;
delete m_blackClip;
m_previewThread.waitForFinished();
}
void Render::slotSwitchFullscreen()
......@@ -2314,3 +2317,63 @@ void Render::updateSlowMotionProducers(const QString &id, QMap <QString, QString
}
}
}
void Render::previewRendering(QPoint zone, const QString &cacheDir, const QString &documentId)
{
if (m_previewThread.isRunning()) {
qDebug()<<"/ / /Already processing a preview render, abort";
return;
}
QDir dir(cacheDir);
dir.mkpath(QStringLiteral("."));
// Data is rendered in 200 frames chunks
int startChunk = zone.x() / 200;
int endChunk = rintl(zone.y() / 200);
// Save temporary scenelist
QString sceneListFile = dir.absoluteFilePath(documentId + ".mlt");
Mlt::Consumer xmlConsumer(*m_qmlView->profile(), "xml", sceneListFile.toUtf8().constData());
if (!xmlConsumer.is_valid())
return;
m_mltProducer->optimise();
xmlConsumer.set("terminate_on_pause", 1);
Mlt::Producer prod(m_mltProducer->get_producer());
if (!prod.is_valid())
return;
xmlConsumer.connect(prod);
xmlConsumer.run();
m_previewThread = QtConcurrent::run(this, &Render::doPreviewRender, startChunk, endChunk, dir, documentId, sceneListFile);
}
void Render::doPreviewRender(int start, int end, QDir folder, QString id, QString scene)
{
int progress;
for (int i = start; i <= end; ++i) {
if (m_abortPreview)
break;
QString fileName = id + QString("-%1.mp4").arg(i);
if (end > start) {
progress = (double) (i - start + 1) / (end +1 - start) * 100;
} else {
progress = 100;
}
if (folder.exists(fileName)) {
// This chunk already exists
emit previewRender(i * 200, folder.absoluteFilePath(fileName), progress);
continue;
}
// Build rendering process
QStringList args;
args << scene;
args << "in=" + QString::number(i * 200);
args << "out=" + QString::number(i * 200 + 199);
args << "-consumer" << "avformat:" + folder.absoluteFilePath(fileName);
int result = QProcess::execute(KdenliveSettings::rendererpath(), args);
if (result < 0) {
// Something is wrong, abort
break;
}
emit previewRender(i * 200, folder.absoluteFilePath(fileName), progress);
}
QFile::remove(scene);
m_abortPreview = false;
}
......@@ -51,6 +51,7 @@
#include <QFuture>
#include <QSemaphore>
#include <QTimer>
#include <QDir>
class KComboBox;
class BinController;
......@@ -287,6 +288,7 @@ class Render: public AbstractRender
void prepareProfileReset(double fps);
void finishProfileReset();
void updateSlowMotionProducers(const QString &id, QMap <QString, QString> passProperties);
void previewRendering(QPoint zone, const QString &cacheDir, const QString &documentId);
private:
......@@ -299,6 +301,7 @@ 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;
......@@ -322,10 +325,9 @@ 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;
/** @brief Build the MLT Consumer object with initial settings.
* @param profileName The MLT profile to use for the consumer */
......@@ -339,12 +341,13 @@ private:
void cloneProperties(Mlt::Properties &dest, Mlt::Properties &source);
/** @brief Get a track producer from a clip's id */
Mlt::Producer *getProducerForTrack(Mlt::Playlist &trackPlaylist, const QString &clipId);
private slots:
/** @brief Refreshes the monitor display. */
void refresh();
void slotCheckSeeking();
void doPreviewRender(int start, int end, QDir folder, QString id, QString scene);
signals:
/** @brief The renderer stopped, either playing or rendering. */
......@@ -381,6 +384,7 @@ 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:
......
......@@ -474,6 +474,13 @@ void CustomRuler::paintEvent(QPaintEvent *e)
p.drawPolyline(pa);
}
// draw Rendering preview zones
QColor preview(Qt::yellow);
foreach(int frame, m_renderingPreviews) {
QRect rec(frame * m_factor - m_offset, MAX_HEIGHT - 2, 199 * m_factor, 2);
p.fillRect(rec, preview);
}
// draw pointer
const int value = m_view->cursorPos() * m_factor - m_offset;
QPolygon pa(3);
......@@ -496,3 +503,11 @@ void CustomRuler::activateZone()
update();
}
void CustomRuler::updatePreview(int frame, bool rendered)
{
if (rendered)
m_renderingPreviews << frame;
else
m_renderingPreviews.removeAll(frame);
update(frame * m_factor - offset(), MAX_HEIGHT - 2, (199) * m_factor, 2);
}
......@@ -50,6 +50,7 @@ public:
void updateProjectFps(const Timecode &t);
void updateFrameSize();
void activateZone();
void updatePreview(int frame, bool rendered = true);
protected:
void paintEvent(QPaintEvent * /*e*/);
......@@ -82,7 +83,7 @@ private:
int m_startRate;
MOUSE_MOVE m_mouseMove;
QMenu *m_goMenu;
QList <int> m_renderingPreviews;
public slots:
void slotMoveRuler(int newPos);
......
......@@ -142,6 +142,7 @@ Timeline::Timeline(KdenliveDoc *doc, const QList<QAction *> &actions, bool *ok,
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);
}
Timeline::~Timeline()
......@@ -318,6 +319,7 @@ int Timeline::getTracks() {
}
connect(tk, &Track::newTrackDuration, this, &Timeline::checkDuration, Qt::DirectConnection);
connect(tk, SIGNAL(storeSlowMotion(QString,Mlt::Producer *)), m_doc->renderer(), SLOT(storeSlowmotionProducer(QString,Mlt::Producer *)));
connect(tk, &Track::invalidatePreview, this, &Timeline::invalidatePreview);
}
}
headers_container->setFixedWidth(headerWidth);
......@@ -1715,3 +1717,54 @@ void Timeline::slotEnableZone(bool enable)
KdenliveSettings::setUseTimelineZoneToEdit(enable);
m_ruler->activateZone();
}
void Timeline::gotPreviewRender(int frame, const QString &file, int progress)
{
m_ruler->updatePreview(frame);
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;
}
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());
prod.set("mlt_service", "avformat-novalidate");
trackPlaylist.insert_at(frame, &prod, 1);
}
m_tractor->unlock();
m_doc->progressInfo(i18n("Rendering preview"), progress);
//m_doc->updatePreview(progress);
}
void Timeline::invalidatePreview(int startFrame, int length)
{
if (!m_hasOverlayTrack)
return;
int start = startFrame / 200;
int end = lrintf((startFrame + length) / 200);
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(200 * i);
if (trackPlaylist.is_blank(i))
continue;
list << i;
Mlt::Producer *prod = trackPlaylist.replace_with_blank(ix);
delete prod;
m_ruler->updatePreview(i * 200, false);
}
trackPlaylist.consolidate_blanks();
m_tractor->unlock();
m_doc->invalidatePreviews(list);
}
......@@ -235,6 +235,8 @@ private slots:
void slotUpdateTrackEffectState(int);
/** @brief Toggle use of timeline zone for editing.*/
void slotEnableZone(bool enable);
void gotPreviewRender(int frame, const QString &file, int progress);
void invalidatePreview(int startFrame, int length);
signals:
void mousePosition(int);
......
......@@ -115,6 +115,7 @@ bool Track::doAdd(qreal t, Mlt::Producer *cut, TimelineMode::EditMode mode)
if (m_playlist.insert_at(pos, cut, 1) == m_playlist.count() - 1) {
emit newTrackDuration(m_playlist.get_playtime());
}
emit invalidatePreview(pos, len);
return true;
}
......@@ -143,6 +144,7 @@ bool Track::move(qreal start, qreal end, TimelineMode::EditMode mode)
if (durationChanged) {
emit newTrackDuration(m_playlist.get_playtime());
}
emit invalidatePreview(pos, clipProducer->get_playtime());
return result;
}
......@@ -159,15 +161,19 @@ bool Track::del(qreal t)
{
m_playlist.lock();
bool durationChanged = false;
int ix = m_playlist.get_clip_index_at(frame(t));
int pos = frame(t);
int ix = m_playlist.get_clip_index_at(pos);
if (ix == m_playlist.count() - 1) {
durationChanged = true;
}
int length = 0;
Mlt::Producer *clip = m_playlist.replace_with_blank(ix);
if (clip)
if (clip) {
length = clip->get_playtime();
delete clip;
}
else {
qWarning("Error deleting clip at %d, tk: %d", frame(t), m_index);
qWarning("Error deleting clip at %d, tk: %d", pos, m_index);
m_playlist.unlock();
return false;
}
......@@ -176,6 +182,7 @@ bool Track::del(qreal t)
if (durationChanged) {
emit newTrackDuration(m_playlist.get_playtime());
}
emit invalidatePreview(pos, length);
return true;
}
......@@ -185,14 +192,17 @@ bool Track::del(qreal t, qreal dt)
m_playlist.insert_blank(m_playlist.remove_region(frame(t), frame(dt) + 1), frame(dt));
m_playlist.consolidate_blanks();
m_playlist.unlock();
emit invalidatePreview(frame(t), frame(dt));
return true;
}
bool Track::resize(qreal t, qreal dt, bool end)
{
m_playlist.lock();
int index = m_playlist.get_clip_index_at(frame(t));
int startFrame = frame(t);
int index = m_playlist.get_clip_index_at(startFrame);
int length = frame(dt);
int updateLength = length;
QScopedPointer<Mlt::Producer> clip(m_playlist.get_clip(index));
if (clip == NULL || clip->is_blank()) {
qWarning("Can't resize clip at %f", t);
......@@ -202,7 +212,14 @@ bool Track::resize(qreal t, qreal dt, bool end)
int in = clip->get_in();
int out = clip->get_out();
if (end) out += length; else in += length;
if (end) {
// Resizing clip end
startFrame += out - in;
out += length;
} else {
// Resizing clip start
in += length;
}
//image or color clips are not bounded
if (in < 0) {out -= in; in = 0;}
......@@ -224,6 +241,10 @@ bool Track::resize(qreal t, qreal dt, bool end)
m_playlist.unlock();
// this is the last clip in track, check tracks length to adjust black track and project duration
emit newTrackDuration(m_playlist.get_playtime());
if (updateLength > 0)
emit invalidatePreview(startFrame, updateLength);
else
emit invalidatePreview(startFrame + updateLength, -updateLength);
return true;
}
length = -length;
......@@ -249,6 +270,10 @@ bool Track::resize(qreal t, qreal dt, bool end)
m_playlist.consolidate_blanks();
m_playlist.unlock();
if (updateLength > 0)
emit invalidatePreview(startFrame, updateLength);
else
emit invalidatePreview(startFrame + updateLength, -updateLength);
return true;
}
......@@ -332,6 +357,7 @@ bool Track::replaceAll(const QString &id, Mlt::Producer *original, Mlt::Producer
QString service = original->parent().get("mlt_service");
QString idForTrack = original->parent().get("id");
QLocale locale;
QList <QPoint> updateList;
if (needsDuplicate(service)) {
// We have to use the track clip duplication functions, because of audio glitches in MLT's multitrack
idForAudioTrack = idForTrack + QLatin1Char('_') + m_playlist.get("id") + "_audio";
......@@ -350,6 +376,7 @@ bool Track::replaceAll(const QString &id, Mlt::Producer *original, Mlt::Producer
}
current.remove(0, 1);
Mlt::Producer *cut = NULL;
updateList << QPoint(m_playlist.clip_start(i), m_playlist.clip_length(i));
if (current.startsWith("slowmotion:" + id + ":")) {
// Slowmotion producer, just update resource
Mlt::Producer *slowProd = newSlowMos.value(current.section(QStringLiteral(":"), 2));
......@@ -398,6 +425,9 @@ bool Track::replaceAll(const QString &id, Mlt::Producer *original, Mlt::Producer
delete cut;
}
}
foreach(const QPoint &pt, updateList) {
emit invalidatePreview(pt.x(), pt.y());
}
return found;
}
......@@ -426,6 +456,7 @@ bool Track::replace(qreal t, Mlt::Producer *prod, PlaylistState::ClipState state
bool ok = m_playlist.insert_at(frame(t), cut, 1) >= 0;
delete cut;
m_playlist.unlock();
emit invalidatePreview(frame(t), orig->get_playtime());
return ok;
}
......@@ -435,7 +466,7 @@ void Track::updateEffects(const QString &id, Mlt::Producer *original)
QString idForVideoTrack;
QString service = original->parent().get("mlt_service");
QString idForTrack = original->parent().get("id");
QList <QPoint> updateList;
if (needsDuplicate(service)) {
// We have to use the track clip duplication functions, because of audio glitches in MLT's multitrack
idForAudioTrack = idForTrack + QLatin1Char('_') + m_playlist.get("id") + "_audio";
......@@ -451,16 +482,22 @@ void Track::updateEffects(const QString &id, Mlt::Producer *original)
if (current.startsWith(QLatin1String("slowmotion:"))) {
if (current.section(QStringLiteral(":"), 1, 1) == id) {
Clip(origin).replaceEffects(*original);
updateList << QPoint(m_playlist.clip_start(i), m_playlist.clip_length(i));
}
}
else if (current == id) {
// we are directly using original producer, no need to update effects
updateList << QPoint(m_playlist.clip_start(i), m_playlist.clip_length(i));
continue;
}
else if (current.section(QStringLiteral("_"), 0, 0) == id) {
updateList << QPoint(m_playlist.clip_start(i), m_playlist.clip_length(i));
Clip(origin).replaceEffects(*original);
}
}
foreach(const QPoint &pt, updateList) {
emit invalidatePreview(pt.x(), pt.y());
}
}
/*Mlt::Producer &Track::find(const QByteArray &name, const QByteArray &value, int startindex) {
......@@ -646,6 +683,7 @@ Mlt::Producer *Track::buildSlowMoProducer(Mlt::Properties passProps, const QStri
int Track::changeClipSpeed(ItemInfo info, ItemInfo speedIndependantInfo, PlaylistState::ClipState state, double speed, int strobe, Mlt::Producer *prod, const QString &id, Mlt::Properties passProps, bool removeEffect)
{
//TODO: invalidate preview rendering
int newLength = 0;
int startPos = info.startPos.frames(fps());
int clipIndex = m_playlist.get_clip_index_at(startPos);
......
......@@ -201,6 +201,7 @@ signals:
* @param duration is the new length */
void newTrackDuration(int duration);
void storeSlowMotion(const QString &url, Mlt::Producer *prod);
void invalidatePreview(int position, int length);
private:
/** Position in MLT's tractor */
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment