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 787 of file KisFFMpegWrapper.cpp.

788{
789 if (name == "bt709") {
791 }
792 if (name == "bt470m") {
794 }
795 if (name == "bt470bg") {
797 }
798 if (name == "smpte170m") {
800 }
801 if (name == "smpte240m") {
803 }
804 if (name == "film") {
806 }
807 if (name == "bt2020") {
809 }
810 if (name.startsWith("smpte428")) {
812 }
813 if (name == "smpte431") {
815 }
816 if (name == "smpte432") {
818 }
819 if (name == "jedec-p22") {
821 }
822
824}
@ 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 433 of file KisFFMpegWrapper.cpp.

434{
435 KisConfig cfg(true);
436 return cfg.ffmpegLocation();
437}

References KisConfig::ffmpegLocation().

◆ ffmpegProbe()

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

Definition at line 686 of file KisFFMpegWrapper.cpp.

687{
688 struct KisFFMpegWrapperSettings ffmpegSettings;
689
690 ffmpegSettings.processPath = ffmpegPath;
691 ffmpegSettings.storeOutput = true;
692 ffmpegSettings.progressMessage = i18nc("Video information probing dialog. arg1: frame number.", "Loading video data... %1 frames examined.", "[progress]");
693 ffmpegSettings.batchMode = batchMode;
694
695 ffmpegSettings.args << "-stats"
696 << "-v" << "info"
697 << "-progress" << "pipe:1"
698 << "-map" << "0:v:0"
699 << "-c" << "copy"
700 << "-f" << "null" << "pipe:1"
701 << "-i" << inputFile;
702
703
704 this->startNonBlocking(ffmpegSettings);
705 this->waitForFinished();
706
707
708 QString ffmpegSTDOUT = m_processSTDERR + "\n" + m_processSTDOUT;
709
710 dbgFile << "ffmpegProbe stdout:" << ffmpegSTDOUT;
711
712 QJsonObject ffmpegJsonObj;
713 QJsonArray ffmpegStreamsJsonArr;
714 QJsonObject ffmpegFormatJsonObj;
715 QJsonObject ffmpegProgressJsonObj;
716
717 QStringList stdoutLines = ffmpegSTDOUT.split('\n');
718
719 ffmpegJsonObj["error"] = FFProbeErrorCodes::UNSUPPORTED_CODEC;
720
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);
725
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()
730 );
731 } else {
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")
737 );
738
739 QRegularExpressionMatch streamMatch = videoInfoStreamRX.match(line);
740
741 if (streamMatch.hasMatch() && ffmpegStreamsJsonArr[streamMatch.captured(1).toInt()].isUndefined() ) {
742 int index = streamMatch.captured(1).toInt();
743 QJsonObject ffmpegJsonOnStreamObj;
744
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();
751
752 ffmpegJsonOnStreamObj["codec_type"] = "video";
753
754 if (streamMatch.captured(11).toFloat() > 0) {
755 float fps = streamMatch.captured(11).toFloat();
756
757 ffmpegProgressJsonObj["ffmpeg_fps"] = QString::number(fps);
758 ffmpegJsonOnStreamObj["r_frame_rate"] = QString::number( fps * 10000 ).append(QString("/10000"));
759 } else {
760 ffmpegProgressJsonObj["ffmpeg_fps"] = 0;
761 }
762
763 ffmpegJsonObj["error"] = FFProbeErrorCodes::NONE;
764
765 dbgFile << "ffmpegProbe stream:" << ffmpegJsonOnStreamObj;
766
767 ffmpegStreamsJsonArr.insert(index, ffmpegJsonOnStreamObj);
768
769 } else {
770
771 QRegularExpression videoInfoFrameRX("^(\\w+?)=([\\w\\./:]+?)$");
772 QRegularExpressionMatch frameMatch = videoInfoFrameRX.match(line);
773
774 if (frameMatch.hasMatch()) ffmpegProgressJsonObj[frameMatch.captured(1)] = frameMatch.captured(2);
775
776 }
777 }
778 }
779
780 ffmpegJsonObj.insert("streams",ffmpegStreamsJsonArr);
781 ffmpegJsonObj.insert("format",ffmpegFormatJsonObj);
782 ffmpegJsonObj.insert("progress",ffmpegProgressJsonObj);
783
784 return ffmpegJsonObj;
785}
@ 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 649 of file KisFFMpegWrapper.cpp.

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

640{
641 return findProcessPath("ffmpeg", customLocation, true);
642}
static QJsonObject findProcessPath(const QString &processName, const QString &customLocation, bool processInfo)

References findProcessPath().

◆ findFFProbe()

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

