Krita Source Code Documentation
Loading...
Searching...
No Matches
KisFFMpegWrapper Class Reference

#include <KisFFMpegWrapper.h>

+ Inheritance diagram for KisFFMpegWrapper:

Signals

void sigFinished ()
 
void sigFinishedWithError (QString message)
 
void sigProgressUpdated (int frameNo)
 
void sigReadLine (int pipe, QString line)
 
void sigReadSTDERR (QByteArray stderrBuffer)
 
void sigReadSTDOUT (QByteArray stdoutBuffer)
 
void sigStarted ()
 

Public Member Functions

QJsonObject ffmpegProbe (const QString &inputFile, const QString &ffmpegPath, bool batchMode)
 
QJsonObject ffprobe (const QString &inputFile, const QString &ffprobePath)
 
 KisFFMpegWrapper (QObject *parent=nullptr)
 
void reset ()
 
KisImportExportErrorCode start (const KisFFMpegWrapperSettings &settings)
 
void startNonBlocking (const KisFFMpegWrapperSettings &settings)
 
bool waitForFinished (int msecs=FFMPEG_TIMEOUT)
 
 ~KisFFMpegWrapper ()
 

Static Public Member Functions

static ColorPrimaries colorPrimariesFromName (QString name)
 
static QString configuredFFMpegLocation ()
 
static QJsonObject findFFMpeg (const QString &customLocation)
 
static QJsonObject findFFProbe (const QString &customLocation)
 
static QJsonObject findProcessInfo (const QString &processName, const QString &processPath, bool includeProcessInfo)
 
static QJsonObject findProcessPath (const QString &processName, const QString &customLocation, bool processInfo)
 
static QStringList getSupportedCodecs (const QJsonObject &ffmpegJsonProcessInput)
 
static QByteArray runProcessAndReturn (const QString &processPath, const QStringList &args, int msecs=FFMPEG_TIMEOUT)
 
static void setConfiguredFFMpegLocation (QString &location)
 
static TransferCharacteristics transferCharacteristicsFromName (QString name)
 

Private Slots

void slotFinished (int exitCode)
 
void slotReadyReadSTDERR ()
 
void slotReadyReadSTDOUT ()
 
void slotStarted ()
 

Private Member Functions

bool ffprobeCheckStreamsValid (const QJsonObject &ffprobeJsonObj, const QString &ffprobeSTDERR)
 ffprobeCheckStreamsValid
 
void updateProgressDialog (int progressValue)
 

Static Private Member Functions

static void fixUpNonEmbeddedProcessEnvironment (const QString &processPath, QProcess &process)
 

Private Attributes

QString m_errorMessage
 
QScopedPointer< QProcess > m_process
 
KisFFMpegWrapperSettings m_processSettings
 
QString m_processSTDERR
 
QByteArray m_processSTDOUT
 
QSharedPointer< QProgressDialog > m_progress = nullptr
 
QString m_stderrBuffer
 
QString m_stdoutBuffer
 

Detailed Description

Definition at line 46 of file KisFFMpegWrapper.h.

Constructor & Destructor Documentation

◆ KisFFMpegWrapper()

KisFFMpegWrapper::KisFFMpegWrapper ( QObject * parent = nullptr)
explicit

Definition at line 36 of file KisFFMpegWrapper.cpp.

37 : QObject(parent)
38{
39}

◆ ~KisFFMpegWrapper()

KisFFMpegWrapper::~KisFFMpegWrapper ( )

Definition at line 41 of file KisFFMpegWrapper.cpp.

42{
43}

Member Function Documentation

◆ colorPrimariesFromName()

ColorPrimaries KisFFMpegWrapper::colorPrimariesFromName ( QString name)
static

Definition at line 790 of file KisFFMpegWrapper.cpp.

791{
792 if (name == "bt709") {
794 }
795 if (name == "bt470m") {
797 }
798 if (name == "bt470bg") {
800 }
801 if (name == "smpte170m") {
803 }
804 if (name == "smpte240m") {
806 }
807 if (name == "film") {
809 }
810 if (name == "bt2020") {
812 }
813 if (name.startsWith("smpte428")) {
815 }
816 if (name == "smpte431") {
818 }
819 if (name == "smpte432") {
821 }
822 if (name == "jedec-p22") {
824 }
825
827}
@ PRIMARIES_ITU_R_BT_2020_2_AND_2100_0
@ PRIMARIES_ITU_R_BT_470_6_SYSTEM_M
@ PRIMARIES_EBU_Tech_3213_E
@ PRIMARIES_UNSPECIFIED
@ PRIMARIES_ITU_R_BT_470_6_SYSTEM_B_G
@ PRIMARIES_SMPTE_240M
@ PRIMARIES_ITU_R_BT_601_6
@ PRIMARIES_SMPTE_RP_431_2
@ PRIMARIES_GENERIC_FILM
@ PRIMARIES_SMPTE_EG_432_1
@ PRIMARIES_ITU_R_BT_709_5
@ PRIMARIES_SMPTE_ST_428_1
const char * name(StandardAction id)

References PRIMARIES_EBU_Tech_3213_E, PRIMARIES_GENERIC_FILM, PRIMARIES_ITU_R_BT_2020_2_AND_2100_0, PRIMARIES_ITU_R_BT_470_6_SYSTEM_B_G, PRIMARIES_ITU_R_BT_470_6_SYSTEM_M, PRIMARIES_ITU_R_BT_601_6, PRIMARIES_ITU_R_BT_709_5, PRIMARIES_SMPTE_240M, PRIMARIES_SMPTE_EG_432_1, PRIMARIES_SMPTE_RP_431_2, PRIMARIES_SMPTE_ST_428_1, and PRIMARIES_UNSPECIFIED.

◆ configuredFFMpegLocation()

QString KisFFMpegWrapper::configuredFFMpegLocation ( )
static

Definition at line 436 of file KisFFMpegWrapper.cpp.

437{
438 KisConfig cfg(true);
439 return cfg.ffmpegLocation();
440}

References KisConfig::ffmpegLocation().

