OSDN Git Service

Got rid of 'pipebuf.exe' tool. Will now use two QProcess objects to handle the redire...
[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 <QLibrary>
36
37 /*
38  * Win32 API definitions
39  */
40 typedef HANDLE (WINAPI *CreateJobObjectFun)(__in_opt LPSECURITY_ATTRIBUTES lpJobAttributes, __in_opt LPCSTR lpName);
41 typedef BOOL (WINAPI *SetInformationJobObjectFun)(__in HANDLE hJob, __in JOBOBJECTINFOCLASS JobObjectInformationClass, __in_bcount(cbJobObjectInformationLength) LPVOID lpJobObjectInformation, __in DWORD cbJobObjectInformationLength);
42 typedef BOOL (WINAPI *AssignProcessToJobObjectFun)(__in HANDLE hJob, __in HANDLE hProcess);
43
44 /*
45  * Static vars
46  */
47 QMutex EncodeThread::m_mutex_startProcess;
48
49 /*
50  * Macros
51  */
52 #define CHECK_STATUS(ABORT_FLAG, OK_FLAG) \
53 { \
54         if(ABORT_FLAG) \
55         { \
56                 log("\nPROCESS ABORTED BY USER !!!"); \
57                 setStatus(JobStatus_Aborted); \
58                 return; \
59         } \
60         else if(!(OK_FLAG)) \
61         { \
62                 setStatus(JobStatus_Failed); \
63                 return; \
64         } \
65 }
66
67 /*
68  * Static vars
69  */
70 static const unsigned int REV_MULT = 10000;
71
72 ///////////////////////////////////////////////////////////////////////////////
73 // Constructor & Destructor
74 ///////////////////////////////////////////////////////////////////////////////
75
76 EncodeThread::EncodeThread(const QString &sourceFileName, const QString &outputFileName, const OptionsModel *options, const QString &binDir, bool x64)
77 :
78         m_jobId(QUuid::createUuid()),
79         m_sourceFileName(sourceFileName),
80         m_outputFileName(outputFileName),
81         m_options(new OptionsModel(*options)),
82         m_binDir(binDir),
83         m_x64(x64),
84         m_handle_jobObject(NULL)
85 {
86         m_abort = false;
87 }
88
89 EncodeThread::~EncodeThread(void)
90 {
91         X264_DELETE(m_options);
92         
93         if(m_handle_jobObject)
94         {
95                 CloseHandle(m_handle_jobObject);
96                 m_handle_jobObject = NULL;
97         }
98 }
99
100 ///////////////////////////////////////////////////////////////////////////////
101 // Thread entry point
102 ///////////////////////////////////////////////////////////////////////////////
103
104 void EncodeThread::run(void)
105 {
106         m_progress = 0;
107         m_status = JobStatus_Starting;
108
109         try
110         {
111                 encode();
112         }
113         catch(char *msg)
114         {
115                 log(tr("EXCEPTION ERROR: ").append(QString::fromLatin1(msg)));
116         }
117         catch(...)
118         {
119                 log(tr("EXCEPTION ERROR !!!"));
120         }
121
122         if(m_handle_jobObject)
123         {
124                 CloseHandle(m_handle_jobObject);
125                 m_handle_jobObject = NULL;
126         }
127 }
128
129 ///////////////////////////////////////////////////////////////////////////////
130 // Encode functions
131 ///////////////////////////////////////////////////////////////////////////////
132
133 void EncodeThread::encode(void)
134 {
135         QDateTime startTime = QDateTime::currentDateTime();
136         
137         //Print some basic info
138         log(tr("Job started at %1, %2.\n").arg(QDate::currentDate().toString(Qt::ISODate), QTime::currentTime().toString( Qt::ISODate)));
139         log(tr("Source file: %1").arg(m_sourceFileName));
140         log(tr("Output file: %1").arg(m_outputFileName));
141         
142         //Print encoder settings
143         log(tr("\n--- SETTINGS ---\n"));
144         log(tr("RC Mode: %1").arg(OptionsModel::rcMode2String(m_options->rcMode())));
145         log(tr("Preset:  %1").arg(m_options->preset()));
146         log(tr("Tuning:  %1").arg(m_options->tune()));
147         log(tr("Profile: %1").arg(m_options->profile()));
148         log(tr("Custom:  %1").arg(m_options->custom().isEmpty() ? tr("(None)") : m_options->custom()));
149         
150         bool ok = false;
151         unsigned int frames = 0;
152
153         //Detect source info
154         bool usePipe = true; //m_x64 && (QFileInfo(m_sourceFileName).suffix().compare("avs", Qt::CaseInsensitive) == 0);
155         if(usePipe)
156         {
157                 log(tr("\n--- AVS INFO ---\n"));
158                 ok = checkProperties(frames);
159                 CHECK_STATUS(m_abort, ok);
160         }
161
162         //Checking version
163         log(tr("\n--- X264 VERSION ---\n"));
164         unsigned int revision;
165         ok = ((revision = checkVersion(m_x64)) != UINT_MAX);
166         CHECK_STATUS(m_abort, ok);
167
168         //Is revision supported?
169         log(tr("\nx264 revision: %1 (core #%2)").arg(QString::number(revision % REV_MULT), QString::number(revision / REV_MULT)));
170         if((revision % REV_MULT) < VER_X264_MINIMUM_REV)
171         {
172                 log(tr("\nERROR: Your revision of x264 is too old! (Minimum required revision is %2)").arg(QString::number(VER_X264_MINIMUM_REV)));
173                 setStatus(JobStatus_Failed);
174                 return;
175         }
176         if((revision / REV_MULT) != VER_X264_CURRENT_API)
177         {
178                 log(tr("\nWARNING: Your revision of x264 uses an unsupported core (API) version, take care!"));
179                 log(tr("This application works best with x264 core (API) version %2.").arg(QString::number(VER_X264_CURRENT_API)));
180         }
181         
182         //Run encoding passes
183         if(m_options->rcMode() == OptionsModel::RCMode_2Pass)
184         {
185                 QFileInfo info(m_outputFileName);
186                 QString passLogFile = QString("%1/%2.stats").arg(info.path(), info.completeBaseName());
187
188                 if(QFileInfo(passLogFile).exists())
189                 {
190                         int n = 2;
191                         while(QFileInfo(passLogFile).exists())
192                         {
193                                 passLogFile = QString("%1/%2.%3.stats").arg(info.path(), info.completeBaseName(), QString::number(n++));
194                         }
195                 }
196                 
197                 log(tr("\n--- PASS 1 ---\n"));
198                 ok = runEncodingPass(m_x64, usePipe, frames, 1, passLogFile);
199                 CHECK_STATUS(m_abort, ok);
200
201                 log(tr("\n--- PASS 2 ---\n"));
202                 ok = runEncodingPass(m_x64, usePipe, frames, 2, passLogFile);
203                 CHECK_STATUS(m_abort, ok);
204         }
205         else
206         {
207                 log(tr("\n--- ENCODING ---\n"));
208                 ok = runEncodingPass(m_x64, usePipe, frames);
209                 CHECK_STATUS(m_abort, ok);
210         }
211
212         log(tr("\n--- DONE ---\n"));
213         int timePassed = startTime.secsTo(QDateTime::currentDateTime());
214         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)));
215         setStatus(JobStatus_Completed);
216 }
217
218 bool EncodeThread::runEncodingPass(bool x64, bool usePipe, unsigned int frames, int pass, const QString &passLogFile)
219 {
220         QProcess processEncode, processAvisynth;
221         
222         if(usePipe)
223         {
224                 QStringList cmdLine_Avisynth;
225                 cmdLine_Avisynth << QDir::toNativeSeparators(m_sourceFileName);
226                 cmdLine_Avisynth << "-";
227                 processAvisynth.setStandardOutputProcess(&processEncode);
228
229                 log("Creating Avisynth process:");
230                 if(!startProcess(processAvisynth, QString("%1/avs2yuv.exe").arg(m_binDir), cmdLine_Avisynth, false))
231                 {
232                         return false;
233                 }
234         }
235
236         QStringList cmdLine_Encode = buildCommandLine(usePipe, frames, pass, passLogFile);
237
238         log("Creating x264 process:");
239         if(!startProcess(processEncode, QString("%1/%2.exe").arg(m_binDir, x64 ? "x264_x64" : "x264"), cmdLine_Encode))
240         {
241                 return false;
242         }
243
244         QRegExp regExpIndexing("indexing.+\\[(\\d+)\\.\\d+%\\]");
245         QRegExp regExpProgress("\\[(\\d+)\\.\\d+%\\].+frames");
246         QRegExp regExpFrameCnt("^(\\d+) frames:");
247         
248         bool bTimeout = false;
249         bool bAborted = false;
250
251         while(processEncode.state() != QProcess::NotRunning)
252         {
253                 if(m_abort)
254                 {
255                         processEncode.kill();
256                         processAvisynth.kill();
257                         bAborted = true;
258                         break;
259                 }
260                 if(!processEncode.waitForReadyRead(m_processTimeoutInterval))
261                 {
262                         if(processEncode.state() == QProcess::Running)
263                         {
264                                 processEncode.kill();
265                                 qWarning("x264 process timed out <-- killing!");
266                                 log("\nPROCESS TIMEOUT !!!");
267                                 bTimeout = true;
268                                 break;
269                         }
270                 }
271                 while(processEncode.bytesAvailable() > 0)
272                 {
273                         QList<QByteArray> lines = processEncode.readLine().split('\r');
274                         while(!lines.isEmpty())
275                         {
276                                 QString text = QString::fromUtf8(lines.takeFirst().constData()).simplified();
277                                 int offset = -1;
278                                 if((offset = regExpProgress.lastIndexIn(text)) >= 0)
279                                 {
280                                         bool ok = false;
281                                         unsigned int progress = regExpProgress.cap(1).toUInt(&ok);
282                                         setStatus((pass == 2) ? JobStatus_Running_Pass2 : ((pass == 1) ? JobStatus_Running_Pass1 : JobStatus_Running));
283                                         setDetails(text.mid(offset).trimmed());
284                                         if(ok) setProgress(progress);
285                                 }
286                                 else if((offset = regExpIndexing.lastIndexIn(text)) >= 0)
287                                 {
288                                         bool ok = false;
289                                         unsigned int progress = regExpIndexing.cap(1).toUInt(&ok);
290                                         setStatus(JobStatus_Indexing);
291                                         setDetails(text.mid(offset).trimmed());
292                                         if(ok) setProgress(progress);
293                                 }
294                                 else if((offset = regExpFrameCnt.lastIndexIn(text)) >= 0)
295                                 {
296                                         setStatus((pass == 2) ? JobStatus_Running_Pass2 : ((pass == 1) ? JobStatus_Running_Pass1 : JobStatus_Running));
297                                         setDetails(text.mid(offset).trimmed());
298                                 }
299                                 else if(!text.isEmpty())
300                                 {
301                                         log(text);
302                                 }
303                         }
304                 }
305         }
306
307         processEncode.waitForFinished(5000);
308         if(processEncode.state() != QProcess::NotRunning)
309         {
310                 qWarning("x264 process still running, going to kill it!");
311                 processEncode.kill();
312                 processEncode.waitForFinished(-1);
313         }
314         
315         processAvisynth.waitForFinished(5000);
316         if(processAvisynth.state() != QProcess::NotRunning)
317         {
318                 qWarning("Avisynth process still running, going to kill it!");
319                 processAvisynth.kill();
320                 processAvisynth.waitForFinished(-1);
321         }
322
323         while(processAvisynth.bytesAvailable() > 0)
324         {
325                 log(tr("av2y [info]: %1").arg(QString::fromUtf8(processAvisynth.readLine()).simplified()));
326         }
327
328         if(usePipe && (processAvisynth.exitCode() != EXIT_SUCCESS))
329         {
330                 if(!(bTimeout || bAborted))
331                 {
332                         log(tr("\nWARNING: Avisynth process exited with error code: %1").arg(QString::number(processAvisynth.exitCode())));
333                 }
334         }
335
336         if(bTimeout || bAborted || processEncode.exitCode() != EXIT_SUCCESS)
337         {
338                 if(!(bTimeout || bAborted))
339                 {
340                         log(tr("\nPROCESS EXITED WITH ERROR CODE: %1").arg(QString::number(processEncode.exitCode())));
341                 }
342                 processEncode.close();
343                 processAvisynth.close();
344                 return false;
345         }
346
347         setStatus((pass == 2) ? JobStatus_Running_Pass2 : ((pass == 1) ? JobStatus_Running_Pass1 : JobStatus_Running));
348         setProgress(100);
349         processEncode.close();
350         processAvisynth.close();
351         return true;
352 }
353
354 QStringList EncodeThread::buildCommandLine(bool usePipe, unsigned int frames, int pass, const QString &passLogFile)
355 {
356         QStringList cmdLine;
357
358         switch(m_options->rcMode())
359         {
360         case OptionsModel::RCMode_CRF:
361                 cmdLine << "--crf" << QString::number(m_options->quantizer());
362                 break;
363         case OptionsModel::RCMode_CQ:
364                 cmdLine << "--qp" << QString::number(m_options->quantizer());
365                 break;
366         case OptionsModel::RCMode_2Pass:
367         case OptionsModel::RCMode_ABR:
368                 cmdLine << "--bitrate" << QString::number(m_options->bitrate());
369                 break;
370         default:
371                 throw "Bad rate-control mode !!!";
372                 break;
373         }
374         
375         if((pass == 1) || (pass == 2))
376         {
377                 cmdLine << "--pass" << QString::number(pass);
378                 cmdLine << "--stats" << QDir::toNativeSeparators(passLogFile);
379         }
380
381         if(m_options->tune().compare("none", Qt::CaseInsensitive))
382         {
383                 cmdLine << "--tune" << m_options->tune().toLower();
384         }
385         
386         cmdLine << "--preset" << m_options->preset().toLower();
387
388         if(!m_options->custom().isEmpty())
389         {
390                 //FIXME: Handle custom parameters that contain spaces!
391                 cmdLine.append(m_options->custom().split(" "));
392         }
393
394         cmdLine << "--output" << QDir::toNativeSeparators(m_outputFileName);
395         
396         if(usePipe)
397         {
398                 if(frames < 1) throw "Frames not set!";
399                 cmdLine << "--frames" << QString::number(frames);
400                 cmdLine << "--demuxer" << "y4m";
401                 cmdLine << "--stdin" << "y4m" << "-";
402         }
403         else
404         {
405                 cmdLine << QDir::toNativeSeparators(m_sourceFileName);
406         }
407
408         return cmdLine;
409 }
410
411 unsigned int EncodeThread::checkVersion(bool x64)
412 {
413         QProcess process;
414         QStringList cmdLine = QStringList() << "--version";
415
416         log("Creating process:");
417         if(!startProcess(process, QString("%1/%2.exe").arg(m_binDir, x64 ? "x264_x64" : "x264"), cmdLine))
418         {
419                 return false;;
420         }
421
422         QRegExp regExpVersion("x264 (\\d)\\.(\\d+)\\.(\\d+) ([0-9A-Fa-f]{7})");
423         
424         bool bTimeout = false;
425         bool bAborted = false;
426
427         unsigned int revision = UINT_MAX;
428         unsigned int coreVers = UINT_MAX;
429
430         while(process.state() != QProcess::NotRunning)
431         {
432                 if(m_abort)
433                 {
434                         process.kill();
435                         bAborted = true;
436                         break;
437                 }
438                 if(!process.waitForReadyRead(m_processTimeoutInterval))
439                 {
440                         if(process.state() == QProcess::Running)
441                         {
442                                 process.kill();
443                                 qWarning("x264 process timed out <-- killing!");
444                                 log("\nPROCESS TIMEOUT !!!");
445                                 bTimeout = true;
446                                 break;
447                         }
448                 }
449                 while(process.bytesAvailable() > 0)
450                 {
451                         QList<QByteArray> lines = process.readLine().split('\r');
452                         while(!lines.isEmpty())
453                         {
454                                 QString text = QString::fromUtf8(lines.takeFirst().constData()).simplified();
455                                 int offset = -1;
456                                 if((offset = regExpVersion.lastIndexIn(text)) >= 0)
457                                 {
458                                         bool ok1 = false, ok2 = false;
459                                         unsigned int temp1 = regExpVersion.cap(2).toUInt(&ok1);
460                                         unsigned int temp2 = regExpVersion.cap(3).toUInt(&ok2);
461                                         if(ok1) coreVers = temp1;
462                                         if(ok2) revision = temp2;
463                                 }
464                                 if(!text.isEmpty())
465                                 {
466                                         log(text);
467                                 }
468                         }
469                 }
470         }
471
472         process.waitForFinished();
473         if(process.state() != QProcess::NotRunning)
474         {
475                 process.kill();
476                 process.waitForFinished(-1);
477         }
478
479         if(bTimeout || bAborted || process.exitCode() != EXIT_SUCCESS)
480         {
481                 if(!(bTimeout || bAborted))
482                 {
483                         log(tr("\nPROCESS EXITED WITH ERROR CODE: %1").arg(QString::number(process.exitCode())));
484                 }
485                 return UINT_MAX;
486         }
487
488         if((revision == UINT_MAX) || (coreVers == UINT_MAX))
489         {
490                 log(tr("\nFAILED TO DETERMINE X264 VERSION !!!"));
491                 return UINT_MAX;
492         }
493         
494         return (coreVers * REV_MULT) + revision;
495 }
496
497 bool EncodeThread::checkProperties(unsigned int &frames)
498 {
499         QProcess process;
500         
501         QStringList cmdLine = QStringList() << "-frames" << "1";
502         cmdLine << QDir::toNativeSeparators(m_sourceFileName) << "NUL";
503
504         log("Creating process:");
505         if(!startProcess(process, QString("%1/avs2yuv.exe").arg(m_binDir), cmdLine))
506         {
507                 return false;;
508         }
509
510         QRegExp regExpInt(": (\\d+)x(\\d+), (\\d+) fps, (\\d+) frames");
511         QRegExp regExpFrc(": (\\d+)x(\\d+), (\\d+)/(\\d+) fps, (\\d+) frames");
512         
513         bool bTimeout = false;
514         bool bAborted = false;
515
516         frames = 0;
517         
518         unsigned int fpsNom = 0;
519         unsigned int fpsDen = 0;
520         unsigned int fSizeW = 0;
521         unsigned int fSizeH = 0;
522         
523         while(process.state() != QProcess::NotRunning)
524         {
525                 if(m_abort)
526                 {
527                         process.kill();
528                         bAborted = true;
529                         break;
530                 }
531                 if(!process.waitForReadyRead(m_processTimeoutInterval))
532                 {
533                         if(process.state() == QProcess::Running)
534                         {
535                                 process.kill();
536                                 qWarning("x264 process timed out <-- killing!");
537                                 log("\nPROCESS TIMEOUT !!!");
538                                 bTimeout = true;
539                                 break;
540                         }
541                 }
542                 while(process.bytesAvailable() > 0)
543                 {
544                         QList<QByteArray> lines = process.readLine().split('\r');
545                         while(!lines.isEmpty())
546                         {
547                                 QString text = QString::fromUtf8(lines.takeFirst().constData()).simplified();
548                                 int offset = -1;
549                                 if((offset = regExpInt.lastIndexIn(text)) >= 0)
550                                 {
551                                         bool ok1 = false, ok2 = false;
552                                         bool ok3 = false, ok4 = false;
553                                         unsigned int temp1 = regExpInt.cap(1).toUInt(&ok1);
554                                         unsigned int temp2 = regExpInt.cap(2).toUInt(&ok2);
555                                         unsigned int temp3 = regExpInt.cap(3).toUInt(&ok3);
556                                         unsigned int temp4 = regExpInt.cap(4).toUInt(&ok4);
557                                         if(ok1) fSizeW = temp1;
558                                         if(ok2) fSizeH = temp2;
559                                         if(ok3) fpsNom = temp3;
560                                         if(ok4) frames = temp4;
561                                 }
562                                 else if((offset = regExpFrc.lastIndexIn(text)) >= 0)
563                                 {
564                                         bool ok1 = false, ok2 = false;
565                                         bool ok3 = false, ok4 = false, ok5 = false;
566                                         unsigned int temp1 = regExpFrc.cap(1).toUInt(&ok1);
567                                         unsigned int temp2 = regExpFrc.cap(2).toUInt(&ok2);
568                                         unsigned int temp3 = regExpFrc.cap(3).toUInt(&ok3);
569                                         unsigned int temp4 = regExpFrc.cap(4).toUInt(&ok4);
570                                         unsigned int temp5 = regExpFrc.cap(5).toUInt(&ok5);
571                                         if(ok1) fSizeW = temp1;
572                                         if(ok2) fSizeH = temp2;
573                                         if(ok3) fpsNom = temp3;
574                                         if(ok4) fpsDen = temp4;
575                                         if(ok5) frames = temp5;
576                                 }
577                                 if(!text.isEmpty())
578                                 {
579                                         log(text);
580                                 }
581                         }
582                 }
583         }
584
585         process.waitForFinished();
586         if(process.state() != QProcess::NotRunning)
587         {
588                 process.kill();
589                 process.waitForFinished(-1);
590         }
591
592         if(bTimeout || bAborted || process.exitCode() != EXIT_SUCCESS)
593         {
594                 if(!(bTimeout || bAborted))
595                 {
596                         log(tr("\nPROCESS EXITED WITH ERROR CODE: %1").arg(QString::number(process.exitCode())));
597                 }
598                 return false;
599         }
600
601         if(frames == 0)
602         {
603                 log(tr("\nFAILED TO DETERMINE AVS PROPERTIES !!!"));
604                 return false;
605         }
606         
607         log("");
608
609         if((fSizeW > 0) && (fSizeH > 0))
610         {
611                 log(tr("Resolution: %1x%2").arg(QString::number(fSizeW), QString::number(fSizeH)));
612         }
613         if((fpsNom > 0) && (fpsDen > 0))
614         {
615                 log(tr("Frame Rate: %1/%2").arg(QString::number(fpsNom), QString::number(fpsDen)));
616         }
617         if((fpsNom > 0) && (fpsDen == 0))
618         {
619                 log(tr("Frame Rate: %1").arg(QString::number(fpsNom)));
620         }
621         if(frames > 0)
622         {
623                 log(tr("No. Frames: %1").arg(QString::number(frames)));
624         }
625
626         return true;
627 }
628
629 ///////////////////////////////////////////////////////////////////////////////
630 // Misc functions
631 ///////////////////////////////////////////////////////////////////////////////
632
633 void EncodeThread::setStatus(JobStatus newStatus)
634 {
635         if(m_status != newStatus)
636         {
637                 m_status = newStatus;
638                 if((newStatus != JobStatus_Completed) && (newStatus != JobStatus_Failed) && (newStatus != JobStatus_Aborted))
639                 {
640                         setProgress(0);
641                 }
642                 if(newStatus == JobStatus_Failed)
643                 {
644                         setDetails("The job has failed. See log for details!");
645                 }
646                 if(newStatus == JobStatus_Aborted)
647                 {
648                         setDetails("The job was aborted by the user!");
649                 }
650                 emit statusChanged(m_jobId, newStatus);
651         }
652 }
653
654 void EncodeThread::setProgress(unsigned int newProgress)
655 {
656         if(m_progress != newProgress)
657         {
658                 m_progress = newProgress;
659                 emit progressChanged(m_jobId, m_progress);
660         }
661 }
662
663 void EncodeThread::setDetails(const QString &text)
664 {
665         emit detailsChanged(m_jobId, text);
666 }
667
668 bool EncodeThread::startProcess(QProcess &process, const QString &program, const QStringList &args, bool mergeChannels)
669 {
670         static AssignProcessToJobObjectFun AssignProcessToJobObjectPtr = NULL;
671         static CreateJobObjectFun CreateJobObjectPtr = NULL;
672         static SetInformationJobObjectFun SetInformationJobObjectPtr = NULL;
673         
674         QMutexLocker lock(&m_mutex_startProcess);
675         log(commandline2string(program, args) + "\n");
676
677         //Create a new job object, if not done yet
678         if(!m_handle_jobObject)
679         {
680                 if(!CreateJobObjectPtr || !SetInformationJobObjectPtr)
681                 {
682                         QLibrary Kernel32Lib("kernel32.dll");
683                         CreateJobObjectPtr = (CreateJobObjectFun) Kernel32Lib.resolve("CreateJobObjectA");
684                         SetInformationJobObjectPtr = (SetInformationJobObjectFun) Kernel32Lib.resolve("SetInformationJobObject");
685                 }
686                 if(CreateJobObjectPtr && SetInformationJobObjectPtr)
687                 {
688                         m_handle_jobObject = CreateJobObjectPtr(NULL, NULL);
689                         if(m_handle_jobObject == INVALID_HANDLE_VALUE)
690                         {
691                                 m_handle_jobObject = NULL;
692                         }
693                         if(m_handle_jobObject)
694                         {
695                                 JOBOBJECT_EXTENDED_LIMIT_INFORMATION jobExtendedLimitInfo;
696                                 memset(&jobExtendedLimitInfo, 0, sizeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION));
697                                 jobExtendedLimitInfo.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE | JOB_OBJECT_LIMIT_DIE_ON_UNHANDLED_EXCEPTION;
698                                 SetInformationJobObjectPtr(m_handle_jobObject, JobObjectExtendedLimitInformation, &jobExtendedLimitInfo, sizeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION));
699                         }
700                 }
701         }
702
703         //Initialize AssignProcessToJobObject function
704         if(!AssignProcessToJobObjectPtr)
705         {
706                 QLibrary Kernel32Lib("kernel32.dll");
707                 AssignProcessToJobObjectPtr = (AssignProcessToJobObjectFun) Kernel32Lib.resolve("AssignProcessToJobObject");
708         }
709         
710         if(mergeChannels)
711         {
712                 process.setProcessChannelMode(QProcess::MergedChannels);
713                 process.setReadChannel(QProcess::StandardOutput);
714         }
715         else
716         {
717                 process.setProcessChannelMode(QProcess::SeparateChannels);
718                 process.setReadChannel(QProcess::StandardError);
719         }
720
721         process.start(program, args);
722         
723         if(process.waitForStarted())
724         {
725                 if(AssignProcessToJobObjectPtr)
726                 {
727                         AssignProcessToJobObjectPtr(m_handle_jobObject, process.pid()->hProcess);
728                 }
729                 if(!SetPriorityClass(process.pid()->hProcess, BELOW_NORMAL_PRIORITY_CLASS))
730                 {
731                         SetPriorityClass(process.pid()->hProcess, IDLE_PRIORITY_CLASS);
732                 }
733                 
734                 lock.unlock();
735                 return true;
736         }
737
738         log("Process creation has failed :-(");
739         QString errorMsg= process.errorString().trimmed();
740         if(!errorMsg.isEmpty()) log(errorMsg);
741
742         process.kill();
743         process.waitForFinished(-1);
744         return false;
745 }
746
747 QString EncodeThread::commandline2string(const QString &program, const QStringList &arguments)
748 {
749         QString commandline = (program.contains(' ') ? QString("\"%1\"").arg(program) : program);
750         
751         for(int i = 0; i < arguments.count(); i++)
752         {
753                 commandline += (arguments.at(i).contains(' ') ? QString(" \"%1\"").arg(arguments.at(i)) : QString(" %1").arg(arguments.at(i)));
754         }
755
756         return commandline;
757 }