OSDN Git Service

Bump version.
[x264-launcher/x264-launcher.git] / src / encoder_x265.cpp
1 ///////////////////////////////////////////////////////////////////////////////
2 // Simple x264 Launcher
3 // Copyright (C) 2004-2016 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_x265.h"
23
24 //Internal
25 #include "global.h"
26 #include "model_options.h"
27 #include "model_status.h"
28 #include "mediainfo.h"
29 #include "model_sysinfo.h"
30
31 //MUtils
32 #include <MUtils/Exception.h>
33
34 //Qt
35 #include <QStringList>
36 #include <QDir>
37 #include <QRegExp>
38
39 //x265 version info
40 static const unsigned int VERSION_X265_MINIMUM_VER =  18;
41 static const unsigned int VERSION_X265_MINIMUM_REV = 187;
42
43 // ------------------------------------------------------------
44 // Helper Macros
45 // ------------------------------------------------------------
46
47 #define X265_UPDATE_PROGRESS(X) do \
48 { \
49         bool ok[2] = { false, false }; \
50         unsigned int progressInt = (X)->cap(1).toUInt(&ok[0]); \
51         unsigned int progressFrc = (X)->cap(2).toUInt(&ok[1]); \
52         setStatus((pass == 2) ? JobStatus_Running_Pass2 : ((pass == 1) ? JobStatus_Running_Pass1 : JobStatus_Running)); \
53         if(ok[0] && ok[1]) \
54         { \
55                 const double progress = (double(progressInt) / 100.0) + (double(progressFrc) / 1000.0); \
56                 if(!qFuzzyCompare(progress, last_progress)) \
57                 { \
58                         setProgress(floor(progress * 100.0)); \
59                         size_estimate = qFuzzyIsNull(size_estimate) ? estimateSize(m_outputFile, progress) : ((0.667 * size_estimate) + (0.333 * estimateSize(m_outputFile, progress))); \
60                         last_progress = progress; \
61                 } \
62         } \
63         setDetails(tr("%1, est. file size %2").arg(line.mid(offset).trimmed(), sizeToString(qRound64(size_estimate)))); \
64 } \
65 while(0)
66
67 #define REMOVE_CUSTOM_ARG(LIST, ITER, FLAG, PARAM) do \
68 { \
69         if(ITER != LIST.end()) \
70         { \
71                 if((*ITER).compare(PARAM, Qt::CaseInsensitive) == 0) \
72                 { \
73                         log(tr("WARNING: Custom parameter \"" PARAM "\" will be ignored in Pipe'd mode!\n")); \
74                         ITER = LIST.erase(ITER); \
75                         if(ITER != LIST.end()) \
76                         { \
77                                 if(!((*ITER).startsWith("--", Qt::CaseInsensitive))) ITER = LIST.erase(ITER); \
78                         } \
79                         FLAG = true; \
80                 } \
81         } \
82 } \
83 while(0)
84
85 // ------------------------------------------------------------
86 // Encoder Info
87 // ------------------------------------------------------------
88
89 class X265EncoderInfo : public AbstractEncoderInfo
90 {
91 public:
92         virtual QFlags<OptionsModel::EncVariant> getVariants(void) const
93         {
94                 QFlags<OptionsModel::EncVariant> variants;
95                 variants |= OptionsModel::EncVariant_8Bit;
96                 variants |= OptionsModel::EncVariant_10Bit;
97                 variants |= OptionsModel::EncVariant_12Bit;
98                 return variants;
99         }
100
101         virtual QStringList getTunings(void) const
102         {
103                 QStringList tunings;
104                 tunings << "Grain" << "PSNR" << "SSIM" << "FastDecode" << "ZeroLatency";
105                 return tunings;
106         }
107
108         virtual QStringList getPresets(void) const
109         {
110                 QStringList presets;
111                 presets << "ultrafast" << "superfast" << "veryfast" << "faster"   << "fast";
112                 presets << "medium"    << "slow"      << "slower"   << "veryslow" << "placebo";
113                 return presets;
114         }
115
116         virtual QStringList getProfiles(const OptionsModel::EncVariant &variant) const
117         {
118                 QStringList profiles;
119                 switch(variant)
120                 {
121                 case OptionsModel::EncVariant_8Bit:
122                         profiles << "main" << "main-intra" << "mainstillpicture" << "main444-8" << "main444-intra" << "main444-stillpicture";
123                         break;
124                 case OptionsModel::EncVariant_10Bit:
125                         profiles << "main10" << "main10-intra" << "main422-10" << "main422-10-intra" << "main444-10" << "main444-10-intra";
126                         break;
127                 case OptionsModel::EncVariant_12Bit:
128                         profiles << "main12" << "main12-intra" << "main422-12" << "main422-12-intra" << "main444-12" << "main444-12-intra";
129                         break;
130                 }
131                 return profiles;
132         }
133
134         virtual QStringList supportedOutputFormats(void) const
135         {
136                 QStringList extLst;
137                 extLst << "hevc";
138                 return extLst;
139         }
140
141         virtual bool isRCModeSupported(const OptionsModel::RCMode &rcMode) const
142         {
143                 switch(rcMode)
144                 {
145                 case OptionsModel::RCMode_CRF:
146                 case OptionsModel::RCMode_CQ:
147                 case OptionsModel::RCMode_2Pass:
148                 case OptionsModel::RCMode_ABR:
149                         return true;
150                 default:
151                         return false;
152                 }
153         }
154
155         virtual bool isInputTypeSupported(const int format) const
156         {
157                 switch(format)
158                 {
159                 case MediaInfo::FILETYPE_YUV4MPEG2:
160                         return true;
161                 default:
162                         return false;
163                 }
164         }
165
166         virtual QString getBinaryPath(const SysinfoModel *sysinfo, const OptionsModel::EncArch &encArch, const OptionsModel::EncVariant &encVariant) const
167         {
168                 QString arch, variant;
169                 switch(encArch)
170                 {
171                         case OptionsModel::EncArch_x86_32: arch = "x86"; break;
172                         case OptionsModel::EncArch_x86_64: arch = "x64"; break;
173                         default: MUTILS_THROW("Unknown encoder arch!");
174                 }
175                 switch(encVariant)
176                 {
177                         case OptionsModel::EncVariant_8Bit:  variant = "8bit";  break;
178                         case OptionsModel::EncVariant_10Bit: variant = "10bit"; break;
179                         case OptionsModel::EncVariant_12Bit: variant = "12bit"; break;
180                         default: MUTILS_THROW("Unknown encoder arch!");
181                 }
182                 return QString("%1/toolset/%2/x265_%3_%2.exe").arg(sysinfo->getAppPath(), arch, variant);
183         }
184 };
185
186 static const X265EncoderInfo s_x265EncoderInfo;
187
188 const AbstractEncoderInfo &X265Encoder::getEncoderInfo(void)
189 {
190         return s_x265EncoderInfo;
191 }
192
193 // ------------------------------------------------------------
194 // Constructor & Destructor
195 // ------------------------------------------------------------
196
197 X265Encoder::X265Encoder(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)
198 :
199         AbstractEncoder(jobObject, options, sysinfo, preferences, jobStatus, abort, pause, semaphorePause, sourceFile, outputFile)
200 {
201         if(options->encType() != OptionsModel::EncType_X265)
202         {
203                 MUTILS_THROW("Invalid encoder type!");
204         }
205 }
206
207 X265Encoder::~X265Encoder(void)
208 {
209         /*Nothing to do here*/
210 }
211
212 QString X265Encoder::getName(void) const
213 {
214         QString arch, variant;
215         switch(m_options->encArch())
216         {
217                 case OptionsModel::EncArch_x86_32: arch = "x86"; break;
218                 case OptionsModel::EncArch_x86_64: arch = "x64"; break;
219                 default: MUTILS_THROW("Unknown encoder arch!");
220         }
221         switch(m_options->encVariant())
222         {
223                 case OptionsModel::EncVariant_8Bit:  variant = "8-Bit";  break;
224                 case OptionsModel::EncVariant_10Bit: variant = "10-Bit"; break;
225                 case OptionsModel::EncVariant_12Bit: variant = "12-Bit"; break;
226                 default: MUTILS_THROW("Unknown encoder arch!");
227         }
228         return QString("x265 (H.265/HEVC), %1, %2").arg(arch, variant);
229 }
230
231 // ------------------------------------------------------------
232 // Check Version
233 // ------------------------------------------------------------
234
235 void X265Encoder::checkVersion_init(QList<QRegExp*> &patterns, QStringList &cmdLine)
236 {
237         cmdLine << "--version";
238         patterns << new QRegExp("\\bHEVC\\s+encoder\\s+version\\s+(\\d)\\.(\\d+)\\+(\\d+)\\b", Qt::CaseInsensitive);
239         patterns << new QRegExp("\\bHEVC\\s+encoder\\s+version\\s+(\\d)\\.(\\d+)\\b", Qt::CaseInsensitive);
240 }
241
242 void X265Encoder::checkVersion_parseLine(const QString &line, QList<QRegExp*> &patterns, unsigned int &core, unsigned int &build, bool &modified)
243 {
244         int offset = -1;
245
246         if((offset = patterns[0]->lastIndexIn(line)) >= 0)
247         {
248                 bool ok[3] = { false, false, false };
249                 unsigned int temp[3];
250                 temp[0] = patterns[0]->cap(1).toUInt(&ok[0]);
251                 temp[1] = patterns[0]->cap(2).toUInt(&ok[1]);
252                 temp[2] = patterns[0]->cap(3).toUInt(&ok[2]);
253                 if(ok[0] && ok[1])
254                 {
255                         core = (10 * temp[0]) + temp[1];
256                 }
257                 if(ok[2]) build = temp[2];
258         }
259         else if((offset = patterns[1]->lastIndexIn(line)) >= 0)
260         {
261                 bool ok[2] = { false, false };
262                 unsigned int temp[2];
263                 temp[0] = patterns[1]->cap(1).toUInt(&ok[0]);
264                 temp[1] = patterns[1]->cap(2).toUInt(&ok[1]);
265                 if(ok[0] && ok[1])
266                 {
267                         core = (10 * temp[0]) + temp[1];
268                 }
269                 build = 0;
270         }
271
272         if(!line.isEmpty())
273         {
274                 log(line);
275         }
276 }
277
278 QString X265Encoder::printVersion(const unsigned int &revision, const bool &modified)
279 {
280         unsigned int core, build;
281         splitRevision(revision, core, build);
282
283         return tr("x265 version: %1.%2+%3").arg(QString::number(core / 10), QString::number(core % 10), QString::number(build));
284 }
285
286 bool X265Encoder::isVersionSupported(const unsigned int &revision, const bool &modified)
287 {
288         unsigned int core, build;
289         splitRevision(revision, core, build);
290
291         if((core < VERSION_X265_MINIMUM_VER) || ((core == VERSION_X265_MINIMUM_VER) && (build < VERSION_X265_MINIMUM_REV)))
292         {
293                 log(tr("\nERROR: Your version of x265 is too old! (Minimum required revision is 0.%1+%2)").arg(QString::number(VERSION_X265_MINIMUM_VER), QString::number(VERSION_X265_MINIMUM_REV)));
294                 return false;
295         }
296         else if(core > VERSION_X265_MINIMUM_VER)
297         {
298                 log(tr("\nWARNING: Your version of x265 is newer than the latest tested version, take care!"));
299                 log(tr("This application works best with x265 version %1.%2. Newer versions may work or not.").arg(QString::number(VERSION_X265_MINIMUM_VER / 10), QString::number(VERSION_X265_MINIMUM_VER % 10)));
300         }
301         
302         return true;
303 }
304
305 // ------------------------------------------------------------
306 // Encoding Functions
307 // ------------------------------------------------------------
308
309 void X265Encoder::buildCommandLine(QStringList &cmdLine, const bool &usePipe, const unsigned int &frames, const QString &indexFile, const int &pass, const QString &passLogFile)
310 {
311         double crf_int = 0.0, crf_frc = 0.0;
312
313         switch(m_options->rcMode())
314         {
315         case OptionsModel::RCMode_CQ:
316                 cmdLine << "--qp" << QString::number(qRound(m_options->quantizer()));
317                 break;
318         case OptionsModel::RCMode_CRF:
319                 crf_frc = modf(m_options->quantizer(), &crf_int);
320                 cmdLine << "--crf" << QString("%1.%2").arg(QString::number(qRound(crf_int)), QString::number(qRound(crf_frc * 10.0)));
321                 break;
322         case OptionsModel::RCMode_2Pass:
323         case OptionsModel::RCMode_ABR:
324                 cmdLine << "--bitrate" << QString::number(m_options->bitrate());
325                 break;
326         default:
327                 MUTILS_THROW("Bad rate-control mode !!!");
328                 break;
329         }
330         
331         if((pass == 1) || (pass == 2))
332         {
333                 cmdLine << "--pass" << QString::number(pass);
334                 cmdLine << "--stats" << QDir::toNativeSeparators(passLogFile);
335         }
336
337         const QString preset = m_options->preset().simplified().toLower();
338         if(!preset.isEmpty())
339         {
340                 if(preset.compare(QString::fromLatin1(OptionsModel::SETTING_UNSPECIFIED), Qt::CaseInsensitive) != 0)
341                 {
342                         cmdLine << "--preset" << preset;
343                 }
344         }
345
346         const QString tune = m_options->tune().simplified().toLower();
347         if(!tune.isEmpty())
348         {
349                 if(tune.compare(QString::fromLatin1(OptionsModel::SETTING_UNSPECIFIED), Qt::CaseInsensitive) != 0)
350                 {
351                         cmdLine << "--tune" << tune;
352                 }
353         }
354
355         const QString profile = m_options->profile().simplified().toLower();
356         if(!profile.isEmpty())
357         {
358                 if(profile.compare(QString::fromLatin1(OptionsModel::PROFILE_UNRESTRICTED), Qt::CaseInsensitive) != 0)
359                 {
360                         cmdLine << "--profile" << profile;
361                 }
362         }
363
364         if(!m_options->customEncParams().isEmpty())
365         {
366                 QStringList customArgs = splitParams(m_options->customEncParams(), m_sourceFile, m_outputFile);
367                 if(usePipe)
368                 {
369                         QStringList::iterator i = customArgs.begin();
370                         while(i != customArgs.end())
371                         {
372                                 bool bModified = false;
373                                 REMOVE_CUSTOM_ARG(customArgs, i, bModified, "--fps");
374                                 REMOVE_CUSTOM_ARG(customArgs, i, bModified, "--frames");
375                                 if(!bModified) i++;
376                         }
377                 }
378                 cmdLine.append(customArgs);
379         }
380
381         cmdLine << "--output" << QDir::toNativeSeparators(m_outputFile);
382         
383         if(usePipe)
384         {
385                 if(frames < 1) MUTILS_THROW("Frames not set!");
386                 cmdLine << "--frames" << QString::number(frames);
387                 cmdLine << "--y4m" << "-";
388         }
389         else
390         {
391                 cmdLine << QDir::toNativeSeparators(m_sourceFile);
392         }
393 }
394
395 void X265Encoder::runEncodingPass_init(QList<QRegExp*> &patterns)
396 {
397         patterns << new QRegExp("\\[(\\d+)\\.(\\d+)%\\].+frames");   //regExpProgress
398         patterns << new QRegExp("indexing.+\\[(\\d+)\\.(\\d+)%\\]"); //regExpIndexing
399         patterns << new QRegExp("^(\\d+) frames:"); //regExpFrameCnt
400         patterns << new QRegExp("\\[\\s*(\\d+)\\.(\\d+)%\\]\\s+(\\d+)/(\\d+)\\s(\\d+).(\\d+)\\s(\\d+).(\\d+)\\s+(\\d+):(\\d+):(\\d+)\\s+(\\d+):(\\d+):(\\d+)"); //regExpModified
401 }
402
403 void X265Encoder::runEncodingPass_parseLine(const QString &line, QList<QRegExp*> &patterns, const int &pass, double &last_progress, double &size_estimate)
404 {
405         int offset = -1;
406         if((offset = patterns[0]->lastIndexIn(line)) >= 0)
407         {
408                 X265_UPDATE_PROGRESS(patterns[0]);
409         }
410         else if((offset = patterns[1]->lastIndexIn(line)) >= 0)
411         {
412                 bool ok = false;
413                 unsigned int progress = patterns[1]->cap(1).toUInt(&ok);
414                 setStatus(JobStatus_Indexing);
415                 if(ok)
416                 {
417                         setProgress(progress);
418                 }
419                 setDetails(line.mid(offset).trimmed());
420         }
421         else if((offset = patterns[2]->lastIndexIn(line)) >= 0)
422         {
423                 setStatus((pass == 2) ? JobStatus_Running_Pass2 : ((pass == 1) ? JobStatus_Running_Pass1 : JobStatus_Running));
424                 setDetails(line.mid(offset).trimmed());
425         }
426         else if((offset = patterns[3]->lastIndexIn(line)) >= 0)
427         {
428                 X265_UPDATE_PROGRESS(patterns[3]);
429         }
430         else if(!line.isEmpty())
431         {
432                 log(line);
433         }
434 }