◆ ffmpegProbe()

QJsonObject KisFFMpegWrapper::ffmpegProbe ( const QString & inputFile,
const QString & ffmpegPath,
bool batchMode )

Definition at line 689 of file KisFFMpegWrapper.cpp.

690{
691 struct KisFFMpegWrapperSettings ffmpegSettings;
692
693 ffmpegSettings.processPath = ffmpegPath;
694 ffmpegSettings.storeOutput = true;
695 ffmpegSettings.progressMessage = i18nc("Video information probing dialog. arg1: frame number.", "Loading video data... %1 frames examined.", "[progress]");
696 ffmpegSettings.batchMode = batchMode;
697
698 ffmpegSettings.args << "-stats"
699 << "-v" << "info"
700 << "-progress" << "pipe:1"
701 << "-map" << "0:v:0"
702 << "-c" << "copy"
703 << "-f" << "null" << "pipe:1"
704 << "-i" << inputFile;
705
706
707 this->startNonBlocking(ffmpegSettings);
708 this->waitForFinished();
709
710
711 QString ffmpegSTDOUT = m_processSTDERR + "\n" + m_processSTDOUT;
712
713 dbgFile << "ffmpegProbe stdout:" << ffmpegSTDOUT;
714
715 QJsonObject ffmpegJsonObj;
716 QJsonArray ffmpegStreamsJsonArr;
717 QJsonObject ffmpegFormatJsonObj;
718 QJsonObject ffmpegProgressJsonObj;
719
720 QStringList stdoutLines = ffmpegSTDOUT.split('\n');
721
722 ffmpegJsonObj["error"] = FFProbeErrorCodes::UNSUPPORTED_CODEC;
723
724 for (const QString &line : stdoutLines) {
725 dbgFile << "ffmpeg probe stdout" << line;
726 QRegularExpression videoInfoInputRX("Duration: (\\d+):(\\d+):([\\d\\.]+),");
727 QRegularExpressionMatch durationMatch = videoInfoInputRX.match(line);
728
729 if (ffmpegFormatJsonObj.value("duration").isUndefined() && durationMatch.hasMatch()) {
730 ffmpegFormatJsonObj["duration"] = QString::number( (durationMatch.captured(1).toInt() * 60 * 60)
731 + (durationMatch.captured(2).toInt() * 60)
732 + durationMatch.captured(3).toFloat()
733 );
734 } else {
735 QRegularExpression videoInfoStreamRX(
736 QString("Stream #(\\d+):(\\d+)(?:[ ]*?\\((\\w+)\\)|): Video: (\\w+?)(?:[ ]*?\\((.+?)\\)|),[ ]*?")
737 .append("(\\w+?)(?:[ ]*?\\((.+?)\\)|),[ ]*?")
738 .append("(\\d+)x(\\d+)([ ]*?\\[.+?\\]|.*?),[ ]*?")
739 .append("(?:([\\d\\.]+) fps,|) (\\S+?) tbr, (.+?) tbn, (.+?) tbc")
740 );
741
742 QRegularExpressionMatch streamMatch = videoInfoStreamRX.match(line);
743
744 if (streamMatch.hasMatch() && ffmpegStreamsJsonArr[streamMatch.captured(1).toInt()].isUndefined() ) {
745 int index = streamMatch.captured(1).toInt();
746 QJsonObject ffmpegJsonOnStreamObj;
747
748 ffmpegJsonOnStreamObj["index"] = index;
749 ffmpegJsonOnStreamObj["codec_name"] = streamMatch.captured(4);
750 ffmpegJsonOnStreamObj["profile"] = streamMatch.captured(5);
751 ffmpegJsonOnStreamObj["pix_fmt"] = streamMatch.captured(6);
752 ffmpegJsonOnStreamObj["width"] = streamMatch.captured(8).toInt();
753 ffmpegJsonOnStreamObj["height"] = streamMatch.captured(9).toInt();
754
755 ffmpegJsonOnStreamObj["codec_type"] = "video";
756
757 if (streamMatch.captured(11).toFloat() > 0) {
758 float fps = streamMatch.captured(11).toFloat();
759
760 ffmpegProgressJsonObj["ffmpeg_fps"] = QString::number(fps);
761 ffmpegJsonOnStreamObj["r_frame_rate"] = QString::number( fps * 10000 ).append(QString("/10000"));
762 } else {
763 ffmpegProgressJsonObj["ffmpeg_fps"] = 0;
764 }
765
766 ffmpegJsonObj["error"] = FFProbeErrorCodes::NONE;
767
768 dbgFile << "ffmpegProbe stream:" << ffmpegJsonOnStreamObj;
769
770 ffmpegStreamsJsonArr.insert(index, ffmpegJsonOnStreamObj);
771
772 } else {
773
774 QRegularExpression videoInfoFrameRX("^(\\w+?)=([\\w\\./:]+?)$");
775 QRegularExpressionMatch frameMatch = videoInfoFrameRX.match(line);
776
777 if (frameMatch.hasMatch()) ffmpegProgressJsonObj[frameMatch.captured(1)] = frameMatch.captured(2);
778
779 }
780 }
781 }
782
783 ffmpegJsonObj.insert("streams",ffmpegStreamsJsonArr);
784 ffmpegJsonObj.insert("format",ffmpegFormatJsonObj);
785 ffmpegJsonObj.insert("progress",ffmpegProgressJsonObj);
786
787 return ffmpegJsonObj;
788}
@ UNSUPPORTED_CODEC
@ NONE
void startNonBlocking(const KisFFMpegWrapperSettings &settings)
QByteArray m_processSTDOUT
bool waitForFinished(int msecs=FFMPEG_TIMEOUT)
#define dbgFile
Definition kis_debug.h:53

References KisFFMpegWrapperSettings::args, KisFFMpegWrapperSettings::batchMode, dbgFile, m_processSTDERR, m_processSTDOUT, NONE, KisFFMpegWrapperSettings::processPath, KisFFMpegWrapperSettings::progressMessage, startNonBlocking(), KisFFMpegWrapperSettings::storeOutput, UNSUPPORTED_CODEC, and waitForFinished().