Definition at line 644 of file KisFFMpegWrapper.cpp.

645{
646 return findProcessPath("ffprobe", customLocation, false);
647}

References findProcessPath().

◆ findProcessInfo()

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

Definition at line 518 of file KisFFMpegWrapper.cpp.

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

446{
447 QJsonObject resultJsonObj;
448 QStringList proposedPaths;
449
450 // User-specified..
451 if (!customLocation.isEmpty()) {
452 proposedPaths << customLocation;
453 proposedPaths << customLocation + '/' + processName;
454 }
455
456 // Krita-bundled..
457 proposedPaths << KoResourcePaths::getApplicationRoot() + '/' + "bin" + '/' + processName;
458
459 // OS-specific..
460#ifdef Q_OS_WIN
461 // Look for winget-installed ffmpeg packages in C:\users\USERNAME\appdata\local\microsoft\winget\packages\FFMPEGVERSION\bin
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/";
470 }
471 }
472 }
473#endif
474
475#ifdef Q_OS_MACOS
476 proposedPaths << QCoreApplication::applicationDirPath() + '/' + processName;
477#endif
478
479#ifndef Q_OS_WIN
480 proposedPaths << QDir::homePath() + "/bin/" + processName;
481 proposedPaths << "/usr/bin/" + processName;
482 proposedPaths << "/usr/local/bin/" + processName;
483#endif
484
485 dbgFile << proposedPaths;
486 for (int i = 0; i != proposedPaths.size(); ++i) {
487 if (proposedPaths[i].isEmpty()) continue;
488
489#ifdef Q_OS_WIN
490 proposedPaths[i] = QDir::toNativeSeparators(QDir::cleanPath(proposedPaths[i]));
491
492 if (proposedPaths[i].endsWith('/')) {
493 continue;
494 }
495
496 if (!proposedPaths[i].endsWith(".exe", Qt::CaseInsensitive)) {
497 if (!QFile::exists(proposedPaths[i])) {
498 proposedPaths[i] += ".exe";
499 if (!QFile::exists(proposedPaths[i])) {
500 continue;
501 }
502 }
503 }
504#endif
505
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;
511 break;
512 }
513 }
514
515 return resultJsonObj;
516}
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#endif /* Q_OS_LINUX */
410}
#define ppVar(var)
Definition kis_debug.h:155

References dbgFile, and ppVar.

◆ getSupportedCodecs()

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

Definition at line 620 of file KisFFMpegWrapper.cpp.

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

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 412 of file KisFFMpegWrapper.cpp.

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

References dbgFile, and fixUpNonEmbeddedProcessEnvironment().

◆ setConfiguredFFMpegLocation()

void KisFFMpegWrapper::setConfiguredFFMpegLocation ( QString & location)
static

Definition at line 439 of file KisFFMpegWrapper.cpp.

440{
441 KisConfig cfg(false);
442 cfg.setFFMpegLocation(location);
443}

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}
connect(this, SIGNAL(optionsChanged()), this, SLOT(saveOptions()))
QSharedPointer< T > toQShared(T *ptr)

References connect(), 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, connect(), 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 826 of file KisFFMpegWrapper.cpp.

827{
828 if (name == "bt709") {
829 return TRC_ITU_R_BT_709_5;
830 }
831 if (name == "gamma22") {
833 }
834 if (name == "gamma28") {
836 }
837 if (name == "smpte170m") {
838 return TRC_ITU_R_BT_601_6;
839 }
840 if (name == "smpte240m") {
841 return TRC_SMPTE_240M;
842 }
843 if (name == "linear") {
844 return TRC_LINEAR;
845 }
846 if (name == "log" || name == "log100") {
847 return TRC_LOGARITHMIC_100;
848 }
849 if (name == "log316" || name == "log_sqrt") {
851 }
852 if (name == "iec61966_2_4" || name == "iec61966-2-4") {
853 return TRC_IEC_61966_2_4;
854 }
855 if (name.startsWith("bt1361")) {
856 return TRC_ITU_R_BT_1361;
857 }
858 if (name == "iec61966_2_1" || name == "iec61966-2-1") {
859 return TRC_IEC_61966_2_1;
860 }
861 if (name.startsWith("bt2020_10")) {
863 }
864 if (name.startsWith("bt2020_12")) {
866 }
867 if (name == "smpte2084") {
869 }
870 if (name == "smpte240m") {
871 return TRC_SMPTE_240M;
872 }
873 if (name.startsWith("smpte428")) {
874 return TRC_SMPTE_ST_428_1;
875 }
876 if (name == "arib-std-b67") {
878 }
879
880 return TRC_UNSPECIFIED;
881}
@ 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: