OSDN Git Service

Implemented 2-Pass support + various fixes.
[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
27 #include <QDate>
28 #include <QTime>
29 #include <QFileInfo>
30 #include <QDir>
31 #include <QProcess>
32 #include <QMutex>
33 #include <QLibrary>
34
35 /*
36  * Win32 API definitions
37  */
38 typedef HANDLE (WINAPI *CreateJobObjectFun)(__in_opt LPSECURITY_ATTRIBUTES lpJobAttributes, __in_opt LPCSTR lpName);
39 typedef BOOL (WINAPI *SetInformationJobObjectFun)(__in HANDLE hJob, __in JOBOBJECTINFOCLASS JobObjectInformationClass, __in_bcount(cbJobObjectInformationLength) LPVOID lpJobObjectInformation, __in DWORD cbJobObjectInformationLength);
40 typedef BOOL (WINAPI *AssignProcessToJobObjectFun)(__in HANDLE hJob, __in HANDLE hProcess);
41
42 /*
43  * Static vars
44  */
45 QMutex EncodeThread::m_mutex_startProcess;
46 HANDLE EncodeThread::m_handle_jobObject = NULL;
47
48 ///////////////////////////////////////////////////////////////////////////////
49 // Constructor & Destructor
50 ///////////////////////////////////////////////////////////////////////////////
51
52 EncodeThread::EncodeThread(const QString &sourceFileName, const QString &outputFileName, const OptionsModel *options, const QString &binDir)
53 :
54         m_jobId(QUuid::createUuid()),
55         m_sourceFileName(sourceFileName),
56         m_outputFileName(outputFileName),
57         m_options(new OptionsModel(*options)),
58         m_binDir(binDir)
59 {
60         m_abort = false;
61 }
62
63 EncodeThread::~EncodeThread(void)
64 {
65         X264_DELETE(m_options);
66 }
67
68 ///////////////////////////////////////////////////////////////////////////////
69 // Thread entry point
70 ///////////////////////////////////////////////////////////////////////////////
71
72 void EncodeThread::run(void)
73 {
74         try
75         {
76                 m_progress = 0;
77                 m_status = JobStatus_Starting;
78                 encode();
79         }
80         catch(char *msg)
81         {
82                 log(tr("EXCEPTION ERROR: ").append(QString::fromLatin1(msg)));
83         }
84         catch(...)
85         {
86                 log(tr("EXCEPTION ERROR !!!"));
87         }
88 }
89
90 ///////////////////////////////////////////////////////////////////////////////
91 // Encode functions
92 ///////////////////////////////////////////////////////////////////////////////
93
94 void EncodeThread::encode(void)
95 {
96         Sleep(500);
97
98         //Print some basic info
99         log(tr("Job started at %1, %2.\n").arg(QDate::currentDate().toString(Qt::ISODate), QTime::currentTime().toString( Qt::ISODate)));
100         log(tr("Source file: %1").arg(m_sourceFileName));
101         log(tr("Output file: %1").arg(m_outputFileName));
102         log(tr("\n[Encoder Options]"));
103         log(tr("RC Mode: %1").arg(OptionsModel::rcMode2String(m_options->rcMode())));
104         log(tr("Preset: %1").arg(m_options->preset()));
105         log(tr("Tuning: %1").arg(m_options->tune()));
106         log(tr("Profile: %1").arg(m_options->profile()));
107         log(tr("Custom: %1").arg(m_options->custom().isEmpty() ? tr("(None)") : m_options->custom()));
108         
109         //Detect source info
110         log(tr("\n[Input Properties]"));
111         log(tr("Not implemented yet, sorry ;-)\n"));
112
113         bool ok = false;
114
115         //Run encoding passes
116         if(m_options->rcMode() == OptionsModel::RCMode_2Pass)
117         {
118                 QFileInfo info(m_outputFileName);
119                 QString passLogFile = QString("%1/%2.stats").arg(info.path(), info.completeBaseName());
120
121                 if(QFileInfo(passLogFile).exists())
122                 {
123                         int n = 2;
124                         while(QFileInfo(passLogFile).exists())
125                         {
126                                 passLogFile = QString("%1/%2.%3.stats").arg(info.path(), info.completeBaseName(), QString::number(n++));
127                         }
128                 }
129                 
130                 log("--- PASS 1 ---\n");
131                 ok = runEncodingPass(1, passLogFile);
132
133                 if(m_abort)
134                 {
135                         log("\nPROCESS ABORTED BY USER !!!");
136                         setStatus(JobStatus_Aborted);
137                         return;
138                 }
139                 else if(!ok)
140                 {
141                         setStatus(JobStatus_Failed);
142                         return;
143                 }
144
145                 log("\n--- PASS 2 ---\n");
146                 ok = runEncodingPass(2, passLogFile);
147
148                 if(m_abort)
149                 {
150                         log("\nPROCESS ABORTED BY USER !!!");
151                         setStatus(JobStatus_Aborted);
152                         return;
153                 }
154                 else if(!ok)
155                 {
156                         setStatus(JobStatus_Failed);
157                         return;
158                 }
159         }
160         else
161         {
162                 log("--- ENCODING ---\n");
163                 ok = runEncodingPass();
164
165                 if(m_abort)
166                 {
167                         log("\nPROCESS ABORTED BY USER !!!");
168                         setStatus(JobStatus_Aborted);
169                         return;
170                 }
171                 else if(!ok)
172                 {
173                         setStatus(JobStatus_Failed);
174                         return;
175                 }
176         }
177
178         log(tr("\nJob finished at %1, %2.\n").arg(QDate::currentDate().toString(Qt::ISODate), QTime::currentTime().toString( Qt::ISODate)));
179         setStatus(JobStatus_Completed);
180 }
181
182 bool EncodeThread::runEncodingPass(int pass, const QString &passLogFile)
183 {
184         QProcess process;
185         QStringList cmdLine = buildCommandLine(pass, passLogFile);
186
187         log("Creating process:");
188         if(!startProcess(process, QString("%1/x264.exe").arg(m_binDir), cmdLine))
189         {
190                 return false;;
191         }
192
193         QRegExp regExpIndexing("indexing.+\\[(\\d+)\\.\\d+%\\]");
194         QRegExp regExpProgress("\\[(\\d+)\\.\\d+%\\].+frames");
195         
196         bool bTimeout = false;
197         bool bAborted = false;
198
199         while(process.state() != QProcess::NotRunning)
200         {
201                 if(m_abort)
202                 {
203                         process.kill();
204                         bAborted = true;
205                         break;
206                 }
207                 if(!process.waitForReadyRead(m_processTimeoutInterval))
208                 {
209                         if(process.state() == QProcess::Running)
210                         {
211                                 process.kill();
212                                 qWarning("x264 process timed out <-- killing!");
213                                 log("\nPROCESS TIMEOUT !!!");
214                                 bTimeout = true;
215                                 break;
216                         }
217                 }
218                 while(process.bytesAvailable() > 0)
219                 {
220                         QList<QByteArray> lines = process.readLine().split('\r');
221                         while(!lines.isEmpty())
222                         {
223                                 QString text = QString::fromUtf8(lines.takeFirst().constData()).simplified();
224                                 int offset = -1;
225                                 if((offset = regExpProgress.lastIndexIn(text)) >= 0)
226                                 {
227                                         bool ok = false;
228                                         unsigned int progress = regExpProgress.cap(1).toUInt(&ok);
229                                         if(ok) setProgress(progress);
230                                         setStatus((pass == 2) ? JobStatus_Running_Pass2 : ((pass == 1) ? JobStatus_Running_Pass1 : JobStatus_Running));
231                                         setDetails(text.mid(offset).trimmed());
232                                 }
233                                 else if((offset = regExpIndexing.lastIndexIn(text)) >= 0)
234                                 {
235                                         bool ok = false;
236                                         unsigned int progress = regExpIndexing.cap(1).toUInt(&ok);
237                                         if(ok) setProgress(progress);
238                                         setStatus(JobStatus_Indexing);
239                                         setDetails(text.mid(offset).trimmed());
240                                 }
241                                 else if(!text.isEmpty())
242                                 {
243                                         log(text);
244                                 }
245                         }
246                 }
247         }
248
249         process.waitForFinished();
250         if(process.state() != QProcess::NotRunning)
251         {
252                 process.kill();
253                 process.waitForFinished(-1);
254         }
255
256         if(bTimeout || bAborted || process.exitCode() != EXIT_SUCCESS)
257         {
258                 return false;
259         }
260         
261         setStatus((pass == 2) ? JobStatus_Running_Pass2 : ((pass == 1) ? JobStatus_Running_Pass1 : JobStatus_Running));
262         setProgress(100);
263         return true;
264 }
265
266 QStringList EncodeThread::buildCommandLine(int pass, const QString &passLogFile)
267 {
268         QStringList cmdLine;
269
270         switch(m_options->rcMode())
271         {
272         case OptionsModel::RCMode_CRF:
273                 cmdLine << "--crf" << QString::number(m_options->quantizer());
274                 break;
275         case OptionsModel::RCMode_CQ:
276                 cmdLine << "--qp" << QString::number(m_options->quantizer());
277                 break;
278         case OptionsModel::RCMode_2Pass:
279         case OptionsModel::RCMode_ABR:
280                 cmdLine << "--bitrate" << QString::number(m_options->bitrate());
281                 break;
282         default:
283                 throw "Bad rate-control mode !!!";
284                 break;
285         }
286         
287         if((pass == 1) || (pass == 2))
288         {
289                 cmdLine << "--pass" << QString::number(pass);
290                 cmdLine << "--stats" << QDir::toNativeSeparators(passLogFile);
291         }
292
293         if(m_options->tune().compare("none", Qt::CaseInsensitive))
294         {
295                 cmdLine << "--tune" << m_options->tune().toLower();
296         }
297         
298         cmdLine << "--preset" << m_options->preset().toLower();
299
300         if(!m_options->custom().isEmpty())
301         {
302                 //FIXME: Handle custom parameters that contain spaces!
303                 cmdLine.append(m_options->custom().split(" "));
304         }
305
306         cmdLine << "--output" << QDir::toNativeSeparators(m_outputFileName);
307         cmdLine << m_sourceFileName;
308
309         return cmdLine;
310 }
311
312 ///////////////////////////////////////////////////////////////////////////////
313 // Misc functions
314 ///////////////////////////////////////////////////////////////////////////////
315
316 void EncodeThread::setStatus(JobStatus newStatus)
317 {
318         if(m_status != newStatus)
319         {
320                 m_status = newStatus;
321                 emit statusChanged(m_jobId, newStatus);
322                 setProgress(0);
323         }
324 }
325
326 void EncodeThread::setProgress(unsigned int newProgress)
327 {
328         if(m_progress != newProgress)
329         {
330                 m_progress = newProgress;
331                 emit progressChanged(m_jobId, m_progress);
332         }
333 }
334
335 void EncodeThread::setDetails(const QString &text)
336 {
337         emit detailsChanged(m_jobId, text);
338 }
339
340 bool EncodeThread::startProcess(QProcess &process, const QString &program, const QStringList &args)
341 {
342         static AssignProcessToJobObjectFun AssignProcessToJobObjectPtr = NULL;
343         
344         QMutexLocker lock(&m_mutex_startProcess);
345         log(commandline2string(program, args) + "\n");
346         
347         if(!AssignProcessToJobObjectPtr)
348         {
349                 QLibrary Kernel32Lib("kernel32.dll");
350                 AssignProcessToJobObjectPtr = (AssignProcessToJobObjectFun) Kernel32Lib.resolve("AssignProcessToJobObject");
351         }
352         
353         process.setProcessChannelMode(QProcess::MergedChannels);
354         process.setReadChannel(QProcess::StandardOutput);
355         process.start(program, args);
356         
357         if(process.waitForStarted())
358         {
359                 
360                 if(AssignProcessToJobObjectPtr)
361                 {
362                         AssignProcessToJobObjectPtr(m_handle_jobObject, process.pid()->hProcess);
363                 }
364                 if(!SetPriorityClass(process.pid()->hProcess, BELOW_NORMAL_PRIORITY_CLASS))
365                 {
366                         SetPriorityClass(process.pid()->hProcess, IDLE_PRIORITY_CLASS);
367                 }
368                 
369                 lock.unlock();
370                 return true;
371         }
372
373         log("Process creation has failed :-(");
374         QString errorMsg= process.errorString().trimmed();
375         if(!errorMsg.isEmpty()) log(errorMsg);
376
377         process.kill();
378         process.waitForFinished(-1);
379         return false;
380 }
381
382 QString EncodeThread::commandline2string(const QString &program, const QStringList &arguments)
383 {
384         QString commandline = (program.contains(' ') ? QString("\"%1\"").arg(program) : program);
385         
386         for(int i = 0; i < arguments.count(); i++)
387         {
388                 commandline += (arguments.at(i).contains(' ') ? QString(" \"%1\"").arg(arguments.at(i)) : QString(" %1").arg(arguments.at(i)));
389         }
390
391         return commandline;
392 }