◆ ffprobe()

QJsonObject KisFFMpegWrapper::ffprobe ( const QString & inputFile,
const QString & ffprobePath )

Definition at line 652 of file KisFFMpegWrapper.cpp.

653{
654 struct KisFFMpegWrapperSettings ffprobeSettings;
655
656 ffprobeSettings.processPath = ffprobePath;
657 ffprobeSettings.storeOutput = true;
658 ffprobeSettings.defaultPrependArgs.clear();
659
660 ffprobeSettings.args << "-hide_banner"
661 << "-v" << "warning"
662 << "-of" << "json=compact=1"
663 << "-show_format"
664 << "-show_streams"
665 << "-i" << inputFile;
666 startNonBlocking(ffprobeSettings);
668
669 QString ffprobeSTDOUT = m_processSTDOUT;
670 QString ffprobeSTDERR = m_processSTDERR;
671
672 QJsonDocument ffprobeJsonDoc = QJsonDocument::fromJson(ffprobeSTDOUT.toUtf8());
673 QJsonObject ffprobeJsonObj;
674
675 if (ffprobeJsonDoc.isNull() || !ffprobeJsonDoc.isObject()) {
676 ffprobeJsonObj["error"] = FFProbeErrorCodes::INVALID_JSON;
677 return ffprobeJsonObj;
678 }
679
680 ffprobeJsonObj = ffprobeJsonDoc.object();
681
682 const bool hasValidStreams = ffprobeCheckStreamsValid(ffprobeJsonObj, ffprobeSTDERR);
683 ffprobeJsonObj["error"] = hasValidStreams ? FFProbeErrorCodes::NONE : FFProbeErrorCodes::UNSUPPORTED_CODEC;
684
685
686 return ffprobeJsonObj;
687}
@ INVALID_JSON
bool ffprobeCheckStreamsValid(const QJsonObject &ffprobeJsonObj, const QString &ffprobeSTDERR)
ffprobeCheckStreamsValid

References KisFFMpegWrapperSettings::args, KisFFMpegWrapperSettings::defaultPrependArgs, ffprobeCheckStreamsValid(), INVALID_JSON, m_processSTDERR, m_processSTDOUT, NONE, KisFFMpegWrapperSettings::processPath, startNonBlocking(), KisFFMpegWrapperSettings::storeOutput, UNSUPPORTED_CODEC, and waitForFinished().

◆ ffprobeCheckStreamsValid()

bool KisFFMpegWrapper::ffprobeCheckStreamsValid ( const QJsonObject & ffprobeJsonObj,
const QString & ffprobeSTDERR )
private

ffprobeCheckStreamsValid

Parameters
ffprobeJsonObjJsonObject resulting from ffprobe process.
ffprobeSTDERRSTDERR output from ffprobe.
Returns
bool -> Whether we support a video format based on it's internal streams.

Definition at line 228 of file KisFFMpegWrapper.cpp.

229{
230 // Sanity checks..
231 KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(ffprobeJsonObj.contains("streams"), false);
232
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);
235
236 while (invalidStreamMatchList.hasNext()) {
237 QRegularExpressionMatch invalidStreamMatch = invalidStreamMatchList.next();
238
239 if (invalidStreamMatch.hasMatch()) {
240 const int invalidStreamId = invalidStreamMatch.captured(1).toInt();
241
242 // We make sure we're talking about video streams here.
243 if (ffprobeJsonObj["streams"].toArray()[invalidStreamId].toObject()["codec_type"] == "video") {
244 return false;
245 }
246 }
247 }
248
249 return true;
250}
#define KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(cond, val)
Definition kis_assert.h:129

References KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE.

◆ findFFMpeg()

QJsonObject KisFFMpegWrapper::findFFMpeg ( const QString & customLocation)
static

Definition at line 642 of file KisFFMpegWrapper.cpp.

643{
644 return findProcessPath("ffmpeg", customLocation, true);
645}
static QJsonObject findProcessPath(const QString &processName, const QString &customLocation, bool processInfo)

References findProcessPath().

◆ findFFProbe()

QJsonObject KisFFMpegWrapper::findFFProbe ( const QString & customLocation)
static

Definition at line 647 of file KisFFMpegWrapper.cpp.

648{
649 return findProcessPath("ffprobe", customLocation, false);
650}

References findProcessPath().

◆ findProcessInfo()

QJsonObject KisFFMpegWrapper::findProcessInfo ( const QString & processName,
const QString & processPath,
bool includeProcessInfo )
static

Definition at line 521 of file KisFFMpegWrapper.cpp.

