2014-02-24 14:57:30 +01:00
///////////////////////////////////////////////////////////////////////////////
// Simple x264 Launcher
2015-01-31 19:56:04 +01:00
// Copyright (C) 2004-2015 LoRd_MuldeR <MuldeR2@GMX.de>
2014-02-24 14:57:30 +01:00
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
//
// http://www.gnu.org/licenses/gpl-2.0.txt
///////////////////////////////////////////////////////////////////////////////
# include "encoder_x264.h"
2015-02-02 22:11:06 +01:00
//Internal
2014-04-16 14:57:32 +02:00
# include "global.h"
2014-02-24 14:57:30 +01:00
# include "model_options.h"
2014-02-24 19:51:53 +01:00
# include "model_status.h"
2014-04-20 15:09:58 +02:00
# include "mediainfo.h"
2015-08-02 19:16:37 +02:00
# include "model_sysinfo.h"
2014-02-24 14:57:30 +01:00
2015-02-02 22:11:06 +01:00
//MUtils
# include <MUtils/Exception.h>
//Qt
2014-02-24 14:57:30 +01:00
# include <QStringList>
2014-02-24 19:51:53 +01:00
# include <QDir>
2014-02-24 14:57:30 +01:00
# include <QRegExp>
//x264 version info
2015-08-02 19:16:37 +02:00
static const unsigned int VERSION_X264_MINIMUM_REV = 2555 ;
static const unsigned int VERSION_X264_CURRENT_API = 148 ;
2014-02-24 14:57:30 +01:00
2014-02-25 23:52:10 +01:00
// ------------------------------------------------------------
// Helper Macros
// ------------------------------------------------------------
2014-02-24 19:51:53 +01:00
# define REMOVE_CUSTOM_ARG(LIST, ITER, FLAG, PARAM) do \
{ \
if ( ITER ! = LIST . end ( ) ) \
{ \
if ( ( * ITER ) . compare ( PARAM , Qt : : CaseInsensitive ) = = 0 ) \
{ \
log ( tr ( " WARNING: Custom parameter \" " PARAM " \" will be ignored in Pipe'd mode! \n " ) ) ; \
ITER = LIST . erase ( ITER ) ; \
if ( ITER ! = LIST . end ( ) ) \
{ \
if ( ! ( ( * ITER ) . startsWith ( " -- " , Qt : : CaseInsensitive ) ) ) ITER = LIST . erase ( ITER ) ; \
} \
FLAG = true ; \
} \
} \
} \
while ( 0 )
# define X264_UPDATE_PROGRESS(X) do \
{ \
2014-05-14 17:17:40 +02:00
bool ok [ 2 ] = { false , false } ; \
unsigned int progressInt = ( X ) - > cap ( 1 ) . toUInt ( & ok [ 0 ] ) ; \
unsigned int progressFrc = ( X ) - > cap ( 2 ) . toUInt ( & ok [ 1 ] ) ; \
2014-02-24 19:51:53 +01:00
setStatus ( ( pass = = 2 ) ? JobStatus_Running_Pass2 : ( ( pass = = 1 ) ? JobStatus_Running_Pass1 : JobStatus_Running ) ) ; \
2014-05-14 17:17:40 +02:00
if ( ok [ 0 ] & & ok [ 1 ] ) \
2014-02-24 19:51:53 +01:00
{ \
2014-05-14 17:17:40 +02:00
const double progress = ( double ( progressInt ) / 100.0 ) + ( double ( progressFrc ) / 1000.0 ) ; \
if ( ! qFuzzyCompare ( progress , last_progress ) ) \
{ \
setProgress ( floor ( progress * 100.0 ) ) ; \
size_estimate = qFuzzyIsNull ( size_estimate ) ? estimateSize ( m_outputFile , progress ) : ( ( 0.667 * size_estimate ) + ( 0.333 * estimateSize ( m_outputFile , progress ) ) ) ; \
last_progress = progress ; \
} \
2014-02-24 19:51:53 +01:00
} \
2014-05-14 17:17:40 +02:00
setDetails ( tr ( " %1, est. file size %2 " ) . arg ( line . mid ( offset ) . trimmed ( ) , sizeToString ( qRound64 ( size_estimate ) ) ) ) ; \
2014-02-24 19:51:53 +01:00
} \
while ( 0 )
2014-04-11 15:33:02 +02:00
// ------------------------------------------------------------
// Encoder Info
// ------------------------------------------------------------
class X264EncoderInfo : public AbstractEncoderInfo
{
public :
2015-08-02 19:16:37 +02:00
virtual QFlags < OptionsModel : : EncVariant > getVariants ( void ) const
2014-04-16 16:05:24 +02:00
{
2015-08-02 19:16:37 +02:00
QFlags < OptionsModel : : EncVariant > variants ;
variants | = OptionsModel : : EncVariant_8Bit ;
variants | = OptionsModel : : EncVariant_10Bit ;
return variants ;
2014-04-16 16:05:24 +02:00
}
2014-04-16 16:50:03 +02:00
virtual QStringList getTunings ( void ) const
{
QStringList tunings ;
tunings < < " Film " < < " Animation " < < " Grain " ;
tunings < < " StillImage " < < " PSNR " < < " SSIM " ;
tunings < < " FastDecode " < < " ZeroLatency " < < " Touhou " ;
return tunings ;
}
2015-08-02 21:16:36 +02:00
virtual QStringList getPresets ( void ) const
{
QStringList presets ;
presets < < " ultrafast " < < " superfast " < < " veryfast " < < " faster " < < " fast " ;
presets < < " medium " < < " slow " < < " slower " < < " veryslow " < < " placebo " ;
return presets ;
}
virtual QStringList getProfiles ( const OptionsModel : : EncVariant & variant ) const
2014-04-16 16:05:24 +02:00
{
QStringList profiles ;
2015-08-02 19:16:37 +02:00
if ( variant = = OptionsModel : : EncVariant_8Bit )
2014-04-16 16:05:24 +02:00
{
profiles < < " Baseline " < < " Main " < < " High " ;
}
2015-08-02 19:16:37 +02:00
if ( ( variant = = OptionsModel : : EncVariant_8Bit ) | | ( variant = = OptionsModel : : EncVariant_10Bit ) )
2014-04-16 16:05:24 +02:00
{
profiles < < " High10 " < < " High422 " < < " High444 " ;
}
return profiles ;
}
2014-04-11 15:33:02 +02:00
virtual QStringList supportedOutputFormats ( void ) const
{
QStringList extLst ;
2014-04-16 16:50:03 +02:00
extLst < < " 264 " < < " mkv " < < " mp4 " ;
2014-04-11 15:33:02 +02:00
return extLst ;
}
2014-04-15 22:12:02 +02:00
2015-08-02 21:16:36 +02:00
virtual bool isRCModeSupported ( const OptionsModel : : RCMode & rcMode ) const
2014-04-15 22:12:02 +02:00
{
switch ( rcMode )
{
case OptionsModel : : RCMode_CRF :
case OptionsModel : : RCMode_CQ :
case OptionsModel : : RCMode_2Pass :
case OptionsModel : : RCMode_ABR :
return true ;
default :
return false ;
}
}
2014-04-20 15:09:58 +02:00
virtual bool isInputTypeSupported ( const int format ) const
{
switch ( format )
{
case MediaInfo : : FILETYPE_AVISYNTH :
case MediaInfo : : FILETYPE_YUV4MPEG2 :
case MediaInfo : : FILETYPE_UNKNOWN :
return true ;
default :
return false ;
}
}
2015-08-02 19:16:37 +02:00
virtual QString getBinaryPath ( const SysinfoModel * sysinfo , const OptionsModel : : EncArch & encArch , const OptionsModel : : EncVariant & encVariant ) const
{
QString arch , variant ;
switch ( encArch )
{
case OptionsModel : : EncArch_x86_32 : arch = " x86 " ; break ;
case OptionsModel : : EncArch_x86_64 : arch = " x64 " ; break ;
default : MUTILS_THROW ( " Unknown encoder arch! " ) ;
}
switch ( encVariant )
{
case OptionsModel : : EncVariant_8Bit : variant = " 8bit " ; break ;
case OptionsModel : : EncVariant_10Bit : variant = " 10bit " ; break ;
default : MUTILS_THROW ( " Unknown encoder arch! " ) ;
}
return QString ( " %1/toolset/%2/x264_%3_%2.exe " ) . arg ( sysinfo - > getAppPath ( ) , arch , variant ) ;
}
2014-04-11 15:33:02 +02:00
} ;
static const X264EncoderInfo s_x264EncoderInfo ;
const AbstractEncoderInfo & X264Encoder : : getEncoderInfo ( void )
{
return s_x264EncoderInfo ;
}
2014-02-25 23:52:10 +01:00
// ------------------------------------------------------------
// Constructor & Destructor
// ------------------------------------------------------------
2014-02-24 19:51:53 +01:00
X264Encoder : : X264Encoder ( 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 )
2014-02-24 14:57:30 +01:00
:
2015-08-02 19:16:37 +02:00
AbstractEncoder ( jobObject , options , sysinfo , preferences , jobStatus , abort , pause , semaphorePause , sourceFile , outputFile )
2014-02-24 14:57:30 +01:00
{
if ( options - > encType ( ) ! = OptionsModel : : EncType_X264 )
{
2015-02-02 22:11:06 +01:00
MUTILS_THROW ( " Invalid encoder type! " ) ;
2014-02-24 14:57:30 +01:00
}
}
X264Encoder : : ~ X264Encoder ( void )
{
/*Nothing to do here*/
}
2015-08-02 19:16:37 +02:00
QString X264Encoder : : getName ( void ) const
2014-02-26 16:08:06 +01:00
{
2015-08-02 19:16:37 +02:00
QString arch , variant ;
switch ( m_options - > encArch ( ) )
{
case OptionsModel : : EncArch_x86_32 : arch = " x86 " ; break ;
case OptionsModel : : EncArch_x86_64 : arch = " x64 " ; break ;
default : MUTILS_THROW ( " Unknown encoder arch! " ) ;
}
switch ( m_options - > encVariant ( ) )
{
case OptionsModel : : EncVariant_8Bit : variant = " 8-Bit " ; break ;
case OptionsModel : : EncVariant_10Bit : variant = " 10-Bit " ; break ;
default : MUTILS_THROW ( " Unknown encoder arch! " ) ;
}
return QString ( " x264 (H.264/AVC) , % 1 , % 2 " ).arg(arch, variant) ;
2014-02-26 16:08:06 +01:00
}
2014-02-25 23:52:10 +01:00
// ------------------------------------------------------------
// Check Version
// ------------------------------------------------------------
2014-02-24 14:57:30 +01:00
void X264Encoder : : checkVersion_init ( QList < QRegExp * > & patterns , QStringList & cmdLine )
{
cmdLine < < " --version " ;
2015-01-05 20:53:01 +01:00
patterns < < new QRegExp ( " \\ bx264 \\ s+( \\ d) \\ .( \\ d+) \\ .( \\ d+) \\ s+([a-f0-9]{7}) " , Qt : : CaseInsensitive ) ;
patterns < < new QRegExp ( " \\ bx264 \\ s+( \\ d) \\ .( \\ d+) \\ .( \\ d+) " , Qt : : CaseInsensitive ) ;
2014-02-24 14:57:30 +01:00
}
2014-05-06 00:22:18 +02:00
void X264Encoder : : checkVersion_parseLine ( const QString & line , QList < QRegExp * > & patterns , unsigned int & core , unsigned int & build , bool & modified )
2014-02-24 14:57:30 +01:00
{
int offset = - 1 ;
2014-02-26 00:55:11 +01:00
2014-02-24 14:57:30 +01:00
if ( ( offset = patterns [ 0 ] - > lastIndexIn ( line ) ) > = 0 )
{
bool ok1 = false , ok2 = false ;
unsigned int temp1 = patterns [ 0 ] - > cap ( 2 ) . toUInt ( & ok1 ) ;
unsigned int temp2 = patterns [ 0 ] - > cap ( 3 ) . toUInt ( & ok2 ) ;
2015-01-05 20:53:01 +01:00
if ( ok1 & & ok2 & & ( temp1 > 0 ) & & ( temp2 > 0 ) )
2014-02-26 00:55:11 +01:00
{
2014-05-06 00:22:18 +02:00
core = temp1 ;
build = temp2 ;
2014-02-26 00:55:11 +01:00
}
2014-02-24 14:57:30 +01:00
}
else if ( ( offset = patterns [ 1 ] - > lastIndexIn ( line ) ) > = 0 )
{
bool ok1 = false , ok2 = false ;
unsigned int temp1 = patterns [ 1 ] - > cap ( 2 ) . toUInt ( & ok1 ) ;
unsigned int temp2 = patterns [ 1 ] - > cap ( 3 ) . toUInt ( & ok2 ) ;
2015-01-05 20:53:01 +01:00
if ( ok1 & & ok2 & & ( temp1 > 0 ) & & ( temp2 > 0 ) )
2014-02-26 00:55:11 +01:00
{
2014-05-06 00:22:18 +02:00
core = temp1 ;
build = temp2 ;
2014-02-26 00:55:11 +01:00
}
2014-02-24 14:57:30 +01:00
modified = true ;
}
2014-02-26 00:55:11 +01:00
if ( ! line . isEmpty ( ) )
{
log ( line ) ;
}
2014-02-24 14:57:30 +01:00
}
2014-02-26 17:39:36 +01:00
QString X264Encoder : : printVersion ( const unsigned int & revision , const bool & modified )
2014-02-24 14:57:30 +01:00
{
2014-05-06 00:22:18 +02:00
unsigned int core , build ;
splitRevision ( revision , core , build ) ;
QString versionStr = tr ( " x264 revision: %1 (core #%2) " ) . arg ( QString : : number ( build ) , QString : : number ( core ) ) ;
if ( modified )
{
versionStr . append ( tr ( " - with custom patches! " ) ) ;
}
return versionStr ;
2014-02-24 14:57:30 +01:00
}
bool X264Encoder : : isVersionSupported ( const unsigned int & revision , const bool & modified )
{
2014-05-06 00:22:18 +02:00
unsigned int core , build ;
splitRevision ( revision , core , build ) ;
2014-04-15 22:12:02 +02:00
2014-05-06 00:22:18 +02:00
if ( build < VERSION_X264_MINIMUM_REV )
2014-02-24 14:57:30 +01:00
{
2014-04-15 22:12:02 +02:00
log ( tr ( " \n ERROR: Your revision of x264 is too old! Minimum required revision is %1. " ) . arg ( QString : : number ( VERSION_X264_MINIMUM_REV ) ) ) ;
2014-02-24 14:57:30 +01:00
return false ;
}
2014-05-06 00:22:18 +02:00
if ( core ! = VERSION_X264_CURRENT_API )
2014-02-24 14:57:30 +01:00
{
2014-04-15 22:12:02 +02:00
log ( tr ( " \n WARNING: Your x264 binary uses an untested core (API) version, take care! " ) ) ;
log ( tr ( " This application works best with x264 core (API) version %1. Newer versions may work or not. " ) . arg ( QString : : number ( VERSION_X264_CURRENT_API ) ) ) ;
2014-02-24 14:57:30 +01:00
}
return true ;
}
2014-02-24 19:51:53 +01:00
2014-02-25 23:52:10 +01:00
// ------------------------------------------------------------
// Encoding Functions
// ------------------------------------------------------------
2014-02-24 19:51:53 +01:00
void X264Encoder : : buildCommandLine ( QStringList & cmdLine , const bool & usePipe , const unsigned int & frames , const QString & indexFile , const int & pass , const QString & passLogFile )
{
double crf_int = 0.0 , crf_frc = 0.0 ;
switch ( m_options - > rcMode ( ) )
{
case OptionsModel : : RCMode_CQ :
cmdLine < < " --qp " < < QString : : number ( qRound ( m_options - > quantizer ( ) ) ) ;
break ;
case OptionsModel : : RCMode_CRF :
crf_frc = modf ( m_options - > quantizer ( ) , & crf_int ) ;
cmdLine < < " --crf " < < QString ( " %1.%2 " ) . arg ( QString : : number ( qRound ( crf_int ) ) , QString : : number ( qRound ( crf_frc * 10.0 ) ) ) ;
break ;
case OptionsModel : : RCMode_2Pass :
case OptionsModel : : RCMode_ABR :
cmdLine < < " --bitrate " < < QString : : number ( m_options - > bitrate ( ) ) ;
break ;
default :
2015-02-02 22:11:06 +01:00
MUTILS_THROW ( " Bad rate-control mode !!! " ) ;
2014-02-24 19:51:53 +01:00
}
if ( ( pass = = 1 ) | | ( pass = = 2 ) )
{
cmdLine < < " --pass " < < QString : : number ( pass ) ;
cmdLine < < " --stats " < < QDir : : toNativeSeparators ( passLogFile ) ;
}
2015-08-02 22:13:14 +02:00
const QString preset = m_options - > preset ( ) . simplified ( ) . toLower ( ) ;
if ( ! preset . isEmpty ( ) )
{
if ( preset . compare ( QString : : fromLatin1 ( OptionsModel : : SETTING_UNSPECIFIED ) , Qt : : CaseInsensitive ) ! = 0 )
{
cmdLine < < " --preset " < < preset ;
}
}
2014-02-24 19:51:53 +01:00
2015-08-02 22:13:14 +02:00
const QString tune = m_options - > tune ( ) . simplified ( ) . toLower ( ) ;
if ( ! tune . isEmpty ( ) )
2014-02-24 19:51:53 +01:00
{
2015-08-02 22:13:14 +02:00
if ( tune . compare ( QString : : fromLatin1 ( OptionsModel : : SETTING_UNSPECIFIED ) , Qt : : CaseInsensitive ) ! = 0 )
2014-04-16 16:50:03 +02:00
{
2015-08-02 22:13:14 +02:00
cmdLine < < " --tune " < < tune ;
2014-04-16 16:50:03 +02:00
}
2014-02-24 19:51:53 +01:00
}
2015-08-02 22:13:14 +02:00
const QString profile = m_options - > profile ( ) . simplified ( ) . toLower ( ) ;
if ( ! profile . isEmpty ( ) )
2014-02-24 19:51:53 +01:00
{
2015-08-02 22:13:14 +02:00
if ( profile . compare ( QString : : fromLatin1 ( OptionsModel : : PROFILE_UNRESTRICTED ) , Qt : : CaseInsensitive ) ! = 0 )
2014-02-24 19:51:53 +01:00
{
2015-08-02 22:13:14 +02:00
cmdLine < < " --profile " < < profile ;
2014-02-24 19:51:53 +01:00
}
}
if ( ! m_options - > customEncParams ( ) . isEmpty ( ) )
{
QStringList customArgs = splitParams ( m_options - > customEncParams ( ) , m_sourceFile , m_outputFile ) ;
if ( usePipe )
{
QStringList : : iterator i = customArgs . begin ( ) ;
while ( i ! = customArgs . end ( ) )
{
bool bModified = false ;
REMOVE_CUSTOM_ARG ( customArgs , i , bModified , " --fps " ) ;
REMOVE_CUSTOM_ARG ( customArgs , i , bModified , " --frames " ) ;
if ( ! bModified ) i + + ;
}
}
cmdLine . append ( customArgs ) ;
}
cmdLine < < " --output " < < QDir : : toNativeSeparators ( m_outputFile ) ;
if ( usePipe )
{
2015-02-02 22:11:06 +01:00
if ( frames < 1 ) MUTILS_THROW ( " Frames not set! " ) ;
2014-02-24 19:51:53 +01:00
cmdLine < < " --frames " < < QString : : number ( frames ) ;
cmdLine < < " --demuxer " < < " y4m " ;
cmdLine < < " --stdin " < < " y4m " < < " - " ;
}
else
{
cmdLine < < " --index " < < QDir : : toNativeSeparators ( indexFile ) ;
cmdLine < < QDir : : toNativeSeparators ( m_sourceFile ) ;
}
}
void X264Encoder : : runEncodingPass_init ( QList < QRegExp * > & patterns )
{
2014-02-25 23:52:10 +01:00
patterns < < new QRegExp ( " \\ [( \\ d+) \\ .( \\ d+)% \\ ].+frames " ) ;
patterns < < new QRegExp ( " indexing.+ \\ [( \\ d+) \\ .( \\ d+)% \\ ] " ) ;
patterns < < new QRegExp ( " ^( \\ d+) frames: " ) ;
2014-02-24 19:51:53 +01:00
patterns < < new QRegExp ( " \\ [ \\ s*( \\ d+) \\ .( \\ d+)% \\ ] \\ s+( \\ d+)/( \\ d+) \\ s( \\ d+).( \\ d+) \\ s( \\ d+).( \\ d+) \\ s+( \\ d+):( \\ d+):( \\ d+) \\ s+( \\ d+):( \\ d+):( \\ d+) " ) ; //regExpModified
}
2014-05-14 17:17:40 +02:00
void X264Encoder : : runEncodingPass_parseLine ( const QString & line , QList < QRegExp * > & patterns , const int & pass , double & last_progress , double & size_estimate )
2014-02-24 19:51:53 +01:00
{
int offset = - 1 ;
if ( ( offset = patterns [ 0 ] - > lastIndexIn ( line ) ) > = 0 )
{
X264_UPDATE_PROGRESS ( patterns [ 0 ] ) ;
}
else if ( ( offset = patterns [ 1 ] - > lastIndexIn ( line ) ) > = 0 )
{
bool ok = false ;
unsigned int progress = patterns [ 1 ] - > cap ( 1 ) . toUInt ( & ok ) ;
setStatus ( JobStatus_Indexing ) ;
if ( ok )
{
setProgress ( progress ) ;
}
setDetails ( line . mid ( offset ) . trimmed ( ) ) ;
}
else if ( ( offset = patterns [ 2 ] - > lastIndexIn ( line ) ) > = 0 )
{
setStatus ( ( pass = = 2 ) ? JobStatus_Running_Pass2 : ( ( pass = = 1 ) ? JobStatus_Running_Pass1 : JobStatus_Running ) ) ;
setDetails ( line . mid ( offset ) . trimmed ( ) ) ;
}
else if ( ( offset = patterns [ 3 ] - > lastIndexIn ( line ) ) > = 0 )
{
X264_UPDATE_PROGRESS ( patterns [ 3 ] ) ;
}
else if ( ! line . isEmpty ( ) )
{
log ( line ) ;
}
}