OSDN Git Service

Bump x264 minimum required version to API-#160 (r2999).
[x264-launcher/x264-launcher.git] / src / encoder_abstract.cpp
index 3c88e40..b0e63d3 100644 (file)
@@ -1,6 +1,6 @@
 ///////////////////////////////////////////////////////////////////////////////
 // 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;
 }