2012-01-28 18:55:40 +01:00
///////////////////////////////////////////////////////////////////////////////
// Simple x264 Launcher
2014-01-27 19:58:24 +01:00
// Copyright (C) 2004-2014 LoRd_MuldeR <MuldeR2@GMX.de>
2012-01-28 18:55:40 +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 "thread_encode.h"
2012-01-29 19:14:46 +01:00
2014-02-24 23:13:42 +01:00
//Internal
2012-01-28 19:59:04 +01:00
# include "global.h"
2012-01-29 19:14:46 +01:00
# include "model_options.h"
2013-07-03 21:34:21 +02:00
# include "model_preferences.h"
2014-02-14 00:01:00 +01:00
# include "model_sysinfo.h"
2013-11-03 18:34:20 +01:00
# include "job_object.h"
2014-02-14 00:01:00 +01:00
# include "binaries.h"
2012-01-29 19:14:46 +01:00
2014-02-24 23:13:42 +01:00
//Encoders
# include "encoder_x264.h"
# include "encoder_x265.h"
//Source
# include "source_avisynth.h"
# include "source_vapoursynth.h"
//Qt Framework
2012-01-29 19:14:46 +01:00
# include <QDate>
# include <QTime>
2012-01-31 00:13:32 +01:00
# include <QDateTime>
2012-01-30 04:58:42 +01:00
# include <QFileInfo>
# include <QDir>
2012-01-29 21:31:09 +01:00
# include <QProcess>
# include <QMutex>
2012-02-05 21:52:23 +01:00
# include <QTextCodec>
2012-02-27 21:39:15 +01:00
# include <QLocale>
2013-08-28 18:10:26 +02:00
# include <QCryptographicHash>
2012-01-29 21:31:09 +01:00
2013-11-10 23:20:24 +01:00
/*
* RAII execution state handler
*/
2013-11-14 02:29:18 +01:00
class ExecutionStateHandler
2013-11-10 23:20:24 +01:00
{
public :
ExecutionStateHandler ( void )
{
x264_set_thread_execution_state ( true ) ;
}
~ ExecutionStateHandler ( void )
{
x264_set_thread_execution_state ( false ) ;
}
private :
//Disable copy constructor and assignment
ExecutionStateHandler ( const ExecutionStateHandler & other ) { }
ExecutionStateHandler & operator = ( const ExecutionStateHandler & ) { }
//Prevent object allocation on the heap
void * operator new ( size_t ) ; void * operator new [ ] ( size_t ) ;
void operator delete ( void * ) ; void operator delete [ ] ( void * ) ;
} ;
2012-01-30 17:50:19 +01:00
/*
* Macros
*/
2012-09-22 14:58:51 +02:00
# define CHECK_STATUS(ABORT_FLAG, OK_FLAG) do \
2012-01-30 17:50:19 +01:00
{ \
if ( ABORT_FLAG ) \
{ \
log ( " \n PROCESS ABORTED BY USER !!! " ) ; \
setStatus ( JobStatus_Aborted ) ; \
2012-02-15 00:45:42 +01:00
if ( QFileInfo ( m_outputFileName ) . exists ( ) & & ( QFileInfo ( m_outputFileName ) . size ( ) = = 0 ) ) QFile : : remove ( m_outputFileName ) ; \
2012-01-30 17:50:19 +01:00
return ; \
} \
else if ( ! ( OK_FLAG ) ) \
{ \
setStatus ( JobStatus_Failed ) ; \
2012-02-15 00:45:42 +01:00
if ( QFileInfo ( m_outputFileName ) . exists ( ) & & ( QFileInfo ( m_outputFileName ) . size ( ) = = 0 ) ) QFile : : remove ( m_outputFileName ) ; \
2012-01-30 17:50:19 +01:00
return ; \
} \
2012-09-22 14:58:51 +02:00
} \
while ( 0 )
2012-01-30 17:50:19 +01:00
2014-02-25 22:44:39 +01:00
# define CONNECT(OBJ) do \
{ \
if ( ( OBJ ) ) \
{ \
connect ( ( OBJ ) , SIGNAL ( statusChanged ( JobStatus ) ) , this , SLOT ( setStatus ( JobStatus ) ) , Qt : : DirectConnection ) ; \
connect ( ( OBJ ) , SIGNAL ( progressChanged ( unsigned int ) ) , this , SLOT ( setProgress ( unsigned int ) ) , Qt : : DirectConnection ) ; \
connect ( ( OBJ ) , SIGNAL ( detailsChanged ( QString ) ) , this , SLOT ( setDetails ( QString ) ) , Qt : : DirectConnection ) ; \
connect ( ( OBJ ) , SIGNAL ( messageLogged ( QString ) ) , this , SLOT ( log ( QString ) ) , Qt : : DirectConnection ) ; \
} \
} \
while ( 0 )
2014-02-24 23:13:42 +01:00
/*
* Input types
*/
typedef enum
{
INPUT_NATIVE = 0 ,
INPUT_AVISYN = 1 ,
INPUT_VAPOUR = 2
} ;
2012-01-30 17:50:19 +01:00
/*
* Static vars
*/
2014-02-24 23:13:42 +01:00
//static const char *VPS_TEST_FILE = "import vapoursynth as vs\ncore = vs.get_core()\nv = core.std.BlankClip()\nv.set_output()\n";
2012-01-30 17:50:19 +01:00
2012-01-29 21:31:09 +01:00
///////////////////////////////////////////////////////////////////////////////
// Constructor & Destructor
///////////////////////////////////////////////////////////////////////////////
2014-02-14 00:01:00 +01:00
EncodeThread : : EncodeThread ( const QString & sourceFileName , const QString & outputFileName , const OptionsModel * options , const SysinfoModel * const sysinfo , const PreferencesModel * const preferences )
2012-01-28 18:55:40 +01:00
:
2012-01-29 15:57:23 +01:00
m_jobId ( QUuid : : createUuid ( ) ) ,
m_sourceFileName ( sourceFileName ) ,
2012-01-29 19:14:46 +01:00
m_outputFileName ( outputFileName ) ,
2012-01-29 21:31:09 +01:00
m_options ( new OptionsModel ( * options ) ) ,
2014-02-14 00:01:00 +01:00
m_sysinfo ( sysinfo ) ,
m_preferences ( preferences ) ,
2013-11-03 18:34:20 +01:00
m_jobObject ( new JobObject ) ,
2014-02-24 23:13:42 +01:00
m_semaphorePaused ( 0 ) ,
m_encoder ( NULL ) ,
m_pipedSource ( NULL )
2012-01-28 18:55:40 +01:00
{
2012-01-28 23:24:41 +01:00
m_abort = false ;
2012-02-02 02:13:02 +01:00
m_pause = false ;
2014-02-24 14:57:30 +01:00
//Create encoder object
switch ( options - > encType ( ) )
{
case OptionsModel : : EncType_X264 :
2014-02-24 19:51:53 +01:00
m_encoder = new X264Encoder ( m_jobObject , m_options , m_sysinfo , m_preferences , m_status , & m_abort , & m_pause , & m_semaphorePaused , m_sourceFileName , m_outputFileName ) ;
2014-02-24 14:57:30 +01:00
break ;
case OptionsModel : : EncType_X265 :
2014-02-24 19:51:53 +01:00
m_encoder = new X265Encoder ( m_jobObject , m_options , m_sysinfo , m_preferences , m_status , & m_abort , & m_pause , & m_semaphorePaused , m_sourceFileName , m_outputFileName ) ;
2014-02-24 14:57:30 +01:00
break ;
default :
throw " Unknown encoder type encountered! " ;
}
2014-02-24 23:13:42 +01:00
//Create input handler object
switch ( getInputType ( QFileInfo ( m_sourceFileName ) . suffix ( ) ) )
{
case INPUT_AVISYN :
m_pipedSource = new AvisynthSource ( m_jobObject , m_options , m_sysinfo , m_preferences , m_status , & m_abort , & m_pause , & m_semaphorePaused , m_sourceFileName ) ;
break ;
case INPUT_VAPOUR :
m_pipedSource = new VapoursynthSource ( m_jobObject , m_options , m_sysinfo , m_preferences , m_status , & m_abort , & m_pause , & m_semaphorePaused , m_sourceFileName ) ;
break ;
}
2014-02-24 19:51:53 +01:00
//Establish connections
2014-02-25 22:44:39 +01:00
CONNECT ( m_encoder ) ;
CONNECT ( m_pipedSource ) ;
2012-01-28 18:55:40 +01:00
}
EncodeThread : : ~ EncodeThread ( void )
{
2014-02-24 14:57:30 +01:00
X264_DELETE ( m_encoder ) ;
2013-11-03 18:34:20 +01:00
X264_DELETE ( m_jobObject ) ;
2014-02-24 14:57:30 +01:00
X264_DELETE ( m_options ) ;
2012-01-28 18:55:40 +01:00
}
///////////////////////////////////////////////////////////////////////////////
// Thread entry point
///////////////////////////////////////////////////////////////////////////////
void EncodeThread : : run ( void )
{
2013-08-02 20:44:47 +02:00
# if !defined(_DEBUG)
2012-02-02 02:13:02 +01:00
__try
2012-01-28 19:59:04 +01:00
{
2012-02-02 02:13:02 +01:00
checkedRun ( ) ;
2012-01-28 19:59:04 +01:00
}
2012-02-02 02:13:02 +01:00
__except ( 1 )
2012-01-28 19:59:04 +01:00
{
2012-02-02 02:13:02 +01:00
qWarning ( " STRUCTURED EXCEPTION ERROR IN ENCODE THREAD !!! " ) ;
2012-01-28 19:59:04 +01:00
}
2013-08-02 20:44:47 +02:00
# else
checkedRun ( ) ;
# endif
2012-01-31 00:13:32 +01:00
2013-11-03 18:34:20 +01:00
if ( m_jobObject )
2012-01-31 00:13:32 +01:00
{
2013-11-03 18:34:20 +01:00
m_jobObject - > terminateJob ( 42 ) ;
X264_DELETE ( m_jobObject ) ;
2012-01-31 00:13:32 +01:00
}
2012-01-28 19:59:04 +01:00
}
2012-02-02 02:13:02 +01:00
void EncodeThread : : checkedRun ( void )
{
m_progress = 0 ;
m_status = JobStatus_Starting ;
try
{
try
{
2013-11-10 23:20:24 +01:00
ExecutionStateHandler executionStateHandler ;
2012-02-02 02:13:02 +01:00
encode ( ) ;
}
catch ( char * msg )
{
log ( tr ( " EXCEPTION ERROR IN THREAD: " ) . append ( QString : : fromLatin1 ( msg ) ) ) ;
setStatus ( JobStatus_Failed ) ;
}
catch ( . . . )
{
log ( tr ( " UNHANDLED EXCEPTION ERROR IN THREAD !!! " ) ) ;
setStatus ( JobStatus_Failed ) ;
}
}
catch ( . . . )
{
2013-11-03 18:34:20 +01:00
x264_fatal_exit ( L " Unhandeled exception error in encode thread! " ) ;
2012-02-02 02:13:02 +01:00
}
}
void EncodeThread : : start ( Priority priority )
{
qDebug ( " Thread starting... " ) ;
m_abort = false ;
m_pause = false ;
while ( m_semaphorePaused . tryAcquire ( 1 , 0 ) ) ;
QThread : : start ( priority ) ;
}
2012-01-29 21:31:09 +01:00
///////////////////////////////////////////////////////////////////////////////
// Encode functions
///////////////////////////////////////////////////////////////////////////////
2012-01-28 19:59:04 +01:00
void EncodeThread : : encode ( void )
{
2012-01-31 00:13:32 +01:00
QDateTime startTime = QDateTime : : currentDateTime ( ) ;
2012-02-02 02:13:02 +01:00
2014-02-24 14:57:30 +01:00
// -----------------------------------------------------------------------------------
// Print Information
// -----------------------------------------------------------------------------------
2012-01-29 19:14:46 +01:00
//Print some basic info
2012-04-30 19:26:41 +02:00
log ( tr ( " Simple x264 Launcher (Build #%1), built %2 \n " ) . arg ( QString : : number ( x264_version_build ( ) ) , x264_version_date ( ) . toString ( Qt : : ISODate ) ) ) ;
2012-01-29 19:14:46 +01:00
log ( tr ( " Job started at %1, %2. \n " ) . arg ( QDate : : currentDate ( ) . toString ( Qt : : ISODate ) , QTime : : currentTime ( ) . toString ( Qt : : ISODate ) ) ) ;
2013-05-11 21:52:07 +02:00
log ( tr ( " Source file: %1 " ) . arg ( QDir : : toNativeSeparators ( m_sourceFileName ) ) ) ;
log ( tr ( " Output file: %1 " ) . arg ( QDir : : toNativeSeparators ( m_outputFileName ) ) ) ;
2012-01-31 00:13:32 +01:00
2014-02-14 23:14:39 +01:00
//Print system info
log ( tr ( " \n --- SYSTEMINFO --- \n " ) ) ;
log ( tr ( " Binary Path: %1 " ) . arg ( QDir : : toNativeSeparators ( m_sysinfo - > getAppPath ( ) ) ) ) ;
2014-02-14 23:32:36 +01:00
log ( tr ( " Avisynth OK: %1 " ) . arg ( m_sysinfo - > hasAVSSupport ( ) ? tr ( " Yes " ) : tr ( " No " ) ) ) ;
2014-02-14 23:14:39 +01:00
log ( tr ( " VapourSynth: %1 " ) . arg ( m_sysinfo - > hasVPSSupport ( ) ? QDir : : toNativeSeparators ( m_sysinfo - > getVPSPath ( ) ) : tr ( " N/A " ) ) ) ;
2013-08-07 15:34:02 +02:00
2012-01-31 00:13:32 +01:00
//Print encoder settings
log ( tr ( " \n --- SETTINGS --- \n " ) ) ;
2012-01-29 19:14:46 +01:00
log ( tr ( " RC Mode: %1 " ) . arg ( OptionsModel : : rcMode2String ( m_options - > rcMode ( ) ) ) ) ;
2012-01-31 00:13:32 +01:00
log ( tr ( " Preset: %1 " ) . arg ( m_options - > preset ( ) ) ) ;
log ( tr ( " Tuning: %1 " ) . arg ( m_options - > tune ( ) ) ) ;
2012-01-29 19:14:46 +01:00
log ( tr ( " Profile: %1 " ) . arg ( m_options - > profile ( ) ) ) ;
2014-02-10 21:33:04 +01:00
log ( tr ( " Custom: %1 " ) . arg ( m_options - > customEncParams ( ) . isEmpty ( ) ? tr ( " (None) " ) : m_options - > customEncParams ( ) ) ) ;
2012-01-29 21:31:09 +01:00
2012-01-30 04:58:42 +01:00
bool ok = false ;
2012-01-31 00:13:32 +01:00
unsigned int frames = 0 ;
2014-02-24 19:51:53 +01:00
2014-02-24 14:57:30 +01:00
// -----------------------------------------------------------------------------------
// Check Versions
// -----------------------------------------------------------------------------------
2012-01-31 20:28:40 +01:00
log ( tr ( " \n --- CHECK VERSION --- \n " ) ) ;
2014-02-24 14:57:30 +01:00
//Check encoder version
2014-02-22 20:32:46 +01:00
bool encoderModified = false ;
2014-02-24 23:13:42 +01:00
const unsigned int encoderRevision = m_encoder - > checkVersion ( encoderModified ) ;
2014-02-24 14:57:30 +01:00
CHECK_STATUS ( m_abort , ( ok = ( encoderRevision ! = UINT_MAX ) ) ) ;
2012-01-31 20:28:40 +01:00
2014-02-24 23:13:42 +01:00
//Print source versions
2014-02-24 14:57:30 +01:00
m_encoder - > printVersion ( encoderRevision , encoderModified ) ;
2012-01-30 17:50:19 +01:00
2014-02-22 20:32:46 +01:00
//Is encoder version suppoprted?
2014-02-25 22:44:39 +01:00
CHECK_STATUS ( m_abort , ( ok = m_encoder - > isVersionSupported ( encoderRevision , encoderModified ) ) ) ;
2014-02-22 20:32:46 +01:00
2014-02-24 23:13:42 +01:00
if ( m_pipedSource )
2012-01-31 20:28:40 +01:00
{
2014-02-25 22:44:39 +01:00
//Is source type available?
CHECK_STATUS ( m_abort , ( ok = m_pipedSource - > isSourceAvailable ( ) ) ) ;
2014-02-24 23:13:42 +01:00
//Checking source version
bool sourceModified = false ;
const unsigned int sourceRevision = m_pipedSource - > checkVersion ( sourceModified ) ;
CHECK_STATUS ( m_abort , ( ok = ( sourceRevision ! = UINT_MAX ) ) ) ;
//Print source versions
2014-02-26 00:55:11 +01:00
m_pipedSource - > printVersion ( sourceRevision , sourceModified ) ;
2014-02-24 23:13:42 +01:00
//Is source version supported?
2014-02-25 22:44:39 +01:00
CHECK_STATUS ( m_abort , ( ok = m_pipedSource - > isVersionSupported ( sourceRevision , sourceModified ) ) ) ;
2012-01-31 20:28:40 +01:00
}
2014-02-24 14:57:30 +01:00
// -----------------------------------------------------------------------------------
// Detect Source Info
// -----------------------------------------------------------------------------------
2012-01-31 20:28:40 +01:00
//Detect source info
2014-02-24 23:13:42 +01:00
if ( m_pipedSource )
2012-01-31 20:28:40 +01:00
{
2014-02-24 23:13:42 +01:00
log ( tr ( " \n --- GET SOURCE INFO --- \n " ) ) ;
ok = m_pipedSource - > checkSourceProperties ( frames ) ;
CHECK_STATUS ( m_abort , ok ) ;
2012-01-31 20:28:40 +01:00
}
2014-02-24 23:13:42 +01:00
// -----------------------------------------------------------------------------------
// Encoding Passes
// -----------------------------------------------------------------------------------
2012-01-30 04:58:42 +01:00
//Run encoding passes
if ( m_options - > rcMode ( ) = = OptionsModel : : RCMode_2Pass )
{
2014-02-24 23:13:42 +01:00
const QString passLogFile = getPasslogFile ( m_outputFileName ) ;
2012-01-30 04:58:42 +01:00
2014-02-24 23:13:42 +01:00
log ( tr ( " \n --- ENCODING PASS #1 --- \n " ) ) ;
ok = m_encoder - > runEncodingPass ( m_pipedSource , m_outputFileName , frames , 1 , passLogFile ) ;
2012-01-30 17:50:19 +01:00
CHECK_STATUS ( m_abort , ok ) ;
2012-01-30 04:58:42 +01:00
2014-02-24 23:13:42 +01:00
log ( tr ( " \n --- ENCODING PASS #2 --- \n " ) ) ;
ok = m_encoder - > runEncodingPass ( m_pipedSource , m_outputFileName , frames , 2 , passLogFile ) ;
2012-01-30 17:50:19 +01:00
CHECK_STATUS ( m_abort , ok ) ;
2012-01-30 04:58:42 +01:00
}
else
{
2014-02-24 23:13:42 +01:00
log ( tr ( " \n --- ENCODING VIDEO --- \n " ) ) ;
ok = m_encoder - > runEncodingPass ( m_pipedSource , m_outputFileName , frames ) ;
2012-01-30 17:50:19 +01:00
CHECK_STATUS ( m_abort , ok ) ;
2012-01-30 04:58:42 +01:00
}
2014-02-24 23:13:42 +01:00
// -----------------------------------------------------------------------------------
// Encoding complete
// -----------------------------------------------------------------------------------
log ( tr ( " \n --- COMPLETED --- \n " ) ) ;
2012-01-31 00:13:32 +01:00
int timePassed = startTime . secsTo ( QDateTime : : currentDateTime ( ) ) ;
2013-11-03 16:56:55 +01:00
log ( tr ( " Job finished at %1, %2. Process took %3 minutes, %4 seconds. " ) . arg ( QDate : : currentDate ( ) . toString ( Qt : : ISODate ) , QTime : : currentTime ( ) . toString ( Qt : : ISODate ) , QString : : number ( timePassed / 60 ) , QString : : number ( timePassed % 60 ) ) ) ;
2012-01-30 04:58:42 +01:00
setStatus ( JobStatus_Completed ) ;
}
2014-02-24 23:13:42 +01:00
///////////////////////////////////////////////////////////////////////////////
// Misc functions
///////////////////////////////////////////////////////////////////////////////
void EncodeThread : : log ( const QString & text )
{
emit messageLogged ( m_jobId , text ) ;
}
void EncodeThread : : setStatus ( const JobStatus & newStatus )
{
if ( m_status ! = newStatus )
{
if ( ( newStatus ! = JobStatus_Completed ) & & ( newStatus ! = JobStatus_Failed ) & & ( newStatus ! = JobStatus_Aborted ) & & ( newStatus ! = JobStatus_Paused ) )
{
if ( m_status ! = JobStatus_Paused ) setProgress ( 0 ) ;
}
if ( newStatus = = JobStatus_Failed )
{
setDetails ( " The job has failed. See log for details! " ) ;
}
if ( newStatus = = JobStatus_Aborted )
{
setDetails ( " The job was aborted by the user! " ) ;
}
m_status = newStatus ;
emit statusChanged ( m_jobId , newStatus ) ;
}
}
void EncodeThread : : setProgress ( const unsigned int & newProgress )
{
if ( m_progress ! = newProgress )
{
m_progress = newProgress ;
emit progressChanged ( m_jobId , m_progress ) ;
}
}
void EncodeThread : : setDetails ( const QString & text )
{
2014-02-25 22:44:39 +01:00
if ( ( ! text . isEmpty ( ) ) & & ( m_details . compare ( text ) ! = 0 ) )
{
emit detailsChanged ( m_jobId , text ) ;
m_details = text ;
}
2014-02-24 23:13:42 +01:00
}
int EncodeThread : : getInputType ( const QString & fileExt )
{
int type = INPUT_NATIVE ;
if ( fileExt . compare ( " avs " , Qt : : CaseInsensitive ) = = 0 ) type = INPUT_AVISYN ;
if ( fileExt . compare ( " avsi " , Qt : : CaseInsensitive ) = = 0 ) type = INPUT_AVISYN ;
if ( fileExt . compare ( " vpy " , Qt : : CaseInsensitive ) = = 0 ) type = INPUT_VAPOUR ;
if ( fileExt . compare ( " py " , Qt : : CaseInsensitive ) = = 0 ) type = INPUT_VAPOUR ;
return type ;
}
QString EncodeThread : : getPasslogFile ( const QString & outputFile )
{
QFileInfo info ( outputFile ) ;
QString passLogFile = QString ( " %1/%2.stats " ) . arg ( info . absolutePath ( ) , info . completeBaseName ( ) ) ;
int counter = 1 ;
while ( QFileInfo ( passLogFile ) . exists ( ) )
{
passLogFile = QString ( " %1/%2_%3.stats " ) . arg ( info . absolutePath ( ) , info . completeBaseName ( ) , QString : : number ( + + counter ) ) ;
}
return passLogFile ;
}