522{
523
524 QJsonObject ffmpegInfo {{"path", rawProcessPath},
525 {"enabled",false},
526 {"version", "0"},
527 {"encoder",QJsonValue::Object},
528 {"decoder",QJsonValue::Object}};
529
530 if (!QFile::exists(rawProcessPath)) return ffmpegInfo;
531
532 const QString processPath = QFileInfo(rawProcessPath).absoluteFilePath();
533 ffmpegInfo["path"] = processPath;
534
535 dbgFile << "Found process at:" << processPath;
536 QString processVersion = KisFFMpegWrapper::runProcessAndReturn(processPath, QStringList() << "-version", FFMPEG_TIMEOUT);
537
538 if (!processVersion.isEmpty()) {
539
540 QRegularExpressionMatch versionMatch = ffmpegVersionRX.match(processVersion);
541
542 if (versionMatch.hasMatch() && versionMatch.captured(1) == processName ) {
543 ffmpegInfo["version"] = versionMatch.captured(2);
544 dbgFile << "found version" << ffmpegInfo.value("version").toString();
545 ffmpegInfo["enabled"] = true;
546 }
547
548 if (!includeProcessInfo || !ffmpegInfo["enabled"].toBool()) return ffmpegInfo;
549
550 QString processCodecs = KisFFMpegWrapper::runProcessAndReturn(processPath, QStringList() << "-codecs", FFMPEG_TIMEOUT);
551
552 QJsonObject codecsJson {};
553
554 {
555 // For regular expression advice, check out https://regexr.com/.
556 // Beware: We need double backslashes here for C++, in regular regex it would be a single backslash instead.
557 QRegularExpression ffmpegCodecsRX("(D|\\.)(E|\\.)....\\s+(.+?)\\s+([^\\r\\n]*)");
558 QRegularExpressionMatchIterator codecsMatchList = ffmpegCodecsRX.globalMatch(processCodecs);
559
560 // Find out codec types.. (e.g. H264, VP9, etc)
561 while (codecsMatchList.hasNext()) {
562 QRegularExpressionMatch codecsMatch = codecsMatchList.next();
563
564 if (codecsMatch.hasMatch()) {
565 QJsonObject codecInfoJson {};
566
567 bool encodingSupported = codecsMatch.captured(2) == "E" ? true:false;
568 bool decodingSupported = codecsMatch.captured(1) == "D" ? true:false;
569 QString codecName = codecsMatch.captured(3);
570 QString codecRemainder = codecsMatch.captured(4);
571
572 codecInfoJson.insert("encoding", encodingSupported);
573 codecInfoJson.insert("decoding", decodingSupported);
574
575
576 // Regular expression for grouping specific encoders and decoders..
577 QRegularExpression ffmpegSpecificCodecRX("\\(decoders:(.+?)\\)|\\(encoders:(.+?)\\)");
578 QRegularExpressionMatchIterator specificCodecMatchList = ffmpegSpecificCodecRX.globalMatch(codecRemainder);
579
580 QJsonArray encodersList;
581 QJsonArray decodersList;
582
583 while (specificCodecMatchList.hasNext()) {
584 QRegularExpressionMatch specificCodecMatch = specificCodecMatchList.next();
585 if (specificCodecMatch.hasMatch()) {
586 // Add specific decoders..
587 QStringList decoders = specificCodecMatch.captured(1).split(" ");
588 Q_FOREACH(const QString& string, decoders) {
589 if (!string.isEmpty()) {
590 decodersList.push_back(string);
591 }
592 }
593
594 // Add specific encoders.. (e.g. for h264: h264_vaapi, libopenh264, etc )
595 QStringList encoders = specificCodecMatch.captured(2).split(" ");
596 Q_FOREACH(const QString& string, encoders) {
597 if (!string.isEmpty()) {
598 encodersList.push_back(string);
599 }
600 }
601 }
602 }
603
604 codecInfoJson.insert("encoders", encodersList);
605 codecInfoJson.insert("decoders", decodersList);
606 codecsJson.insert(codecName, codecInfoJson);
607 }
608 }
609 }
610
611 ffmpegInfo.insert("codecs", codecsJson);
612
613
614 dbgFile << "codec support:" << ffmpegInfo;
615 } else {
616 dbgFile << "Not a valid process at:" << processPath;
617 }
618
619 return ffmpegInfo;
620
621}
const int FFMPEG_TIMEOUT
QList< QString > QStringList
static QByteArray runProcessAndReturn(const QString &processPath, const QStringList &args, int msecs=FFMPEG_TIMEOUT)

References dbgFile, FFMPEG_TIMEOUT, and runProcessAndReturn().

◆ findProcessPath()

QJsonObject KisFFMpegWrapper::findProcessPath ( const QString & processName,
const QString & customLocation,
bool processInfo )
static

Definition at line 448 of file KisFFMpegWrapper.cpp.

449{
450 QJsonObject resultJsonObj;
451 QStringList proposedPaths;
452
453 // User-specified..
454 if (!customLocation.isEmpty()) {
455 proposedPaths << customLocation;
456 proposedPaths << customLocation + '/' + processName;
457 }
458
459 // Krita-bundled..
460 proposedPaths << KoResourcePaths::getApplicationRoot() + '/' + "bin" + '/' + processName;
461
462 // OS-specific..
463#ifdef Q_OS_WIN
464 // Look for winget-installed ffmpeg packages in C:\users\USERNAME\appdata\local\microsoft\winget\packages\FFMPEGVERSION\bin
465 QDir wingetPackageDir = QDir(QDir::homePath() + "/AppData/Local/Microsoft/WinGet/Packages/");
466 QStringList wingetPackageFolderNames = wingetPackageDir.entryList();
467 for (const auto &folderName : wingetPackageFolderNames) {
468 if (folderName.contains("ffmpeg", Qt::CaseInsensitive)) {
469 QDir ffmpegPackageFolder = QDir(wingetPackageDir.path() + "/" + folderName);
470 QStringList ffmpegRootNames = ffmpegPackageFolder.entryList();
471 for (const auto &ffmpegRootName : ffmpegRootNames) {
472 proposedPaths << ffmpegPackageFolder.path() + "/" + ffmpegRootName + "/bin/";
473 }
474 }
475 }
476#endif
477
478#ifdef Q_OS_MACOS
479 proposedPaths << QCoreApplication::applicationDirPath() + '/' + processName;
480#endif
481
482#ifndef Q_OS_WIN
483 proposedPaths << QDir::homePath() + "/bin/" + processName;
484 proposedPaths << "/usr/bin/" + processName;
485 proposedPaths << "/usr/local/bin/" + processName;
486#endif
487
488 dbgFile << proposedPaths;
489 for (int i = 0; i != proposedPaths.size(); ++i) {
490 if (proposedPaths[i].isEmpty()) continue;
491
492#ifdef Q_OS_WIN
493 proposedPaths[i] = QDir::toNativeSeparators(QDir::cleanPath(proposedPaths[i]));
494
495 if (proposedPaths[i].endsWith('/')) {
496 continue;
497 }
498
499 if (!proposedPaths[i].endsWith(".exe", Qt::CaseInsensitive)) {
500 if (!QFile::exists(proposedPaths[i])) {
501 proposedPaths[i] += ".exe";
502 if (!QFile::exists(proposedPaths[i])) {
503 continue;
504 }
505 }
506 }
507#endif
508
509 QJsonObject processInfoJsonObj = findProcessInfo(processName, proposedPaths[i], includeProcessInfo);
510 dbgFile << "PATH" << proposedPaths[i] << processInfoJsonObj.value("enabled").toBool();
511 if (processInfoJsonObj.value("enabled").toBool()) {
512 processInfoJsonObj["custom"]=(!customLocation.isEmpty() && i <= 1) ? true:false;
513 resultJsonObj = processInfoJsonObj;
514 break;
515 }
516 }
517
518 return resultJsonObj;
519}
static QJsonObject findProcessInfo(const QString &processName, const QString &processPath, bool includeProcessInfo)
static QString getApplicationRoot()

