OSDN Git Service

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