OSDN Git Service

Improved logging output, if Avisynth (Avs2YUV) or VapurSynth (vspipe) crashes.
[x264-launcher/x264-launcher.git] / src / thread_encode.cpp
1 ///////////////////////////////////////////////////////////////////////////////
2 // Simple x264 Launcher
3 // Copyright (C) 2004-2013 LoRd_MuldeR <MuldeR2@GMX.de>
4 //
5 // This program is free software; you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License as published by
7 // the Free Software Foundation; either version 2 of the License, or
8 // (at your option) any later version.
9 //
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 // GNU General Public License for more details.
14 //
15 // You should have received a copy of the GNU General Public License along
16 // with this program; if not, write to the Free Software Foundation, Inc.,
17 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 //
19 // http://www.gnu.org/licenses/gpl-2.0.txt
20 ///////////////////////////////////////////////////////////////////////////////
21
22 #include "thread_encode.h"
23
24 #include "global.h"
25 #include "model_options.h"
26 #include "model_preferences.h"
27 #include "job_object.h"
28
29 #include <QDate>
30 #include <QTime>
31 #include <QDateTime>
32 #include <QFileInfo>
33 #include <QDir>
34 #include <QProcess>
35 #include <QMutex>
36 #include <QTextCodec>
37 #include <QLocale>
38 #include <QCryptographicHash>
39
40 /*
41  * Static vars
42  */
43 QMutex EncodeThread::m_mutex_startProcess;
44
45 /*
46  * Macros
47  */
48 #define CHECK_STATUS(ABORT_FLAG, OK_FLAG) do \
49 { \
50         if(ABORT_FLAG) \
51         { \
52                 log("\nPROCESS ABORTED BY USER !!!"); \
53                 setStatus(JobStatus_Aborted); \
54                 if(QFileInfo(indexFile).exists()) QFile::remove(indexFile); \
55                 if(QFileInfo(m_outputFileName).exists() && (QFileInfo(m_outputFileName).size() == 0)) QFile::remove(m_outputFileName); \
56                 return; \
57         } \
58         else if(!(OK_FLAG)) \
59         { \
60                 setStatus(JobStatus_Failed); \
61                 if(QFileInfo(indexFile).exists()) QFile::remove(indexFile); \
62                 if(QFileInfo(m_outputFileName).exists() && (QFileInfo(m_outputFileName).size() == 0)) QFile::remove(m_outputFileName); \
63                 return; \
64         } \
65 } \
66 while(0)
67
68 #define APPEND_AND_CLEAR(LIST, STR) do \
69 { \
70         if(!((STR).isEmpty())) \
71         { \
72                 (LIST) << (STR); \
73                 (STR).clear(); \
74         } \
75 } \
76 while(0)
77
78 #define REMOVE_CUSTOM_ARG(LIST, ITER, FLAG, PARAM) do \
79 { \
80         if(ITER != LIST.end()) \
81         { \
82                 if((*ITER).compare(PARAM, Qt::CaseInsensitive) == 0) \
83                 { \
84                         log(tr("WARNING: Custom parameter \"" PARAM "\" will be ignored in Pipe'd mode!\n")); \
85                         ITER = LIST.erase(ITER); \
86                         if(ITER != LIST.end()) \
87                         { \
88                                 if(!((*ITER).startsWith("--", Qt::CaseInsensitive))) ITER = LIST.erase(ITER); \
89                         } \
90                         FLAG = true; \
91                 } \
92         } \
93 } \
94 while(0)
95
96 #define AVS2_BINARY(BIN_DIR, IS_X64) (QString("%1/%2/avs2yuv_%2.exe").arg((BIN_DIR), ((IS_X64) ? "x64" : "x86")))
97 #define X264_BINARY(BIN_DIR, IS_10BIT, IS_X64) (QString("%1/%2/x264_%3_%2.exe").arg((BIN_DIR), ((IS_X64) ? "x64" : "x86"), ((IS_10BIT) ? "10bit" : "8bit")))
98 #define VPSP_BINARY(VPS_DIR) (QString("%1/core/vspipe.exe").arg((VPS_DIR)))
99
100 /*
101  * Static vars
102  */
103 static const unsigned int REV_MULT = 10000;
104 static const char *VPS_TEST_FILE = "import vapoursynth as vs\ncore = vs.get_core()\nv = core.std.BlankClip()\nv.set_output()\n";
105
106 ///////////////////////////////////////////////////////////////////////////////
107 // Constructor & Destructor
108 ///////////////////////////////////////////////////////////////////////////////
109
110 EncodeThread::EncodeThread(const QString &sourceFileName, const QString &outputFileName, const OptionsModel *options, const QString &binDir, const QString &vpsDir, const bool &x264_x64, const bool &x264_10bit, const bool &avs2yuv_x64, const bool &skipVersionTest, const int &processPriroity, const bool &abortOnTimeout)
111 :
112         m_jobId(QUuid::createUuid()),
113         m_sourceFileName(sourceFileName),
114         m_outputFileName(outputFileName),
115         m_options(new OptionsModel(*options)),
116         m_binDir(binDir),
117         m_vpsDir(vpsDir),
118         m_x264_x64(x264_x64),
119         m_x264_10bit(x264_10bit),
120         m_avs2yuv_x64(avs2yuv_x64),
121         m_skipVersionTest(skipVersionTest),
122         m_processPriority(processPriroity),
123         m_abortOnTimeout(abortOnTimeout),
124         m_jobObject(new JobObject),
125         m_semaphorePaused(0)
126 {
127         m_abort = false;
128         m_pause = false;
129 }
130
131 EncodeThread::~EncodeThread(void)
132 {
133         X264_DELETE(m_options);
134         X264_DELETE(m_jobObject);
135 }
136
137 ///////////////////////////////////////////////////////////////////////////////
138 // Thread entry point
139 ///////////////////////////////////////////////////////////////////////////////
140
141 void EncodeThread::run(void)
142 {
143 #if !defined(_DEBUG)
144         __try
145         {
146                 checkedRun();
147         }
148         __except(1)
149         {
150                 qWarning("STRUCTURED EXCEPTION ERROR IN ENCODE THREAD !!!");
151         }
152 #else
153         checkedRun();
154 #endif
155
156         if(m_jobObject)
157         {
158                 m_jobObject->terminateJob(42);
159                 X264_DELETE(m_jobObject);
160         }
161 }
162
163 void EncodeThread::checkedRun(void)
164 {
165         m_progress = 0;
166         m_status = JobStatus_Starting;
167
168         try
169         {
170                 try
171                 {
172                         encode();
173                 }
174                 catch(char *msg)
175                 {
176                         log(tr("EXCEPTION ERROR IN THREAD: ").append(QString::fromLatin1(msg)));
177                         setStatus(JobStatus_Failed);
178                 }
179                 catch(...)
180                 {
181                         log(tr("UNHANDLED EXCEPTION ERROR IN THREAD !!!"));
182                         setStatus(JobStatus_Failed);
183                 }
184         }
185         catch(...)
186         {
187                 x264_fatal_exit(L"Unhandeled exception error in encode thread!");
188         }
189 }
190
191 void EncodeThread::start(Priority priority)
192 {
193         qDebug("Thread starting...");
194
195         m_abort = false;
196         m_pause = false;
197
198         while(m_semaphorePaused.tryAcquire(1, 0));
199         QThread::start(priority);
200 }
201
202 ///////////////////////////////////////////////////////////////////////////////
203 // Encode functions
204 ///////////////////////////////////////////////////////////////////////////////
205
206 void EncodeThread::encode(void)
207 {
208         QDateTime startTime = QDateTime::currentDateTime();
209
210         //Print some basic info
211         log(tr("Simple x264 Launcher (Build #%1), built %2\n").arg(QString::number(x264_version_build()), x264_version_date().toString(Qt::ISODate)));
212         log(tr("Job started at %1, %2.\n").arg(QDate::currentDate().toString(Qt::ISODate), QTime::currentTime().toString( Qt::ISODate)));
213         log(tr("Source file: %1").arg(QDir::toNativeSeparators(m_sourceFileName)));
214         log(tr("Output file: %1").arg(QDir::toNativeSeparators(m_outputFileName)));
215         
216         if(!m_vpsDir.isEmpty())
217         {
218                 log(tr("\nVapourSynth: %1").arg(m_vpsDir));
219         }
220
221         //Print encoder settings
222         log(tr("\n--- SETTINGS ---\n"));
223         log(tr("RC Mode: %1").arg(OptionsModel::rcMode2String(m_options->rcMode())));
224         log(tr("Preset:  %1").arg(m_options->preset()));
225         log(tr("Tuning:  %1").arg(m_options->tune()));
226         log(tr("Profile: %1").arg(m_options->profile()));
227         log(tr("Custom:  %1").arg(m_options->customX264().isEmpty() ? tr("(None)") : m_options->customX264()));
228         
229         log(m_binDir);
230
231         bool ok = false;
232         unsigned int frames = 0;
233
234         //Seletct type of input
235         const int inputType = getInputType(QFileInfo(m_sourceFileName).suffix());
236         const QString indexFile = QString("%1/x264_%2.ffindex").arg(QDir::tempPath(), stringToHash(m_sourceFileName));
237
238         //Checking x264 version
239         log(tr("\n--- CHECK VERSION ---\n"));
240         unsigned int revision_x264 = UINT_MAX;
241         bool x264_modified = false;
242         ok = ((revision_x264 = checkVersionX264(m_x264_x64, m_x264_10bit, x264_modified)) != UINT_MAX);
243         CHECK_STATUS(m_abort, ok);
244         
245         //Checking avs2yuv version
246         unsigned int revision_avs2yuv = UINT_MAX;
247         switch(inputType)
248         {
249         case INPUT_AVISYN:
250                 ok = ((revision_avs2yuv = checkVersionAvs2yuv(m_avs2yuv_x64)) != UINT_MAX);
251                 CHECK_STATUS(m_abort, ok);
252                 break;
253         case INPUT_VAPOUR:
254                 ok = checkVersionVapoursynth();
255                 CHECK_STATUS(m_abort, ok);
256                 break;
257         }
258
259         //Print versions
260         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()));
261         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)));
262
263         //Is x264 revision supported?
264         if((revision_x264 % REV_MULT) < x264_version_x264_minimum_rev())
265         {
266                 log(tr("\nERROR: Your revision of x264 is too old! (Minimum required revision is %2)").arg(QString::number(x264_version_x264_minimum_rev())));
267                 setStatus(JobStatus_Failed);
268                 return;
269         }
270         if((revision_x264 / REV_MULT) != x264_version_x264_current_api())
271         {
272                 log(tr("\nWARNING: Your revision of x264 uses an unsupported core (API) version, take care!"));
273                 log(tr("This application works best with x264 core (API) version %2.").arg(QString::number(x264_version_x264_current_api())));
274         }
275         if((revision_avs2yuv != UINT_MAX) && ((revision_avs2yuv % REV_MULT) != x264_version_x264_avs2yuv_ver()))
276         {
277                 log(tr("\nERROR: Your version of avs2yuv is unsupported (Required version: v0.24 BugMaster's mod 2)"));
278                 log(tr("You can find the required version at: http://komisar.gin.by/tools/avs2yuv/"));
279                 setStatus(JobStatus_Failed);
280                 return;
281         }
282
283         //Detect source info
284         if(inputType != INPUT_NATIVE)
285         {
286                 log(tr("\n--- SOURCE INFO ---\n"));
287                 switch(inputType)
288                 {
289                 case INPUT_AVISYN:
290                         ok = checkPropertiesAvisynth(m_avs2yuv_x64, frames);
291                         CHECK_STATUS(m_abort, ok);
292                         break;
293                 case INPUT_VAPOUR:
294                         ok = checkPropertiesVapoursynth(frames);
295                         CHECK_STATUS(m_abort, ok);
296                         break;
297                 }
298         }
299
300         //Run encoding passes
301         if(m_options->rcMode() == OptionsModel::RCMode_2Pass)
302         {
303                 QFileInfo info(m_outputFileName);
304                 QString passLogFile = QString("%1/%2.stats").arg(info.path(), info.completeBaseName());
305
306                 if(QFileInfo(passLogFile).exists())
307                 {
308                         int n = 2;
309                         while(QFileInfo(passLogFile).exists())
310                         {
311                                 passLogFile = QString("%1/%2.%3.stats").arg(info.path(), info.completeBaseName(), QString::number(n++));
312                         }
313                 }
314                 
315                 log(tr("\n--- PASS 1 ---\n"));
316                 ok = runEncodingPass(m_x264_x64, m_x264_10bit, m_avs2yuv_x64, inputType, frames, indexFile, 1, passLogFile);
317                 CHECK_STATUS(m_abort, ok);
318
319                 log(tr("\n--- PASS 2 ---\n"));
320                 ok = runEncodingPass(m_x264_x64, m_x264_10bit, m_avs2yuv_x64, inputType, frames, indexFile, 2, passLogFile);
321                 CHECK_STATUS(m_abort, ok);
322         }
323         else
324         {
325                 log(tr("\n--- ENCODING ---\n"));
326                 ok = runEncodingPass(m_x264_x64, m_x264_10bit, m_avs2yuv_x64, inputType, frames, indexFile);
327                 CHECK_STATUS(m_abort, ok);
328         }
329
330         log(tr("\n--- DONE ---\n"));
331         int timePassed = startTime.secsTo(QDateTime::currentDateTime());
332         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)));
333         setStatus(JobStatus_Completed);
334 }
335
336 #define X264_UPDATE_PROGRESS(X) do \
337 { \
338         bool ok = false; \
339         unsigned int progress = (X).cap(1).toUInt(&ok); \
340         setStatus((pass == 2) ? JobStatus_Running_Pass2 : ((pass == 1) ? JobStatus_Running_Pass1 : JobStatus_Running)); \
341         if(ok && ((progress > last_progress) || (last_progress == UINT_MAX))) \
342         { \
343                 setProgress(progress); \
344                 size_estimate = estimateSize(progress); \
345                 last_progress = progress; \
346         } \
347         setDetails(tr("%1, est. file size %2").arg(text.mid(offset).trimmed(), sizeToString(size_estimate))); \
348         last_indexing = UINT_MAX; \
349 } \
350 while(0)
351
352 bool EncodeThread::runEncodingPass(bool x264_x64, bool x264_10bit, bool avs2yuv_x64, int inputType, unsigned int frames, const QString &indexFile, int pass, const QString &passLogFile)
353 {
354         QProcess processEncode, processInput;
355         
356         if(inputType != INPUT_NATIVE)
357         {
358                 QStringList cmdLine_Input;
359                 processInput.setStandardOutputProcess(&processEncode);
360                 switch(inputType)
361                 {
362                 case INPUT_AVISYN:
363                         if(!m_options->customAvs2YUV().isEmpty())
364                         {
365                                 cmdLine_Input.append(splitParams(m_options->customAvs2YUV()));
366                         }
367                         cmdLine_Input << QDir::toNativeSeparators(x264_path2ansi(m_sourceFileName, true));
368                         cmdLine_Input << "-";
369                         log("Creating Avisynth process:");
370                         if(!startProcess(processInput, AVS2_BINARY(m_binDir, avs2yuv_x64), cmdLine_Input, false))
371                         {
372                                 return false;
373                         }
374                         break;
375                 case INPUT_VAPOUR:
376                         cmdLine_Input << QDir::toNativeSeparators(x264_path2ansi(m_sourceFileName, true));
377                         cmdLine_Input << "-" << "-y4m";
378                         log("Creating Vapoursynth process:");
379                         if(!startProcess(processInput, VPSP_BINARY(m_vpsDir), cmdLine_Input, false))
380                         {
381                                 return false;
382                         }
383                         break;
384                 default:
385                         throw "Bad input type encontered!";
386                 }
387         }
388
389         QStringList cmdLine_Encode = buildCommandLine((inputType != INPUT_NATIVE), x264_10bit, frames, indexFile, pass, passLogFile);
390
391         log("Creating x264 process:");
392         if(!startProcess(processEncode, X264_BINARY(m_binDir, x264_10bit, x264_x64), cmdLine_Encode))
393         {
394                 return false;
395         }
396
397         QRegExp regExpIndexing("indexing.+\\[(\\d+)\\.(\\d+)%\\]");
398         QRegExp regExpProgress("\\[(\\d+)\\.(\\d+)%\\].+frames");
399         QRegExp regExpModified("\\[\\s*(\\d+)\\.(\\d+)%\\]\\s+(\\d+)/(\\d+)\\s(\\d+).(\\d+)\\s(\\d+).(\\d+)\\s+(\\d+):(\\d+):(\\d+)\\s+(\\d+):(\\d+):(\\d+)");
400         QRegExp regExpFrameCnt("^(\\d+) frames:");
401         
402         QTextCodec *localCodec = QTextCodec::codecForName("System");
403
404         bool bTimeout = false;
405         bool bAborted = false;
406
407         unsigned int last_progress = UINT_MAX;
408         unsigned int last_indexing = UINT_MAX;
409         qint64 size_estimate = 0I64;
410
411         //Main processing loop
412         while(processEncode.state() != QProcess::NotRunning)
413         {
414                 unsigned int waitCounter = 0;
415
416                 //Wait until new output is available
417                 forever
418                 {
419                         if(m_abort)
420                         {
421                                 processEncode.kill();
422                                 processInput.kill();
423                                 bAborted = true;
424                                 break;
425                         }
426                         if(m_pause && (processEncode.state() == QProcess::Running))
427                         {
428                                 JobStatus previousStatus = m_status;
429                                 setStatus(JobStatus_Paused);
430                                 log(tr("Job paused by user at %1, %2.").arg(QDate::currentDate().toString(Qt::ISODate), QTime::currentTime().toString( Qt::ISODate)));
431                                 bool ok[2] = {false, false};
432                                 QProcess *proc[2] = { &processEncode, &processInput };
433                                 ok[0] = x264_suspendProcess(proc[0], true);
434                                 ok[1] = x264_suspendProcess(proc[1], true);
435                                 while(m_pause) m_semaphorePaused.tryAcquire(1, 5000);
436                                 while(m_semaphorePaused.tryAcquire(1, 0));
437                                 ok[0] = x264_suspendProcess(proc[0], false);
438                                 ok[1] = x264_suspendProcess(proc[1], false);
439                                 if(!m_abort) setStatus(previousStatus);
440                                 log(tr("Job resumed by user at %1, %2.").arg(QDate::currentDate().toString(Qt::ISODate), QTime::currentTime().toString( Qt::ISODate)));
441                                 waitCounter = 0;
442                                 continue;
443                         }
444                         if(!processEncode.waitForReadyRead(m_processTimeoutInterval))
445                         {
446                                 if(processEncode.state() == QProcess::Running)
447                                 {
448                                         if(++waitCounter > m_processTimeoutMaxCounter)
449                                         {
450                                                 if(m_abortOnTimeout)
451                                                 {
452                                                         processEncode.kill();
453                                                         qWarning("x264 process timed out <-- killing!");
454                                                         log("\nPROCESS TIMEOUT !!!");
455                                                         bTimeout = true;
456                                                         break;
457                                                 }
458                                         }
459                                         else if(waitCounter == m_processTimeoutWarning)
460                                         {
461                                                 unsigned int timeOut = (waitCounter * m_processTimeoutInterval) / 1000U;
462                                                 log(tr("Warning: x264 did not respond for %1 seconds, potential deadlock...").arg(QString::number(timeOut)));
463                                         }
464                                         continue;
465                                 }
466                         }
467                         if(m_abort || (m_pause && (processEncode.state() == QProcess::Running)))
468                         {
469                                 continue;
470                         }
471                         break;
472                 }
473                 
474                 //Exit main processing loop now?
475                 if(bAborted || bTimeout)
476                 {
477                         break;
478                 }
479
480                 //Process all output
481                 while(processEncode.bytesAvailable() > 0)
482                 {
483                         QList<QByteArray> lines = processEncode.readLine().split('\r');
484                         while(!lines.isEmpty())
485                         {
486                                 QString text = localCodec->toUnicode(lines.takeFirst().constData()).simplified();
487                                 int offset = -1;
488                                 if((offset = regExpProgress.lastIndexIn(text)) >= 0)
489                                 {
490                                         X264_UPDATE_PROGRESS(regExpProgress);
491                                 }
492                                 else if((offset = regExpIndexing.lastIndexIn(text)) >= 0)
493                                 {
494                                         bool ok = false;
495                                         unsigned int progress = regExpIndexing.cap(1).toUInt(&ok);
496                                         setStatus(JobStatus_Indexing);
497                                         if(ok && ((progress > last_indexing) || (last_indexing == UINT_MAX)))
498                                         {
499                                                 setProgress(progress);
500                                                 last_indexing = progress;
501                                         }
502                                         setDetails(text.mid(offset).trimmed());
503                                         last_progress = UINT_MAX;
504                                 }
505                                 else if((offset = regExpFrameCnt.lastIndexIn(text)) >= 0)
506                                 {
507                                         last_progress = last_indexing = UINT_MAX;
508                                         setStatus((pass == 2) ? JobStatus_Running_Pass2 : ((pass == 1) ? JobStatus_Running_Pass1 : JobStatus_Running));
509                                         setDetails(text.mid(offset).trimmed());
510                                 }
511                                 else if((offset = regExpModified.lastIndexIn(text)) >= 0)
512                                 {
513                                         X264_UPDATE_PROGRESS(regExpModified);
514                                 }
515                                 else if(!text.isEmpty())
516                                 {
517                                         last_progress = last_indexing = UINT_MAX;
518                                         log(text);
519                                 }
520                         }
521                 }
522         }
523
524         processEncode.waitForFinished(5000);
525         if(processEncode.state() != QProcess::NotRunning)
526         {
527                 qWarning("x264 process still running, going to kill it!");
528                 processEncode.kill();
529                 processEncode.waitForFinished(-1);
530         }
531         
532         processInput.waitForFinished(5000);
533         if(processInput.state() != QProcess::NotRunning)
534         {
535                 qWarning("Input process still running, going to kill it!");
536                 processInput.kill();
537                 processInput.waitForFinished(-1);
538         }
539
540         if(!(bTimeout || bAborted))
541         {
542                 while(processInput.bytesAvailable() > 0)
543                 {
544                         switch(inputType)
545                         {
546                         case INPUT_AVISYN:
547                                 log(tr("av2y [info]: %1").arg(QString::fromUtf8(processInput.readLine()).simplified()));
548                                 break;
549                         case INPUT_VAPOUR:
550                                 log(tr("vpyp [info]: %1").arg(QString::fromUtf8(processInput.readLine()).simplified()));
551                                 break;
552                         }
553                 }
554         }
555
556         if((inputType != INPUT_NATIVE) && (processInput.exitCode() != EXIT_SUCCESS))
557         {
558                 if(!(bTimeout || bAborted))
559                 {
560                         const int exitCode = processInput.exitCode();
561                         log(tr("\nWARNING: Input process exited with error (code: %1), your encode might be *incomplete* !!!").arg(QString::number(exitCode)));
562                         if((inputType == INPUT_AVISYN) && ((exitCode < 0) || (exitCode > 2)))
563                         {
564                                 log(tr("\nIMPORTANT: The Avs2YUV process terminated abnormally. This means Avisynth or one of your Avisynth-Plugin's just crashed."));
565                                 log(tr("IMPORTANT: Please fix your Avisynth script and try again! If you use Avisynth-MT, try using a *stable* Avisynth instead!"));
566                         }
567                         if((inputType == INPUT_VAPOUR) && ((exitCode < 0) || (exitCode > 1)))
568                         {
569                                 log(tr("\nIMPORTANT: The Vapoursynth process terminated abnormally. This means Vapoursynth or one of your Vapoursynth-Plugin's just crashed."));
570                         }
571                 }
572         }
573
574         if(bTimeout || bAborted || processEncode.exitCode() != EXIT_SUCCESS)
575         {
576                 if(!(bTimeout || bAborted))
577                 {
578                         log(tr("\nPROCESS EXITED WITH ERROR CODE: %1").arg(QString::number(processEncode.exitCode())));
579                 }
580                 processEncode.close();
581                 processInput.close();
582                 return false;
583         }
584
585         QThread::yieldCurrentThread();
586
587         const qint64 finalSize = QFileInfo(m_outputFileName).size();
588         QLocale locale(QLocale::English);
589         log(tr("Final file size is %1 bytes.").arg(locale.toString(finalSize)));
590
591         switch(pass)
592         {
593         case 1:
594                 setStatus(JobStatus_Running_Pass1);
595                 setDetails(tr("First pass completed. Preparing for second pass..."));
596                 break;
597         case 2:
598                 setStatus(JobStatus_Running_Pass2);
599                 setDetails(tr("Second pass completed successfully. Final size is %1.").arg(sizeToString(finalSize)));
600                 break;
601         default:
602                 setStatus(JobStatus_Running);
603                 setDetails(tr("Encode completed successfully. Final size is %1.").arg(sizeToString(finalSize)));
604                 break;
605         }
606
607         setProgress(100);
608         processEncode.close();
609         processInput.close();
610         return true;
611 }
612
613 QStringList EncodeThread::buildCommandLine(bool usePipe, bool use10Bit, unsigned int frames, const QString &indexFile, int pass, const QString &passLogFile)
614 {
615         QStringList cmdLine;
616         double crf_int = 0.0, crf_frc = 0.0;
617
618         switch(m_options->rcMode())
619         {
620         case OptionsModel::RCMode_CQ:
621                 cmdLine << "--qp" << QString::number(qRound(m_options->quantizer()));
622                 break;
623         case OptionsModel::RCMode_CRF:
624                 crf_frc = modf(m_options->quantizer(), &crf_int);
625                 cmdLine << "--crf" << QString("%1.%2").arg(QString::number(qRound(crf_int)), QString::number(qRound(crf_frc * 10.0)));
626                 break;
627         case OptionsModel::RCMode_2Pass:
628         case OptionsModel::RCMode_ABR:
629                 cmdLine << "--bitrate" << QString::number(m_options->bitrate());
630                 break;
631         default:
632                 throw "Bad rate-control mode !!!";
633                 break;
634         }
635         
636         if((pass == 1) || (pass == 2))
637         {
638                 cmdLine << "--pass" << QString::number(pass);
639                 cmdLine << "--stats" << QDir::toNativeSeparators(passLogFile);
640         }
641
642         cmdLine << "--preset" << m_options->preset().toLower();
643
644         if(m_options->tune().compare("none", Qt::CaseInsensitive))
645         {
646                 cmdLine << "--tune" << m_options->tune().toLower();
647         }
648
649         if(m_options->profile().compare("auto", Qt::CaseInsensitive))
650         {
651                 if(use10Bit)
652                 {
653                         if(m_options->profile().compare("baseline", Qt::CaseInsensitive) || m_options->profile().compare("main", Qt::CaseInsensitive) || m_options->profile().compare("high", Qt::CaseInsensitive))
654                         {
655                                 log(tr("WARNING: Selected H.264 Profile not compatible with 10-Bit encoding. Ignoring!\n"));
656                         }
657                         else
658                         {
659                                 cmdLine << "--profile" << m_options->profile().toLower();
660                         }
661                 }
662                 else
663                 {
664                         cmdLine << "--profile" << m_options->profile().toLower();
665                 }
666         }
667
668         if(!m_options->customX264().isEmpty())
669         {
670                 QStringList customArgs = splitParams(m_options->customX264());
671                 if(usePipe)
672                 {
673                         QStringList::iterator i = customArgs.begin();
674                         while(i != customArgs.end())
675                         {
676                                 bool bModified = false;
677                                 REMOVE_CUSTOM_ARG(customArgs, i, bModified, "--fps");
678                                 REMOVE_CUSTOM_ARG(customArgs, i, bModified, "--frames");
679                                 if(!bModified) i++;
680                         }
681                 }
682                 cmdLine.append(customArgs);
683         }
684
685         cmdLine << "--output" << QDir::toNativeSeparators(m_outputFileName);
686         
687         if(usePipe)
688         {
689                 if(frames < 1) throw "Frames not set!";
690                 cmdLine << "--frames" << QString::number(frames);
691                 cmdLine << "--demuxer" << "y4m";
692                 cmdLine << "--stdin" << "y4m" << "-";
693         }
694         else
695         {
696                 cmdLine << "--index" << QDir::toNativeSeparators(indexFile);
697                 cmdLine << QDir::toNativeSeparators(m_sourceFileName);
698         }
699
700         return cmdLine;
701 }
702
703 unsigned int EncodeThread::checkVersionX264(bool use_x64, bool use_10bit, bool &modified)
704 {
705         if(m_skipVersionTest)
706         {
707                 log("Warning: Skipping x264 version check this time!");
708                 return (999 * REV_MULT) + (9999 % REV_MULT);
709         }
710
711         QProcess process;
712         QStringList cmdLine = QStringList() << "--version";
713
714         log("Creating process:");
715         if(!startProcess(process, X264_BINARY(m_binDir, use_10bit, use_x64), cmdLine))
716         {
717                 return false;;
718         }
719
720         QRegExp regExpVersion("\\bx264\\s(\\d)\\.(\\d+)\\.(\\d+)\\s([a-f0-9]{7})", Qt::CaseInsensitive);
721         QRegExp regExpVersionMod("\\bx264 (\\d)\\.(\\d+)\\.(\\d+)", Qt::CaseInsensitive);
722         
723         bool bTimeout = false;
724         bool bAborted = false;
725
726         unsigned int revision = UINT_MAX;
727         unsigned int coreVers = UINT_MAX;
728         modified = false;
729
730         while(process.state() != QProcess::NotRunning)
731         {
732                 if(m_abort)
733                 {
734                         process.kill();
735                         bAborted = true;
736                         break;
737                 }
738                 if(!process.waitForReadyRead())
739                 {
740                         if(process.state() == QProcess::Running)
741                         {
742                                 process.kill();
743                                 qWarning("x264 process timed out <-- killing!");
744                                 log("\nPROCESS TIMEOUT !!!");
745                                 bTimeout = true;
746                                 break;
747                         }
748                 }
749                 while(process.bytesAvailable() > 0)
750                 {
751                         QList<QByteArray> lines = process.readLine().split('\r');
752                         while(!lines.isEmpty())
753                         {
754                                 QString text = QString::fromUtf8(lines.takeFirst().constData()).simplified();
755                                 int offset = -1;
756                                 if((offset = regExpVersion.lastIndexIn(text)) >= 0)
757                                 {
758                                         bool ok1 = false, ok2 = false;
759                                         unsigned int temp1 = regExpVersion.cap(2).toUInt(&ok1);
760                                         unsigned int temp2 = regExpVersion.cap(3).toUInt(&ok2);
761                                         if(ok1) coreVers = temp1;
762                                         if(ok2) revision = temp2;
763                                 }
764                                 else if((offset = regExpVersionMod.lastIndexIn(text)) >= 0)
765                                 {
766                                         bool ok1 = false, ok2 = false;
767                                         unsigned int temp1 = regExpVersionMod.cap(2).toUInt(&ok1);
768                                         unsigned int temp2 = regExpVersionMod.cap(3).toUInt(&ok2);
769                                         if(ok1) coreVers = temp1;
770                                         if(ok2) revision = temp2;
771                                         modified = true;
772                                 }
773                                 if(!text.isEmpty())
774                                 {
775                                         log(text);
776                                 }
777                         }
778                 }
779         }
780
781         process.waitForFinished();
782         if(process.state() != QProcess::NotRunning)
783         {
784                 process.kill();
785                 process.waitForFinished(-1);
786         }
787
788         if(bTimeout || bAborted || process.exitCode() != EXIT_SUCCESS)
789         {
790                 if(!(bTimeout || bAborted))
791                 {
792                         log(tr("\nPROCESS EXITED WITH ERROR CODE: %1").arg(QString::number(process.exitCode())));
793                 }
794                 return UINT_MAX;
795         }
796
797         if((revision == UINT_MAX) || (coreVers == UINT_MAX))
798         {
799                 log(tr("\nFAILED TO DETERMINE X264 VERSION !!!"));
800                 return UINT_MAX;
801         }
802         
803         return (coreVers * REV_MULT) + (revision % REV_MULT);
804 }
805
806 unsigned int EncodeThread::checkVersionAvs2yuv(bool x64)
807 {
808         QProcess process;
809
810         log("\nCreating process:");
811         if(!startProcess(process, AVS2_BINARY(m_binDir, x64), QStringList()))
812         {
813                 return false;;
814         }
815
816         QRegExp regExpVersionMod("\\bAvs2YUV (\\d+).(\\d+)bm(\\d)\\b", Qt::CaseInsensitive);
817         QRegExp regExpVersionOld("\\bAvs2YUV (\\d+).(\\d+)\\b", Qt::CaseInsensitive);
818         
819         bool bTimeout = false;
820         bool bAborted = false;
821
822         unsigned int ver_maj = UINT_MAX;
823         unsigned int ver_min = UINT_MAX;
824         unsigned int ver_mod = 0;
825
826         while(process.state() != QProcess::NotRunning)
827         {
828                 if(m_abort)
829                 {
830                         process.kill();
831                         bAborted = true;
832                         break;
833                 }
834                 if(!process.waitForReadyRead())
835                 {
836                         if(process.state() == QProcess::Running)
837                         {
838                                 process.kill();
839                                 qWarning("Avs2YUV process timed out <-- killing!");
840                                 log("\nPROCESS TIMEOUT !!!");
841                                 bTimeout = true;
842                                 break;
843                         }
844                 }
845                 while(process.bytesAvailable() > 0)
846                 {
847                         QList<QByteArray> lines = process.readLine().split('\r');
848                         while(!lines.isEmpty())
849                         {
850                                 QString text = QString::fromUtf8(lines.takeFirst().constData()).simplified();
851                                 int offset = -1;
852                                 if((ver_maj == UINT_MAX) || (ver_min == UINT_MAX) || (ver_mod == UINT_MAX))
853                                 {
854                                         if(!text.isEmpty())
855                                         {
856                                                 log(text);
857                                         }
858                                 }
859                                 if((offset = regExpVersionMod.lastIndexIn(text)) >= 0)
860                                 {
861                                         bool ok1 = false, ok2 = false, ok3 = false;
862                                         unsigned int temp1 = regExpVersionMod.cap(1).toUInt(&ok1);
863                                         unsigned int temp2 = regExpVersionMod.cap(2).toUInt(&ok2);
864                                         unsigned int temp3 = regExpVersionMod.cap(3).toUInt(&ok3);
865                                         if(ok1) ver_maj = temp1;
866                                         if(ok2) ver_min = temp2;
867                                         if(ok3) ver_mod = temp3;
868                                 }
869                                 else if((offset = regExpVersionOld.lastIndexIn(text)) >= 0)
870                                 {
871                                         bool ok1 = false, ok2 = false;
872                                         unsigned int temp1 = regExpVersionOld.cap(1).toUInt(&ok1);
873                                         unsigned int temp2 = regExpVersionOld.cap(2).toUInt(&ok2);
874                                         if(ok1) ver_maj = temp1;
875                                         if(ok2) ver_min = temp2;
876                                 }
877                         }
878                 }
879         }
880
881         process.waitForFinished();
882         if(process.state() != QProcess::NotRunning)
883         {
884                 process.kill();
885                 process.waitForFinished(-1);
886         }
887
888         if(bTimeout || bAborted || ((process.exitCode() != EXIT_SUCCESS) && (process.exitCode() != 2)))
889         {
890                 if(!(bTimeout || bAborted))
891                 {
892                         log(tr("\nPROCESS EXITED WITH ERROR CODE: %1").arg(QString::number(process.exitCode())));
893                 }
894                 return UINT_MAX;
895         }
896
897         if((ver_maj == UINT_MAX) || (ver_min == UINT_MAX))
898         {
899                 log(tr("\nFAILED TO DETERMINE AVS2YUV VERSION !!!"));
900                 return UINT_MAX;
901         }
902         
903         return (ver_maj * REV_MULT) + ((ver_min % REV_MULT) * 10) + (ver_mod % 10);
904 }
905
906 bool EncodeThread::checkVersionVapoursynth(/*const QString &vspipePath*/)
907 {
908         //Is VapourSynth available at all?
909         if(m_vpsDir.isEmpty() || (!QFileInfo(VPSP_BINARY(m_vpsDir)).isFile()))
910         {
911                 log(tr("\nVPY INPUT REQUIRES VAPOURSYNTH, BUT IT IS *NOT* AVAILABLE !!!"));
912                 return false;
913         }
914
915         QProcess process;
916
917         log("\nCreating process:");
918         if(!startProcess(process, VPSP_BINARY(m_vpsDir), QStringList()))
919         {
920                 return false;;
921         }
922
923         QRegExp regExpSignature("\\bVSPipe\\s+usage\\b", Qt::CaseInsensitive);
924         
925         bool bTimeout = false;
926         bool bAborted = false;
927
928         bool vspipeSignature = false;
929
930         while(process.state() != QProcess::NotRunning)
931         {
932                 if(m_abort)
933                 {
934                         process.kill();
935                         bAborted = true;
936                         break;
937                 }
938                 if(!process.waitForReadyRead())
939                 {
940                         if(process.state() == QProcess::Running)
941                         {
942                                 process.kill();
943                                 qWarning("VSPipe process timed out <-- killing!");
944                                 log("\nPROCESS TIMEOUT !!!");
945                                 bTimeout = true;
946                                 break;
947                         }
948                 }
949                 while(process.bytesAvailable() > 0)
950                 {
951                         QList<QByteArray> lines = process.readLine().split('\r');
952                         while(!lines.isEmpty())
953                         {
954                                 QString text = QString::fromUtf8(lines.takeFirst().constData()).simplified();
955                                 if(regExpSignature.lastIndexIn(text) >= 0)
956                                 {
957                                         vspipeSignature = true;
958                                 }
959                                 if(!text.isEmpty())
960                                 {
961                                         log(text);
962                                 }
963                         }
964                 }
965         }
966
967         process.waitForFinished();
968         if(process.state() != QProcess::NotRunning)
969         {
970                 process.kill();
971                 process.waitForFinished(-1);
972         }
973
974         if(bTimeout || bAborted || ((process.exitCode() != EXIT_SUCCESS) && (process.exitCode() != 1)))
975         {
976                 if(!(bTimeout || bAborted))
977                 {
978                         log(tr("\nPROCESS EXITED WITH ERROR CODE: %1").arg(QString::number(process.exitCode())));
979                 }
980                 return false;
981         }
982
983         if(!vspipeSignature)
984         {
985                 log(tr("\nFAILED TO DETECT VSPIPE SIGNATURE !!!"));
986                 return false;
987         }
988         
989         return vspipeSignature;
990 }
991
992 bool EncodeThread::checkPropertiesAvisynth(bool x64, unsigned int &frames)
993 {
994         QProcess process;
995         QStringList cmdLine;
996
997         if(!m_options->customAvs2YUV().isEmpty())
998         {
999                 cmdLine.append(splitParams(m_options->customAvs2YUV()));
1000         }
1001
1002         cmdLine << "-frames" << "1";
1003         cmdLine << QDir::toNativeSeparators(x264_path2ansi(m_sourceFileName, true)) << "NUL";
1004
1005         log("Creating process:");
1006         if(!startProcess(process, AVS2_BINARY(m_binDir, x64), cmdLine))
1007         {
1008                 return false;;
1009         }
1010
1011         QRegExp regExpInt(": (\\d+)x(\\d+), (\\d+) fps, (\\d+) frames");
1012         QRegExp regExpFrc(": (\\d+)x(\\d+), (\\d+)/(\\d+) fps, (\\d+) frames");
1013         
1014         QTextCodec *localCodec = QTextCodec::codecForName("System");
1015
1016         bool bTimeout = false;
1017         bool bAborted = false;
1018
1019         frames = 0;
1020         
1021         unsigned int fpsNom = 0;
1022         unsigned int fpsDen = 0;
1023         unsigned int fSizeW = 0;
1024         unsigned int fSizeH = 0;
1025         
1026         unsigned int waitCounter = 0;
1027
1028         while(process.state() != QProcess::NotRunning)
1029         {
1030                 if(m_abort)
1031                 {
1032                         process.kill();
1033                         bAborted = true;
1034                         break;
1035                 }
1036                 if(!process.waitForReadyRead(m_processTimeoutInterval))
1037                 {
1038                         if(process.state() == QProcess::Running)
1039                         {
1040                                 if(++waitCounter > m_processTimeoutMaxCounter)
1041                                 {
1042                                         if(m_abortOnTimeout)
1043                                         {
1044                                                 process.kill();
1045                                                 qWarning("Avs2YUV process timed out <-- killing!");
1046                                                 log("\nPROCESS TIMEOUT !!!");
1047                                                 log("\nAvisynth has encountered a deadlock or your script takes EXTREMELY long to initialize!");
1048                                                 bTimeout = true;
1049                                                 break;
1050                                         }
1051                                 }
1052                                 else if(waitCounter == m_processTimeoutWarning)
1053                                 {
1054                                         unsigned int timeOut = (waitCounter * m_processTimeoutInterval) / 1000U;
1055                                         log(tr("Warning: Avisynth did not respond for %1 seconds, potential deadlock...").arg(QString::number(timeOut)));
1056                                 }
1057                         }
1058                         continue;
1059                 }
1060                 
1061                 waitCounter = 0;
1062                 
1063                 while(process.bytesAvailable() > 0)
1064                 {
1065                         QList<QByteArray> lines = process.readLine().split('\r');
1066                         while(!lines.isEmpty())
1067                         {
1068                                 QString text = localCodec->toUnicode(lines.takeFirst().constData()).simplified();
1069                                 int offset = -1;
1070                                 if((offset = regExpInt.lastIndexIn(text)) >= 0)
1071                                 {
1072                                         bool ok1 = false, ok2 = false;
1073                                         bool ok3 = false, ok4 = false;
1074                                         unsigned int temp1 = regExpInt.cap(1).toUInt(&ok1);
1075                                         unsigned int temp2 = regExpInt.cap(2).toUInt(&ok2);
1076                                         unsigned int temp3 = regExpInt.cap(3).toUInt(&ok3);
1077                                         unsigned int temp4 = regExpInt.cap(4).toUInt(&ok4);
1078                                         if(ok1) fSizeW = temp1;
1079                                         if(ok2) fSizeH = temp2;
1080                                         if(ok3) fpsNom = temp3;
1081                                         if(ok4) frames = temp4;
1082                                 }
1083                                 else if((offset = regExpFrc.lastIndexIn(text)) >= 0)
1084                                 {
1085                                         bool ok1 = false, ok2 = false;
1086                                         bool ok3 = false, ok4 = false, ok5 = false;
1087                                         unsigned int temp1 = regExpFrc.cap(1).toUInt(&ok1);
1088                                         unsigned int temp2 = regExpFrc.cap(2).toUInt(&ok2);
1089                                         unsigned int temp3 = regExpFrc.cap(3).toUInt(&ok3);
1090                                         unsigned int temp4 = regExpFrc.cap(4).toUInt(&ok4);
1091                                         unsigned int temp5 = regExpFrc.cap(5).toUInt(&ok5);
1092                                         if(ok1) fSizeW = temp1;
1093                                         if(ok2) fSizeH = temp2;
1094                                         if(ok3) fpsNom = temp3;
1095                                         if(ok4) fpsDen = temp4;
1096                                         if(ok5) frames = temp5;
1097                                 }
1098                                 if(!text.isEmpty())
1099                                 {
1100                                         log(text);
1101                                 }
1102                                 if(text.contains("failed to load avisynth.dll", Qt::CaseInsensitive))
1103                                 {
1104                                         log(tr("\nWarning: It seems that %1-Bit Avisynth is not currently installed !!!").arg(x64 ? "64" : "32"));
1105                                 }
1106                                 if(text.contains(QRegExp("couldn't convert input clip to (YV16|YV24)", Qt::CaseInsensitive)))
1107                                 {
1108                                         log(tr("\nWarning: YV16 (4:2:2) and YV24 (4:4:4) color-spaces only supported in Avisynth 2.6 !!!"));
1109                                 }
1110                         }
1111                 }
1112         }
1113
1114         process.waitForFinished();
1115         if(process.state() != QProcess::NotRunning)
1116         {
1117                 process.kill();
1118                 process.waitForFinished(-1);
1119         }
1120
1121         if(bTimeout || bAborted || process.exitCode() != EXIT_SUCCESS)
1122         {
1123                 if(!(bTimeout || bAborted))
1124                 {
1125                         const int exitCode = process.exitCode();
1126                         log(tr("\nPROCESS EXITED WITH ERROR CODE: %1").arg(QString::number(exitCode)));
1127                         if((exitCode < 0) || (exitCode > 2))
1128                         {
1129                                 log(tr("\nIMPORTANT: The Avs2YUV process terminated abnormally. This means Avisynth or one of your Avisynth-Plugin's just crashed."));
1130                                 log(tr("IMPORTANT: Please fix your Avisynth script and try again! If you use Avisynth-MT, try using a *stable* Avisynth instead!"));
1131                         }
1132                 }
1133                 return false;
1134         }
1135
1136         if(frames == 0)
1137         {
1138                 log(tr("\nFAILED TO DETERMINE AVS PROPERTIES !!!"));
1139                 return false;
1140         }
1141         
1142         log("");
1143
1144         if((fSizeW > 0) && (fSizeH > 0))
1145         {
1146                 log(tr("Resolution: %1x%2").arg(QString::number(fSizeW), QString::number(fSizeH)));
1147         }
1148         if((fpsNom > 0) && (fpsDen > 0))
1149         {
1150                 log(tr("Frame Rate: %1/%2").arg(QString::number(fpsNom), QString::number(fpsDen)));
1151         }
1152         if((fpsNom > 0) && (fpsDen == 0))
1153         {
1154                 log(tr("Frame Rate: %1").arg(QString::number(fpsNom)));
1155         }
1156         if(frames > 0)
1157         {
1158                 log(tr("No. Frames: %1").arg(QString::number(frames)));
1159         }
1160
1161         return true;
1162 }
1163
1164 bool EncodeThread::checkPropertiesVapoursynth(/*const QString &vspipePath,*/ unsigned int &frames)
1165 {
1166         QProcess process;
1167         QStringList cmdLine;
1168
1169         cmdLine << QDir::toNativeSeparators(x264_path2ansi(m_sourceFileName, true));
1170         cmdLine << "-" << "-info";
1171
1172         log("Creating process:");
1173         if(!startProcess(process, VPSP_BINARY(m_vpsDir), cmdLine))
1174         {
1175                 return false;;
1176         }
1177
1178         QRegExp regExpFrm("\\bFrames:\\s+(\\d+)\\b");
1179         QRegExp regExpSzW("\\bWidth:\\s+(\\d+)\\b");
1180         QRegExp regExpSzH("\\bHeight:\\s+(\\d+)\\b");
1181         
1182         QTextCodec *localCodec = QTextCodec::codecForName("System");
1183
1184         bool bTimeout = false;
1185         bool bAborted = false;
1186
1187         frames = 0;
1188         
1189         unsigned int fSizeW = 0;
1190         unsigned int fSizeH = 0;
1191         
1192         unsigned int waitCounter = 0;
1193
1194         while(process.state() != QProcess::NotRunning)
1195         {
1196                 if(m_abort)
1197                 {
1198                         process.kill();
1199                         bAborted = true;
1200                         break;
1201                 }
1202                 if(!process.waitForReadyRead(m_processTimeoutInterval))
1203                 {
1204                         if(process.state() == QProcess::Running)
1205                         {
1206                                 if(++waitCounter > m_processTimeoutMaxCounter)
1207                                 {
1208                                         if(m_abortOnTimeout)
1209                                         {
1210                                                 process.kill();
1211                                                 qWarning("VSPipe process timed out <-- killing!");
1212                                                 log("\nPROCESS TIMEOUT !!!");
1213                                                 log("\nVapoursynth has encountered a deadlock or your script takes EXTREMELY long to initialize!");
1214                                                 bTimeout = true;
1215                                                 break;
1216                                         }
1217                                 }
1218                                 else if(waitCounter == m_processTimeoutWarning)
1219                                 {
1220                                         unsigned int timeOut = (waitCounter * m_processTimeoutInterval) / 1000U;
1221                                         log(tr("Warning: nVapoursynth did not respond for %1 seconds, potential deadlock...").arg(QString::number(timeOut)));
1222                                 }
1223                         }
1224                         continue;
1225                 }
1226                 
1227                 waitCounter = 0;
1228                 
1229                 while(process.bytesAvailable() > 0)
1230                 {
1231                         QList<QByteArray> lines = process.readLine().split('\r');
1232                         while(!lines.isEmpty())
1233                         {
1234                                 QString text = localCodec->toUnicode(lines.takeFirst().constData()).simplified();
1235                                 int offset = -1;
1236                                 if((offset = regExpFrm.lastIndexIn(text)) >= 0)
1237                                 {
1238                                         bool ok = false;
1239                                         unsigned int temp = regExpFrm.cap(1).toUInt(&ok);
1240                                         if(ok) frames = temp;
1241                                 }
1242                                 if((offset = regExpSzW.lastIndexIn(text)) >= 0)
1243                                 {
1244                                         bool ok = false;
1245                                         unsigned int temp = regExpSzW.cap(1).toUInt(&ok);
1246                                         if(ok) fSizeW = temp;
1247                                 }
1248                                 if((offset = regExpSzH.lastIndexIn(text)) >= 0)
1249                                 {
1250                                         bool ok = false;
1251                                         unsigned int temp = regExpSzH.cap(1).toUInt(&ok);
1252                                         if(ok) fSizeH = temp;
1253                                 }
1254                                 if(!text.isEmpty())
1255                                 {
1256                                         log(text);
1257                                 }
1258                         }
1259                 }
1260         }
1261
1262         process.waitForFinished();
1263         if(process.state() != QProcess::NotRunning)
1264         {
1265                 process.kill();
1266                 process.waitForFinished(-1);
1267         }
1268
1269         if(bTimeout || bAborted || process.exitCode() != EXIT_SUCCESS)
1270         {
1271                 if(!(bTimeout || bAborted))
1272                 {
1273                         const int exitCode = process.exitCode();
1274                         log(tr("\nPROCESS EXITED WITH ERROR CODE: %1").arg(QString::number(exitCode)));
1275                         if((exitCode < 0) || (exitCode > 1))
1276                         {
1277                                 log(tr("\nIMPORTANT: The Vapoursynth process terminated abnormally. This means Vapoursynth or one of your Vapoursynth-Plugin's just crashed."));
1278                         }
1279                 }
1280                 return false;
1281         }
1282
1283         if(frames == 0)
1284         {
1285                 log(tr("\nFAILED TO DETERMINE VPY PROPERTIES !!!"));
1286                 return false;
1287         }
1288         
1289         log("");
1290
1291         if((fSizeW > 0) && (fSizeH > 0))
1292         {
1293                 log(tr("Resolution: %1x%2").arg(QString::number(fSizeW), QString::number(fSizeH)));
1294         }
1295         if(frames > 0)
1296         {
1297                 log(tr("No. Frames: %1").arg(QString::number(frames)));
1298         }
1299
1300         return true;
1301 }
1302
1303 ///////////////////////////////////////////////////////////////////////////////
1304 // Misc functions
1305 ///////////////////////////////////////////////////////////////////////////////
1306
1307 void EncodeThread::setStatus(JobStatus newStatus)
1308 {
1309         if(m_status != newStatus)
1310         {
1311                 if((newStatus != JobStatus_Completed) && (newStatus != JobStatus_Failed) && (newStatus != JobStatus_Aborted) && (newStatus != JobStatus_Paused))
1312                 {
1313                         if(m_status != JobStatus_Paused) setProgress(0);
1314                 }
1315                 if(newStatus == JobStatus_Failed)
1316                 {
1317                         setDetails("The job has failed. See log for details!");
1318                 }
1319                 if(newStatus == JobStatus_Aborted)
1320                 {
1321                         setDetails("The job was aborted by the user!");
1322                 }
1323                 m_status = newStatus;
1324                 emit statusChanged(m_jobId, newStatus);
1325         }
1326 }
1327
1328 void EncodeThread::setProgress(unsigned int newProgress)
1329 {
1330         if(m_progress != newProgress)
1331         {
1332                 m_progress = newProgress;
1333                 emit progressChanged(m_jobId, m_progress);
1334         }
1335 }
1336
1337 void EncodeThread::setDetails(const QString &text)
1338 {
1339         emit detailsChanged(m_jobId, text);
1340 }
1341
1342 QString EncodeThread::stringToHash(const QString &string)
1343 {
1344         QByteArray result(10, char(0));
1345         const QByteArray hash = QCryptographicHash::hash(string.toUtf8(), QCryptographicHash::Sha1);
1346
1347         if((hash.size() == 20) && (result.size() == 10))
1348         {
1349                 unsigned char *out = reinterpret_cast<unsigned char*>(result.data());
1350                 const unsigned char *in = reinterpret_cast<const unsigned char*>(hash.constData());
1351                 for(int i = 0; i < 10; i++)
1352                 {
1353                         out[i] = (in[i] ^ in[10+i]);
1354                 }
1355         }
1356
1357         return QString::fromLatin1(result.toHex().constData());
1358 }
1359
1360 bool EncodeThread::startProcess(QProcess &process, const QString &program, const QStringList &args, bool mergeChannels)
1361 {
1362         QMutexLocker lock(&m_mutex_startProcess);
1363         log(commandline2string(program, args) + "\n");
1364
1365         process.setWorkingDirectory(QDir::tempPath());
1366
1367         if(mergeChannels)
1368         {
1369                 process.setProcessChannelMode(QProcess::MergedChannels);
1370                 process.setReadChannel(QProcess::StandardOutput);
1371         }
1372         else
1373         {
1374                 process.setProcessChannelMode(QProcess::SeparateChannels);
1375                 process.setReadChannel(QProcess::StandardError);
1376         }
1377
1378         process.start(program, args);
1379         
1380         if(process.waitForStarted())
1381         {
1382                 m_jobObject->addProcessToJob(&process);
1383                 x264_change_process_priority(&process, m_processPriority);
1384                 lock.unlock();
1385                 return true;
1386         }
1387
1388         log("Process creation has failed :-(");
1389         QString errorMsg= process.errorString().trimmed();
1390         if(!errorMsg.isEmpty()) log(errorMsg);
1391
1392         process.kill();
1393         process.waitForFinished(-1);
1394         return false;
1395 }
1396
1397 QString EncodeThread::commandline2string(const QString &program, const QStringList &arguments)
1398 {
1399         QString commandline = (program.contains(' ') ? QString("\"%1\"").arg(program) : program);
1400         
1401         for(int i = 0; i < arguments.count(); i++)
1402         {
1403                 commandline += (arguments.at(i).contains(' ') ? QString(" \"%1\"").arg(arguments.at(i)) : QString(" %1").arg(arguments.at(i)));
1404         }
1405
1406         return commandline;
1407 }
1408
1409 QStringList EncodeThread::splitParams(const QString &params)
1410 {
1411         QStringList list; 
1412         bool ignoreWhitespaces = false;
1413         QString temp;
1414
1415         for(int i = 0; i < params.length(); i++)
1416         {
1417                 const QChar c = params.at(i);
1418
1419                 if(c == QChar::fromLatin1('"'))
1420                 {
1421                         ignoreWhitespaces = (!ignoreWhitespaces);
1422                         continue;
1423                 }
1424                 else if((!ignoreWhitespaces) && (c == QChar::fromLatin1(' ')))
1425                 {
1426                         APPEND_AND_CLEAR(list, temp);
1427                         continue;
1428                 }
1429                 
1430                 temp.append(c);
1431         }
1432         
1433         APPEND_AND_CLEAR(list, temp);
1434
1435         list.replaceInStrings("$(INPUT)", QDir::toNativeSeparators(m_sourceFileName), Qt::CaseInsensitive);
1436         list.replaceInStrings("$(OUTPUT)", QDir::toNativeSeparators(m_outputFileName), Qt::CaseInsensitive);
1437
1438         return list;
1439 }
1440
1441 qint64 EncodeThread::estimateSize(int progress)
1442 {
1443         if(progress >= 3)
1444         {
1445                 qint64 currentSize = QFileInfo(m_outputFileName).size();
1446                 qint64 estimatedSize = (currentSize * 100I64) / static_cast<qint64>(progress);
1447                 return estimatedSize;
1448         }
1449
1450         return 0I64;
1451 }
1452
1453 QString EncodeThread::sizeToString(qint64 size)
1454 {
1455         static char *prefix[5] = {"Byte", "KB", "MB", "GB", "TB"};
1456
1457         if(size > 1024I64)
1458         {
1459                 qint64 estimatedSize = size;
1460                 qint64 remainderSize = 0I64;
1461
1462                 int prefixIdx = 0;
1463                 while((estimatedSize > 1024I64) && (prefixIdx < 4))
1464                 {
1465                         remainderSize = estimatedSize % 1024I64;
1466                         estimatedSize = estimatedSize / 1024I64;
1467                         prefixIdx++;
1468                 }
1469                         
1470                 double value = static_cast<double>(estimatedSize) + (static_cast<double>(remainderSize) / 1024.0);
1471                 return QString().sprintf((value < 10.0) ? "%.2f %s" : "%.1f %s", value, prefix[prefixIdx]);
1472         }
1473
1474         return tr("N/A");
1475 }
1476
1477 int EncodeThread::getInputType(const QString &fileExt)
1478 {
1479         int type = INPUT_NATIVE;
1480         if(fileExt.compare("avs", Qt::CaseInsensitive) == 0)       type = INPUT_AVISYN;
1481         else if(fileExt.compare("avsi", Qt::CaseInsensitive) == 0) type = INPUT_AVISYN;
1482         else if(fileExt.compare("vpy", Qt::CaseInsensitive) == 0)  type = INPUT_VAPOUR;
1483         else if(fileExt.compare("py", Qt::CaseInsensitive) == 0)   type = INPUT_VAPOUR;
1484         return type;
1485 }