OSDN Git Service

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