diff --git a/data/encodingprofiles.rc b/data/encodingprofiles.rc index bd80e6838..f68ffb388 100644 --- a/data/encodingprofiles.rc +++ b/data/encodingprofiles.rc @@ -1,35 +1,35 @@ [decklink] x264=crf=25 ab=192k vcodec=libx264 acodec=libvorbis ab=192k preset=veryfast threads=%threads;mov DNxHD=vcodec=dnxhd vb=145000k acodec=pcm_s16le threads=%threads;mov MPEG=qscale=4 ab=192k vcodec=mpeg2video acodec=mp2 threads=%threads;mpg [proxy] x264=-vf scale=640:-2 -vcodec libx264 -g 1 -bf 0 -vb 0 -crf 20 -preset veryfast -acodec aac -ab 128k;mov -x264-vaapi=-hwaccel vaapi -vaapi_device /dev/dri/renderD128 -i -vf format=nv12,hwupload,scale_vaapi=640:-2 -vcodec h264_vaapi -g 1 -bf 0 -vb 0 -crf 20 -acodec aac -ab 128k;mov +x264-vaapi=-hwaccel vaapi -vaapi_device /dev/dri/renderD128 -i -vf format=nv12,hwupload,scale_vaapi=640:-2 -vcodec h264_vaapi -g 1 -bf 0 -qp 26 -acodec aac -ab 128k;mov x264-nvenc=-hwaccel cuvid -c:v %nvcodec -i -vf scale_npp=640:-2 -vcodec h264_nvenc -g 1 -bf 0 -vb 0 -preset fast -acodec copy;mov MPEG2=-vf scale=640:-2 -g 1 -bf 0 -vb 0 -qscale 6 -ab 128k -vcodec mpeg2video -acodec ac3;mpg MJPEG=-vf yadif,scale=640:-2 -qscale 3 -vcodec mjpeg -acodec pcm_s16le;mkv MJPEG-vaapi=-hwaccel vaapi -vaapi_device /dev/dri/renderD128 -i -vf format=nv12,hwupload,scale_vaapi=640:-2 -vcodec mjpeg_vaapi -acodec copy;mkv ProRes=-vcodec prores_ks -vb 0 -g 1 -bf 0 -vprofile 1 -vendor ap10 -qscale 1;mov [screengrab] X264 mute=-crf 25 -vcodec libx264 -preset veryfast -threads 0;mov X264 with audio=-f alsa -i default -crf 25 -ab 192k -vcodec libx264 -acodec libvorbis -preset veryfast -threads 0;mov [video4linux] x264=crf=25 ab=192k vcodec=libx264 acodec=libvorbis ab=192k preset=veryfast threads=%threads;mov MPEG=qscale=4 ab=192k vcodec=mpeg2video acodec=mp2 threads=%threads;mpg [timelinepreview] DNxHD 1080p 23.976fps=r=23.976 s=1920x1080 vb=36M threads=0 vcodec=dnxhd progressive=1;mov DNxHD 1080p 24fps=r=24 s=1920x1080 vb=36M threads=0 vcodec=dnxhd progressive=1;mov DNxHD 1080p 25fps=r=25 s=1920x1080 vb=36M threads=0 vcodec=dnxhd progressive=1;mov DNxHD 1080p 29.97fps=r=29.97 s=1920x1080 vb=45M threads=0 vcodec=dnxhd progressive=1;mov DNxHD 1080p 30fps=r=30 s=1920x1080 vb=45M threads=0 vcodec=dnxhd progressive=1;mov DNxHD 1080p 50fps=r=50 s=1920x1080 vb=75M threads=0 vcodec=dnxhd progressive=1;mov DNxHD 1080p 59.94fps=r=59.94 s=1920x1080 vb=90M threads=0 vcodec=dnxhd progressive=1;mov DNxHD 1080p 60fps=r=60 s=1920x1080 vb=90M threads=0 vcodec=dnxhd progressive=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 preset=fast qmin=10 qmax=30;mkv x264-vaapi=vcodec=h264_vaapi g=1 bf=0 profile=578;mkv diff --git a/src/jobs/proxyclipjob.cpp b/src/jobs/proxyclipjob.cpp index d60aeda10..0e5d12c41 100644 --- a/src/jobs/proxyclipjob.cpp +++ b/src/jobs/proxyclipjob.cpp @@ -1,395 +1,396 @@ /*************************************************************************** * * * Copyright (C) 2011 by Jean-Baptiste Mardelle (jb@kdenlive.org) * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * 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 "proxyclipjob.h" #include "bin/bin.h" #include "bin/projectclip.h" #include "bin/projectitemmodel.h" #include "core.h" #include "doc/kdenlivedoc.h" #include "kdenlive_debug.h" #include "kdenlivesettings.h" #include "macros.hpp" #include #include #include #include ProxyJob::ProxyJob(const QString &binId) : AbstractClipJob(PROXYJOB, binId) , m_jobDuration(0) , m_isFfmpegJob(true) , m_jobProcess(nullptr) , m_done(false) { } const QString ProxyJob::getDescription() const { return i18n("Creating proxy %1", m_clipId); } bool ProxyJob::startJob() { auto binClip = pCore->projectItemModel()->getClipByBinID(m_clipId); const QString dest = binClip->getProducerProperty(QStringLiteral("kdenlive:proxy")); QFileInfo fInfo(dest); if (binClip->getProducerIntProperty(QStringLiteral("_overwriteproxy")) == 0 && fInfo.exists() && fInfo.size() > 0) { // Proxy clip already created m_done = true; return true; } ClipType::ProducerType type = binClip->clipType(); bool result; QString source = binClip->getProducerProperty(QStringLiteral("kdenlive:originalurl")); int exif = binClip->getProducerIntProperty(QStringLiteral("_exif_orientation")); if (type == ClipType::Playlist || type == ClipType::SlideShow) { // change FFmpeg params to MLT format m_isFfmpegJob = false; QStringList mltParameters; QTemporaryFile *playlist = nullptr; // set clip origin if (type == ClipType::Playlist) { // Special case: playlists use the special 'consumer' producer to support resizing source.prepend(QStringLiteral("consumer:")); } else { // create temporary playlist to generate proxy // we save a temporary .mlt clip for rendering QDomDocument doc; QDomElement xml = binClip->toXml(doc, false); playlist = new QTemporaryFile(); playlist->setFileTemplate(playlist->fileTemplate() + QStringLiteral(".mlt")); if (playlist->open()) { source = playlist->fileName(); QTextStream out(playlist); out << doc.toString(); playlist->close(); } } mltParameters << source; // set destination mltParameters << QStringLiteral("-consumer") << QStringLiteral("avformat:") + dest; QString parameter = pCore->currentDoc()->getDocumentProperty(QStringLiteral("proxyparams")).simplified(); if (parameter.isEmpty()) { // Automatic setting, decide based on hw support parameter = pCore->currentDoc()->getAutoProxyProfile(); bool nvenc = parameter.contains(QStringLiteral("%nvcodec")); if (nvenc) { parameter = parameter.section(QStringLiteral("-i"), 1); parameter.replace(QStringLiteral("scale_cuda"), QStringLiteral("scale")); parameter.replace(QStringLiteral("scale_npp"), QStringLiteral("scale")); parameter.prepend(QStringLiteral("-pix_fmt yuv420p")); } } #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) QStringList params = parameter.split(QLatin1Char('-'), QString::SkipEmptyParts); #else QStringList params = parameter.split(QLatin1Char('-'), Qt::SkipEmptyParts); #endif double display_ratio; if (source.startsWith(QLatin1String("consumer:"))) { display_ratio = KdenliveDoc::getDisplayRatio(source.section(QLatin1Char(':'), 1)); } else { display_ratio = KdenliveDoc::getDisplayRatio(source); } if (display_ratio < 1e-6) { display_ratio = 1; } bool skipNext = false; for (const QString &s : params) { QString t = s.simplified(); if (skipNext) { skipNext = false; continue; } if (t.count(QLatin1Char(' ')) == 0) { t.append(QLatin1String("=1")); } else if (t.startsWith(QLatin1String("vf "))) { skipNext = true; bool ok = false; int width = t.section(QLatin1Char('='), 1, 1).section(QLatin1Char(':'), 0, 0).toInt(&ok); if (!ok) { width = 640; } int height = width / display_ratio; // Make sure we get an even height height += height % 2; mltParameters << QStringLiteral("s=%1x%2").arg(width).arg(height); if (t.contains(QStringLiteral("yadif"))) { mltParameters << QStringLiteral("progressive=1"); } continue; } else { t.replace(QLatin1Char(' '), QLatin1String("=")); } mltParameters << t; } int threadCount = QThread::idealThreadCount(); if (threadCount > 2) { threadCount = qMin(threadCount - 1, 4); } else { threadCount = 1; } mltParameters.append(QStringLiteral("real_time=-%1").arg(threadCount)); mltParameters.append(QStringLiteral("threads=%1").arg(threadCount)); mltParameters.append(QStringLiteral("terminate_on_pause=1")); // TODO: currently, when rendering an xml file through melt, the display ration is lost, so we enforce it manually mltParameters << QStringLiteral("aspect=") + QLocale().toString(display_ratio); // Ask for progress reporting mltParameters << QStringLiteral("progress=1"); m_jobProcess = new QProcess; // m_jobProcess->setProcessChannelMode(QProcess::MergedChannels); connect(this, &ProxyJob::jobCanceled, m_jobProcess, &QProcess::kill, Qt::DirectConnection); connect(m_jobProcess, &QProcess::readyReadStandardError, this, &ProxyJob::processLogInfo); m_jobProcess->start(KdenliveSettings::rendererpath(), mltParameters); m_jobProcess->waitForFinished(-1); result = m_jobProcess->exitStatus() == QProcess::NormalExit; delete playlist; } else if (type == ClipType::Image) { m_isFfmpegJob = false; // Image proxy QImage i(source); if (i.isNull()) { m_done = false; m_errorMessage.append(i18n("Cannot load image %1.", source)); return false; } QImage proxy; // Images are scaled to profile size. // TODO: Make it be configurable? if (i.width() > i.height()) { proxy = i.scaledToWidth(KdenliveSettings::proxyimagesize()); } else { proxy = i.scaledToHeight(KdenliveSettings::proxyimagesize()); } if (exif > 1) { // Rotate image according to exif data QImage processed; QMatrix matrix; switch (exif) { case 2: matrix.scale(-1, 1); break; case 3: matrix.rotate(180); break; case 4: matrix.scale(1, -1); break; case 5: matrix.rotate(270); matrix.scale(-1, 1); break; case 6: matrix.rotate(90); break; case 7: matrix.rotate(90); matrix.scale(-1, 1); break; case 8: matrix.rotate(270); break; } processed = proxy.transformed(matrix); processed.save(dest); } else { proxy.save(dest); } m_done = true; return true; } else { m_isFfmpegJob = true; if (KdenliveSettings::ffmpegpath().isEmpty()) { // FFmpeg not detected, cannot process the Job m_errorMessage.prepend(i18n("Failed to create proxy. FFmpeg not found, please set path in Kdenlive's settings Environment")); m_done = true; return false; } - // Only output error data + // Only output error data, make sure we don't block when proxy file already exists QStringList parameters = {QStringLiteral("-hide_banner"), QStringLiteral("-y"), QStringLiteral("-stats"), QStringLiteral("-v"), QStringLiteral("error")}; m_jobDuration = (int)binClip->duration().seconds(); QString proxyParams = pCore->currentDoc()->getDocumentProperty(QStringLiteral("proxyparams")).simplified(); if (proxyParams.isEmpty()) { // Automatic setting, decide based on hw support proxyParams = pCore->currentDoc()->getAutoProxyProfile(); } bool nvenc = proxyParams.contains(QStringLiteral("%nvcodec")); if (nvenc) { QString pix_fmt = binClip->videoCodecProperty(QStringLiteral("pix_fmt")); QString codec = binClip->videoCodecProperty(QStringLiteral("name")); QStringList supportedCodecs{QStringLiteral("hevc"), QStringLiteral("h264"), QStringLiteral("mjpeg"), QStringLiteral("mpeg1"), QStringLiteral("mpeg2"), QStringLiteral("mpeg4"), QStringLiteral("vc1"), QStringLiteral("vp8"), QStringLiteral("vp9")}; QStringList supportedPixFmts{QStringLiteral("yuv420p"), QStringLiteral("yuyv422"), QStringLiteral("rgb24"), QStringLiteral("bgr24"), QStringLiteral("yuv422p"), QStringLiteral("yuv444p"), QStringLiteral("rgb32"), QStringLiteral("yuv410p"), QStringLiteral("yuv411p")}; bool supported = KdenliveSettings::nvScalingEnabled() && supportedCodecs.contains(codec) && supportedPixFmts.contains(pix_fmt); if (supported) { // Full hardware decoding supported codec.append(QStringLiteral("_cuvid")); proxyParams.replace(QStringLiteral("%nvcodec"), codec); } else { proxyParams = proxyParams.section(QStringLiteral("-i"), 1); proxyParams.replace(QStringLiteral("scale_cuda"), QStringLiteral("scale")); proxyParams.replace(QStringLiteral("scale_npp"), QStringLiteral("scale")); if (!supportedPixFmts.contains(pix_fmt)) { proxyParams.prepend(QStringLiteral("-pix_fmt yuv420p")); } } } bool disableAutorotate = binClip->getProducerProperty(QStringLiteral("autorotate")) == QLatin1String("0"); if (disableAutorotate || proxyParams.contains(QStringLiteral("-noautorotate"))) { // The noautorotate flag must be passed before input source parameters << QStringLiteral("-noautorotate"); } if (proxyParams.contains(QLatin1String("-i "))) { // we have some pre-filename parameters, filename will be inserted later } else { parameters << QStringLiteral("-i") << source; } QString params = proxyParams; #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) for (const QString &s : params.split(QLatin1Char(' '), QString::SkipEmptyParts)) { #else for (const QString &s : params.split(QLatin1Char(' '), Qt::SkipEmptyParts)) { #endif QString t = s.simplified(); if (t != QLatin1String("-noautorotate")) { parameters << t; if (t == QLatin1String("-i")) { parameters << source; } } } - // Make sure we don't block when proxy file already exists + // Make sure we keep the stream order + parameters << QStringLiteral("-map") << QStringLiteral("0"); parameters << dest; - qDebug()<<"/// FULL PROXY PARAMS:\n"<setProcessChannelMode(QProcess::MergedChannels); connect(m_jobProcess, &QProcess::readyReadStandardError, this, &ProxyJob::processLogInfo); connect(this, &ProxyJob::jobCanceled, m_jobProcess, &QProcess::kill, Qt::DirectConnection); m_jobProcess->start(KdenliveSettings::ffmpegpath(), parameters, QIODevice::ReadOnly); m_jobProcess->waitForFinished(-1); result = m_jobProcess->exitStatus() == QProcess::NormalExit; } // remove temporary playlist if it exists if (result) { if (QFileInfo(dest).size() == 0) { QFile::remove(dest); // File was not created m_done = false; m_errorMessage.append(i18n("Failed to create proxy clip.")); } else { m_done = true; } } else { // Proxy process crashed QFile::remove(dest); m_done = false; m_errorMessage.append(QString::fromUtf8(m_jobProcess->readAll())); } m_jobProcess->deleteLater(); return result; } void ProxyJob::processLogInfo() { const QString buffer = QString::fromUtf8(m_jobProcess->readAllStandardError()); m_logDetails.append(buffer); int progress = 0; if (m_isFfmpegJob) { // Parse FFmpeg output if (m_jobDuration == 0) { if (buffer.contains(QLatin1String("Duration:"))) { QString data = buffer.section(QStringLiteral("Duration:"), 1, 1).section(QLatin1Char(','), 0, 0).simplified(); if (!data.isEmpty()) { QStringList numbers = data.split(QLatin1Char(':')); if (numbers.size() < 3) { return; } m_jobDuration = (int)(numbers.at(0).toInt() * 3600 + numbers.at(1).toInt() * 60 + numbers.at(2).toDouble()); } } } else if (buffer.contains(QLatin1String("time="))) { QString time = buffer.section(QStringLiteral("time="), 1, 1).simplified().section(QLatin1Char(' '), 0, 0); if (!time.isEmpty()) { QStringList numbers = time.split(QLatin1Char(':')); if (numbers.size() < 3) { progress = (int)time.toDouble(); if (progress == 0) { return; } } else { progress = numbers.at(0).toInt() * 3600 + numbers.at(1).toInt() * 60 + numbers.at(2).toDouble(); } } emit jobProgress((int)(100.0 * progress / m_jobDuration)); } } else { // Parse MLT output if (buffer.contains(QLatin1String("percentage:"))) { progress = buffer.section(QStringLiteral("percentage:"), 1).simplified().section(QLatin1Char(' '), 0, 0).toInt(); emit jobProgress(progress); } } } bool ProxyJob::commitResult(Fun &undo, Fun &redo) { Q_ASSERT(!m_resultConsumed); if (!m_done) { qDebug() << "ERROR: Trying to consume invalid results"; auto binClip = pCore->projectItemModel()->getClipByBinID(m_clipId); binClip->setProducerProperty(QStringLiteral("kdenlive:proxy"), QStringLiteral("-")); return false; } m_resultConsumed = true; auto operation = [clipId = m_clipId]() { auto binClip = pCore->projectItemModel()->getClipByBinID(clipId); binClip->setProducerProperty(QStringLiteral("_overwriteproxy"), QString()); const QString dest = binClip->getProducerProperty(QStringLiteral("kdenlive:proxy")); binClip->setProducerProperty(QStringLiteral("resource"), dest); pCore->bin()->reloadClip(clipId, false); return true; }; auto reverse = [clipId = m_clipId]() { auto binClip = pCore->projectItemModel()->getClipByBinID(clipId); const QString dest = binClip->getProducerProperty(QStringLiteral("kdenlive:originalurl")); binClip->setProducerProperty(QStringLiteral("resource"), dest); pCore->bin()->reloadClip(clipId, false); return true; }; bool ok = operation(); if (ok) { UPDATE_UNDO_REDO_NOLOCK(operation, reverse, undo, redo); } return ok; return true; }