///////////////////////////////////////////////////////////////////////////////
// Simple x264 Launcher
-// Copyright (C) 2004-2014 LoRd_MuldeR <MuldeR2@GMX.de>
+// Copyright (C) 2004-2020 LoRd_MuldeR <MuldeR2@GMX.de>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
#include "encoder_abstract.h"
+//Internal
#include "global.h"
#include "model_options.h"
#include "model_preferences.h"
#include "model_sysinfo.h"
-#include "binaries.h"
+#include "model_status.h"
+#include "source_abstract.h"
+//MUtils
+#include <MUtils/Global.h>
+#include <MUtils/Exception.h>
+#include <MUtils/OSSupport.h>
+
+//Qt
#include <QProcess>
+#include <QDir>
+#include <QTextCodec>
+#include <QSemaphore>
+#include <QDate>
+#include <QTime>
+#include <QThread>
+#include <QLocale>
+
+// ------------------------------------------------------------
+// Constructor & Destructor
+// ------------------------------------------------------------
-unsigned int AbstractEncoder::checkVersion(bool &modified)
+AbstractEncoder::AbstractEncoder(JobObject *jobObject, const OptionsModel *options, const SysinfoModel *const sysinfo, const PreferencesModel *const preferences, JobStatus &jobStatus, volatile bool *abort, volatile bool *pause, QSemaphore *semaphorePause, const QString &sourceFile, const QString &outputFile)
+:
+ AbstractTool(jobObject, options, sysinfo, preferences, jobStatus, abort, pause, semaphorePause),
+ m_sourceFile(sourceFile),
+ m_outputFile(outputFile),
+ m_indexFile(QString("%1/~%2.ffindex").arg(QDir::tempPath(), stringToHash(m_sourceFile)))
{
- if(m_preferences->getSkipVersionTest())
- {
- log("Warning: Skipping encoder version check this time!");
- return (999 * REV_MULT) + (REV_MULT-1);
- }
+ /*Nothing to do here*/
+}
- QProcess process;
- QStringList cmdLine = QStringList() << "--version";
+AbstractEncoder::~AbstractEncoder(void)
+{
+ /*Nothing to do here*/
+}
- log("Creating process:");
- if(!startProcess(process, ENC_BINARY(m_sysinfo, m_options), cmdLine))
+// ------------------------------------------------------------
+// Encoding Functions
+// ------------------------------------------------------------
+
+bool AbstractEncoder::runEncodingPass(AbstractSource* pipedSource, const QString outputFile, const ClipInfo &clipInfo, const int &pass, const QString &passLogFile)
+{
+ QProcess processEncode, processInput;
+
+ if(pipedSource)
{
- return false;;
+ pipedSource->createProcess(processEncode, processInput);
}
- QRegExp regExpVersion("", Qt::CaseInsensitive);
- QRegExp regExpVersionMod("\\bx264 (\\d)\\.(\\d+)\\.(\\d+)", Qt::CaseInsensitive);
-
- switch(m_options->encType())
+ QStringList cmdLine_Encode;
+ buildCommandLine(cmdLine_Encode, (pipedSource != NULL), clipInfo, m_indexFile, pass, passLogFile);
+
+ log("Creating encoder process:");
+ if(!startProcess(processEncode, getBinaryPath(), cmdLine_Encode, true, &getExtraPaths(), &getExtraEnv()))
{
- case OptionsModel::EncType_X264: regExpVersion.setPattern("\\bx264\\s(\\d)\\.(\\d+)\\.(\\d+)\\s([a-f0-9]{7})");
- case OptionsModel::EncType_X265: regExpVersion.setPattern("\\bHEVC\\s+encoder\\s+version\\s+0\\.(\\d+)\\+(\\d+)-[a-f0-9]+\\b");
- default: throw "Invalid encoder type!";
+ return false;
}
+ QList<QRegExp*> patterns;
+ runEncodingPass_init(patterns);
+
+ double last_progress = 0.0;
+ double size_estimate = 0.0;
+
bool bTimeout = false;
bool bAborted = false;
- unsigned int revision = UINT_MAX;
- unsigned int coreVers = UINT_MAX;
- modified = false;
-
- while(process.state() != QProcess::NotRunning)
+ //Main processing loop
+ while(processEncode.state() != QProcess::NotRunning)
{
- if(m_abort)
- {
- process.kill();
- bAborted = true;
- break;
- }
- if(!process.waitForReadyRead())
+ unsigned int waitCounter = 0;
+
+ //Wait until new output is available
+ forever
{
- if(process.state() == QProcess::Running)
+ if(*m_abort)
{
- process.kill();
- qWarning("encoder process timed out <-- killing!");
- log("\nPROCESS TIMEOUT !!!");
- bTimeout = true;
+ processEncode.kill();
+ processInput.kill();
+ bAborted = true;
break;
}
- }
- while(process.bytesAvailable() > 0)
- {
- QList<QByteArray> lines = process.readLine().split('\r');
- while(!lines.isEmpty())
+ if((*m_pause) && (processEncode.state() == QProcess::Running))
{
- QString text = QString::fromUtf8(lines.takeFirst().constData()).simplified();
- int offset = -1;
- if((offset = regExpVersion.lastIndexIn(text)) >= 0)
- {
- bool ok1 = false, ok2 = false;
- unsigned int temp1 = regExpVersion.cap(2).toUInt(&ok1);
- unsigned int temp2 = regExpVersion.cap(3).toUInt(&ok2);
- if(ok1) coreVers = temp1;
- if(ok2) revision = temp2;
- }
- else if((offset = regExpVersionMod.lastIndexIn(text)) >= 0)
- {
- bool ok1 = false, ok2 = false;
- unsigned int temp1 = regExpVersionMod.cap(2).toUInt(&ok1);
- unsigned int temp2 = regExpVersionMod.cap(3).toUInt(&ok2);
- if(ok1) coreVers = temp1;
- if(ok2) revision = temp2;
- modified = true;
- }
- if(!text.isEmpty())
+ JobStatus previousStatus = m_jobStatus;
+ setStatus(JobStatus_Paused);
+ log(tr("Job paused by user at %1, %2.").arg(QDate::currentDate().toString(Qt::ISODate), QTime::currentTime().toString( Qt::ISODate)));
+ bool ok[2] = {false, false};
+ QProcess *proc[2] = { &processEncode, &processInput };
+ ok[0] = MUtils::OS::suspend_process(proc[0], true);
+ ok[1] = MUtils::OS::suspend_process(proc[1], true);
+ while(*m_pause) m_semaphorePause->tryAcquire(1, 5000);
+ while(m_semaphorePause->tryAcquire(1, 0));
+ ok[0] = MUtils::OS::suspend_process(proc[0], false);
+ ok[1] = MUtils::OS::suspend_process(proc[1], false);
+ if(!(*m_abort)) setStatus(previousStatus);
+ log(tr("Job resumed by user at %1, %2.").arg(QDate::currentDate().toString(Qt::ISODate), QTime::currentTime().toString( Qt::ISODate)));
+ waitCounter = 0;
+ continue;
+ }
+ if(!processEncode.waitForReadyRead(m_processTimeoutInterval))
+ {
+ if(processEncode.state() == QProcess::Running)
{
- log(text);
+ if(++waitCounter > m_processTimeoutMaxCounter)
+ {
+ if(m_preferences->getAbortOnTimeout())
+ {
+ processEncode.kill();
+ qWarning("encoder process timed out <-- killing!");
+ log("\nPROCESS TIMEOUT !!!");
+ bTimeout = true;
+ break;
+ }
+ }
+ else if(waitCounter == m_processTimeoutWarning)
+ {
+ unsigned int timeOut = (waitCounter * m_processTimeoutInterval) / 1000U;
+ log(tr("Warning: encoder did not respond for %1 seconds, potential deadlock...").arg(QString::number(timeOut)));
+ }
+ continue;
}
}
+ if((*m_abort) || ((*m_pause) && (processEncode.state() == QProcess::Running)))
+ {
+ continue;
+ }
+ break;
}
+
+ //Exit main processing loop now?
+ if(bAborted || bTimeout)
+ {
+ break;
+ }
+
+ //Process all output
+ PROCESS_PENDING_LINES(processEncode, runEncodingPass_parseLine, patterns, clipInfo, pass, last_progress, size_estimate);
+ }
+
+ if(!(bTimeout || bAborted))
+ {
+ PROCESS_PENDING_LINES(processEncode, runEncodingPass_parseLine, patterns, clipInfo, pass, last_progress, size_estimate);
}
- process.waitForFinished();
- if(process.state() != QProcess::NotRunning)
+ processEncode.waitForFinished(5000);
+ if(processEncode.state() != QProcess::NotRunning)
{
- process.kill();
- process.waitForFinished(-1);
+ qWarning("Encoder process still running, going to kill it!");
+ processEncode.kill();
+ processEncode.waitForFinished(-1);
+ }
+
+ if(pipedSource)
+ {
+ processInput.waitForFinished(5000);
+ if(processInput.state() != QProcess::NotRunning)
+ {
+ qWarning("Input process still running, going to kill it!");
+ processInput.kill();
+ processInput.waitForFinished(-1);
+ }
+ if(!(bTimeout || bAborted))
+ {
+ pipedSource->flushProcess(processInput);
+ }
+ }
+
+ while(!patterns.isEmpty())
+ {
+ QRegExp *pattern = patterns.takeFirst();
+ MUTILS_DELETE(pattern);
}
- if(bTimeout || bAborted || process.exitCode() != EXIT_SUCCESS)
+ if(bTimeout || bAborted || processEncode.exitCode() != EXIT_SUCCESS)
{
if(!(bTimeout || bAborted))
{
- log(tr("\nPROCESS EXITED WITH ERROR CODE: %1").arg(QString::number(process.exitCode())));
+ const int exitCode = processEncode.exitCode();
+ if((exitCode < -1) || (exitCode >= 32))
+ {
+ log(tr("\nFATAL ERROR: The encoder process has *crashed* -> your encode probably is *incomplete* !!!"));
+ log(tr("Note that this indicates a bug in the current encoder, *not* in Simple x264/x265 Launcher."));
+ }
+ log(tr("\nPROCESS EXITED WITH ERROR CODE: %1").arg(QString::number(exitCode)));
}
- return UINT_MAX;
+ processEncode.close();
+ processInput.close();
+ return false;
}
- if((revision == UINT_MAX) || (coreVers == UINT_MAX))
+ QThread::yieldCurrentThread();
+
+ QFileInfo completedFileInfo(m_outputFile);
+ const qint64 finalSize = (completedFileInfo.exists() && completedFileInfo.isFile()) ? completedFileInfo.size() : 0;
+ log(tr("Final file size is %1 bytes.").arg(sizeToString(finalSize)));
+
+ switch(pass)
{
- log(tr("\nFAILED TO DETERMINE ENCODER VERSION !!!"));
- return UINT_MAX;
+ case 1:
+ setStatus(JobStatus_Running_Pass1);
+ setDetails(tr("First pass completed. Preparing for second pass..."));
+ break;
+ case 2:
+ setStatus(JobStatus_Running_Pass2);
+ setDetails(tr("Second pass completed successfully. Final size is %1.").arg(sizeToString(finalSize)));
+ break;
+ default:
+ setStatus(JobStatus_Running);
+ setDetails(tr("Encode completed successfully. Final size is %1.").arg(sizeToString(finalSize)));
+ break;
}
-
- return (coreVers * REV_MULT) + (revision % REV_MULT);
+
+ setProgress(100);
+ processEncode.close();
+ processInput.close();
+ return true;
+}
+
+// ------------------------------------------------------------
+// Utilities
+// ------------------------------------------------------------
+
+double AbstractEncoder::estimateSize(const QString &fileName, const double &progress)
+{
+ double estimatedSize = 0.0;
+ if(progress >= 0.03)
+ {
+ QFileInfo fileInfo(fileName);
+ if(fileInfo.exists() && fileInfo.isFile())
+ {
+ const qint64 currentSize = QFileInfo(fileName).size();
+ estimatedSize = static_cast<double>(currentSize) * (1.0 / qBound(0.0, progress, 1.0));
+ }
+ }
+ return estimatedSize;
+}
+
+QString AbstractEncoder::sizeToString(qint64 size)
+{
+ static char *prefix[5] = {"Byte", "KB", "MB", "GB", "TB"};
+
+ if(size > 1024I64)
+ {
+ qint64 estimatedSize = size;
+ qint64 remainderSize = 0I64;
+
+ int prefixIdx = 0;
+ while((estimatedSize > 1024I64) && (prefixIdx < 4))
+ {
+ remainderSize = estimatedSize % 1024I64;
+ estimatedSize = estimatedSize / 1024I64;
+ prefixIdx++;
+ }
+
+ double value = static_cast<double>(estimatedSize) + (static_cast<double>(remainderSize) / 1024.0);
+ return QString().sprintf((value < 10.0) ? "%.2f %s" : "%.1f %s", value, prefix[prefixIdx]);
+ }
+
+ return tr("N/A");
+}
+
+// ------------------------------------------------------------
+// Encoder Info
+// ------------------------------------------------------------
+
+template <class T>
+static T getElementAt(const QList<T> &list, const quint32 &index)
+{
+ if (index >= quint32(list.count()))
+ {
+ MUTILS_THROW("Index is out of bounds!");
+ }
+ return list[index];
+}
+
+QStringList AbstractEncoderInfo::getDependencies(const SysinfoModel *sysinfo, const quint32 &encArch, const quint32 &encVariant) const
+{
+ return QStringList();
+}
+
+QString AbstractEncoderInfo::getFullName(const quint32 &encArch, const quint32 &encVariant) const
+{
+ return QString("%1, %2, %3").arg(getName(), archToString(encArch), variantToString(encVariant));
+}
+
+QString AbstractEncoderInfo::archToString(const quint32 &index) const
+{
+ return getElementAt(getArchitectures(), index).first;
+}
+
+AbstractEncoderInfo::ArchBit AbstractEncoderInfo::archToType(const quint32 &index) const
+{
+ return getElementAt(getArchitectures(), index).second;
+}
+
+QString AbstractEncoderInfo::variantToString(const quint32 &index) const
+{
+ return getElementAt(getVariants(), index);
+}
+
+QString AbstractEncoderInfo::rcModeToString(const quint32 &index) const
+{
+ return getElementAt(getRCModes(), index).first;
+}
+
+AbstractEncoderInfo::RCType AbstractEncoderInfo::rcModeToType(const quint32 &index) const
+{
+ return getElementAt(getRCModes(), index).second;
}