References dbgFile, findProcessInfo(), and KoResourcePaths::getApplicationRoot().

◆ fixUpNonEmbeddedProcessEnvironment()

void KisFFMpegWrapper::fixUpNonEmbeddedProcessEnvironment ( const QString & processPath,
QProcess & process )
staticprivate

Definition at line 368 of file KisFFMpegWrapper.cpp.

369{
370#ifdef Q_OS_LINUX
371
380 QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
381
382 const QStringList libraryPaths = env.value("LD_LIBRARY_PATH").split(':');
383 const QString processAbsPath = QFileInfo(QFileInfo(processPath).absolutePath() + "/../").absoluteFilePath();
384
385 bool isCustomBuildOfFFmpeg = false;
386
387 Q_FOREACH (const QString &path, libraryPaths) {
388 const QString absPath1 = QFileInfo(path + "/").absoluteFilePath();
389 const QString absPath2 = QFileInfo(path + "/../").absoluteFilePath();
390
391 if (absPath1 == processAbsPath || absPath2 == processAbsPath) {
392 dbgFile << "Detected embedded ffmpeg:" << processPath;
393 dbgFile << " " << ppVar(processAbsPath);
394 dbgFile << " " << ppVar(absPath1);
395 dbgFile << " " << ppVar(absPath2);
396
397 isCustomBuildOfFFmpeg = true;
398
399 break;
400 }
401 }
402
403 if (!isCustomBuildOfFFmpeg) {
404 dbgFile << "Removing LD_LIBRARY_PATH for running" << processPath;
405
406 env.remove("LD_LIBRARY_PATH");
407 process.setProcessEnvironment(env);
408 }
409#else
410 Q_UNUSED(processPath);
411 Q_UNUSED(process);
412#endif /* Q_OS_LINUX */
413}
#define ppVar(var)
Definition kis_debug.h:155

References dbgFile, and ppVar.

◆ getSupportedCodecs()

QStringList KisFFMpegWrapper::getSupportedCodecs ( const QJsonObject & ffmpegJsonProcessInput)
static

Definition at line 623 of file KisFFMpegWrapper.cpp.

623 {
624 // TODO: I really don't like having to deal with JSON in C++ code as frequently as we are here.
625 // We should make a proper datatype for FFMPEG Process Information!
626
627 QStringList encodersToReturn = {};
628
629 // For now, I'm just treating this as strictly typed using KIS_SAFE_ASSERT_RECOVER_RETURN
630 KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(ffmpegProcessInfo["enabled"].toBool(), encodersToReturn);
631
632 QJsonObject encoders = ffmpegProcessInfo["codecs"].toObject();
633 Q_FOREACH( const QString& key, encoders.keys()) {
634 if (encoders[key].toObject()["encoding"].toBool()) {
635 encodersToReturn << key;
636 }
637 }
638
639 return encodersToReturn;
640}

References KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE.

◆ reset()

void KisFFMpegWrapper::reset ( )

Definition at line 252 of file KisFFMpegWrapper.cpp.

253{
254 if (m_process == nullptr)
255 return;
256
257 m_process->disconnect(this);
258 if (m_process->state() != QProcess::NotRunning) {
259 m_process->kill();
260 }
261 m_process.reset();
262}
QScopedPointer< QProcess > m_process

References m_process.

◆ runProcessAndReturn()

QByteArray KisFFMpegWrapper::runProcessAndReturn ( const QString & processPath,
const QStringList & args,
int msecs = FFMPEG_TIMEOUT )
static

Definition at line 415 of file KisFFMpegWrapper.cpp.

416{
417 QProcess runProcess;
418
419 fixUpNonEmbeddedProcessEnvironment(processPath, runProcess);
420
421 runProcess.start(processPath, args);
422
423 if (runProcess.waitForStarted(msecs)) runProcess.waitForFinished(msecs);
424
425 const bool successfulStart =
426 runProcess.state() == QProcess::NotRunning &&
427 runProcess.error() == QProcess::UnknownError;
428
429 dbgFile << "runProcessAndReturn Success:" << successfulStart;
430
431 if (successfulStart) return runProcess.readAllStandardOutput();
432
433 return "";
434}
static void fixUpNonEmbeddedProcessEnvironment(const QString &processPath, QProcess &process)

References dbgFile, and fixUpNonEmbeddedProcessEnvironment().

◆ setConfiguredFFMpegLocation()

void KisFFMpegWrapper::setConfiguredFFMpegLocation ( QString & location)
static

Definition at line 442 of file KisFFMpegWrapper.cpp.

443{
444 KisConfig cfg(false);
445 cfg.setFFMpegLocation(location);
446}

References KisConfig::setFFMpegLocation().

◆ sigFinished

void KisFFMpegWrapper::sigFinished ( )
signal

◆ sigFinishedWithError

void KisFFMpegWrapper::sigFinishedWithError ( QString message)
signal

◆ sigProgressUpdated

void KisFFMpegWrapper::sigProgressUpdated ( int frameNo)
signal

◆ sigReadLine

void KisFFMpegWrapper::sigReadLine ( int pipe,
QString line )
signal

◆ sigReadSTDERR

void KisFFMpegWrapper::sigReadSTDERR ( QByteArray stderrBuffer)
signal

◆ sigReadSTDOUT

void KisFFMpegWrapper::sigReadSTDOUT ( QByteArray stdoutBuffer)
signal

