Commit 2954a757 authored by Jean-Baptiste Mardelle's avatar Jean-Baptiste Mardelle

rewrite timeline preview to use external kdenlive_render process, fixes memory...

rewrite timeline preview to use external kdenlive_render process, fixes memory consumption and risks of crash
parent da443d2e
......@@ -29,7 +29,7 @@ DNxHD 1080p 30fps=r=30 s=1920x1080 vb=45M threads=0 vcodec=dnxhd;mov
DNxHD 1080p 50fps=r=50 s=1920x1080 vb=72M threads=0 vcodec=dnxhd;mov
DNxHD 1080p 59.94fps=r=59.94 s=1920x1080 vb=90M threads=0 vcodec=dnxhd;mov
DNxHD 1080p 60fps=r=60 s=1920x1080 vb=90M threads=0 vcodec=dnxhd;mov
ProRes=vcodec=prores_ks vb=0 g=1 bf=0 vprofile=2 vendor=ap10 qscale=1;mov
ProRes=vcodec=prores_ks vb=0 g=1 bf=0 vprofile=0 vendor=ap10 qscale=4 s=800x450;mov
MJPEG=f=avi vcodec=mjpeg progressive=1 qscale=1;avi
x264-nvenc=vcodec=h264_nvenc g=1 bf=0 profile=0;mkv
x264-vaapi=vcodec=h264_vaapi g=1 bf=0 profile=0;mkv
......@@ -13,7 +13,9 @@ set(kdenlive_render_SRCS
add_executable(kdenlive_render ${kdenlive_render_SRCS})
ecm_mark_nongui_executable(kdenlive_render)
target_link_libraries(kdenlive_render Qt5::Core Qt5::DBus Qt5::Xml)
target_link_libraries(kdenlive_render Qt5::Core Qt5::DBus Qt5::Xml
${MLT_LIBRARIES}
${MLTPP_LIBRARIES})
install(TARGETS kdenlive_render DESTINATION ${BIN_INSTALL_DIR})
......@@ -25,8 +25,10 @@
#include <QStringList>
#include <QDomDocument>
#include <QUrl>
#include <QDir>
#include <stdio.h>
#include "framework/mlt_version.h"
#include "mlt++/Mlt.h"
int main(int argc, char **argv)
{
......@@ -37,17 +39,70 @@ int main(int argc, char **argv)
if (args.count() >= 4) {
// Remove program name
args.removeFirst();
// renderer path (melt)
QString render = args.at(0);
args.removeFirst();
// Source playlist path
QString playlist = args.at(0);
args.removeFirst();
// target - where to save result
QString target = args.at(0);
args.removeFirst();
int pid = 0;
// pid to send back progress
if (args.count() > 0 && args.at(0).startsWith(QLatin1String("-pid:"))) {
pid = args.at(0).section(QLatin1Char(':'), 1).toInt();
args.removeFirst();
}
// Do we want a split render
if (args.count() > 0 && args.at(0) == QLatin1String("-split")) {
args.removeFirst();
// chunks to render
QStringList chunks = args.at(0).split(QLatin1Char(','), QString::SkipEmptyParts);
args.removeFirst();
// chunk size in frames
int chunkSize = args.at(0).toInt();
args.removeFirst();
// rendered file extension
QString extension = args.at(0);
args.removeFirst();
// avformat consumer params
QStringList consumerParams = args.at(0).split(QLatin1Char(' '), QString::SkipEmptyParts);
args.removeFirst();
QDir baseFolder(target);
Mlt::Factory::init();
Mlt::Profile profile;
Mlt::Producer prod(profile, nullptr, playlist.toUtf8().constData());
if (!prod.is_valid()) {
fprintf(stderr, "INVALID playlist: %s \n", playlist.toUtf8().constData());
}
for (const QString frame : chunks) {
fprintf(stderr, "START:%d \n", frame.toInt());
QString fileName = QStringLiteral("%1.%2").arg(frame).arg(extension);
if (baseFolder.exists(fileName)) {
// Don't overwrite an existing file
fprintf(stderr, "DONE:%d \n", frame.toInt());
continue;
}
QScopedPointer<Mlt::Producer> playlst(prod.cut(frame.toInt(), frame.toInt() + chunkSize));
QScopedPointer<Mlt::Consumer> cons(new Mlt::Consumer(profile, QString("avformat:%1").arg(baseFolder.absoluteFilePath(fileName)).toUtf8().constData()));
for (const QString &param : consumerParams) {
if (param.contains(QLatin1Char('='))) {
cons->set(param.section(QLatin1Char('='), 0, 0).toUtf8().constData(), param.section(QLatin1Char('='), 1).toUtf8().constData());
}
}
cons->set("terminate_on_pause", 1);
cons->connect(*playlst);
playlst.reset();
cons->run();
cons->stop();
cons->purge();
fprintf(stderr, "DONE:%d \n", frame.toInt());
}
//Mlt::Factory::close();
fprintf(stderr, "+ + + RENDERING FINSHED + + + \n");
return 0;
}
int in = -1;
int out = -1;
if (LIBMLT_VERSION_INT < 396544) {
......@@ -65,114 +120,6 @@ int main(int argc, char **argv)
RenderJob *rJob = new RenderJob(render, playlist, target, pid, in, out);
rJob->start();
app.exec();
}
else if (args.count() >= 7) {
// Deprecated
int pid = 0;
int in = -1;
int out = -1;
// Remove program name
args.removeFirst();
bool erase = false;
if (args.at(0) == QLatin1String("-erase")) {
erase = true;
args.removeFirst();
}
bool usekuiserver = false;
if (args.at(0) == QLatin1String("-kuiserver")) {
usekuiserver = true;
args.removeFirst();
}
if (args.at(0).startsWith(QLatin1String("-pid:"))) {
pid = args.at(0).section(QLatin1Char(':'), 1).toInt();
args.removeFirst();
}
if (args.at(0).startsWith(QLatin1String("-locale:"))) {
locale = args.at(0).section(QLatin1Char(':'), 1);
args.removeFirst();
}
if (args.at(0).startsWith(QLatin1String("in="))) {
in = args.takeFirst().section(QLatin1Char('='), -1).toInt();
}
if (args.at(0).startsWith(QLatin1String("out="))) {
out = args.takeFirst().section(QLatin1Char('='), -1).toInt();
}
if (args.at(0).startsWith(QLatin1String("preargs="))) {
preargs = args.takeFirst().section(QLatin1Char('='), 1).split(QLatin1Char(' '), QString::SkipEmptyParts);
}
QString render = args.takeFirst();
QString profile = args.takeFirst();
QString rendermodule = args.takeFirst();
QString player = args.takeFirst();
QString srcString = args.takeFirst();
QUrl srcurl;
if (srcString.startsWith(QLatin1String("consumer:"))) {
srcurl = QUrl::fromEncoded(srcString.section(QLatin1Char(':'), 1).toUtf8().constData());
} else {
srcurl = QUrl::fromEncoded(srcString.toUtf8().constData());
}
QString src = srcurl.toLocalFile();
// The QUrl path() strips the consumer: protocol, so re-add it if necessary
if (srcString.startsWith(QStringLiteral("consumer:"))) {
src.prepend(QLatin1String("consumer:"));
}
QString dest = QFileInfo(QUrl::fromEncoded(args.takeFirst().toUtf8()).toLocalFile()).absoluteFilePath();
bool dualpass = false;
bool doerase;
QString vpre;
int vprepos = args.indexOf(QRegExp(QLatin1String("vpre=.*")));
if (vprepos >= 0) {
vpre = args.at(vprepos);
}
QStringList vprelist = vpre.remove(QStringLiteral("vpre=")).split(QLatin1Char(','));
if (!vprelist.isEmpty()) {
args.replaceInStrings(QRegExp(QLatin1String("^vpre=.*")), QStringLiteral("vpre=%1").arg(vprelist.at(0)));
}
if (args.contains(QStringLiteral("pass=2"))) {
// dual pass encoding
dualpass = true;
doerase = false;
args.replace(args.indexOf(QStringLiteral("pass=2")), QStringLiteral("pass=1"));
if (args.contains(QStringLiteral("vcodec=libx264"))) {
args << QStringLiteral("passlogfile=%1").arg(dest + QStringLiteral(".log"));
}
} else {
args.removeAll(QStringLiteral("pass=1"));
doerase = erase;
}
// Decode metadata
for (int i = 0; i < args.count(); ++i) {
if (args.at(i).startsWith(QLatin1String("meta.attr"))) {
QString data = args.at(i);
args.replace(i, data.section(QLatin1Char('='), 0, 0) + QStringLiteral("=\"") +
QUrl::fromPercentEncoding(data.section(QLatin1Char('='), 1).toUtf8()) + QLatin1Char('\"'));
}
}
qDebug() << "//STARTING RENDERING: " << erase << ',' << usekuiserver << ',' << render << ',' << profile << ',' << rendermodule << ',' << player << ','
<< src << ',' << dest << ',' << preargs << ',' << args << ',' << in << ',' << out;
auto *job = new RenderJob(doerase, usekuiserver, pid, render, profile, rendermodule, player, src, dest, preargs, args, in, out);
if (!locale.isEmpty()) {
job->setLocale(locale);
}
job->start();
RenderJob *dualjob = nullptr;
if (dualpass) {
if (vprelist.size() > 1) {
args.replaceInStrings(QRegExp(QLatin1String("^vpre=.*")), QStringLiteral("vpre=%1").arg(vprelist.at(1)));
}
args.replace(args.indexOf(QStringLiteral("pass=1")), QStringLiteral("pass=2"));
dualjob = new RenderJob(erase, usekuiserver, pid, render, profile, rendermodule, player, src, dest, preargs, args, in, out);
QObject::connect(job, &RenderJob::renderingFinished, dualjob, &RenderJob::start);
}
app.exec();
delete dualjob;
} else {
fprintf(stderr,
"Kdenlive video renderer for MLT.\nUsage: "
......
......@@ -72,57 +72,6 @@ RenderJob::RenderJob(const QString &render, const QString &scenelist, const QStr
}
}
RenderJob::RenderJob(bool erase, bool usekuiserver, int pid, const QString &renderer, const QString &profile, const QString &rendermodule,
const QString &player, const QString &scenelist, const QString &dest, const QStringList &preargs, const QStringList &args, int in, int out)
: QObject()
, m_scenelist(scenelist)
, m_dest(dest)
, m_progress(0)
, m_prog(renderer)
, m_player(player)
, m_jobUiserver(nullptr)
, m_kdenliveinterface(nullptr)
, m_usekuiserver(usekuiserver)
, m_logfile(dest + QStringLiteral(".txt"))
, m_erase(erase)
, m_seconds(0)
, m_frame(0)
, m_pid(pid)
, m_dualpass(false)
{
m_renderProcess = new QProcess;
m_renderProcess->setReadChannel(QProcess::StandardError);
connect(m_renderProcess, &QProcess::stateChanged, this, &RenderJob::slotCheckProcess);
// Disable VDPAU so that rendering will work even if there is a Kdenlive instance using VDPAU
qputenv("MLT_NO_VDPAU", "1");
m_args << scenelist;
if (in != -1) {
m_args << QStringLiteral("in=") + QString::number(in);
}
if (out != -1) {
m_args << QStringLiteral("out=") + QString::number(out);
}
m_args << preargs;
if (scenelist.startsWith(QLatin1String("consumer:"))) {
// Use MLT's producer_consumer, safer to pass profile in an explicit way
m_args << QStringLiteral("profile=") + profile;
}
m_args << QStringLiteral("-profile") << profile;
m_args << QStringLiteral("-consumer") << rendermodule + QLatin1Char(':') + m_dest << QStringLiteral("progress=1") << args;
m_dualpass = args.contains(QStringLiteral("pass=1"));
// Create a log of every render process.
if (!m_logfile.open(QIODevice::WriteOnly | QIODevice::Text)) {
qWarning() << "Unable to log to " << m_logfile.fileName();
} else {
m_logstream.setDevice(&m_logfile);
}
}
RenderJob::~RenderJob()
{
delete m_renderProcess;
......
......@@ -34,8 +34,6 @@ class RenderJob : public QObject
public:
RenderJob(const QString &render, const QString &scenelist, const QString &target, int pid = -1, int in = -1, int out = -1);
Q_DECL_DEPRECATED RenderJob(bool erase, bool usekuiserver, int pid, const QString &renderer, const QString &profile, const QString &rendermodule, const QString &player,
const QString &scenelist, const QString &dest, const QStringList &preargs, const QStringList &args, int in = -1, int out = -1);
~RenderJob();
void setLocale(const QString &locale);
......
......@@ -2135,3 +2135,4 @@ void Monitor::setConsumerProperty(const QString &name, const QString &value)
{
m_glMonitor->setConsumerProperty(name, value);
}
This diff is collapsed.
......@@ -26,12 +26,14 @@
#include <QFuture>
#include <QMutex>
#include <QTimer>
#include <QProcess>
class TimelineController;
namespace Mlt {
class Tractor;
class Playlist;
class Producer;
class Profile;
} // namespace Mlt
......@@ -96,8 +98,11 @@ private:
Mlt::Tractor *m_tractor;
Mlt::Playlist *m_previewTrack;
Mlt::Playlist *m_overlayTrack;
std::shared_ptr<Mlt::Profile>m_previewProfile;
int m_previewTrackIndex;
/** @brief: The kdenlive renderer app. */
QString m_renderer;
/** @brief: The kdenlive timeline preview process. */
QProcess m_previewProcess;
/** @brief: The directory used to store the preview files. */
QDir m_cacheDir;
/** @brief: The directory used to store undo history of preview files (child of m_cacheDir). */
......@@ -110,9 +115,14 @@ private:
/** @brief: Since some timeline operations generate several invalidate calls, use a timer to get them all. */
QTimer m_previewGatherTimer;
bool m_initialized;
bool m_abortPreview;
QList<int> m_waitingThumbs;
QFuture<void> m_previewThread;
/** @brief: The count of chunks to process - to calculate job progress */
int m_chunksToRender;
/** @brief: The count of already processed chunks - to calculate job progress */
int m_processedChunks;
/** @brief: The render process output, useful in case of failure */
QString m_errorLog;
/** @brief: After an undo/redo, if we have preview history, use it. */
void reloadChunks(const QVariantList chunks);
/** @brief: A chunk failed to render, abort. */
......@@ -122,11 +132,13 @@ private slots:
/** @brief: To avoid filling the hard drive, remove preview undo history after 5 steps. */
void doCleanupOldPreviews();
/** @brief: Start the real rendering process. */
void doPreviewRender(const QString &scene);
void doPreviewRender(const QString &scene); //std::shared_ptr<Mlt::Producer> sourceProd);
/** @brief: If user does an undo, then makes a new timeline operation, delete undo history of more recent stack . */
void slotRemoveInvalidUndo(int ix);
/** @brief: When the timer collecting invalid zones is done, process. */
void slotProcessDirtyChunks();
/** @brief: Process preview rendering output. */
void receivedStderr();
public slots:
/** @brief: Prepare and start rendering. */
......
......@@ -61,7 +61,7 @@ void ProgressButton::setProgress(int progress)
QString remaining;
if (m_timer.isValid() && progress > 0) {
// calculate remaining time
qint64 ms = m_timer.elapsed() * (m_max / progress - 1);
qint64 ms = m_timer.elapsed() * (m_max - progress) / progress;
if (ms < 60000)
// xgettext:no-c-format
{
......@@ -91,10 +91,13 @@ void ProgressButton::setProgress(int progress)
m_remainingTime.clear();
m_timer.invalidate();
}
m_progress = prog;
if (remaining != m_remainingTime) {
if (remaining != m_remainingTime || m_progress != prog ) {
m_progress = prog;
m_remainingTime = remaining;
update();
} else {
m_progress = prog;
m_remainingTime = remaining;
}
}
......
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