OSDN Git Service

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