◆ sigStarted

void KisFFMpegWrapper::sigStarted ( )
signal

◆ slotFinished

void KisFFMpegWrapper::slotFinished ( int exitCode)
privateslot

Definition at line 349 of file KisFFMpegWrapper.cpp.

350{
351 dbgFile << "FFMpeg finished with code" << exitCode;
353 m_progress->setValue(100);
354 }
355
356 if (exitCode != 0) {
357 m_errorMessage.remove(junkRegex);
358 if (m_process->exitStatus() == QProcess::CrashExit) {
359 m_errorMessage = i18n("FFMpeg Crashed") % "\n" % m_errorMessage;
360 }
361
363 } else {
364 Q_EMIT sigFinished();
365 }
366}
KisFFMpegWrapperSettings m_processSettings
QSharedPointer< QProgressDialog > m_progress
void sigFinishedWithError(QString message)

References KisFFMpegWrapperSettings::batchMode, dbgFile, m_errorMessage, m_process, m_processSettings, m_progress, sigFinished(), and sigFinishedWithError().

◆ slotReadyReadSTDERR

void KisFFMpegWrapper::slotReadyReadSTDERR ( )
privateslot

Definition at line 264 of file KisFFMpegWrapper.cpp.

265{
266 QByteArray stderrRawBuffer = m_process->readAllStandardError();
267
268 Q_EMIT sigReadSTDERR(stderrRawBuffer);
269 m_stderrBuffer += stderrRawBuffer;
270
271 int frameNo = -1;
272 int startPos = 0;
273 int endPos = 0;
274
275 while ((endPos = m_stderrBuffer.indexOf(lineDelimiter, startPos)) != -1) {
276 const QString &line = m_stderrBuffer.mid(startPos, endPos - startPos).trimmed();
277
279
280 Q_EMIT sigReadLine(2,line);
281
282 for (const QString &word : errorWords) {
283 if (line.contains(word)) {
284 m_errorMessage += line % "\n";
285 break;
286 }
287 }
288
289 const QRegularExpressionMatch &match = frameRegexp.match(line);
290
291 if (match.hasMatch()) {
292 frameNo = match.captured(1).toInt();
293 }
294
295
296 dbgFile << "ffmpeg stderr:" << line;
297 startPos = endPos + 1;
298 }
299
300 m_stderrBuffer.remove(0, startPos);
301
302 if (frameNo != -1) {
303 updateProgressDialog(frameNo);
304 Q_EMIT sigProgressUpdated(frameNo);
305 }
306
307}
void updateProgressDialog(int progressValue)
void sigProgressUpdated(int frameNo)
void sigReadLine(int pipe, QString line)
void sigReadSTDERR(QByteArray stderrBuffer)

References dbgFile, m_errorMessage, m_process, m_processSettings, m_processSTDERR, m_stderrBuffer, sigProgressUpdated(), sigReadLine(), sigReadSTDERR(), KisFFMpegWrapperSettings::storeOutput, and updateProgressDialog().

◆ slotReadyReadSTDOUT

void KisFFMpegWrapper::slotReadyReadSTDOUT ( )
privateslot

Definition at line 309 of file KisFFMpegWrapper.cpp.

310{
311 QByteArray stdoutRawBuffer = m_process->readAllStandardOutput();
312
313 Q_EMIT sigReadSTDOUT(stdoutRawBuffer);
314 m_stdoutBuffer += stdoutRawBuffer;
315
316
318 if (m_processSettings.storeOutput) m_processSTDOUT += stdoutRawBuffer;
319 } else {
320
321 int startPos = 0;
322 int endPos = 0;
323 QString str;
324
325 if (m_processSettings.storeOutput) m_processSTDOUT += stdoutRawBuffer + "\n";
326
327 // ffmpeg splits normal lines by '\n' and progress data lines by '\r'
328 while ((endPos = m_stdoutBuffer.indexOf(lineDelimiter, startPos)) != -1) {
329 const QString &line = m_stdoutBuffer.mid(startPos, endPos - startPos).trimmed();
330
331 dbgFile << "ffmpeg stdout:" << line;
332 Q_EMIT sigReadLine(1,line);
333 startPos = endPos + 1;
334 }
335
336 m_stdoutBuffer.remove(0, startPos);
337 }
338
339
340}
void sigReadSTDOUT(QByteArray stdoutBuffer)

References KisFFMpegWrapperSettings::binaryOutput, dbgFile, m_process, m_processSettings, m_processSTDOUT, m_stdoutBuffer, sigReadLine(), sigReadSTDOUT(), and KisFFMpegWrapperSettings::storeOutput.

◆ slotStarted

void KisFFMpegWrapper::slotStarted ( )
privateslot

Definition at line 342 of file KisFFMpegWrapper.cpp.

343{
344 dbgFile << "ffmpeg process started!";
345
346 Q_EMIT sigStarted();
347}

References dbgFile, and sigStarted().

◆ start()

KisImportExportErrorCode KisFFMpegWrapper::start ( const KisFFMpegWrapperSettings & settings)

Definition at line 162 of file KisFFMpegWrapper.cpp.

163{
164 struct ProcessResults {
165 bool finish = false;
166 QString error = QString();
167 };
168
169 QSharedPointer<ProcessResults> processResults = toQShared(new ProcessResults);
170
171 connect( this, &KisFFMpegWrapper::sigFinishedWithError, [processResults](const QString& errMsg){
172 processResults->finish = true;
173 processResults->error = errMsg;
174 });
175
176 connect( this, &KisFFMpegWrapper::sigFinished, [processResults](){
177 processResults->finish = true;
178 });
179
180 startNonBlocking(settings);
182
183 if (processResults->finish == true) {
184 if (processResults->error.isEmpty()) {
186 } else {
188 }
189 } else {
190 reset(); // Ensure process isn't running before returning failure here.
192 }
193}
QSharedPointer< T > toQShared(T *ptr)

References ImportExportCodes::Failure, FFMPEG_TIMEOUT, ImportExportCodes::OK, reset(), sigFinished(), sigFinishedWithError(), startNonBlocking(), toQShared(), and waitForFinished().

