1 ///////////////////////////////////////////////////////////////////////////////
2 // Simple x264 Launcher
3 // Copyright (C) 2004-2012 LoRd_MuldeR <MuldeR2@GMX.de>
5 // This program is free software; you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License as published by
7 // the Free Software Foundation; either version 2 of the License, or
8 // (at your option) any later version.
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
15 // You should have received a copy of the GNU General Public License along
16 // with this program; if not, write to the Free Software Foundation, Inc.,
17 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 // http://www.gnu.org/licenses/gpl-2.0.txt
20 ///////////////////////////////////////////////////////////////////////////////
22 #include "thread_encode.h"
25 #include "model_options.h"
40 QMutex EncodeThread::m_mutex_startProcess;
45 #define CHECK_STATUS(ABORT_FLAG, OK_FLAG) \
49 log("\nPROCESS ABORTED BY USER !!!"); \
50 setStatus(JobStatus_Aborted); \
51 if(QFileInfo(indexFile).exists()) QFile::remove(indexFile); \
52 if(QFileInfo(m_outputFileName).exists() && (QFileInfo(m_outputFileName).size() == 0)) QFile::remove(m_outputFileName); \
57 setStatus(JobStatus_Failed); \
58 if(QFileInfo(indexFile).exists()) QFile::remove(indexFile); \
59 if(QFileInfo(m_outputFileName).exists() && (QFileInfo(m_outputFileName).size() == 0)) QFile::remove(m_outputFileName); \
64 #define APPEND_AND_CLEAR(LIST, STR) \
66 if(!((STR).isEmpty())) \
76 static const unsigned int REV_MULT = 10000;
78 ///////////////////////////////////////////////////////////////////////////////
79 // Constructor & Destructor
80 ///////////////////////////////////////////////////////////////////////////////
82 EncodeThread::EncodeThread(const QString &sourceFileName, const QString &outputFileName, const OptionsModel *options, const QString &binDir, bool x264_x64, bool avs2yuv_x64)
84 m_jobId(QUuid::createUuid()),
85 m_sourceFileName(sourceFileName),
86 m_outputFileName(outputFileName),
87 m_options(new OptionsModel(*options)),
90 m_avs2yuv_x64(avs2yuv_x64),
91 m_handle_jobObject(NULL),
98 EncodeThread::~EncodeThread(void)
100 X264_DELETE(m_options);
102 if(m_handle_jobObject)
104 CloseHandle(m_handle_jobObject);
105 m_handle_jobObject = NULL;
109 ///////////////////////////////////////////////////////////////////////////////
110 // Thread entry point
111 ///////////////////////////////////////////////////////////////////////////////
113 void EncodeThread::run(void)
121 qWarning("STRUCTURED EXCEPTION ERROR IN ENCODE THREAD !!!");
124 if(m_handle_jobObject)
126 TerminateJobObject(m_handle_jobObject, 42);
127 m_handle_jobObject = NULL;
131 void EncodeThread::checkedRun(void)
134 m_status = JobStatus_Starting;
144 log(tr("EXCEPTION ERROR IN THREAD: ").append(QString::fromLatin1(msg)));
145 setStatus(JobStatus_Failed);
149 log(tr("UNHANDLED EXCEPTION ERROR IN THREAD !!!"));
150 setStatus(JobStatus_Failed);
155 RaiseException(EXCEPTION_ACCESS_VIOLATION, 0, 0, NULL);
159 void EncodeThread::start(Priority priority)
161 qDebug("Thread starting...");
166 while(m_semaphorePaused.tryAcquire(1, 0));
167 QThread::start(priority);
170 ///////////////////////////////////////////////////////////////////////////////
172 ///////////////////////////////////////////////////////////////////////////////
174 void EncodeThread::encode(void)
176 QDateTime startTime = QDateTime::currentDateTime();
178 //Print some basic info
179 log(tr("Job started at %1, %2.\n").arg(QDate::currentDate().toString(Qt::ISODate), QTime::currentTime().toString( Qt::ISODate)));
180 log(tr("Source file: %1").arg(m_sourceFileName));
181 log(tr("Output file: %1").arg(m_outputFileName));
183 //Print encoder settings
184 log(tr("\n--- SETTINGS ---\n"));
185 log(tr("RC Mode: %1").arg(OptionsModel::rcMode2String(m_options->rcMode())));
186 log(tr("Preset: %1").arg(m_options->preset()));
187 log(tr("Tuning: %1").arg(m_options->tune()));
188 log(tr("Profile: %1").arg(m_options->profile()));
189 log(tr("Custom: %1").arg(m_options->customX264().isEmpty() ? tr("(None)") : m_options->customX264()));
192 unsigned int frames = 0;
195 const bool usePipe = (QFileInfo(m_sourceFileName).suffix().compare("avs", Qt::CaseInsensitive) == 0);
196 const QString indexFile = QString("%1/%2.ffindex").arg(QDir::tempPath(), m_jobId.toString());
198 //Checking x264 version
199 log(tr("\n--- CHECK VERSION ---\n"));
200 unsigned int revision_x264 = UINT_MAX;
201 bool x264_modified = false;
202 ok = ((revision_x264 = checkVersionX264(m_x264_x64, x264_modified)) != UINT_MAX);
203 CHECK_STATUS(m_abort, ok);
205 //Checking avs2yuv version
206 unsigned int revision_avs2yuv = UINT_MAX;
209 ok = ((revision_avs2yuv = checkVersionAvs2yuv(m_avs2yuv_x64)) != UINT_MAX);
210 CHECK_STATUS(m_abort, ok);
214 log(tr("\nx264 revision: %1 (core #%2)").arg(QString::number(revision_x264 % REV_MULT), QString::number(revision_x264 / REV_MULT)).append(x264_modified ? tr(" - with custom patches!") : QString()));
215 if(revision_avs2yuv != UINT_MAX) log(tr("Avs2YUV version: %1.%2.%3").arg(QString::number(revision_avs2yuv / REV_MULT), QString::number((revision_avs2yuv % REV_MULT) / 10),QString::number((revision_avs2yuv % REV_MULT) % 10)));
217 //Is x264 revision supported?
218 if((revision_x264 % REV_MULT) < (VER_X264_MINIMUM_REV))
220 log(tr("\nERROR: Your revision of x264 is too old! (Minimum required revision is %2)").arg(QString::number(VER_X264_MINIMUM_REV)));
221 setStatus(JobStatus_Failed);
224 if((revision_x264 / REV_MULT) != (VER_X264_CURRENT_API))
226 log(tr("\nWARNING: Your revision of x264 uses an unsupported core (API) version, take care!"));
227 log(tr("This application works best with x264 core (API) version %2.").arg(QString::number(VER_X264_CURRENT_API)));
229 if((revision_avs2yuv != UINT_MAX) && ((revision_avs2yuv % REV_MULT) != (VER_X264_AVS2YUV_VER)))
231 log(tr("\nERROR: Your version of avs2yuv is unsupported (Required version: v0.24 BugMaster's mod 2)"));
232 log(tr("You can find the required version at: http://komisar.gin.by/tools/avs2yuv/"));
233 setStatus(JobStatus_Failed);
240 log(tr("\n--- AVS INFO ---\n"));
241 ok = checkProperties(m_avs2yuv_x64, frames);
242 CHECK_STATUS(m_abort, ok);
245 //Run encoding passes
246 if(m_options->rcMode() == OptionsModel::RCMode_2Pass)
248 QFileInfo info(m_outputFileName);
249 QString passLogFile = QString("%1/%2.stats").arg(info.path(), info.completeBaseName());
251 if(QFileInfo(passLogFile).exists())
254 while(QFileInfo(passLogFile).exists())
256 passLogFile = QString("%1/%2.%3.stats").arg(info.path(), info.completeBaseName(), QString::number(n++));
260 log(tr("\n--- PASS 1 ---\n"));
261 ok = runEncodingPass(m_x264_x64, m_avs2yuv_x64, usePipe, frames, indexFile, 1, passLogFile);
262 CHECK_STATUS(m_abort, ok);
264 log(tr("\n--- PASS 2 ---\n"));
265 ok = runEncodingPass(m_x264_x64, m_avs2yuv_x64, usePipe, frames, indexFile, 2, passLogFile);
266 CHECK_STATUS(m_abort, ok);
270 log(tr("\n--- ENCODING ---\n"));
271 ok = runEncodingPass(m_x264_x64, m_avs2yuv_x64, usePipe, frames, indexFile);
272 CHECK_STATUS(m_abort, ok);
275 log(tr("\n--- DONE ---\n"));
276 if(QFileInfo(indexFile).exists()) QFile::remove(indexFile);
277 int timePassed = startTime.secsTo(QDateTime::currentDateTime());
278 log(tr("Job finished at %1, %2. Process took %3 minutes, %4 seconds.").arg(QDate::currentDate().toString(Qt::ISODate), QTime::currentTime().toString( Qt::ISODate), QString::number(timePassed / 60), QString::number(timePassed % 60)));
279 setStatus(JobStatus_Completed);
282 bool EncodeThread::runEncodingPass(bool x264_x64, bool avs2yuv_x64, bool usePipe, unsigned int frames, const QString &indexFile, int pass, const QString &passLogFile)
284 QProcess processEncode, processAvisynth;
288 QStringList cmdLine_Avisynth;
289 if(!m_options->customAvs2YUV().isEmpty())
291 cmdLine_Avisynth.append(splitParams(m_options->customAvs2YUV()));
293 cmdLine_Avisynth << pathToLocal(QDir::toNativeSeparators(m_sourceFileName));
294 cmdLine_Avisynth << "-";
295 processAvisynth.setStandardOutputProcess(&processEncode);
297 log("Creating Avisynth process:");
298 if(!startProcess(processAvisynth, QString("%1/%2.exe").arg(m_binDir, avs2yuv_x64 ? "avs2yuv_x64" : "avs2yuv"), cmdLine_Avisynth, false))
304 QStringList cmdLine_Encode = buildCommandLine(usePipe, frames, indexFile, pass, passLogFile);
306 log("Creating x264 process:");
307 if(!startProcess(processEncode, QString("%1/%2.exe").arg(m_binDir, x264_x64 ? "x264_x64" : "x264"), cmdLine_Encode))
312 QRegExp regExpIndexing("indexing.+\\[(\\d+)\\.\\d+%\\]");
313 QRegExp regExpProgress("\\[(\\d+)\\.(\\d+)%\\].+frames");
314 QRegExp regExpFrameCnt("^(\\d+) frames:");
316 QTextCodec *localCodec = QTextCodec::codecForName("System");
318 bool bTimeout = false;
319 bool bAborted = false;
321 //Main processing loop
322 while(processEncode.state() != QProcess::NotRunning)
324 unsigned int waitCounter = 0;
326 //Wait until new output is available
331 processEncode.kill();
332 processAvisynth.kill();
336 if(m_pause && (processEncode.state() == QProcess::Running))
338 JobStatus previousStatus = m_status;
339 setStatus(JobStatus_Paused);
340 log(tr("Job paused by user at %1, %2.").arg(QDate::currentDate().toString(Qt::ISODate), QTime::currentTime().toString( Qt::ISODate)));
341 bool ok[2] = {false, false};
342 Q_PID pid[2] = {processEncode.pid(), processAvisynth.pid()};
343 if(pid[0]) { ok[0] = (SuspendThread(pid[0]->hThread) != (DWORD)(-1)); }
344 if(pid[1]) { ok[1] = (SuspendThread(pid[1]->hThread) != (DWORD)(-1)); }
345 while(m_pause) m_semaphorePaused.acquire();
346 while(m_semaphorePaused.tryAcquire(1, 0));
347 if(pid[0]) { if(ok[0]) ResumeThread(pid[0]->hThread); }
348 if(pid[1]) { if(ok[1]) ResumeThread(pid[1]->hThread); }
349 if(!m_abort) setStatus(previousStatus);
350 log(tr("Job resumed by user at %1, %2.").arg(QDate::currentDate().toString(Qt::ISODate), QTime::currentTime().toString( Qt::ISODate)));
354 if(!processEncode.waitForReadyRead(m_processTimeoutInterval))
356 if(processEncode.state() == QProcess::Running)
358 if(waitCounter++ > m_processTimeoutMaxCounter)
360 processEncode.kill();
361 qWarning("x264 process timed out <-- killing!");
362 log("\nPROCESS TIMEOUT !!!");
366 else if(waitCounter == m_processTimeoutWarning)
368 unsigned int timeOut = (waitCounter * m_processTimeoutInterval) / 1000U;
369 log(tr("Warning: x264 did not respond for %1 seconds, potential deadlock...").arg(QString::number(timeOut)));
374 if(m_abort || (m_pause && (processEncode.state() == QProcess::Running)))
381 //Exit main processing loop now?
382 if(bAborted || bTimeout)
388 while(processEncode.bytesAvailable() > 0)
390 QList<QByteArray> lines = processEncode.readLine().split('\r');
391 while(!lines.isEmpty())
393 QString text = localCodec->toUnicode(lines.takeFirst().constData()).simplified();
395 if((offset = regExpProgress.lastIndexIn(text)) >= 0)
397 bool ok[2] = {false, false};
398 unsigned int progressInt = regExpProgress.cap(1).toUInt(&ok[0]);
399 unsigned int progressFrc = regExpProgress.cap(2).toUInt(&ok[1]);
400 setStatus((pass == 2) ? JobStatus_Running_Pass2 : ((pass == 1) ? JobStatus_Running_Pass1 : JobStatus_Running));
401 setDetails(tr("%1, est. size %2").arg(text.mid(offset).trimmed(), estimateSize(ok[0] ? progressInt : 0, ok[1] ? progressFrc : 0)));
402 if(ok[0]) setProgress(progressInt);
404 else if((offset = regExpIndexing.lastIndexIn(text)) >= 0)
407 unsigned int progress = regExpIndexing.cap(1).toUInt(&ok);
408 setStatus(JobStatus_Indexing);
409 setDetails(text.mid(offset).trimmed());
410 if(ok) setProgress(progress);
412 else if((offset = regExpFrameCnt.lastIndexIn(text)) >= 0)
414 setStatus((pass == 2) ? JobStatus_Running_Pass2 : ((pass == 1) ? JobStatus_Running_Pass1 : JobStatus_Running));
415 setDetails(text.mid(offset).trimmed());
417 else if(!text.isEmpty())
425 processEncode.waitForFinished(5000);
426 if(processEncode.state() != QProcess::NotRunning)
428 qWarning("x264 process still running, going to kill it!");
429 processEncode.kill();
430 processEncode.waitForFinished(-1);
433 processAvisynth.waitForFinished(5000);
434 if(processAvisynth.state() != QProcess::NotRunning)
436 qWarning("Avisynth process still running, going to kill it!");
437 processAvisynth.kill();
438 processAvisynth.waitForFinished(-1);
441 if(!(bTimeout || bAborted))
443 while(processAvisynth.bytesAvailable() > 0)
445 log(tr("av2y [info]: %1").arg(QString::fromUtf8(processAvisynth.readLine()).simplified()));
449 if(usePipe && (processAvisynth.exitCode() != EXIT_SUCCESS))
451 if(!(bTimeout || bAborted))
453 log(tr("\nWARNING: Avisynth process exited with error code: %1").arg(QString::number(processAvisynth.exitCode())));
457 if(bTimeout || bAborted || processEncode.exitCode() != EXIT_SUCCESS)
459 if(!(bTimeout || bAborted))
461 log(tr("\nPROCESS EXITED WITH ERROR CODE: %1").arg(QString::number(processEncode.exitCode())));
463 processEncode.close();
464 processAvisynth.close();
471 setStatus(JobStatus_Running_Pass1);
472 setDetails(tr("First pass completed. Preparing for second pass..."));
475 setStatus(JobStatus_Running_Pass2);
476 setDetails(tr("Second pass completed successfully."));
479 setStatus(JobStatus_Running);
480 setDetails(tr("Encode completed successfully."));
485 processEncode.close();
486 processAvisynth.close();
490 QStringList EncodeThread::buildCommandLine(bool usePipe, unsigned int frames, const QString &indexFile, int pass, const QString &passLogFile)
493 double crf_int = 0.0, crf_frc = 0.0;
495 switch(m_options->rcMode())
497 case OptionsModel::RCMode_CQ:
498 cmdLine << "--qp" << QString::number(qRound(m_options->quantizer()));
500 case OptionsModel::RCMode_CRF:
501 crf_frc = modf(m_options->quantizer(), &crf_int);
502 cmdLine << "--crf" << QString("%1.%2").arg(QString::number(qRound(crf_int)), QString::number(qRound(crf_frc * 10.0)));
504 case OptionsModel::RCMode_2Pass:
505 case OptionsModel::RCMode_ABR:
506 cmdLine << "--bitrate" << QString::number(m_options->bitrate());
509 throw "Bad rate-control mode !!!";
513 if((pass == 1) || (pass == 2))
515 cmdLine << "--pass" << QString::number(pass);
516 cmdLine << "--stats" << pathToLocal(QDir::toNativeSeparators(passLogFile), true);
519 cmdLine << "--preset" << m_options->preset().toLower();
521 if(m_options->tune().compare("none", Qt::CaseInsensitive))
523 cmdLine << "--tune" << m_options->tune().toLower();
526 if(m_options->profile().compare("auto", Qt::CaseInsensitive))
528 cmdLine << "--profile" << m_options->profile().toLower();
531 if(!m_options->customX264().isEmpty())
533 cmdLine.append(splitParams(m_options->customX264()));
536 cmdLine << "--output" << pathToLocal(QDir::toNativeSeparators(m_outputFileName), true);
540 if(frames < 1) throw "Frames not set!";
541 cmdLine << "--frames" << QString::number(frames);
542 cmdLine << "--demuxer" << "y4m";
543 cmdLine << "--stdin" << "y4m" << "-";
547 cmdLine << "--index" << pathToLocal(QDir::toNativeSeparators(indexFile), true, false);
548 cmdLine << pathToLocal(QDir::toNativeSeparators(m_sourceFileName));
554 unsigned int EncodeThread::checkVersionX264(bool x64, bool &modified)
557 QStringList cmdLine = QStringList() << "--version";
559 log("Creating process:");
560 if(!startProcess(process, QString("%1/%2.exe").arg(m_binDir, x64 ? "x264_x64" : "x264"), cmdLine))
565 QRegExp regExpVersion("\\bx264\\s(\\d)\\.(\\d+)\\.(\\d+)\\s([a-f0-9]{7})", Qt::CaseInsensitive);
566 QRegExp regExpVersionMod("\\bx264 (\\d)\\.(\\d+)\\.(\\d+)", Qt::CaseInsensitive);
568 bool bTimeout = false;
569 bool bAborted = false;
571 unsigned int revision = UINT_MAX;
572 unsigned int coreVers = UINT_MAX;
575 while(process.state() != QProcess::NotRunning)
583 if(!process.waitForReadyRead())
585 if(process.state() == QProcess::Running)
588 qWarning("x264 process timed out <-- killing!");
589 log("\nPROCESS TIMEOUT !!!");
594 while(process.bytesAvailable() > 0)
596 QList<QByteArray> lines = process.readLine().split('\r');
597 while(!lines.isEmpty())
599 QString text = QString::fromUtf8(lines.takeFirst().constData()).simplified();
601 if((offset = regExpVersion.lastIndexIn(text)) >= 0)
603 bool ok1 = false, ok2 = false;
604 unsigned int temp1 = regExpVersion.cap(2).toUInt(&ok1);
605 unsigned int temp2 = regExpVersion.cap(3).toUInt(&ok2);
606 if(ok1) coreVers = temp1;
607 if(ok2) revision = temp2;
609 else if((offset = regExpVersionMod.lastIndexIn(text)) >= 0)
611 bool ok1 = false, ok2 = false;
612 unsigned int temp1 = regExpVersionMod.cap(2).toUInt(&ok1);
613 unsigned int temp2 = regExpVersionMod.cap(3).toUInt(&ok2);
614 if(ok1) coreVers = temp1;
615 if(ok2) revision = temp2;
626 process.waitForFinished();
627 if(process.state() != QProcess::NotRunning)
630 process.waitForFinished(-1);
633 if(bTimeout || bAborted || process.exitCode() != EXIT_SUCCESS)
635 if(!(bTimeout || bAborted))
637 log(tr("\nPROCESS EXITED WITH ERROR CODE: %1").arg(QString::number(process.exitCode())));
642 if((revision == UINT_MAX) || (coreVers == UINT_MAX))
644 log(tr("\nFAILED TO DETERMINE X264 VERSION !!!"));
648 return (coreVers * REV_MULT) + (revision % REV_MULT);
651 unsigned int EncodeThread::checkVersionAvs2yuv(bool x64)
655 log("\nCreating process:");
656 if(!startProcess(process, QString("%1/%2.exe").arg(m_binDir, x64 ? "avs2yuv_x64" : "avs2yuv"), QStringList()))
661 QRegExp regExpVersionMod("\\bAvs2YUV (\\d+).(\\d+)bm(\\d)\\b", Qt::CaseInsensitive);
662 QRegExp regExpVersionOld("\\bAvs2YUV (\\d+).(\\d+)\\b", Qt::CaseInsensitive);
664 bool bTimeout = false;
665 bool bAborted = false;
667 unsigned int ver_maj = UINT_MAX;
668 unsigned int ver_min = UINT_MAX;
669 unsigned int ver_mod = 0;
671 while(process.state() != QProcess::NotRunning)
679 if(!process.waitForReadyRead())
681 if(process.state() == QProcess::Running)
684 qWarning("Avs2YUV process timed out <-- killing!");
685 log("\nPROCESS TIMEOUT !!!");
690 while(process.bytesAvailable() > 0)
692 QList<QByteArray> lines = process.readLine().split('\r');
693 while(!lines.isEmpty())
695 QString text = QString::fromUtf8(lines.takeFirst().constData()).simplified();
697 if((ver_maj == UINT_MAX) || (ver_min == UINT_MAX) || (ver_mod == UINT_MAX))
704 if((offset = regExpVersionMod.lastIndexIn(text)) >= 0)
706 bool ok1 = false, ok2 = false, ok3 = false;
707 unsigned int temp1 = regExpVersionMod.cap(1).toUInt(&ok1);
708 unsigned int temp2 = regExpVersionMod.cap(2).toUInt(&ok2);
709 unsigned int temp3 = regExpVersionMod.cap(3).toUInt(&ok3);
710 if(ok1) ver_maj = temp1;
711 if(ok2) ver_min = temp2;
712 if(ok3) ver_mod = temp3;
714 else if((offset = regExpVersionOld.lastIndexIn(text)) >= 0)
716 bool ok1 = false, ok2 = false;
717 unsigned int temp1 = regExpVersionOld.cap(1).toUInt(&ok1);
718 unsigned int temp2 = regExpVersionOld.cap(2).toUInt(&ok2);
719 if(ok1) ver_maj = temp1;
720 if(ok2) ver_min = temp2;
726 process.waitForFinished();
727 if(process.state() != QProcess::NotRunning)
730 process.waitForFinished(-1);
733 if(bTimeout || bAborted || ((process.exitCode() != EXIT_SUCCESS) && (process.exitCode() != 2)))
735 if(!(bTimeout || bAborted))
737 log(tr("\nPROCESS EXITED WITH ERROR CODE: %1").arg(QString::number(process.exitCode())));
742 if((ver_maj == UINT_MAX) || (ver_min == UINT_MAX))
744 log(tr("\nFAILED TO DETERMINE AVS2YUV VERSION !!!"));
748 return (ver_maj * REV_MULT) + ((ver_min % REV_MULT) * 10) + (ver_mod % 10);
751 bool EncodeThread::checkProperties(bool x64, unsigned int &frames)
756 if(!m_options->customAvs2YUV().isEmpty())
758 cmdLine.append(splitParams(m_options->customAvs2YUV()));
761 cmdLine << "-frames" << "1";
762 cmdLine << pathToLocal(QDir::toNativeSeparators(m_sourceFileName)) << "NUL";
764 log("Creating process:");
765 if(!startProcess(process, QString("%1/%2.exe").arg(m_binDir, x64 ? "avs2yuv_x64" : "avs2yuv"), cmdLine))
770 QRegExp regExpInt(": (\\d+)x(\\d+), (\\d+) fps, (\\d+) frames");
771 QRegExp regExpFrc(": (\\d+)x(\\d+), (\\d+)/(\\d+) fps, (\\d+) frames");
773 QTextCodec *localCodec = QTextCodec::codecForName("System");
775 bool bTimeout = false;
776 bool bAborted = false;
780 unsigned int fpsNom = 0;
781 unsigned int fpsDen = 0;
782 unsigned int fSizeW = 0;
783 unsigned int fSizeH = 0;
785 unsigned int waitCounter = 0;
787 while(process.state() != QProcess::NotRunning)
795 if(!process.waitForReadyRead(m_processTimeoutInterval))
797 if(process.state() == QProcess::Running)
799 if(waitCounter++ > m_processTimeoutMaxCounter)
802 qWarning("Avs2YUV process timed out <-- killing!");
803 log("\nPROCESS TIMEOUT !!!");
804 log("\nAvisynth has encountered a deadlock or your script takes EXTREMELY long to initialize!");
808 else if(waitCounter == m_processTimeoutWarning)
810 unsigned int timeOut = (waitCounter * m_processTimeoutInterval) / 1000U;
811 log(tr("Warning: Avisynth did not respond for %1 seconds, potential deadlock...").arg(QString::number(timeOut)));
819 while(process.bytesAvailable() > 0)
821 QList<QByteArray> lines = process.readLine().split('\r');
822 while(!lines.isEmpty())
824 QString text = localCodec->toUnicode(lines.takeFirst().constData()).simplified();
826 if((offset = regExpInt.lastIndexIn(text)) >= 0)
828 bool ok1 = false, ok2 = false;
829 bool ok3 = false, ok4 = false;
830 unsigned int temp1 = regExpInt.cap(1).toUInt(&ok1);
831 unsigned int temp2 = regExpInt.cap(2).toUInt(&ok2);
832 unsigned int temp3 = regExpInt.cap(3).toUInt(&ok3);
833 unsigned int temp4 = regExpInt.cap(4).toUInt(&ok4);
834 if(ok1) fSizeW = temp1;
835 if(ok2) fSizeH = temp2;
836 if(ok3) fpsNom = temp3;
837 if(ok4) frames = temp4;
839 else if((offset = regExpFrc.lastIndexIn(text)) >= 0)
841 bool ok1 = false, ok2 = false;
842 bool ok3 = false, ok4 = false, ok5 = false;
843 unsigned int temp1 = regExpFrc.cap(1).toUInt(&ok1);
844 unsigned int temp2 = regExpFrc.cap(2).toUInt(&ok2);
845 unsigned int temp3 = regExpFrc.cap(3).toUInt(&ok3);
846 unsigned int temp4 = regExpFrc.cap(4).toUInt(&ok4);
847 unsigned int temp5 = regExpFrc.cap(5).toUInt(&ok5);
848 if(ok1) fSizeW = temp1;
849 if(ok2) fSizeH = temp2;
850 if(ok3) fpsNom = temp3;
851 if(ok4) fpsDen = temp4;
852 if(ok5) frames = temp5;
858 if(text.contains("failed to load avisynth.dll", Qt::CaseInsensitive))
860 log(tr("\nWarning: It seems that %1-Bit Avisynth is not currently installed !!!").arg(x64 ? "64" : "32"));
862 if(text.contains(QRegExp("couldn't convert input clip to (YV16|YV24)", Qt::CaseInsensitive)))
864 log(tr("\nWarning: YV16 (4:2:2) and YV24 (4:4:4) color-spaces only supported in Avisynth 2.6 !!!"));
870 process.waitForFinished();
871 if(process.state() != QProcess::NotRunning)
874 process.waitForFinished(-1);
877 if(bTimeout || bAborted || process.exitCode() != EXIT_SUCCESS)
879 if(!(bTimeout || bAborted))
881 log(tr("\nPROCESS EXITED WITH ERROR CODE: %1").arg(QString::number(process.exitCode())));
888 log(tr("\nFAILED TO DETERMINE AVS PROPERTIES !!!"));
894 if((fSizeW > 0) && (fSizeH > 0))
896 log(tr("Resolution: %1x%2").arg(QString::number(fSizeW), QString::number(fSizeH)));
898 if((fpsNom > 0) && (fpsDen > 0))
900 log(tr("Frame Rate: %1/%2").arg(QString::number(fpsNom), QString::number(fpsDen)));
902 if((fpsNom > 0) && (fpsDen == 0))
904 log(tr("Frame Rate: %1").arg(QString::number(fpsNom)));
908 log(tr("No. Frames: %1").arg(QString::number(frames)));
914 ///////////////////////////////////////////////////////////////////////////////
916 ///////////////////////////////////////////////////////////////////////////////
918 void EncodeThread::setStatus(JobStatus newStatus)
920 if(m_status != newStatus)
922 if((newStatus != JobStatus_Completed) && (newStatus != JobStatus_Failed) && (newStatus != JobStatus_Aborted) && (newStatus != JobStatus_Paused))
924 if(m_status != JobStatus_Paused) setProgress(0);
926 if(newStatus == JobStatus_Failed)
928 setDetails("The job has failed. See log for details!");
930 if(newStatus == JobStatus_Aborted)
932 setDetails("The job was aborted by the user!");
934 m_status = newStatus;
935 emit statusChanged(m_jobId, newStatus);
939 void EncodeThread::setProgress(unsigned int newProgress)
941 if(m_progress != newProgress)
943 m_progress = newProgress;
944 emit progressChanged(m_jobId, m_progress);
948 void EncodeThread::setDetails(const QString &text)
950 emit detailsChanged(m_jobId, text);
953 QString EncodeThread::pathToLocal(const QString &longPath, bool create, bool keep)
955 QTextCodec *localCodec = QTextCodec::codecForName("System");
957 //Do NOT convert to short, if path can be represented in local Codepage
958 if(localCodec->toUnicode(localCodec->fromUnicode(longPath)).compare(longPath, Qt::CaseInsensitive) == 0)
963 //Create dummy file, if required (only existing files can have a short path!)
965 if((!QFileInfo(longPath).exists()) && create)
967 tempFile.setFileName(longPath);
968 tempFile.open(QIODevice::WriteOnly);
972 DWORD buffSize = GetShortPathNameW(reinterpret_cast<const wchar_t*>(longPath.utf16()), NULL, NULL);
976 wchar_t *buffer = new wchar_t[buffSize];
977 DWORD result = GetShortPathNameW(reinterpret_cast<const wchar_t*>(longPath.utf16()), buffer, buffSize);
979 if(result > 0 && result < buffSize)
981 shortPath = QString::fromUtf16(reinterpret_cast<const unsigned short*>(buffer));
988 //Remove the dummy file now (FFMS2 fails, if index file does exist but is empty!)
989 if(tempFile.isOpen())
991 if(!keep) tempFile.remove();
995 if(shortPath.isEmpty())
997 log(tr("Warning: Failed to convert path \"%1\" to short!\n").arg(longPath));
1000 return (shortPath.isEmpty() ? longPath : shortPath);
1003 bool EncodeThread::startProcess(QProcess &process, const QString &program, const QStringList &args, bool mergeChannels)
1005 QMutexLocker lock(&m_mutex_startProcess);
1006 log(commandline2string(program, args) + "\n");
1008 //Create a new job object, if not done yet
1009 if(!m_handle_jobObject)
1011 m_handle_jobObject = CreateJobObject(NULL, NULL);
1012 if(m_handle_jobObject == INVALID_HANDLE_VALUE)
1014 m_handle_jobObject = NULL;
1016 if(m_handle_jobObject)
1018 JOBOBJECT_EXTENDED_LIMIT_INFORMATION jobExtendedLimitInfo;
1019 memset(&jobExtendedLimitInfo, 0, sizeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION));
1020 jobExtendedLimitInfo.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE | JOB_OBJECT_LIMIT_DIE_ON_UNHANDLED_EXCEPTION;
1021 SetInformationJobObject(m_handle_jobObject, JobObjectExtendedLimitInformation, &jobExtendedLimitInfo, sizeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION));
1027 process.setProcessChannelMode(QProcess::MergedChannels);
1028 process.setReadChannel(QProcess::StandardOutput);
1032 process.setProcessChannelMode(QProcess::SeparateChannels);
1033 process.setReadChannel(QProcess::StandardError);
1036 process.start(program, args);
1038 if(process.waitForStarted())
1040 Q_PID pid = process.pid();
1041 AssignProcessToJobObject(m_handle_jobObject, process.pid()->hProcess);
1044 if(!SetPriorityClass(process.pid()->hProcess, BELOW_NORMAL_PRIORITY_CLASS))
1046 SetPriorityClass(process.pid()->hProcess, IDLE_PRIORITY_CLASS);
1054 log("Process creation has failed :-(");
1055 QString errorMsg= process.errorString().trimmed();
1056 if(!errorMsg.isEmpty()) log(errorMsg);
1059 process.waitForFinished(-1);
1063 QString EncodeThread::commandline2string(const QString &program, const QStringList &arguments)
1065 QString commandline = (program.contains(' ') ? QString("\"%1\"").arg(program) : program);
1067 for(int i = 0; i < arguments.count(); i++)
1069 commandline += (arguments.at(i).contains(' ') ? QString(" \"%1\"").arg(arguments.at(i)) : QString(" %1").arg(arguments.at(i)));
1075 QStringList EncodeThread::splitParams(const QString ¶ms)
1078 bool ignoreWhitespaces = false;
1081 for(int i = 0; i < params.length(); i++)
1083 const QChar c = params.at(i);
1085 if(c == QChar::fromLatin1('"'))
1087 ignoreWhitespaces = (!ignoreWhitespaces);
1090 else if((!ignoreWhitespaces) && (c == QChar::fromLatin1(' ')))
1092 APPEND_AND_CLEAR(list, temp);
1099 APPEND_AND_CLEAR(list, temp);
1101 list.replaceInStrings("$(INPUT)", QDir::toNativeSeparators(m_sourceFileName), Qt::CaseInsensitive);
1102 list.replaceInStrings("$(OUTPUT)", QDir::toNativeSeparators(m_outputFileName), Qt::CaseInsensitive);
1107 QString EncodeThread::estimateSize(int progressInt, int progressFrc)
1109 int progress = (10 * progressInt) + (progressFrc % 10);
1110 static char *prefix[5] = {"Byte", "KB", "MB", "GB", "TB"};
1114 qint64 currentSize = QFileInfo(m_outputFileName).size();
1115 if(currentSize > 1024I64)
1117 qint64 estimatedSize = (currentSize * 1000I64) / static_cast<qint64>(progress);
1118 qint64 remainderSize = 0I64;
1121 while((estimatedSize > 1024I64) && (prefixIdx < 4))
1123 remainderSize = estimatedSize % 1024I64;
1124 estimatedSize = estimatedSize / 1024I64;
1128 double value = static_cast<double>(estimatedSize) + (static_cast<double>(remainderSize) / 1024.0);
1129 return QString().sprintf((value < 10.0) ? "%.2f %s" : "%.1f %s", value, prefix[prefixIdx]);