OSDN Git Service

Allow "--frames" and "--fps" as custom parameters, because they might be useful when...
[x264-launcher/x264-launcher.git] / src / thread_encode.cpp
1 ///////////////////////////////////////////////////////////////////////////////
2 // Simple x264 Launcher
3 // Copyright (C) 2004-2012 LoRd_MuldeR <MuldeR2@GMX.de>
4 //
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.
9 //
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.
14 //
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.
18 //
19 // http://www.gnu.org/licenses/gpl-2.0.txt
20 ///////////////////////////////////////////////////////////////////////////////
21
22 #include "thread_encode.h"
23
24 #include "global.h"
25 #include "model_options.h"
26 #include "version.h"
27
28 #include <QDate>
29 #include <QTime>
30 #include <QDateTime>
31 #include <QFileInfo>
32 #include <QDir>
33 #include <QProcess>
34 #include <QMutex>
35 #include <QTextCodec>
36 #include <QLocale>
37
38 /*
39  * Static vars
40  */
41 QMutex EncodeThread::m_mutex_startProcess;
42
43 /*
44  * Macros
45  */
46 #define CHECK_STATUS(ABORT_FLAG, OK_FLAG) do \
47 { \
48         if(ABORT_FLAG) \
49         { \
50                 log("\nPROCESS ABORTED BY USER !!!"); \
51                 setStatus(JobStatus_Aborted); \
52                 if(QFileInfo(indexFile).exists()) QFile::remove(indexFile); \
53                 if(QFileInfo(m_outputFileName).exists() && (QFileInfo(m_outputFileName).size() == 0)) QFile::remove(m_outputFileName); \
54                 return; \
55         } \
56         else if(!(OK_FLAG)) \
57         { \
58                 setStatus(JobStatus_Failed); \
59                 if(QFileInfo(indexFile).exists()) QFile::remove(indexFile); \
60                 if(QFileInfo(m_outputFileName).exists() && (QFileInfo(m_outputFileName).size() == 0)) QFile::remove(m_outputFileName); \
61                 return; \
62         } \
63 } \
64 while(0)
65
66 #define APPEND_AND_CLEAR(LIST, STR) do \
67 { \
68         if(!((STR).isEmpty())) \
69         { \
70                 (LIST) << (STR); \
71                 (STR).clear(); \
72         } \
73 } \
74 while(0)
75
76 #define REMOVE_CUSTOM_ARG(LIST, ITER, FLAG, PARAM) do \
77 { \
78         if(ITER != LIST.end()) \
79         { \
80                 if((*ITER).compare(PARAM, Qt::CaseInsensitive) == 0) \
81                 { \
82                         log(tr("WARNING: Custom parameter \"" PARAM "\" will be ignored in Pipe'd mode!\n")); \
83                         ITER = LIST.erase(ITER); \
84                         if(ITER != LIST.end()) \
85                         { \
86                                 if(!((*ITER).startsWith("--", Qt::CaseInsensitive))) ITER = LIST.erase(ITER); \
87                         } \
88                         FLAG = true; \
89                 } \
90         } \
91 } \
92 while(0)
93
94 #define X264_BINARY(BIN_DIR, IS_10BIT, IS_X64) QString("%1/x264_%2_%3.exe").arg((BIN_DIR), ((IS_10BIT) ? "10bit" : "8bit"), ((IS_X64) ? "x64" : "x86"))
95
96 /*
97  * Static vars
98  */
99 static const unsigned int REV_MULT = 10000;
100
101 ///////////////////////////////////////////////////////////////////////////////
102 // Constructor & Destructor
103 ///////////////////////////////////////////////////////////////////////////////
104
105 EncodeThread::EncodeThread(const QString &sourceFileName, const QString &outputFileName, const OptionsModel *options, const QString &binDir, bool x264_x64, bool x264_10bit, bool avs2yuv_x64)
106 :
107         m_jobId(QUuid::createUuid()),
108         m_sourceFileName(sourceFileName),
109         m_outputFileName(outputFileName),
110         m_options(new OptionsModel(*options)),
111         m_binDir(binDir),
112         m_x264_x64(x264_x64),
113         m_x264_10bit(x264_10bit),
114         m_avs2yuv_x64(avs2yuv_x64),
115         m_handle_jobObject(NULL),
116         m_semaphorePaused(0)
117 {
118         m_abort = false;
119         m_pause = false;
120 }
121
122 EncodeThread::~EncodeThread(void)
123 {
124         X264_DELETE(m_options);
125         
126         if(m_handle_jobObject)
127         {
128                 CloseHandle(m_handle_jobObject);
129                 m_handle_jobObject = NULL;
130         }
131 }
132
133 ///////////////////////////////////////////////////////////////////////////////
134 // Thread entry point
135 ///////////////////////////////////////////////////////////////////////////////
136
137 void EncodeThread::run(void)
138 {
139         __try
140         {
141                 checkedRun();
142         }
143         __except(1)
144         {
145                 qWarning("STRUCTURED EXCEPTION ERROR IN ENCODE THREAD !!!");
146         }
147
148         if(m_handle_jobObject)
149         {
150                 TerminateJobObject(m_handle_jobObject, 42);
151                 m_handle_jobObject = NULL;
152         }
153 }
154
155 void EncodeThread::checkedRun(void)
156 {
157         m_progress = 0;
158         m_status = JobStatus_Starting;
159
160         try
161         {
162                 try
163                 {
164                         encode();
165                 }
166                 catch(char *msg)
167                 {
168                         log(tr("EXCEPTION ERROR IN THREAD: ").append(QString::fromLatin1(msg)));
169                         setStatus(JobStatus_Failed);
170                 }
171                 catch(...)
172                 {
173                         log(tr("UNHANDLED EXCEPTION ERROR IN THREAD !!!"));
174                         setStatus(JobStatus_Failed);
175                 }
176         }
177         catch(...)
178         {
179                 RaiseException(EXCEPTION_ACCESS_VIOLATION, 0, 0, NULL);
180         }
181 }
182
183 void EncodeThread::start(Priority priority)
184 {
185         qDebug("Thread starting...");
186
187         m_abort = false;
188         m_pause = false;
189
190         while(m_semaphorePaused.tryAcquire(1, 0));
191         QThread::start(priority);
192 }
193
194 ///////////////////////////////////////////////////////////////////////////////
195 // Encode functions
196 ///////////////////////////////////////////////////////////////////////////////
197
198 void EncodeThread::encode(void)
199 {
200         QDateTime startTime = QDateTime::currentDateTime();
201
202         //Print some basic info
203         log(tr("Simple x264 Launcher (Build #%1), built %2\n").arg(QString::number(x264_version_build()), x264_version_date().toString(Qt::ISODate)));
204         log(tr("Job started at %1, %2.\n").arg(QDate::currentDate().toString(Qt::ISODate), QTime::currentTime().toString( Qt::ISODate)));
205         log(tr("Source file: %1").arg(m_sourceFileName));
206         log(tr("Output file: %1").arg(m_outputFileName));
207         
208         //Print encoder settings
209         log(tr("\n--- SETTINGS ---\n"));
210         log(tr("RC Mode: %1").arg(OptionsModel::rcMode2String(m_options->rcMode())));
211         log(tr("Preset:  %1").arg(m_options->preset()));
212         log(tr("Tuning:  %1").arg(m_options->tune()));
213         log(tr("Profile: %1").arg(m_options->profile()));
214         log(tr("Custom:  %1").arg(m_options->customX264().isEmpty() ? tr("(None)") : m_options->customX264()));
215         
216         bool ok = false;
217         unsigned int frames = 0;
218
219         //Use Avisynth?
220         const bool usePipe = (QFileInfo(m_sourceFileName).suffix().compare("avs", Qt::CaseInsensitive) == 0);
221         const QString indexFile = QString("%1/%2.ffindex").arg(QDir::tempPath(), m_jobId.toString());
222
223         //Checking x264 version
224         log(tr("\n--- CHECK VERSION ---\n"));
225         unsigned int revision_x264 = UINT_MAX;
226         bool x264_modified = false;
227         ok = ((revision_x264 = checkVersionX264(m_x264_x64, m_x264_10bit, x264_modified)) != UINT_MAX);
228         CHECK_STATUS(m_abort, ok);
229         
230         //Checking avs2yuv version
231         unsigned int revision_avs2yuv = UINT_MAX;
232         if(usePipe)
233         {
234                 ok = ((revision_avs2yuv = checkVersionAvs2yuv(m_avs2yuv_x64)) != UINT_MAX);
235                 CHECK_STATUS(m_abort, ok);
236         }
237
238         //Print versions
239         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()));
240         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)));
241
242         //Is x264 revision supported?
243         if((revision_x264 % REV_MULT) < (VER_X264_MINIMUM_REV))
244         {
245                 log(tr("\nERROR: Your revision of x264 is too old! (Minimum required revision is %2)").arg(QString::number(VER_X264_MINIMUM_REV)));
246                 setStatus(JobStatus_Failed);
247                 return;
248         }
249         if((revision_x264 / REV_MULT) != (VER_X264_CURRENT_API))
250         {
251                 log(tr("\nWARNING: Your revision of x264 uses an unsupported core (API) version, take care!"));
252                 log(tr("This application works best with x264 core (API) version %2.").arg(QString::number(VER_X264_CURRENT_API)));
253         }
254         if((revision_avs2yuv != UINT_MAX) && ((revision_avs2yuv % REV_MULT) != (VER_X264_AVS2YUV_VER)))
255         {
256                 log(tr("\nERROR: Your version of avs2yuv is unsupported (Required version: v0.24 BugMaster's mod 2)"));
257                 log(tr("You can find the required version at: http://komisar.gin.by/tools/avs2yuv/"));
258                 setStatus(JobStatus_Failed);
259                 return;
260         }
261
262         //Detect source info
263         if(usePipe)
264         {
265                 log(tr("\n--- AVS INFO ---\n"));
266                 ok = checkProperties(m_avs2yuv_x64, frames);
267                 CHECK_STATUS(m_abort, ok);
268         }
269
270         //Run encoding passes
271         if(m_options->rcMode() == OptionsModel::RCMode_2Pass)
272         {
273                 QFileInfo info(m_outputFileName);
274                 QString passLogFile = QString("%1/%2.stats").arg(info.path(), info.completeBaseName());
275
276                 if(QFileInfo(passLogFile).exists())
277                 {
278                         int n = 2;
279                         while(QFileInfo(passLogFile).exists())
280                         {
281                                 passLogFile = QString("%1/%2.%3.stats").arg(info.path(), info.completeBaseName(), QString::number(n++));
282                         }
283                 }
284                 
285                 log(tr("\n--- PASS 1 ---\n"));
286                 ok = runEncodingPass(m_x264_x64, m_x264_10bit, m_avs2yuv_x64, usePipe, frames, indexFile, 1, passLogFile);
287                 CHECK_STATUS(m_abort, ok);
288
289                 log(tr("\n--- PASS 2 ---\n"));
290                 ok = runEncodingPass(m_x264_x64, m_x264_10bit, m_avs2yuv_x64, usePipe, frames, indexFile, 2, passLogFile);
291                 CHECK_STATUS(m_abort, ok);
292         }
293         else
294         {
295                 log(tr("\n--- ENCODING ---\n"));
296                 ok = runEncodingPass(m_x264_x64, m_x264_10bit, m_avs2yuv_x64, usePipe, frames, indexFile);
297                 CHECK_STATUS(m_abort, ok);
298         }
299
300         log(tr("\n--- DONE ---\n"));
301         if(QFileInfo(indexFile).exists()) QFile::remove(indexFile);
302         int timePassed = startTime.secsTo(QDateTime::currentDateTime());
303         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)));
304         setStatus(JobStatus_Completed);
305 }
306
307 bool EncodeThread::runEncodingPass(bool x264_x64, bool x264_10bit, bool avs2yuv_x64, bool usePipe, unsigned int frames, const QString &indexFile, int pass, const QString &passLogFile)
308 {
309         QProcess processEncode, processAvisynth;
310         
311         if(usePipe)
312         {
313                 QStringList cmdLine_Avisynth;
314                 if(!m_options->customAvs2YUV().isEmpty())
315                 {
316                         cmdLine_Avisynth.append(splitParams(m_options->customAvs2YUV()));
317                 }
318                 cmdLine_Avisynth << pathToLocal(QDir::toNativeSeparators(m_sourceFileName));
319                 cmdLine_Avisynth << "-";
320                 processAvisynth.setStandardOutputProcess(&processEncode);
321
322                 log("Creating Avisynth process:");
323                 if(!startProcess(processAvisynth, QString("%1/%2.exe").arg(m_binDir, avs2yuv_x64 ? "avs2yuv_x64" : "avs2yuv_x86"), cmdLine_Avisynth, false))
324                 {
325                         return false;
326                 }
327         }
328
329         QStringList cmdLine_Encode = buildCommandLine(usePipe, x264_10bit, frames, indexFile, pass, passLogFile);
330
331         log("Creating x264 process:");
332         if(!startProcess(processEncode, X264_BINARY(m_binDir, x264_10bit, x264_x64), cmdLine_Encode))
333         {
334                 return false;
335         }
336
337         QRegExp regExpIndexing("indexing.+\\[(\\d+)\\.(\\d+)%\\]");
338         QRegExp regExpProgress("\\[(\\d+)\\.(\\d+)%\\].+frames");
339         QRegExp regExpFrameCnt("^(\\d+) frames:");
340         
341         QTextCodec *localCodec = QTextCodec::codecForName("System");
342
343         bool bTimeout = false;
344         bool bAborted = false;
345
346         unsigned int last_progress = UINT_MAX;
347         unsigned int last_indexing = UINT_MAX;
348         qint64 size_estimate = 0I64;
349
350         //Main processing loop
351         while(processEncode.state() != QProcess::NotRunning)
352         {
353                 unsigned int waitCounter = 0;
354
355                 //Wait until new output is available
356                 forever
357                 {
358                         if(m_abort)
359                         {
360                                 processEncode.kill();
361                                 processAvisynth.kill();
362                                 bAborted = true;
363                                 break;
364                         }
365                         if(m_pause && (processEncode.state() == QProcess::Running))
366                         {
367                                 JobStatus previousStatus = m_status;
368                                 setStatus(JobStatus_Paused);
369                                 log(tr("Job paused by user at %1, %2.").arg(QDate::currentDate().toString(Qt::ISODate), QTime::currentTime().toString( Qt::ISODate)));
370                                 bool ok[2] = {false, false};
371                                 Q_PID pid[2] = {processEncode.pid(), processAvisynth.pid()};
372                                 if(pid[0]) { ok[0] = (SuspendThread(pid[0]->hThread) != (DWORD)(-1)); }
373                                 if(pid[1]) { ok[1] = (SuspendThread(pid[1]->hThread) != (DWORD)(-1)); }
374                                 while(m_pause) m_semaphorePaused.acquire();
375                                 while(m_semaphorePaused.tryAcquire(1, 0));
376                                 if(pid[0]) { if(ok[0]) ResumeThread(pid[0]->hThread); }
377                                 if(pid[1]) { if(ok[1]) ResumeThread(pid[1]->hThread); }
378                                 if(!m_abort) setStatus(previousStatus);
379                                 log(tr("Job resumed by user at %1, %2.").arg(QDate::currentDate().toString(Qt::ISODate), QTime::currentTime().toString( Qt::ISODate)));
380                                 waitCounter = 0;
381                                 continue;
382                         }
383                         if(!processEncode.waitForReadyRead(m_processTimeoutInterval))
384                         {
385                                 if(processEncode.state() == QProcess::Running)
386                                 {
387                                         if(waitCounter++ > m_processTimeoutMaxCounter)
388                                         {
389                                                 processEncode.kill();
390                                                 qWarning("x264 process timed out <-- killing!");
391                                                 log("\nPROCESS TIMEOUT !!!");
392                                                 bTimeout = true;
393                                                 break;
394                                         }
395                                         else if(waitCounter == m_processTimeoutWarning)
396                                         {
397                                                 unsigned int timeOut = (waitCounter * m_processTimeoutInterval) / 1000U;
398                                                 log(tr("Warning: x264 did not respond for %1 seconds, potential deadlock...").arg(QString::number(timeOut)));
399                                         }
400                                         continue;
401                                 }
402                         }
403                         if(m_abort || (m_pause && (processEncode.state() == QProcess::Running)))
404                         {
405                                 continue;
406                         }
407                         break;
408                 }
409                 
410                 //Exit main processing loop now?
411                 if(bAborted || bTimeout)
412                 {
413                         break;
414                 }
415
416                 //Process all output
417                 while(processEncode.bytesAvailable() > 0)
418                 {
419                         QList<QByteArray> lines = processEncode.readLine().split('\r');
420                         while(!lines.isEmpty())
421                         {
422                                 QString text = localCodec->toUnicode(lines.takeFirst().constData()).simplified();
423                                 int offset = -1;
424                                 if((offset = regExpProgress.lastIndexIn(text)) >= 0)
425                                 {
426                                         bool ok = false;
427                                         unsigned int progress = regExpProgress.cap(1).toUInt(&ok);
428                                         setStatus((pass == 2) ? JobStatus_Running_Pass2 : ((pass == 1) ? JobStatus_Running_Pass1 : JobStatus_Running));
429                                         if(ok && ((progress > last_progress) || (last_progress == UINT_MAX)))
430                                         {
431                                                 setProgress(progress);
432                                                 size_estimate = estimateSize(progress);
433                                                 last_progress = progress;
434                                         }
435                                         setDetails(tr("%1, est. file size %2").arg(text.mid(offset).trimmed(), sizeToString(size_estimate)));
436                                         last_indexing = UINT_MAX;
437                                 }
438                                 else if((offset = regExpIndexing.lastIndexIn(text)) >= 0)
439                                 {
440                                         bool ok = false;
441                                         unsigned int progress = regExpIndexing.cap(1).toUInt(&ok);
442                                         setStatus(JobStatus_Indexing);
443                                         if(ok && ((progress > last_indexing) || (last_indexing == UINT_MAX)))
444                                         {
445                                                 setProgress(progress);
446                                                 last_indexing = progress;
447                                         }
448                                         setDetails(text.mid(offset).trimmed());
449                                         last_progress = UINT_MAX;
450                                 }
451                                 else if((offset = regExpFrameCnt.lastIndexIn(text)) >= 0)
452                                 {
453                                         last_progress = last_indexing = UINT_MAX;
454                                         setStatus((pass == 2) ? JobStatus_Running_Pass2 : ((pass == 1) ? JobStatus_Running_Pass1 : JobStatus_Running));
455                                         setDetails(text.mid(offset).trimmed());
456                                 }
457                                 else if(!text.isEmpty())
458                                 {
459                                         last_progress = last_indexing = UINT_MAX;
460                                         log(text);
461                                 }
462                         }
463                 }
464         }
465
466         processEncode.waitForFinished(5000);
467         if(processEncode.state() != QProcess::NotRunning)
468         {
469                 qWarning("x264 process still running, going to kill it!");
470                 processEncode.kill();
471                 processEncode.waitForFinished(-1);
472         }
473         
474         processAvisynth.waitForFinished(5000);
475         if(processAvisynth.state() != QProcess::NotRunning)
476         {
477                 qWarning("Avisynth process still running, going to kill it!");
478                 processAvisynth.kill();
479                 processAvisynth.waitForFinished(-1);
480         }
481
482         if(!(bTimeout || bAborted))
483         {
484                 while(processAvisynth.bytesAvailable() > 0)
485                 {
486                         log(tr("av2y [info]: %1").arg(QString::fromUtf8(processAvisynth.readLine()).simplified()));
487                 }
488         }
489
490         if(usePipe && (processAvisynth.exitCode() != EXIT_SUCCESS))
491         {
492                 if(!(bTimeout || bAborted))
493                 {
494                         log(tr("\nWARNING: Avisynth process exited with error code: %1").arg(QString::number(processAvisynth.exitCode())));
495                 }
496         }
497
498         if(bTimeout || bAborted || processEncode.exitCode() != EXIT_SUCCESS)
499         {
500                 if(!(bTimeout || bAborted))
501                 {
502                         log(tr("\nPROCESS EXITED WITH ERROR CODE: %1").arg(QString::number(processEncode.exitCode())));
503                 }
504                 processEncode.close();
505                 processAvisynth.close();
506                 return false;
507         }
508
509         QThread::yieldCurrentThread();
510
511         const qint64 finalSize = QFileInfo(m_outputFileName).size();
512         QLocale locale(QLocale::English);
513         log(tr("Final file size is %1 bytes.").arg(locale.toString(finalSize)));
514
515         switch(pass)
516         {
517         case 1:
518                 setStatus(JobStatus_Running_Pass1);
519                 setDetails(tr("First pass completed. Preparing for second pass..."));
520                 break;
521         case 2:
522                 setStatus(JobStatus_Running_Pass2);
523                 setDetails(tr("Second pass completed successfully. Final size is %1.").arg(sizeToString(finalSize)));
524                 break;
525         default:
526                 setStatus(JobStatus_Running);
527                 setDetails(tr("Encode completed successfully. Final size is %1.").arg(sizeToString(finalSize)));
528                 break;
529         }
530
531         setProgress(100);
532         processEncode.close();
533         processAvisynth.close();
534         return true;
535 }
536
537 QStringList EncodeThread::buildCommandLine(bool usePipe, bool use10Bit, unsigned int frames, const QString &indexFile, int pass, const QString &passLogFile)
538 {
539         QStringList cmdLine;
540         double crf_int = 0.0, crf_frc = 0.0;
541
542         switch(m_options->rcMode())
543         {
544         case OptionsModel::RCMode_CQ:
545                 cmdLine << "--qp" << QString::number(qRound(m_options->quantizer()));
546                 break;
547         case OptionsModel::RCMode_CRF:
548                 crf_frc = modf(m_options->quantizer(), &crf_int);
549                 cmdLine << "--crf" << QString("%1.%2").arg(QString::number(qRound(crf_int)), QString::number(qRound(crf_frc * 10.0)));
550                 break;
551         case OptionsModel::RCMode_2Pass:
552         case OptionsModel::RCMode_ABR:
553                 cmdLine << "--bitrate" << QString::number(m_options->bitrate());
554                 break;
555         default:
556                 throw "Bad rate-control mode !!!";
557                 break;
558         }
559         
560         if((pass == 1) || (pass == 2))
561         {
562                 cmdLine << "--pass" << QString::number(pass);
563                 cmdLine << "--stats" << pathToLocal(QDir::toNativeSeparators(passLogFile), true);
564         }
565
566         cmdLine << "--preset" << m_options->preset().toLower();
567
568         if(m_options->tune().compare("none", Qt::CaseInsensitive))
569         {
570                 cmdLine << "--tune" << m_options->tune().toLower();
571         }
572
573         if(m_options->profile().compare("auto", Qt::CaseInsensitive))
574         {
575                 if(use10Bit)
576                 {
577                         if(m_options->profile().compare("baseline", Qt::CaseInsensitive) || m_options->profile().compare("main", Qt::CaseInsensitive) || m_options->profile().compare("high", Qt::CaseInsensitive))
578                         {
579                                 log(tr("WARNING: Selected H.264 Profile not compatible with 10-Bit encoding. Ignoring!\n"));
580                         }
581                         else
582                         {
583                                 cmdLine << "--profile" << m_options->profile().toLower();
584                         }
585                 }
586                 else
587                 {
588                         cmdLine << "--profile" << m_options->profile().toLower();
589                 }
590         }
591
592         if(!m_options->customX264().isEmpty())
593         {
594                 QStringList customArgs = splitParams(m_options->customX264());
595                 if(usePipe)
596                 {
597                         QStringList::iterator i = customArgs.begin();
598                         while(i != customArgs.end())
599                         {
600                                 bool bModified = false;
601                                 REMOVE_CUSTOM_ARG(customArgs, i, bModified, "--fps");
602                                 REMOVE_CUSTOM_ARG(customArgs, i, bModified, "--frames");
603                                 if(!bModified) i++;
604                         }
605                 }
606                 cmdLine.append(customArgs);
607         }
608
609         cmdLine << "--output" << pathToLocal(QDir::toNativeSeparators(m_outputFileName), true);
610         
611         if(usePipe)
612         {
613                 if(frames < 1) throw "Frames not set!";
614                 cmdLine << "--frames" << QString::number(frames);
615                 cmdLine << "--demuxer" << "y4m";
616                 cmdLine << "--stdin" << "y4m" << "-";
617         }
618         else
619         {
620                 cmdLine << "--index" << pathToLocal(QDir::toNativeSeparators(indexFile), true, false);
621                 cmdLine << pathToLocal(QDir::toNativeSeparators(m_sourceFileName));
622         }
623
624         return cmdLine;
625 }
626
627 unsigned int EncodeThread::checkVersionX264(bool use_x64, bool use_10bit, bool &modified)
628 {
629         QProcess process;
630         QStringList cmdLine = QStringList() << "--version";
631
632         log("Creating process:");
633         if(!startProcess(process, X264_BINARY(m_binDir, use_10bit, use_x64), cmdLine))
634         {
635                 return false;;
636         }
637
638         QRegExp regExpVersion("\\bx264\\s(\\d)\\.(\\d+)\\.(\\d+)\\s([a-f0-9]{7})", Qt::CaseInsensitive);
639         QRegExp regExpVersionMod("\\bx264 (\\d)\\.(\\d+)\\.(\\d+)", Qt::CaseInsensitive);
640         
641         bool bTimeout = false;
642         bool bAborted = false;
643
644         unsigned int revision = UINT_MAX;
645         unsigned int coreVers = UINT_MAX;
646         modified = false;
647
648         while(process.state() != QProcess::NotRunning)
649         {
650                 if(m_abort)
651                 {
652                         process.kill();
653                         bAborted = true;
654                         break;
655                 }
656                 if(!process.waitForReadyRead())
657                 {
658                         if(process.state() == QProcess::Running)
659                         {
660                                 process.kill();
661                                 qWarning("x264 process timed out <-- killing!");
662                                 log("\nPROCESS TIMEOUT !!!");
663                                 bTimeout = true;
664                                 break;
665                         }
666                 }
667                 while(process.bytesAvailable() > 0)
668                 {
669                         QList<QByteArray> lines = process.readLine().split('\r');
670                         while(!lines.isEmpty())
671                         {
672                                 QString text = QString::fromUtf8(lines.takeFirst().constData()).simplified();
673                                 int offset = -1;
674                                 if((offset = regExpVersion.lastIndexIn(text)) >= 0)
675                                 {
676                                         bool ok1 = false, ok2 = false;
677                                         unsigned int temp1 = regExpVersion.cap(2).toUInt(&ok1);
678                                         unsigned int temp2 = regExpVersion.cap(3).toUInt(&ok2);
679                                         if(ok1) coreVers = temp1;
680                                         if(ok2) revision = temp2;
681                                 }
682                                 else if((offset = regExpVersionMod.lastIndexIn(text)) >= 0)
683                                 {
684                                         bool ok1 = false, ok2 = false;
685                                         unsigned int temp1 = regExpVersionMod.cap(2).toUInt(&ok1);
686                                         unsigned int temp2 = regExpVersionMod.cap(3).toUInt(&ok2);
687                                         if(ok1) coreVers = temp1;
688                                         if(ok2) revision = temp2;
689                                         modified = true;
690                                 }
691                                 if(!text.isEmpty())
692                                 {
693                                         log(text);
694                                 }
695                         }
696                 }
697         }
698
699         process.waitForFinished();
700         if(process.state() != QProcess::NotRunning)
701         {
702                 process.kill();
703                 process.waitForFinished(-1);
704         }
705
706         if(bTimeout || bAborted || process.exitCode() != EXIT_SUCCESS)
707         {
708                 if(!(bTimeout || bAborted))
709                 {
710                         log(tr("\nPROCESS EXITED WITH ERROR CODE: %1").arg(QString::number(process.exitCode())));
711                 }
712                 return UINT_MAX;
713         }
714
715         if((revision == UINT_MAX) || (coreVers == UINT_MAX))
716         {
717                 log(tr("\nFAILED TO DETERMINE X264 VERSION !!!"));
718                 return UINT_MAX;
719         }
720         
721         return (coreVers * REV_MULT) + (revision % REV_MULT);
722 }
723
724 unsigned int EncodeThread::checkVersionAvs2yuv(bool x64)
725 {
726         QProcess process;
727
728         log("\nCreating process:");
729         if(!startProcess(process, QString("%1/%2.exe").arg(m_binDir, x64 ? "avs2yuv_x64" : "avs2yuv_x86"), QStringList()))
730         {
731                 return false;;
732         }
733
734         QRegExp regExpVersionMod("\\bAvs2YUV (\\d+).(\\d+)bm(\\d)\\b", Qt::CaseInsensitive);
735         QRegExp regExpVersionOld("\\bAvs2YUV (\\d+).(\\d+)\\b", Qt::CaseInsensitive);
736         
737         bool bTimeout = false;
738         bool bAborted = false;
739
740         unsigned int ver_maj = UINT_MAX;
741         unsigned int ver_min = UINT_MAX;
742         unsigned int ver_mod = 0;
743
744         while(process.state() != QProcess::NotRunning)
745         {
746                 if(m_abort)
747                 {
748                         process.kill();
749                         bAborted = true;
750                         break;
751                 }
752                 if(!process.waitForReadyRead())
753                 {
754                         if(process.state() == QProcess::Running)
755                         {
756                                 process.kill();
757                                 qWarning("Avs2YUV process timed out <-- killing!");
758                                 log("\nPROCESS TIMEOUT !!!");
759                                 bTimeout = true;
760                                 break;
761                         }
762                 }
763                 while(process.bytesAvailable() > 0)
764                 {
765                         QList<QByteArray> lines = process.readLine().split('\r');
766                         while(!lines.isEmpty())
767                         {
768                                 QString text = QString::fromUtf8(lines.takeFirst().constData()).simplified();
769                                 int offset = -1;
770                                 if((ver_maj == UINT_MAX) || (ver_min == UINT_MAX) || (ver_mod == UINT_MAX))
771                                 {
772                                         if(!text.isEmpty())
773                                         {
774                                                 log(text);
775                                         }
776                                 }
777                                 if((offset = regExpVersionMod.lastIndexIn(text)) >= 0)
778                                 {
779                                         bool ok1 = false, ok2 = false, ok3 = false;
780                                         unsigned int temp1 = regExpVersionMod.cap(1).toUInt(&ok1);
781                                         unsigned int temp2 = regExpVersionMod.cap(2).toUInt(&ok2);
782                                         unsigned int temp3 = regExpVersionMod.cap(3).toUInt(&ok3);
783                                         if(ok1) ver_maj = temp1;
784                                         if(ok2) ver_min = temp2;
785                                         if(ok3) ver_mod = temp3;
786                                 }
787                                 else if((offset = regExpVersionOld.lastIndexIn(text)) >= 0)
788                                 {
789                                         bool ok1 = false, ok2 = false;
790                                         unsigned int temp1 = regExpVersionOld.cap(1).toUInt(&ok1);
791                                         unsigned int temp2 = regExpVersionOld.cap(2).toUInt(&ok2);
792                                         if(ok1) ver_maj = temp1;
793                                         if(ok2) ver_min = temp2;
794                                 }
795                         }
796                 }
797         }
798
799         process.waitForFinished();
800         if(process.state() != QProcess::NotRunning)
801         {
802                 process.kill();
803                 process.waitForFinished(-1);
804         }
805
806         if(bTimeout || bAborted || ((process.exitCode() != EXIT_SUCCESS) && (process.exitCode() != 2)))
807         {
808                 if(!(bTimeout || bAborted))
809                 {
810                         log(tr("\nPROCESS EXITED WITH ERROR CODE: %1").arg(QString::number(process.exitCode())));
811                 }
812                 return UINT_MAX;
813         }
814
815         if((ver_maj == UINT_MAX) || (ver_min == UINT_MAX))
816         {
817                 log(tr("\nFAILED TO DETERMINE AVS2YUV VERSION !!!"));
818                 return UINT_MAX;
819         }
820         
821         return (ver_maj * REV_MULT) + ((ver_min % REV_MULT) * 10) + (ver_mod % 10);
822 }
823
824 bool EncodeThread::checkProperties(bool x64, unsigned int &frames)
825 {
826         QProcess process;
827         QStringList cmdLine;
828
829         if(!m_options->customAvs2YUV().isEmpty())
830         {
831                 cmdLine.append(splitParams(m_options->customAvs2YUV()));
832         }
833
834         cmdLine << "-frames" << "1";
835         cmdLine << pathToLocal(QDir::toNativeSeparators(m_sourceFileName)) << "NUL";
836
837         log("Creating process:");
838         if(!startProcess(process, QString("%1/%2.exe").arg(m_binDir, x64 ? "avs2yuv_x64" : "avs2yuv_x86"), cmdLine))
839         {
840                 return false;;
841         }
842
843         QRegExp regExpInt(": (\\d+)x(\\d+), (\\d+) fps, (\\d+) frames");
844         QRegExp regExpFrc(": (\\d+)x(\\d+), (\\d+)/(\\d+) fps, (\\d+) frames");
845         
846         QTextCodec *localCodec = QTextCodec::codecForName("System");
847
848         bool bTimeout = false;
849         bool bAborted = false;
850
851         frames = 0;
852         
853         unsigned int fpsNom = 0;
854         unsigned int fpsDen = 0;
855         unsigned int fSizeW = 0;
856         unsigned int fSizeH = 0;
857         
858         unsigned int waitCounter = 0;
859
860         while(process.state() != QProcess::NotRunning)
861         {
862                 if(m_abort)
863                 {
864                         process.kill();
865                         bAborted = true;
866                         break;
867                 }
868                 if(!process.waitForReadyRead(m_processTimeoutInterval))
869                 {
870                         if(process.state() == QProcess::Running)
871                         {
872                                 if(waitCounter++ > m_processTimeoutMaxCounter)
873                                 {
874                                         process.kill();
875                                         qWarning("Avs2YUV process timed out <-- killing!");
876                                         log("\nPROCESS TIMEOUT !!!");
877                                         log("\nAvisynth has encountered a deadlock or your script takes EXTREMELY long to initialize!");
878                                         bTimeout = true;
879                                         break;
880                                 }
881                                 else if(waitCounter == m_processTimeoutWarning)
882                                 {
883                                         unsigned int timeOut = (waitCounter * m_processTimeoutInterval) / 1000U;
884                                         log(tr("Warning: Avisynth did not respond for %1 seconds, potential deadlock...").arg(QString::number(timeOut)));
885                                 }
886                         }
887                         continue;
888                 }
889                 
890                 waitCounter = 0;
891                 
892                 while(process.bytesAvailable() > 0)
893                 {
894                         QList<QByteArray> lines = process.readLine().split('\r');
895                         while(!lines.isEmpty())
896                         {
897                                 QString text = localCodec->toUnicode(lines.takeFirst().constData()).simplified();
898                                 int offset = -1;
899                                 if((offset = regExpInt.lastIndexIn(text)) >= 0)
900                                 {
901                                         bool ok1 = false, ok2 = false;
902                                         bool ok3 = false, ok4 = false;
903                                         unsigned int temp1 = regExpInt.cap(1).toUInt(&ok1);
904                                         unsigned int temp2 = regExpInt.cap(2).toUInt(&ok2);
905                                         unsigned int temp3 = regExpInt.cap(3).toUInt(&ok3);
906                                         unsigned int temp4 = regExpInt.cap(4).toUInt(&ok4);
907                                         if(ok1) fSizeW = temp1;
908                                         if(ok2) fSizeH = temp2;
909                                         if(ok3) fpsNom = temp3;
910                                         if(ok4) frames = temp4;
911                                 }
912                                 else if((offset = regExpFrc.lastIndexIn(text)) >= 0)
913                                 {
914                                         bool ok1 = false, ok2 = false;
915                                         bool ok3 = false, ok4 = false, ok5 = false;
916                                         unsigned int temp1 = regExpFrc.cap(1).toUInt(&ok1);
917                                         unsigned int temp2 = regExpFrc.cap(2).toUInt(&ok2);
918                                         unsigned int temp3 = regExpFrc.cap(3).toUInt(&ok3);
919                                         unsigned int temp4 = regExpFrc.cap(4).toUInt(&ok4);
920                                         unsigned int temp5 = regExpFrc.cap(5).toUInt(&ok5);
921                                         if(ok1) fSizeW = temp1;
922                                         if(ok2) fSizeH = temp2;
923                                         if(ok3) fpsNom = temp3;
924                                         if(ok4) fpsDen = temp4;
925                                         if(ok5) frames = temp5;
926                                 }
927                                 if(!text.isEmpty())
928                                 {
929                                         log(text);
930                                 }
931                                 if(text.contains("failed to load avisynth.dll", Qt::CaseInsensitive))
932                                 {
933                                         log(tr("\nWarning: It seems that %1-Bit Avisynth is not currently installed !!!").arg(x64 ? "64" : "32"));
934                                 }
935                                 if(text.contains(QRegExp("couldn't convert input clip to (YV16|YV24)", Qt::CaseInsensitive)))
936                                 {
937                                         log(tr("\nWarning: YV16 (4:2:2) and YV24 (4:4:4) color-spaces only supported in Avisynth 2.6 !!!"));
938                                 }
939                         }
940                 }
941         }
942
943         process.waitForFinished();
944         if(process.state() != QProcess::NotRunning)
945         {
946                 process.kill();
947                 process.waitForFinished(-1);
948         }
949
950         if(bTimeout || bAborted || process.exitCode() != EXIT_SUCCESS)
951         {
952                 if(!(bTimeout || bAborted))
953                 {
954                         log(tr("\nPROCESS EXITED WITH ERROR CODE: %1").arg(QString::number(process.exitCode())));
955                 }
956                 return false;
957         }
958
959         if(frames == 0)
960         {
961                 log(tr("\nFAILED TO DETERMINE AVS PROPERTIES !!!"));
962                 return false;
963         }
964         
965         log("");
966
967         if((fSizeW > 0) && (fSizeH > 0))
968         {
969                 log(tr("Resolution: %1x%2").arg(QString::number(fSizeW), QString::number(fSizeH)));
970         }
971         if((fpsNom > 0) && (fpsDen > 0))
972         {
973                 log(tr("Frame Rate: %1/%2").arg(QString::number(fpsNom), QString::number(fpsDen)));
974         }
975         if((fpsNom > 0) && (fpsDen == 0))
976         {
977                 log(tr("Frame Rate: %1").arg(QString::number(fpsNom)));
978         }
979         if(frames > 0)
980         {
981                 log(tr("No. Frames: %1").arg(QString::number(frames)));
982         }
983
984         return true;
985 }
986
987 ///////////////////////////////////////////////////////////////////////////////
988 // Misc functions
989 ///////////////////////////////////////////////////////////////////////////////
990
991 void EncodeThread::setStatus(JobStatus newStatus)
992 {
993         if(m_status != newStatus)
994         {
995                 if((newStatus != JobStatus_Completed) && (newStatus != JobStatus_Failed) && (newStatus != JobStatus_Aborted) && (newStatus != JobStatus_Paused))
996                 {
997                         if(m_status != JobStatus_Paused) setProgress(0);
998                 }
999                 if(newStatus == JobStatus_Failed)
1000                 {
1001                         setDetails("The job has failed. See log for details!");
1002                 }
1003                 if(newStatus == JobStatus_Aborted)
1004                 {
1005                         setDetails("The job was aborted by the user!");
1006                 }
1007                 m_status = newStatus;
1008                 emit statusChanged(m_jobId, newStatus);
1009         }
1010 }
1011
1012 void EncodeThread::setProgress(unsigned int newProgress)
1013 {
1014         if(m_progress != newProgress)
1015         {
1016                 m_progress = newProgress;
1017                 emit progressChanged(m_jobId, m_progress);
1018         }
1019 }
1020
1021 void EncodeThread::setDetails(const QString &text)
1022 {
1023         emit detailsChanged(m_jobId, text);
1024 }
1025
1026 QString EncodeThread::pathToLocal(const QString &longPath, bool create, bool keep)
1027 {
1028         QTextCodec *localCodec = QTextCodec::codecForName("System");
1029         
1030         //Do NOT convert to short, if path can be represented in local Codepage
1031         if(localCodec->toUnicode(localCodec->fromUnicode(longPath)).compare(longPath, Qt::CaseInsensitive) == 0)
1032         {
1033                 return longPath;
1034         }
1035         
1036         //Create dummy file, if required (only existing files can have a short path!)
1037         QFile tempFile;
1038         if((!QFileInfo(longPath).exists()) && create)
1039         {
1040                 tempFile.setFileName(longPath);
1041                 tempFile.open(QIODevice::WriteOnly);
1042         }
1043         
1044         QString shortPath;
1045         DWORD buffSize = GetShortPathNameW(reinterpret_cast<const wchar_t*>(longPath.utf16()), NULL, NULL);
1046         
1047         if(buffSize > 0)
1048         {
1049                 wchar_t *buffer = new wchar_t[buffSize];
1050                 DWORD result = GetShortPathNameW(reinterpret_cast<const wchar_t*>(longPath.utf16()), buffer, buffSize);
1051
1052                 if(result > 0 && result < buffSize)
1053                 {
1054                         shortPath = QString::fromUtf16(reinterpret_cast<const unsigned short*>(buffer));
1055                 }
1056
1057                 delete[] buffer;
1058                 buffer = NULL;
1059         }
1060
1061         //Remove the dummy file now (FFMS2 fails, if index file does exist but is empty!)
1062         if(tempFile.isOpen())
1063         {
1064                 if(!keep) tempFile.remove();
1065                 tempFile.close();
1066         }
1067
1068         if(shortPath.isEmpty())
1069         {
1070                 log(tr("Warning: Failed to convert path \"%1\" to short!\n").arg(longPath));
1071         }
1072
1073         return (shortPath.isEmpty() ? longPath : shortPath);
1074 }
1075
1076 bool EncodeThread::startProcess(QProcess &process, const QString &program, const QStringList &args, bool mergeChannels)
1077 {
1078         QMutexLocker lock(&m_mutex_startProcess);
1079         log(commandline2string(program, args) + "\n");
1080
1081         //Create a new job object, if not done yet
1082         if(!m_handle_jobObject)
1083         {
1084                 m_handle_jobObject = CreateJobObject(NULL, NULL);
1085                 if(m_handle_jobObject == INVALID_HANDLE_VALUE)
1086                 {
1087                         m_handle_jobObject = NULL;
1088                 }
1089                 if(m_handle_jobObject)
1090                 {
1091                         JOBOBJECT_EXTENDED_LIMIT_INFORMATION jobExtendedLimitInfo;
1092                         memset(&jobExtendedLimitInfo, 0, sizeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION));
1093                         jobExtendedLimitInfo.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE | JOB_OBJECT_LIMIT_DIE_ON_UNHANDLED_EXCEPTION;
1094                         SetInformationJobObject(m_handle_jobObject, JobObjectExtendedLimitInformation, &jobExtendedLimitInfo, sizeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION));
1095                 }
1096         }
1097
1098         if(mergeChannels)
1099         {
1100                 process.setProcessChannelMode(QProcess::MergedChannels);
1101                 process.setReadChannel(QProcess::StandardOutput);
1102         }
1103         else
1104         {
1105                 process.setProcessChannelMode(QProcess::SeparateChannels);
1106                 process.setReadChannel(QProcess::StandardError);
1107         }
1108
1109         process.start(program, args);
1110         
1111         if(process.waitForStarted())
1112         {
1113                 Q_PID pid = process.pid();
1114                 AssignProcessToJobObject(m_handle_jobObject, process.pid()->hProcess);
1115                 if(pid != NULL)
1116                 {
1117                         if(!SetPriorityClass(process.pid()->hProcess, BELOW_NORMAL_PRIORITY_CLASS))
1118                         {
1119                                 SetPriorityClass(process.pid()->hProcess, IDLE_PRIORITY_CLASS);
1120                         }
1121                 }
1122                 
1123                 lock.unlock();
1124                 return true;
1125         }
1126
1127         log("Process creation has failed :-(");
1128         QString errorMsg= process.errorString().trimmed();
1129         if(!errorMsg.isEmpty()) log(errorMsg);
1130
1131         process.kill();
1132         process.waitForFinished(-1);
1133         return false;
1134 }
1135
1136 QString EncodeThread::commandline2string(const QString &program, const QStringList &arguments)
1137 {
1138         QString commandline = (program.contains(' ') ? QString("\"%1\"").arg(program) : program);
1139         
1140         for(int i = 0; i < arguments.count(); i++)
1141         {
1142                 commandline += (arguments.at(i).contains(' ') ? QString(" \"%1\"").arg(arguments.at(i)) : QString(" %1").arg(arguments.at(i)));
1143         }
1144
1145         return commandline;
1146 }
1147
1148 QStringList EncodeThread::splitParams(const QString &params)
1149 {
1150         QStringList list; 
1151         bool ignoreWhitespaces = false;
1152         QString temp;
1153
1154         for(int i = 0; i < params.length(); i++)
1155         {
1156                 const QChar c = params.at(i);
1157
1158                 if(c == QChar::fromLatin1('"'))
1159                 {
1160                         ignoreWhitespaces = (!ignoreWhitespaces);
1161                         continue;
1162                 }
1163                 else if((!ignoreWhitespaces) && (c == QChar::fromLatin1(' ')))
1164                 {
1165                         APPEND_AND_CLEAR(list, temp);
1166                         continue;
1167                 }
1168                 
1169                 temp.append(c);
1170         }
1171         
1172         APPEND_AND_CLEAR(list, temp);
1173
1174         list.replaceInStrings("$(INPUT)", QDir::toNativeSeparators(m_sourceFileName), Qt::CaseInsensitive);
1175         list.replaceInStrings("$(OUTPUT)", QDir::toNativeSeparators(m_outputFileName), Qt::CaseInsensitive);
1176
1177         return list;
1178 }
1179
1180 qint64 EncodeThread::estimateSize(int progress)
1181 {
1182         if(progress >= 3)
1183         {
1184                 qint64 currentSize = QFileInfo(m_outputFileName).size();
1185                 qint64 estimatedSize = (currentSize * 100I64) / static_cast<qint64>(progress);
1186                 return estimatedSize;
1187         }
1188
1189         return 0I64;
1190 }
1191
1192 QString EncodeThread::sizeToString(qint64 size)
1193 {
1194         static char *prefix[5] = {"Byte", "KB", "MB", "GB", "TB"};
1195
1196         if(size > 1024I64)
1197         {
1198                 qint64 estimatedSize = size;
1199                 qint64 remainderSize = 0I64;
1200
1201                 int prefixIdx = 0;
1202                 while((estimatedSize > 1024I64) && (prefixIdx < 4))
1203                 {
1204                         remainderSize = estimatedSize % 1024I64;
1205                         estimatedSize = estimatedSize / 1024I64;
1206                         prefixIdx++;
1207                 }
1208                         
1209                 double value = static_cast<double>(estimatedSize) + (static_cast<double>(remainderSize) / 1024.0);
1210                 return QString().sprintf((value < 10.0) ? "%.2f %s" : "%.1f %s", value, prefix[prefixIdx]);
1211         }
1212
1213         return tr("N/A");
1214 }