◆ startNonBlocking()

void KisFFMpegWrapper::startNonBlocking ( const KisFFMpegWrapperSettings & settings)

Definition at line 46 of file KisFFMpegWrapper.cpp.

47{
48 KIS_ASSERT(m_process == nullptr);
49
50 m_stdoutBuffer.clear();
51 m_errorMessage.clear();
52 m_processSTDOUT.clear();
53 m_processSTDERR.clear();
54
55 m_process.reset(new QProcess(this));
56 QStringList env(m_process->systemEnvironment());
57
58 m_processSettings = settings;
59
60 const QString renderLogPath = m_processSettings.outputFile + ".log";
61
62 // Create a new log per each ffmpeg operation..
63 if (QFile::exists(renderLogPath)) {
64 QFile existingFile(renderLogPath);
65 existingFile.remove();
66 }
67
68 QFile renderLog(renderLogPath); // Logs ONLY this render operation.
69
70 // Log ffmpeg command info..
71 if (renderLog.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
72 QString command = m_processSettings.processPath + " " + settings.defaultPrependArgs.join(" ") + " " + m_processSettings.args.join(" ") + " " + m_processSettings.outputFile;
73 renderLog.write(command.toUtf8());
74 renderLog.write("\n");
75 renderLog.write("=====================================================\n");
76
77 // Handle logged errors..
78 // Due to various reasons (including image preview), ffmpeg uses STDERR and not STDOUT for general output logging.
79 connect(this, &KisFFMpegWrapper::sigReadSTDERR, [renderLogPath](QByteArray stderrBuffer){
80 QFile renderLog(renderLogPath);
81 if (renderLog.open(QIODevice::WriteOnly | QIODevice::Append)) {
82 renderLog.write(stderrBuffer);
83 }
84 });
85 }
86
87 if (!settings.logPath.isEmpty()) {
88 QString sessionRenderLogPath(settings.logPath);
89 // Logs FULL history of Krita session render operations.
90 QFile sessionRenderLog(sessionRenderLogPath);
91
92 // Make directory..
93 const QString logDirPath = QFileInfo(sessionRenderLogPath).dir().path();
94 QDir().mkpath(logDirPath);
95
96 if (sessionRenderLog.open(QIODevice::WriteOnly | QIODevice::Append)) {
97 // Append finished renderlog to sessionlog..
98 connect(this, &KisFFMpegWrapper::sigFinishedWithError, [renderLogPath, sessionRenderLogPath](QString){
99 QFile renderLog(renderLogPath);
100 QFile sessionRenderLog(sessionRenderLogPath);
101 renderLog.open(QIODevice::ReadOnly);
102 sessionRenderLog.open(QIODevice::WriteOnly | QIODevice::Append);
103
104 QByteArray buffer;
105 int chunksize = 256;
106 while (!(buffer = renderLog.read(chunksize)).isEmpty()) {
107 sessionRenderLog.write(buffer);
108 }
109 });
110 }
111 }
112
114 QString progressText = m_processSettings.progressMessage;
115
116 progressText.replace("[progress]", "0");
117
118 m_progress = toQShared(new QProgressDialog(progressText, "", 0, 0));
119
120 m_progress->setWindowModality(Qt::ApplicationModal);
121 m_progress->setCancelButton(0);
122 m_progress->setMinimumDuration(0);
123 m_progress->setValue(0);
124
125 if (settings.progressIndeterminate) {
126 m_progress->setRange(0,0);
127 } else {
128 m_progress->setRange(0, 100);
129 }
130
131 connect(m_progress.data(), SIGNAL(canceled()), m_process.data(), SLOT(kill()));
132
133 m_progress->show();
134
135 dbgFile << "Open progress dialog!";
136 }
137
138 connect(m_process.data(), SIGNAL(readyReadStandardOutput()), SLOT(slotReadyReadSTDOUT()));
139 connect(m_process.data(), SIGNAL(readyReadStandardError()), SLOT(slotReadyReadSTDERR()));
140 connect(m_process.data(), SIGNAL(started()), SLOT(slotStarted()));
141 connect(m_process.data(), SIGNAL(finished(int, QProcess::ExitStatus)), SLOT(slotFinished(int)));
142
143 QStringList args;
144
145 if ( !settings.defaultPrependArgs.isEmpty() ) {
146 args << settings.defaultPrependArgs;
147 }
148
149 args << settings.args;
150
151 if ( !settings.outputFile.isEmpty() ) {
152 args << settings.outputFile;
153 }
154
155 dbgFile << "starting process: " << qUtf8Printable(settings.processPath) << args;
156
158
159 m_process->start(settings.processPath, args);
160}
void slotFinished(int exitCode)
#define KIS_ASSERT(cond)
Definition kis_assert.h:33

References KisFFMpegWrapperSettings::args, KisFFMpegWrapperSettings::batchMode, dbgFile, KisFFMpegWrapperSettings::defaultPrependArgs, fixUpNonEmbeddedProcessEnvironment(), KIS_ASSERT, KisFFMpegWrapperSettings::logPath, m_errorMessage, m_process, m_processSettings, m_processSTDERR, m_processSTDOUT, m_progress, m_stdoutBuffer, KisFFMpegWrapperSettings::outputFile, KisFFMpegWrapperSettings::processPath, KisFFMpegWrapperSettings::progressIndeterminate, KisFFMpegWrapperSettings::progressMessage, sigFinishedWithError(), sigReadSTDERR(), slotFinished(), slotReadyReadSTDERR(), slotReadyReadSTDOUT(), slotStarted(), and toQShared().

◆ transferCharacteristicsFromName()

TransferCharacteristics KisFFMpegWrapper::transferCharacteristicsFromName ( QString name)
static

Definition at line 829 of file KisFFMpegWrapper.cpp.

