9#include <klocalizedstring.h>
13#include <QJsonDocument>
18#include <QRegularExpression>
19#include <QApplication>
28QRegularExpression frameRegexp(
"^frame=[ ]*([0-9]+) .*$");
29QRegularExpression lineDelimiter(
"[\n\r]");
30QRegularExpression junkRegex(
"\\[[a-zA-Z0-9]+ @ 0x[a-fA-F0-9]*\\][ ]*");
31QStringList errorWords = {
"Unable",
"Invalid",
"Error",
"failed",
"NULL",
"No such",
"divisible",
"not" };
32QRegularExpression ffmpegVersionRX(
"(ffmpeg|ffprobe) version (.+?)\\s");
63 if (QFile::exists(renderLogPath)) {
64 QFile existingFile(renderLogPath);
65 existingFile.remove();
68 QFile renderLog(renderLogPath);
71 if (renderLog.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
73 renderLog.write(command.toUtf8());
74 renderLog.write(
"\n");
75 renderLog.write(
"=====================================================\n");
80 QFile renderLog(renderLogPath);
81 if (renderLog.open(QIODevice::WriteOnly | QIODevice::Append)) {
82 renderLog.write(stderrBuffer);
87 if (!settings.
logPath.isEmpty()) {
88 QString sessionRenderLogPath(settings.
logPath);
90 QFile sessionRenderLog(sessionRenderLogPath);
93 const QString logDirPath = QFileInfo(sessionRenderLogPath).dir().path();
94 QDir().mkpath(logDirPath);
96 if (sessionRenderLog.open(QIODevice::WriteOnly | QIODevice::Append)) {
99 QFile renderLog(renderLogPath);
100 QFile sessionRenderLog(sessionRenderLogPath);
101 renderLog.open(QIODevice::ReadOnly);
102 sessionRenderLog.open(QIODevice::WriteOnly | QIODevice::Append);
106 while (!(buffer = renderLog.read(chunksize)).isEmpty()) {
107 sessionRenderLog.write(buffer);
116 progressText.replace(
"[progress]",
"0");
120 m_progress->setWindowModality(Qt::ApplicationModal);
135 dbgFile <<
"Open progress dialog!";
149 args << settings.
args;
164 struct ProcessResults {
166 QString error = QString();
172 processResults->finish =
true;
173 processResults->error = errMsg;
177 processResults->finish =
true;
183 if (processResults->finish ==
true) {
184 if (processResults->error.isEmpty()) {
200 return (
m_process->waitForFinished(msecs) &&
m_process->exitStatus() == QProcess::ExitStatus::NormalExit);
216 progressText.replace(
"[progress]", QString::number(progressValue));
218 progressText.replace(
"[suffix]", suffix );
224 QApplication::processEvents();
233 QRegularExpression invalidStreamRX(
"(?:Unsupported codec with id .+? for input stream|Could not find codec parameters for stream) ([0-9]+)");
234 QRegularExpressionMatchIterator invalidStreamMatchList = invalidStreamRX.globalMatch(ffprobeSTDERR);
236 while (invalidStreamMatchList.hasNext()) {
237 QRegularExpressionMatch invalidStreamMatch = invalidStreamMatchList.next();
239 if (invalidStreamMatch.hasMatch()) {
240 const int invalidStreamId = invalidStreamMatch.captured(1).toInt();
243 if (ffprobeJsonObj[
"streams"].toArray()[invalidStreamId].toObject()[
"codec_type"] ==
"video") {
258 if (
m_process->state() != QProcess::NotRunning) {
266 QByteArray stderrRawBuffer =
m_process->readAllStandardError();
275 while ((endPos =
m_stderrBuffer.indexOf(lineDelimiter, startPos)) != -1) {
276 const QString &line =
m_stderrBuffer.mid(startPos, endPos - startPos).trimmed();
282 for (
const QString &word : errorWords) {
283 if (line.contains(word)) {
289 const QRegularExpressionMatch &match = frameRegexp.match(line);
291 if (match.hasMatch()) {
292 frameNo = match.captured(1).toInt();
296 dbgFile <<
"ffmpeg stderr:" << line;
297 startPos = endPos + 1;
311 QByteArray stdoutRawBuffer =
m_process->readAllStandardOutput();
328 while ((endPos =
m_stdoutBuffer.indexOf(lineDelimiter, startPos)) != -1) {
329 const QString &line =
m_stdoutBuffer.mid(startPos, endPos - startPos).trimmed();
331 dbgFile <<
"ffmpeg stdout:" << line;
333 startPos = endPos + 1;
344 dbgFile <<
"ffmpeg process started!";
351 dbgFile <<
"FFMpeg finished with code" << exitCode;
358 if (
m_process->exitStatus() == QProcess::CrashExit) {
380 QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
382 const QStringList libraryPaths = env.value(
"LD_LIBRARY_PATH").split(
':');
383 const QString processAbsPath = QFileInfo(QFileInfo(processPath).absolutePath() +
"/../").absoluteFilePath();
385 bool isCustomBuildOfFFmpeg =
false;
387 Q_FOREACH (
const QString &path, libraryPaths) {
388 const QString absPath1 = QFileInfo(path +
"/").absoluteFilePath();
389 const QString absPath2 = QFileInfo(path +
"/../").absoluteFilePath();
391 if (absPath1 == processAbsPath || absPath2 == processAbsPath) {
392 dbgFile <<
"Detected embedded ffmpeg:" << processPath;
397 isCustomBuildOfFFmpeg =
true;
403 if (!isCustomBuildOfFFmpeg) {
404 dbgFile <<
"Removing LD_LIBRARY_PATH for running" << processPath;
406 env.remove(
"LD_LIBRARY_PATH");
407 process.setProcessEnvironment(env);
418 runProcess.start(processPath, args);
420 if (runProcess.waitForStarted(msecs)) runProcess.waitForFinished(msecs);
422 const bool successfulStart =
423 runProcess.state() == QProcess::NotRunning &&
424 runProcess.error() == QProcess::UnknownError;
426 dbgFile <<
"runProcessAndReturn Success:" << successfulStart;
428 if (successfulStart)
return runProcess.readAllStandardOutput();
447 QJsonObject resultJsonObj;
451 if (!customLocation.isEmpty()) {
452 proposedPaths << customLocation;
453 proposedPaths << customLocation +
'/' + processName;
462 QDir wingetPackageDir = QDir(QDir::homePath() +
"/AppData/Local/Microsoft/WinGet/Packages/");
463 QStringList wingetPackageFolderNames = wingetPackageDir.entryList();
464 for (
const auto &folderName : wingetPackageFolderNames) {
465 if (folderName.contains(
"ffmpeg", Qt::CaseInsensitive)) {
466 QDir ffmpegPackageFolder = QDir(wingetPackageDir.path() +
"/" + folderName);
467 QStringList ffmpegRootNames = ffmpegPackageFolder.entryList();
468 for (
const auto &ffmpegRootName : ffmpegRootNames) {
469 proposedPaths << ffmpegPackageFolder.path() +
"/" + ffmpegRootName +
"/bin/";
476 proposedPaths << QCoreApplication::applicationDirPath() +
'/' + processName;
480 proposedPaths << QDir::homePath() +
"/bin/" + processName;
481 proposedPaths <<
"/usr/bin/" + processName;
482 proposedPaths <<
"/usr/local/bin/" + processName;
486 for (
int i = 0; i != proposedPaths.size(); ++i) {
487 if (proposedPaths[i].isEmpty())
continue;
490 proposedPaths[i] = QDir::toNativeSeparators(QDir::cleanPath(proposedPaths[i]));
492 if (proposedPaths[i].endsWith(
'/')) {
496 if (!proposedPaths[i].endsWith(
".exe", Qt::CaseInsensitive)) {
497 if (!QFile::exists(proposedPaths[i])) {
498 proposedPaths[i] +=
".exe";
499 if (!QFile::exists(proposedPaths[i])) {
506 QJsonObject processInfoJsonObj =
findProcessInfo(processName, proposedPaths[i], includeProcessInfo);
507 dbgFile <<
"PATH" << proposedPaths[i] << processInfoJsonObj.value(
"enabled").toBool();
508 if (processInfoJsonObj.value(
"enabled").toBool()) {
509 processInfoJsonObj[
"custom"]=(!customLocation.isEmpty() && i <= 1) ?
true:
false;
510 resultJsonObj = processInfoJsonObj;
515 return resultJsonObj;
521 QJsonObject ffmpegInfo {{
"path", rawProcessPath},
524 {
"encoder",QJsonValue::Object},
525 {
"decoder",QJsonValue::Object}};
527 if (!QFile::exists(rawProcessPath))
return ffmpegInfo;
529 const QString processPath = QFileInfo(rawProcessPath).absoluteFilePath();
530 ffmpegInfo[
"path"] = processPath;
532 dbgFile <<
"Found process at:" << processPath;
535 if (!processVersion.isEmpty()) {
537 QRegularExpressionMatch versionMatch = ffmpegVersionRX.match(processVersion);
539 if (versionMatch.hasMatch() && versionMatch.captured(1) == processName ) {
540 ffmpegInfo[
"version"] = versionMatch.captured(2);
541 dbgFile <<
"found version" << ffmpegInfo.value(
"version").toString();
542 ffmpegInfo[
"enabled"] =
true;
545 if (!includeProcessInfo || !ffmpegInfo[
"enabled"].toBool())
return ffmpegInfo;
549 QJsonObject codecsJson {};
554 QRegularExpression ffmpegCodecsRX(
"(D|\\.)(E|\\.)....\\s+(.+?)\\s+([^\\r\\n]*)");
555 QRegularExpressionMatchIterator codecsMatchList = ffmpegCodecsRX.globalMatch(processCodecs);
558 while (codecsMatchList.hasNext()) {
559 QRegularExpressionMatch codecsMatch = codecsMatchList.next();
561 if (codecsMatch.hasMatch()) {
562 QJsonObject codecInfoJson {};
564 bool encodingSupported = codecsMatch.captured(2) ==
"E" ?
true:
false;
565 bool decodingSupported = codecsMatch.captured(1) ==
"D" ?
true:
false;
566 QString codecName = codecsMatch.captured(3);
567 QString codecRemainder = codecsMatch.captured(4);
569 codecInfoJson.insert(
"encoding", encodingSupported);
570 codecInfoJson.insert(
"decoding", decodingSupported);
574 QRegularExpression ffmpegSpecificCodecRX(
"\\(decoders:(.+?)\\)|\\(encoders:(.+?)\\)");
575 QRegularExpressionMatchIterator specificCodecMatchList = ffmpegSpecificCodecRX.globalMatch(codecRemainder);
577 QJsonArray encodersList;
578 QJsonArray decodersList;
580 while (specificCodecMatchList.hasNext()) {
581 QRegularExpressionMatch specificCodecMatch = specificCodecMatchList.next();
582 if (specificCodecMatch.hasMatch()) {
584 QStringList decoders = specificCodecMatch.captured(1).split(
" ");
585 Q_FOREACH(
const QString&
string, decoders) {
586 if (!
string.isEmpty()) {
587 decodersList.push_back(
string);
592 QStringList encoders = specificCodecMatch.captured(2).split(
" ");
593 Q_FOREACH(
const QString&
string, encoders) {
594 if (!
string.isEmpty()) {
595 encodersList.push_back(
string);
601 codecInfoJson.insert(
"encoders", encodersList);
602 codecInfoJson.insert(
"decoders", decodersList);
603 codecsJson.insert(codecName, codecInfoJson);
608 ffmpegInfo.insert(
"codecs", codecsJson);
611 dbgFile <<
"codec support:" << ffmpegInfo;
613 dbgFile <<
"Not a valid process at:" << processPath;
629 QJsonObject encoders = ffmpegProcessInfo[
"codecs"].toObject();
630 Q_FOREACH(
const QString& key, encoders.keys()) {
631 if (encoders[key].toObject()[
"encoding"].toBool()) {
632 encodersToReturn << key;
636 return encodersToReturn;
657 ffprobeSettings.
args <<
"-hide_banner"
659 <<
"-of" <<
"json=compact=1"
662 <<
"-i" << inputFile;
669 QJsonDocument ffprobeJsonDoc = QJsonDocument::fromJson(ffprobeSTDOUT.toUtf8());
670 QJsonObject ffprobeJsonObj;
672 if (ffprobeJsonDoc.isNull() || !ffprobeJsonDoc.isObject()) {
674 return ffprobeJsonObj;
677 ffprobeJsonObj = ffprobeJsonDoc.object();
683 return ffprobeJsonObj;
692 ffmpegSettings.
progressMessage = i18nc(
"Video information probing dialog. arg1: frame number.",
"Loading video data... %1 frames examined.",
"[progress]");
695 ffmpegSettings.
args <<
"-stats"
697 <<
"-progress" <<
"pipe:1"
700 <<
"-f" <<
"null" <<
"pipe:1"
701 <<
"-i" << inputFile;
710 dbgFile <<
"ffmpegProbe stdout:" << ffmpegSTDOUT;
712 QJsonObject ffmpegJsonObj;
713 QJsonArray ffmpegStreamsJsonArr;
714 QJsonObject ffmpegFormatJsonObj;
715 QJsonObject ffmpegProgressJsonObj;
717 QStringList stdoutLines = ffmpegSTDOUT.split(
'\n');
721 for (
const QString &line : stdoutLines) {
722 dbgFile <<
"ffmpeg probe stdout" << line;
723 QRegularExpression videoInfoInputRX(
"Duration: (\\d+):(\\d+):([\\d\\.]+),");
724 QRegularExpressionMatch durationMatch = videoInfoInputRX.match(line);
726 if (ffmpegFormatJsonObj.value(
"duration").isUndefined() && durationMatch.hasMatch()) {
727 ffmpegFormatJsonObj[
"duration"] = QString::number( (durationMatch.captured(1).toInt() * 60 * 60)
728 + (durationMatch.captured(2).toInt() * 60)
729 + durationMatch.captured(3).toFloat()
732 QRegularExpression videoInfoStreamRX(
733 QString(
"Stream #(\\d+):(\\d+)(?:[ ]*?\\((\\w+)\\)|): Video: (\\w+?)(?:[ ]*?\\((.+?)\\)|),[ ]*?")
734 .append(
"(\\w+?)(?:[ ]*?\\((.+?)\\)|),[ ]*?")
735 .append(
"(\\d+)x(\\d+)([ ]*?\\[.+?\\]|.*?),[ ]*?")
736 .append(
"(?:([\\d\\.]+) fps,|) (\\S+?) tbr, (.+?) tbn, (.+?) tbc")
739 QRegularExpressionMatch streamMatch = videoInfoStreamRX.match(line);
741 if (streamMatch.hasMatch() && ffmpegStreamsJsonArr[streamMatch.captured(1).toInt()].isUndefined() ) {
742 int index = streamMatch.captured(1).toInt();
743 QJsonObject ffmpegJsonOnStreamObj;
745 ffmpegJsonOnStreamObj[
"index"] = index;
746 ffmpegJsonOnStreamObj[
"codec_name"] = streamMatch.captured(4);
747 ffmpegJsonOnStreamObj[
"profile"] = streamMatch.captured(5);
748 ffmpegJsonOnStreamObj[
"pix_fmt"] = streamMatch.captured(6);
749 ffmpegJsonOnStreamObj[
"width"] = streamMatch.captured(8).toInt();
750 ffmpegJsonOnStreamObj[
"height"] = streamMatch.captured(9).toInt();
752 ffmpegJsonOnStreamObj[
"codec_type"] =
"video";
754 if (streamMatch.captured(11).toFloat() > 0) {
755 float fps = streamMatch.captured(11).toFloat();
757 ffmpegProgressJsonObj[
"ffmpeg_fps"] = QString::number(fps);
758 ffmpegJsonOnStreamObj[
"r_frame_rate"] = QString::number( fps * 10000 ).append(QString(
"/10000"));
760 ffmpegProgressJsonObj[
"ffmpeg_fps"] = 0;
765 dbgFile <<
"ffmpegProbe stream:" << ffmpegJsonOnStreamObj;
767 ffmpegStreamsJsonArr.insert(index, ffmpegJsonOnStreamObj);
771 QRegularExpression videoInfoFrameRX(
"^(\\w+?)=([\\w\\./:]+?)$");
772 QRegularExpressionMatch frameMatch = videoInfoFrameRX.match(line);
774 if (frameMatch.hasMatch()) ffmpegProgressJsonObj[frameMatch.captured(1)] = frameMatch.captured(2);
780 ffmpegJsonObj.insert(
"streams",ffmpegStreamsJsonArr);
781 ffmpegJsonObj.insert(
"format",ffmpegFormatJsonObj);
782 ffmpegJsonObj.insert(
"progress",ffmpegProgressJsonObj);
784 return ffmpegJsonObj;
789 if (name ==
"bt709") {
792 if (name ==
"bt470m") {
795 if (name ==
"bt470bg") {
798 if (name ==
"smpte170m") {
801 if (name ==
"smpte240m") {
804 if (name ==
"film") {
807 if (name ==
"bt2020") {
810 if (name.startsWith(
"smpte428")) {
813 if (name ==
"smpte431") {
816 if (name ==
"smpte432") {
819 if (name ==
"jedec-p22") {
828 if (name ==
"bt709") {
831 if (name ==
"gamma22") {
834 if (name ==
"gamma28") {
837 if (name ==
"smpte170m") {
840 if (name ==
"smpte240m") {
843 if (name ==
"linear") {
846 if (name ==
"log" || name ==
"log100") {
849 if (name ==
"log316" || name ==
"log_sqrt") {
852 if (name ==
"iec61966_2_4" || name ==
"iec61966-2-4") {
855 if (name.startsWith(
"bt1361")) {
858 if (name ==
"iec61966_2_1" || name ==
"iec61966-2-1") {
861 if (name.startsWith(
"bt2020_10")) {
864 if (name.startsWith(
"bt2020_12")) {
867 if (name ==
"smpte2084") {
870 if (name ==
"smpte240m") {
873 if (name.startsWith(
"smpte428")) {
876 if (name ==
"arib-std-b67") {
QList< QString > QStringList
ColorPrimaries
The colorPrimaries enum Enum of colorants, follows ITU H.273 for values 0 to 255, and has extra known...
@ PRIMARIES_ITU_R_BT_2020_2_AND_2100_0
@ PRIMARIES_ITU_R_BT_470_6_SYSTEM_M
@ PRIMARIES_EBU_Tech_3213_E
@ PRIMARIES_ITU_R_BT_470_6_SYSTEM_B_G
@ PRIMARIES_ITU_R_BT_601_6
@ PRIMARIES_SMPTE_RP_431_2
@ PRIMARIES_SMPTE_EG_432_1
@ PRIMARIES_ITU_R_BT_709_5
@ PRIMARIES_SMPTE_ST_428_1
TransferCharacteristics
The transferCharacteristics enum Enum of transfer characteristics, follows ITU H.273 for values 0 to ...
@ TRC_ITU_R_BT_2020_2_10bit
@ TRC_ITU_R_BT_470_6_SYSTEM_M
@ TRC_ITU_R_BT_470_6_SYSTEM_B_G
@ TRC_ITU_R_BT_2100_0_HLG
@ TRC_LOGARITHMIC_100_sqrt10
@ TRC_ITU_R_BT_2020_2_12bit
connect(this, SIGNAL(optionsChanged()), this, SLOT(saveOptions()))
void setFFMpegLocation(const QString &value)
QString ffmpegLocation(bool defaultValue=false) const
void slotReadyReadSTDERR()
KisFFMpegWrapperSettings m_processSettings
void startNonBlocking(const KisFFMpegWrapperSettings &settings)
KisFFMpegWrapper(QObject *parent=nullptr)
static TransferCharacteristics transferCharacteristicsFromName(QString name)
void updateProgressDialog(int progressValue)
QSharedPointer< QProgressDialog > m_progress
static QJsonObject findFFProbe(const QString &customLocation)
void sigProgressUpdated(int frameNo)
void sigReadLine(int pipe, QString line)
QByteArray m_processSTDOUT
QJsonObject ffprobe(const QString &inputFile, const QString &ffprobePath)
static void fixUpNonEmbeddedProcessEnvironment(const QString &processPath, QProcess &process)
static QJsonObject findProcessInfo(const QString &processName, const QString &processPath, bool includeProcessInfo)
void sigReadSTDOUT(QByteArray stdoutBuffer)
bool ffprobeCheckStreamsValid(const QJsonObject &ffprobeJsonObj, const QString &ffprobeSTDERR)
ffprobeCheckStreamsValid
void sigReadSTDERR(QByteArray stderrBuffer)
static QString configuredFFMpegLocation()
static QJsonObject findProcessPath(const QString &processName, const QString &customLocation, bool processInfo)
QJsonObject ffmpegProbe(const QString &inputFile, const QString &ffmpegPath, bool batchMode)
static QStringList getSupportedCodecs(const QJsonObject &ffmpegJsonProcessInput)
void slotReadyReadSTDOUT()
KisImportExportErrorCode start(const KisFFMpegWrapperSettings &settings)
void sigFinishedWithError(QString message)
static ColorPrimaries colorPrimariesFromName(QString name)
QScopedPointer< QProcess > m_process
void slotFinished(int exitCode)
static QJsonObject findFFMpeg(const QString &customLocation)
bool waitForFinished(int msecs=FFMPEG_TIMEOUT)
static QByteArray runProcessAndReturn(const QString &processPath, const QStringList &args, int msecs=FFMPEG_TIMEOUT)
static void setConfiguredFFMpegLocation(QString &location)
static QString getApplicationRoot()
#define KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(cond, val)
QSharedPointer< T > toQShared(T *ptr)
QStringList defaultPrependArgs
bool progressIndeterminate