OSDN Git Service

Some improvements to version number handling.
[x264-launcher/x264-launcher.git] / src / encoder_x265.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_x265.h"
23
24 #include "global.h"
25 #include "model_options.h"
26 #include "model_status.h"
27 #include "binaries.h"
28 #include "mediainfo.h"
29
30 #include <QStringList>
31 #include <QDir>
32 #include <QRegExp>
33
34 //x265 version info
35 static const unsigned int VERSION_X265_MINIMUM_VER = 9;
36 static const unsigned int VERSION_X265_MINIMUM_REV = 133;
37
38 // ------------------------------------------------------------
39 // Helper Macros
40 // ------------------------------------------------------------
41
42 #define X264_UPDATE_PROGRESS(X) do \
43 { \
44         bool ok = false; qint64 size_estimate = 0; \
45         unsigned int progress = (X)->cap(1).toUInt(&ok); \
46         setStatus((pass == 2) ? JobStatus_Running_Pass2 : ((pass == 1) ? JobStatus_Running_Pass1 : JobStatus_Running)); \
47         if(ok) \
48         { \
49                 setProgress(progress); \
50                 size_estimate = estimateSize(m_outputFile, progress); \
51         } \
52         setDetails(tr("%1, est. file size %2").arg(line.mid(offset).trimmed(), sizeToString(size_estimate))); \
53 } \
54 while(0)
55
56 #define REMOVE_CUSTOM_ARG(LIST, ITER, FLAG, PARAM) do \
57 { \
58         if(ITER != LIST.end()) \
59         { \
60                 if((*ITER).compare(PARAM, Qt::CaseInsensitive) == 0) \
61                 { \
62                         log(tr("WARNING: Custom parameter \"" PARAM "\" will be ignored in Pipe'd mode!\n")); \
63                         ITER = LIST.erase(ITER); \
64                         if(ITER != LIST.end()) \
65                         { \
66                                 if(!((*ITER).startsWith("--", Qt::CaseInsensitive))) ITER = LIST.erase(ITER); \
67                         } \
68                         FLAG = true; \
69                 } \
70         } \
71 } \
72 while(0)
73
74 static QString MAKE_NAME(const char *baseName, const OptionsModel *options)
75 {
76         const QString arch = (options->encArch() == OptionsModel::EncArch_x64) ? "x64" : "x86";
77         const QString vari = (options->encVariant() == OptionsModel::EncVariant_HiBit ) ? "16-Bit" : "8-Bit";
78         return QString("%1, %2, %3").arg(QString::fromLatin1(baseName), arch, vari);
79 }
80
81 // ------------------------------------------------------------
82 // Encoder Info
83 // ------------------------------------------------------------
84
85 class X265EncoderInfo : public AbstractEncoderInfo
86 {
87 public:
88         virtual QString getVariantId(const int &variant) const
89         {
90                 switch(variant)
91                 {
92                 case OptionsModel::EncVariant_LoBit:
93                         return QString::fromLatin1("8-Bit");
94                 case OptionsModel::EncVariant_HiBit:
95                         return QString::fromLatin1("16-Bit");
96                 default:
97                         return QString::fromLatin1("N/A");
98                 }
99         }
100
101         virtual QStringList getTunings(void) const
102         {
103                 QStringList tunings;
104                 tunings << "SSIM" << "FastDecode" << "ZeroLatency";
105                 return tunings;
106         }
107
108         virtual QStringList getProfiles(const int &variant) const
109         {
110                 return QStringList();
111         }
112
113         virtual QStringList supportedOutputFormats(void) const
114         {
115                 QStringList extLst;
116                 extLst << "hevc";
117                 return extLst;
118         }
119
120         virtual bool isRCModeSupported(const int rcMode) const
121         {
122                 switch(rcMode)
123                 {
124                 case OptionsModel::RCMode_CRF:
125                 case OptionsModel::RCMode_CQ:
126                 case OptionsModel::RCMode_ABR:
127                         return true;
128                 default:
129                         return false;
130                 }
131         }
132
133         virtual bool isInputTypeSupported(const int format) const
134         {
135                 switch(format)
136                 {
137                 case MediaInfo::FILETYPE_YUV4MPEG2:
138                         return true;
139                 default:
140                         return false;
141                 }
142         }
143 };
144
145 static const X265EncoderInfo s_x265EncoderInfo;
146
147 const AbstractEncoderInfo &X265Encoder::getEncoderInfo(void)
148 {
149         return s_x265EncoderInfo;
150 }
151
152 // ------------------------------------------------------------
153 // Constructor & Destructor
154 // ------------------------------------------------------------
155
156 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)
157 :
158         AbstractEncoder(jobObject, options, sysinfo, preferences, jobStatus, abort, pause, semaphorePause, sourceFile, outputFile),
159         m_encoderName(MAKE_NAME("x265 (H.265/HEVC)", m_options)),
160         m_binaryFile(ENC_BINARY(sysinfo, options))
161 {
162         if(options->encType() != OptionsModel::EncType_X265)
163         {
164                 THROW("Invalid encoder type!");
165         }
166 }
167
168 X265Encoder::~X265Encoder(void)
169 {
170         /*Nothing to do here*/
171 }
172
173 const QString &X265Encoder::getName(void)
174 {
175         return m_encoderName;
176 }
177
178 // ------------------------------------------------------------
179 // Check Version
180 // ------------------------------------------------------------
181
182 void X265Encoder::checkVersion_init(QList<QRegExp*> &patterns, QStringList &cmdLine)
183 {
184         cmdLine << "--version";
185         patterns << new QRegExp("\\bHEVC\\s+encoder\\s+version\\s+(\\d)\\.(\\d+)\\+(\\d+)-[a-f0-9]+\\b", Qt::CaseInsensitive);
186 }
187
188 void X265Encoder::checkVersion_parseLine(const QString &line, QList<QRegExp*> &patterns, unsigned int &core, unsigned int &build, bool &modified)
189 {
190         int offset = -1;
191
192         if((offset = patterns[0]->lastIndexIn(line)) >= 0)
193         {
194                 bool ok[3] = { false, false, false };
195                 unsigned int temp[3];
196                 temp[0] = patterns[0]->cap(1).toUInt(&ok[0]);
197                 temp[1] = patterns[0]->cap(2).toUInt(&ok[1]);
198                 temp[2] = patterns[0]->cap(3).toUInt(&ok[2]);
199                 if(ok[0] && ok[1])
200                 {
201                         core = (10 * temp[0]) + temp[1];
202                 }
203                 if(ok[2]) build = temp[2];
204         }
205
206         if(!line.isEmpty())
207         {
208                 log(line);
209         }
210 }
211
212 QString X265Encoder::printVersion(const unsigned int &revision, const bool &modified)
213 {
214         unsigned int core, build;
215         splitRevision(revision, core, build);
216
217         return tr("x265 version: %1.%2+%3").arg(QString::number(core / 10), QString::number(core % 10), QString::number(build));
218 }
219
220 bool X265Encoder::isVersionSupported(const unsigned int &revision, const bool &modified)
221 {
222         unsigned int core, build;
223         splitRevision(revision, core, build);
224
225         if((core < VERSION_X265_MINIMUM_VER) || ((core == VERSION_X265_MINIMUM_VER) && (build < VERSION_X265_MINIMUM_REV)))
226         {
227                 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)));
228                 return false;
229         }
230         else if(core > VERSION_X265_MINIMUM_VER)
231         {
232                 log(tr("\nWARNING: Your version of x265 is newer than the latest tested version, take care!"));
233                 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)));
234         }
235         
236         return true;
237 }
238
239 // ------------------------------------------------------------
240 // Encoding Functions
241 // ------------------------------------------------------------
242
243 void X265Encoder::buildCommandLine(QStringList &cmdLine, const bool &usePipe, const unsigned int &frames, const QString &indexFile, const int &pass, const QString &passLogFile)
244 {
245         double crf_int = 0.0, crf_frc = 0.0;
246
247         switch(m_options->rcMode())
248         {
249         case OptionsModel::RCMode_CQ:
250                 cmdLine << "--qp" << QString::number(qRound(m_options->quantizer()));
251                 break;
252         case OptionsModel::RCMode_CRF:
253                 crf_frc = modf(m_options->quantizer(), &crf_int);
254                 cmdLine << "--crf" << QString("%1.%2").arg(QString::number(qRound(crf_int)), QString::number(qRound(crf_frc * 10.0)));
255                 break;
256         case OptionsModel::RCMode_2Pass:
257         case OptionsModel::RCMode_ABR:
258                 cmdLine << "--bitrate" << QString::number(m_options->bitrate());
259                 break;
260         default:
261                 THROW("Bad rate-control mode !!!");
262                 break;
263         }
264         
265         if((pass == 1) || (pass == 2))
266         {
267                 cmdLine << "--pass" << QString::number(pass);
268                 cmdLine << "--stats" << QDir::toNativeSeparators(passLogFile);
269         }
270
271         cmdLine << "--preset" << m_options->preset().toLower();
272
273         if(!m_options->tune().simplified().isEmpty())
274         {
275                 if(m_options->tune().simplified().compare(QString::fromLatin1(OptionsModel::TUNING_UNSPECIFIED), Qt::CaseInsensitive) != 0)
276                 {
277                         cmdLine << "--tune" << m_options->tune().simplified().toLower();
278                 }
279         }
280
281         if(m_options->profile().compare("auto", Qt::CaseInsensitive) != 0)
282         {
283                 if((m_options->encType() == OptionsModel::EncType_X264) && (m_options->encVariant() == OptionsModel::EncVariant_LoBit))
284                 {
285                         cmdLine << "--profile" << m_options->profile().toLower();
286                 }
287         }
288
289         if(!m_options->customEncParams().isEmpty())
290         {
291                 QStringList customArgs = splitParams(m_options->customEncParams(), m_sourceFile, m_outputFile);
292                 if(usePipe)
293                 {
294                         QStringList::iterator i = customArgs.begin();
295                         while(i != customArgs.end())
296                         {
297                                 bool bModified = false;
298                                 REMOVE_CUSTOM_ARG(customArgs, i, bModified, "--fps");
299                                 REMOVE_CUSTOM_ARG(customArgs, i, bModified, "--frames");
300                                 if(!bModified) i++;
301                         }
302                 }
303                 cmdLine.append(customArgs);
304         }
305
306         cmdLine << "--output" << QDir::toNativeSeparators(m_outputFile);
307         
308         if(usePipe)
309         {
310                 if(frames < 1) THROW("Frames not set!");
311                 cmdLine << "--frames" << QString::number(frames);
312                 cmdLine << "--y4m" << "-";
313         }
314         else
315         {
316                 cmdLine << QDir::toNativeSeparators(m_sourceFile);
317         }
318 }
319
320 void X265Encoder::runEncodingPass_init(QList<QRegExp*> &patterns)
321 {
322         patterns << new QRegExp("\\[(\\d+)\\.(\\d+)%\\].+frames");   //regExpProgress
323         patterns << new QRegExp("indexing.+\\[(\\d+)\\.(\\d+)%\\]"); //regExpIndexing
324         patterns << new QRegExp("^(\\d+) frames:"); //regExpFrameCnt
325         patterns << new QRegExp("\\[\\s*(\\d+)\\.(\\d+)%\\]\\s+(\\d+)/(\\d+)\\s(\\d+).(\\d+)\\s(\\d+).(\\d+)\\s+(\\d+):(\\d+):(\\d+)\\s+(\\d+):(\\d+):(\\d+)"); //regExpModified
326 }
327
328 void X265Encoder::runEncodingPass_parseLine(const QString &line, QList<QRegExp*> &patterns, const int &pass)
329 {
330         int offset = -1;
331         if((offset = patterns[0]->lastIndexIn(line)) >= 0)
332         {
333                 X264_UPDATE_PROGRESS(patterns[0]);
334         }
335         else if((offset = patterns[1]->lastIndexIn(line)) >= 0)
336         {
337                 bool ok = false;
338                 unsigned int progress = patterns[1]->cap(1).toUInt(&ok);
339                 setStatus(JobStatus_Indexing);
340                 if(ok)
341                 {
342                         setProgress(progress);
343                 }
344                 setDetails(line.mid(offset).trimmed());
345         }
346         else if((offset = patterns[2]->lastIndexIn(line)) >= 0)
347         {
348                 setStatus((pass == 2) ? JobStatus_Running_Pass2 : ((pass == 1) ? JobStatus_Running_Pass1 : JobStatus_Running));
349                 setDetails(line.mid(offset).trimmed());
350         }
351         else if((offset = patterns[3]->lastIndexIn(line)) >= 0)
352         {
353                 X264_UPDATE_PROGRESS(patterns[3]);
354         }
355         else if(!line.isEmpty())
356         {
357                 log(line);
358         }
359 }