830{
831 if (name == "bt709") {
832 return TRC_ITU_R_BT_709_5;
833 }
834 if (name == "gamma22") {
836 }
837 if (name == "gamma28") {
839 }
840 if (name == "smpte170m") {
841 return TRC_ITU_R_BT_601_6;
842 }
843 if (name == "smpte240m") {
844 return TRC_SMPTE_240M;
845 }
846 if (name == "linear") {
847 return TRC_LINEAR;
848 }
849 if (name == "log" || name == "log100") {
850 return TRC_LOGARITHMIC_100;
851 }
852 if (name == "log316" || name == "log_sqrt") {
854 }
855 if (name == "iec61966_2_4" || name == "iec61966-2-4") {
856 return TRC_IEC_61966_2_4;
857 }
858 if (name.startsWith("bt1361")) {
859 return TRC_ITU_R_BT_1361;
860 }
861 if (name == "iec61966_2_1" || name == "iec61966-2-1") {
862 return TRC_IEC_61966_2_1;
863 }
864 if (name.startsWith("bt2020_10")) {
866 }
867 if (name.startsWith("bt2020_12")) {
869 }
870 if (name == "smpte2084") {
872 }
873 if (name == "smpte240m") {
874 return TRC_SMPTE_240M;
875 }
876 if (name.startsWith("smpte428")) {
877 return TRC_SMPTE_ST_428_1;
878 }
879 if (name == "arib-std-b67") {
881 }
882
883 return TRC_UNSPECIFIED;
884}
@ TRC_IEC_61966_2_4
@ TRC_ITU_R_BT_2020_2_10bit
@ TRC_LOGARITHMIC_100
@ TRC_ITU_R_BT_470_6_SYSTEM_M
@ TRC_ITU_R_BT_470_6_SYSTEM_B_G
@ TRC_ITU_R_BT_1361
@ TRC_ITU_R_BT_2100_0_HLG
@ TRC_ITU_R_BT_2100_0_PQ
@ TRC_ITU_R_BT_601_6
@ TRC_IEC_61966_2_1
@ TRC_ITU_R_BT_709_5
@ TRC_SMPTE_ST_428_1
@ TRC_LOGARITHMIC_100_sqrt10
@ TRC_ITU_R_BT_2020_2_12bit

References TRC_IEC_61966_2_1, TRC_IEC_61966_2_4, TRC_ITU_R_BT_1361, TRC_ITU_R_BT_2020_2_10bit, TRC_ITU_R_BT_2020_2_12bit, TRC_ITU_R_BT_2100_0_HLG, TRC_ITU_R_BT_2100_0_PQ, TRC_ITU_R_BT_470_6_SYSTEM_B_G, TRC_ITU_R_BT_470_6_SYSTEM_M, TRC_ITU_R_BT_601_6, TRC_ITU_R_BT_709_5, TRC_LINEAR, TRC_LOGARITHMIC_100, TRC_LOGARITHMIC_100_sqrt10, TRC_SMPTE_240M, TRC_SMPTE_ST_428_1, and TRC_UNSPECIFIED.

◆ updateProgressDialog()

void KisFFMpegWrapper::updateProgressDialog ( int progressValue)
private

Definition at line 206 of file KisFFMpegWrapper.cpp.

206 {
207
208 dbgFile << "Update Progress" << progressValue << "/" << m_processSettings.totalFrames;
209
210 if (!m_progress) return;
211
212 QString progressText = m_processSettings.progressMessage;
213 QStringList outputFileParts = m_processSettings.outputFile.split(".");
214 QString suffix = outputFileParts.size() == 2 ? outputFileParts[1] : m_processSettings.outputFile;
215
216 progressText.replace("[progress]", QString::number(progressValue));
217 progressText.replace("[framecount]", QString::number(m_processSettings.totalFrames));
218 progressText.replace("[suffix]", suffix );
219 m_progress->setLabelText(progressText);
220
221 if (m_processSettings.totalFrames > 0) m_progress->setValue(100 * progressValue / m_processSettings.totalFrames);
222
223 if (m_process && m_process->state() == QProcess::Running) {
224 QApplication::processEvents();
225 }
226}

References dbgFile, m_process, m_processSettings, m_progress, KisFFMpegWrapperSettings::outputFile, KisFFMpegWrapperSettings::progressMessage, and KisFFMpegWrapperSettings::totalFrames.

◆ waitForFinished()

bool KisFFMpegWrapper::waitForFinished ( int msecs = FFMPEG_TIMEOUT)

Definition at line 195 of file KisFFMpegWrapper.cpp.

196{
197 if (!m_process) return false;
198
199 if (m_process->waitForStarted(msecs)) {
200 return (m_process->waitForFinished(msecs) && m_process->exitStatus() == QProcess::ExitStatus::NormalExit);
201 }
202
203 return false;
204}

References m_process.

Member Data Documentation

◆ m_errorMessage

QString KisFFMpegWrapper::m_errorMessage
private

Definition at line 108 of file KisFFMpegWrapper.h.

◆ m_process

QScopedPointer<QProcess> KisFFMpegWrapper::m_process
private

Definition at line 102 of file KisFFMpegWrapper.h.

◆ m_processSettings

KisFFMpegWrapperSettings KisFFMpegWrapper::m_processSettings
private

Definition at line 104 of file KisFFMpegWrapper.h.

◆ m_processSTDERR

QString KisFFMpegWrapper::m_processSTDERR
private

Definition at line 111 of file KisFFMpegWrapper.h.

◆ m_processSTDOUT

QByteArray KisFFMpegWrapper::m_processSTDOUT
private

Definition at line 110 of file KisFFMpegWrapper.h.

◆ m_progress

QSharedPointer<QProgressDialog> KisFFMpegWrapper::m_progress = nullptr
private

Definition at line 103 of file KisFFMpegWrapper.h.

◆ m_stderrBuffer

QString KisFFMpegWrapper::m_stderrBuffer
private

Definition at line 107 of file KisFFMpegWrapper.h.

◆ m_stdoutBuffer

QString KisFFMpegWrapper::m_stdoutBuffer
private

Definition at line 106 of file KisFFMpegWrapper.h.


The documentation for this class was generated from the following files: