OSDN Git Service

Make it possible to move jobs up/down the in the queue. Hold CTRL while pressing...
[x264-launcher/x264-launcher.git] / src / encoder_abstract.cpp
1 ///////////////////////////////////////////////////////////////////////////////
2 // Simple x264 Launcher
3 // Copyright (C) 2004-2014 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 "encoder_abstract.h"
23
24 #include "global.h"
25 #include "model_options.h"
26 #include "model_preferences.h"
27 #include "model_sysinfo.h"
28 #include "model_status.h"
29 #include "source_abstract.h"
30 #include "binaries.h"
31
32 #include <QProcess>
33 #include <QDir>
34 #include <QTextCodec>
35 #include <QSemaphore>
36 #include <QDate>
37 #include <QTime>
38 #include <QThread>
39 #include <QLocale>
40
41 // ------------------------------------------------------------
42 // Helper Macros
43 // ------------------------------------------------------------
44
45 #define APPEND_AND_CLEAR(LIST, STR) do \
46 { \
47         if(!((STR).isEmpty())) \
48         { \
49                 (LIST) << (STR); \
50                 (STR).clear(); \
51         } \
52 } \
53 while(0)
54
55 // ------------------------------------------------------------
56 // Constructor & Destructor
57 // ------------------------------------------------------------
58
59 AbstractEncoder::AbstractEncoder(JobObject *jobObject, const OptionsModel *options, const SysinfoModel *const sysinfo, const PreferencesModel *const preferences, JobStatus &jobStatus, volatile bool *abort, volatile bool *pause, QSemaphore *semaphorePause, const QString &sourceFile, const QString &outputFile)
60 :
61         AbstractTool(jobObject, options, sysinfo, preferences, jobStatus, abort, pause, semaphorePause),
62         m_sourceFile(sourceFile),
63         m_outputFile(outputFile),
64         m_indexFile(QString("%1/~%2.ffindex").arg(QDir::tempPath(), stringToHash(m_sourceFile)))
65 {
66         /*Nothing to do here*/
67 }
68
69 AbstractEncoder::~AbstractEncoder(void)
70 {
71         /*Nothing to do here*/
72 }
73
74 // ------------------------------------------------------------
75 // Encoding Functions
76 // ------------------------------------------------------------
77
78 bool AbstractEncoder::runEncodingPass(AbstractSource* pipedSource, const QString outputFile, const unsigned int &frames, const int &pass, const QString &passLogFile)
79 {
80         QProcess processEncode, processInput;
81         
82         if(pipedSource)
83         {
84                 pipedSource->createProcess(processEncode, processInput);
85         }
86
87         QStringList cmdLine_Encode;
88         buildCommandLine(cmdLine_Encode, (pipedSource != NULL), frames, m_indexFile, pass, passLogFile);
89
90         log("Creating encoder process:");
91         if(!startProcess(processEncode, ENC_BINARY(m_sysinfo, m_options), cmdLine_Encode))
92         {
93                 return false;
94         }
95
96         QList<QRegExp*> patterns;
97         runEncodingPass_init(patterns);
98         
99         bool bTimeout = false;
100         bool bAborted = false;
101
102         unsigned int last_progress = UINT_MAX;
103         unsigned int last_indexing = UINT_MAX;
104         qint64 size_estimate = 0I64;
105
106         //Main processing loop
107         while(processEncode.state() != QProcess::NotRunning)
108         {
109                 unsigned int waitCounter = 0;
110
111                 //Wait until new output is available
112                 forever
113                 {
114                         if(*m_abort)
115                         {
116                                 processEncode.kill();
117                                 processInput.kill();
118                                 bAborted = true;
119                                 break;
120                         }
121                         if((*m_pause) && (processEncode.state() == QProcess::Running))
122                         {
123                                 JobStatus previousStatus = m_jobStatus;
124                                 setStatus(JobStatus_Paused);
125                                 log(tr("Job paused by user at %1, %2.").arg(QDate::currentDate().toString(Qt::ISODate), QTime::currentTime().toString( Qt::ISODate)));
126                                 bool ok[2] = {false, false};
127                                 QProcess *proc[2] = { &processEncode, &processInput };
128                                 ok[0] = x264_suspendProcess(proc[0], true);
129                                 ok[1] = x264_suspendProcess(proc[1], true);
130                                 while(*m_pause) m_semaphorePause->tryAcquire(1, 5000);
131                                 while(m_semaphorePause->tryAcquire(1, 0));
132                                 ok[0] = x264_suspendProcess(proc[0], false);
133                                 ok[1] = x264_suspendProcess(proc[1], false);
134                                 if(!(*m_abort)) setStatus(previousStatus);
135                                 log(tr("Job resumed by user at %1, %2.").arg(QDate::currentDate().toString(Qt::ISODate), QTime::currentTime().toString( Qt::ISODate)));
136                                 waitCounter = 0;
137                                 continue;
138                         }
139                         if(!processEncode.waitForReadyRead(m_processTimeoutInterval))
140                         {
141                                 if(processEncode.state() == QProcess::Running)
142                                 {
143                                         if(++waitCounter > m_processTimeoutMaxCounter)
144                                         {
145                                                 if(m_preferences->getAbortOnTimeout())
146                                                 {
147                                                         processEncode.kill();
148                                                         qWarning("encoder process timed out <-- killing!");
149                                                         log("\nPROCESS TIMEOUT !!!");
150                                                         bTimeout = true;
151                                                         break;
152                                                 }
153                                         }
154                                         else if(waitCounter == m_processTimeoutWarning)
155                                         {
156                                                 unsigned int timeOut = (waitCounter * m_processTimeoutInterval) / 1000U;
157                                                 log(tr("Warning: encoder did not respond for %1 seconds, potential deadlock...").arg(QString::number(timeOut)));
158                                         }
159                                         continue;
160                                 }
161                         }
162                         if((*m_abort) || ((*m_pause) && (processEncode.state() == QProcess::Running)))
163                         {
164                                 continue;
165                         }
166                         break;
167                 }
168                 
169                 //Exit main processing loop now?
170                 if(bAborted || bTimeout)
171                 {
172                         break;
173                 }
174
175                 //Process all output
176                 PROCESS_PENDING_LINES(processEncode, runEncodingPass_parseLine, patterns, pass);
177         }
178         
179         if(!(bTimeout || bAborted))
180         {
181                 PROCESS_PENDING_LINES(processEncode, runEncodingPass_parseLine, patterns, pass);
182         }
183
184         processEncode.waitForFinished(5000);
185         if(processEncode.state() != QProcess::NotRunning)
186         {
187                 qWarning("Encoder process still running, going to kill it!");
188                 processEncode.kill();
189                 processEncode.waitForFinished(-1);
190         }
191         
192         if(pipedSource)
193         {
194                 processInput.waitForFinished(5000);
195                 if(processInput.state() != QProcess::NotRunning)
196                 {
197                         qWarning("Input process still running, going to kill it!");
198                         processInput.kill();
199                         processInput.waitForFinished(-1);
200                 }
201                 if(!(bTimeout || bAborted))
202                 {
203                         pipedSource->flushProcess(processInput);
204                 }
205         }
206
207         while(!patterns.isEmpty())
208         {
209                 QRegExp *pattern = patterns.takeFirst();
210                 X264_DELETE(pattern);
211         }
212
213         if(bTimeout || bAborted || processEncode.exitCode() != EXIT_SUCCESS)
214         {
215                 if(!(bTimeout || bAborted))
216                 {
217                         const int exitCode = processEncode.exitCode();
218                         if((exitCode < 0) || (exitCode >= 32))
219                         {
220                                 log(tr("\nFATAL ERROR: The encoder process has *crashed* -> your encode probably is *incomplete* !!!"));
221                                 log(tr("Note that this indicates a bug in the current encoder, *not* in Simple x264/x265 Launcher."));
222                         }
223                         log(tr("\nPROCESS EXITED WITH ERROR CODE: %1").arg(QString::number(exitCode)));
224                 }
225                 processEncode.close();
226                 processInput.close();
227                 return false;
228         }
229
230         QThread::yieldCurrentThread();
231
232         QFileInfo completedFileInfo(m_outputFile);
233         const qint64 finalSize = (completedFileInfo.exists() && completedFileInfo.isFile()) ? completedFileInfo.size() : 0;
234         QLocale locale(QLocale::English);
235         log(tr("Final file size is %1 bytes.").arg(sizeToString(finalSize)));
236
237         switch(pass)
238         {
239         case 1:
240                 setStatus(JobStatus_Running_Pass1);
241                 setDetails(tr("First pass completed. Preparing for second pass..."));
242                 break;
243         case 2:
244                 setStatus(JobStatus_Running_Pass2);
245                 setDetails(tr("Second pass completed successfully. Final size is %1.").arg(sizeToString(finalSize)));
246                 break;
247         default:
248                 setStatus(JobStatus_Running);
249                 setDetails(tr("Encode completed successfully. Final size is %1.").arg(sizeToString(finalSize)));
250                 break;
251         }
252
253         setProgress(100);
254         processEncode.close();
255         processInput.close();
256         return true;
257 }
258
259 // ------------------------------------------------------------
260 // Utilities
261 // ------------------------------------------------------------
262
263 QStringList AbstractEncoder::splitParams(const QString &params, const QString &sourceFile, const QString &outputFile)
264 {
265         QStringList list; 
266         bool ignoreWhitespaces = false;
267         QString temp;
268
269         for(int i = 0; i < params.length(); i++)
270         {
271                 const QChar c = params.at(i);
272
273                 if(c == QChar::fromLatin1('"'))
274                 {
275                         ignoreWhitespaces = (!ignoreWhitespaces);
276                         continue;
277                 }
278                 else if((!ignoreWhitespaces) && (c == QChar::fromLatin1(' ')))
279                 {
280                         APPEND_AND_CLEAR(list, temp);
281                         continue;
282                 }
283                 
284                 temp.append(c);
285         }
286         
287         APPEND_AND_CLEAR(list, temp);
288
289         list.replaceInStrings("$(INPUT)",  QDir::toNativeSeparators(sourceFile), Qt::CaseInsensitive);
290         list.replaceInStrings("$(OUTPUT)", QDir::toNativeSeparators(outputFile), Qt::CaseInsensitive);
291
292         return list;
293 }
294
295 qint64 AbstractEncoder::estimateSize(const QString &fileName, const int &progress)
296 {
297         QFileInfo fileInfo(fileName);
298         if((progress >= 3) && fileInfo.exists() && fileInfo.isFile())
299         {
300                 qint64 currentSize = QFileInfo(fileName).size();
301                 qint64 estimatedSize = (currentSize * 100I64) / static_cast<qint64>(progress);
302                 return estimatedSize;
303         }
304         return 0I64;
305 }
306
307 QString AbstractEncoder::sizeToString(qint64 size)
308 {
309         static char *prefix[5] = {"Byte", "KB", "MB", "GB", "TB"};
310
311         if(size > 1024I64)
312         {
313                 qint64 estimatedSize = size;
314                 qint64 remainderSize = 0I64;
315
316                 int prefixIdx = 0;
317                 while((estimatedSize > 1024I64) && (prefixIdx < 4))
318                 {
319                         remainderSize = estimatedSize % 1024I64;
320                         estimatedSize = estimatedSize / 1024I64;
321                         prefixIdx++;
322                 }
323                         
324                 double value = static_cast<double>(estimatedSize) + (static_cast<double>(remainderSize) / 1024.0);
325                 return QString().sprintf((value < 10.0) ? "%.2f %s" : "%.1f %s", value, prefix[prefixIdx]);
326         }
327
328         return tr("N/A");
329 }
330
331 // ------------------------------------------------------------
332 // Encoder Info
333 // ------------------------------------------------------------
334
335 const AbstractEncoderInfo& AbstractEncoder::getEncoderInfo(void)
336 {
337         THROW("[getEncoderInfo] This function must be overwritten in sub-classes!");
338 }