2014-02-24 14:57:30 +01:00
///////////////////////////////////////////////////////////////////////////////
// Simple x264 Launcher
2018-02-26 15:57:56 +01:00
// Copyright (C) 2004-2018 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_x265.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 23:13:42 +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"
2016-05-08 18:33:48 +02:00
# include "model_clipInfo.h"
2014-02-24 14:57:30 +01:00
2015-02-02 22:11:06 +01:00
//MUtils
2016-10-21 11:59:41 +02:00
# include <MUtils/Global.h>
2015-02-02 22:11:06 +01:00
# include <MUtils/Exception.h>
//Qt
2014-02-24 14:57:30 +01:00
# include <QStringList>
2014-02-24 23:13:42 +01:00
# include <QDir>
2014-02-24 14:57:30 +01:00
# include <QRegExp>
2016-05-08 15:33:09 +02:00
# include <QPair>
2014-02-24 14:57:30 +01:00
//x265 version info
2018-02-26 15:57:56 +01:00
static const unsigned int VERSION_X265_MINIMUM_VER = 27 ;
static const unsigned int VERSION_X265_MINIMUM_REV = 1 ;
2014-02-24 14:57:30 +01:00
2014-02-25 23:52:10 +01:00
// ------------------------------------------------------------
// Helper Macros
// ------------------------------------------------------------
2014-05-14 17:17:40 +02:00
# define X265_UPDATE_PROGRESS(X) do \
2014-02-24 23:13:42 +01:00
{ \
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 23:13:42 +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 23:13:42 +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 23:13:42 +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 23:13:42 +01:00
} \
while ( 0 )
# 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 )
2014-04-11 15:33:02 +02:00
// ------------------------------------------------------------
// Encoder Info
// ------------------------------------------------------------
class X265EncoderInfo : public AbstractEncoderInfo
{
public :
2016-05-08 15:33:09 +02:00
virtual QString getName ( void ) const
2014-04-16 16:05:24 +02:00
{
2016-05-08 15:33:09 +02:00
return " x265 (HEVC/H.265) " ;
}
2016-05-08 16:30:31 +02:00
virtual QList < ArchId > getArchitectures ( void ) const
2016-05-08 15:33:09 +02:00
{
2016-05-08 16:30:31 +02:00
return QList < ArchId > ( )
< < qMakePair ( QString ( " 32-Bit (x86) " ) , ARCH_TYPE_X86 )
< < qMakePair ( QString ( " 64-Bit (x64) " ) , ARCH_TYPE_X64 ) ;
2016-05-08 15:33:09 +02:00
}
virtual QStringList getVariants ( void ) const
{
return QStringList ( ) < < " 8-Bit " < < " 10-Bit " < < " 12-Bit " ;
}
virtual QList < RCMode > getRCModes ( void ) const
{
return QList < RCMode > ( )
< < qMakePair ( QString ( " CRF " ) , RC_TYPE_QUANTIZER )
< < qMakePair ( QString ( " CQ " ) , RC_TYPE_QUANTIZER )
< < qMakePair ( QString ( " 2-Pass " ) , RC_TYPE_MULTIPASS )
< < qMakePair ( QString ( " ABR " ) , RC_TYPE_RATE_KBPS ) ;
2014-04-16 16:05:24 +02:00
}
2014-04-16 16:50:03 +02:00
virtual QStringList getTunings ( void ) const
{
2016-05-08 15:33:09 +02:00
return QStringList ( ) < < " Grain " < < " PSNR " < < " SSIM " < < " FastDecode " < < " ZeroLatency " ;
2014-04-16 16:50:03 +02:00
}
2015-08-02 21:16:36 +02:00
virtual QStringList getPresets ( void ) const
{
2016-05-08 15:33:09 +02:00
return QStringList ( )
< < " ultrafast " < < " superfast " < < " veryfast " < < " faster " < < " fast "
< < " medium " < < " slow " < < " slower " < < " veryslow " < < " placebo " ;
2015-08-02 21:16:36 +02:00
}
2016-05-08 15:33:09 +02:00
virtual QStringList getProfiles ( const quint32 & variant ) const
2014-04-16 16:05:24 +02:00
{
2015-08-02 19:16:37 +02:00
QStringList profiles ;
switch ( variant )
{
2016-05-08 15:33:09 +02:00
case 0 : profiles < < " main " < < " main-intra " < < " mainstillpicture " < < " main444-8 " < < " main444-intra " < < " main444-stillpicture " ; break ;
case 1 : profiles < < " main10 " < < " main10-intra " < < " main422-10 " < < " main422-10-intra " < < " main444-10 " < < " main444-10-intra " ; break ;
case 2 : profiles < < " main12 " < < " main12-intra " < < " main422-12 " < < " main422-12-intra " < < " main444-12 " < < " main444-12-intra " ; break ;
default : MUTILS_THROW ( " Unknown encoder variant! " ) ;
2015-08-02 19:16:37 +02:00
}
return profiles ;
2014-04-16 16:05:24 +02:00
}
2014-04-11 15:33:02 +02:00
virtual QStringList supportedOutputFormats ( void ) const
{
2016-05-08 15:33:09 +02:00
return QStringList ( ) < < " hevc " ;
2014-04-15 22:12:02 +02:00
}
2014-04-20 15:09:58 +02:00
virtual bool isInputTypeSupported ( const int format ) const
{
switch ( format )
{
case MediaInfo : : FILETYPE_YUV4MPEG2 :
return true ;
default :
return false ;
}
}
2015-08-02 19:16:37 +02:00
2016-05-08 15:33:09 +02:00
virtual QString getBinaryPath ( const SysinfoModel * sysinfo , const quint32 & encArch , const quint32 & encVariant ) const
2015-08-02 19:16:37 +02:00
{
2017-07-15 20:38:23 +02:00
QString arch ;
2015-08-02 19:16:37 +02:00
switch ( encArch )
{
2016-05-08 15:33:09 +02:00
case 0 : arch = " x86 " ; break ;
case 1 : arch = " x64 " ; break ;
2015-08-02 19:16:37 +02:00
default : MUTILS_THROW ( " Unknown encoder arch! " ) ;
}
2017-07-15 20:38:23 +02:00
if ( ( encVariant < 0 ) | | ( encVariant > 2 ) )
2015-08-02 19:16:37 +02:00
{
2017-07-15 20:38:23 +02:00
MUTILS_THROW ( " Unknown encoder variant! " ) ;
2015-08-02 19:16:37 +02:00
}
2017-07-15 20:38:23 +02:00
return QString ( " %1/toolset/%2/x265_%2.exe " ) . arg ( sysinfo - > getAppPath ( ) , arch ) ;
2015-08-02 19:16:37 +02:00
}
2016-05-08 22:51:10 +02:00
virtual QString getHelpCommand ( void ) const
{
return " --fullhelp " ;
}
2014-04-11 15:33:02 +02:00
} ;
static const X265EncoderInfo s_x265EncoderInfo ;
2016-05-08 16:30:31 +02:00
const AbstractEncoderInfo & X265Encoder : : encoderInfo ( void )
2014-04-11 15:33:02 +02:00
{
return s_x265EncoderInfo ;
}
2016-05-08 16:30:31 +02:00
const AbstractEncoderInfo & X265Encoder : : getEncoderInfo ( void ) const
{
return encoderInfo ( ) ;
}
2014-02-25 23:52:10 +01:00
// ------------------------------------------------------------
// Constructor & Destructor
// ------------------------------------------------------------
2014-02-24 19:51:53 +01:00
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 )
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_X265 )
{
2015-02-02 22:11:06 +01:00
MUTILS_THROW ( " Invalid encoder type! " ) ;
2014-02-24 14:57:30 +01:00
}
}
X265Encoder : : ~ X265Encoder ( void )
{
/*Nothing to do here*/
}
2015-08-02 19:16:37 +02:00
QString X265Encoder : : getName ( void ) const
2014-02-26 16:08:06 +01:00
{
2016-05-08 15:33:09 +02:00
return s_x265EncoderInfo . getFullName ( m_options - > encArch ( ) , m_options - > encVariant ( ) ) ;
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 X265Encoder : : checkVersion_init ( QList < QRegExp * > & patterns , QStringList & cmdLine )
{
cmdLine < < " --version " ;
2014-06-04 19:36:20 +02:00
patterns < < new QRegExp ( " \\ bHEVC \\ s+encoder \\ s+version \\ s+( \\ d) \\ .( \\ d+) \\ +( \\ d+) \\ b " , Qt : : CaseInsensitive ) ;
patterns < < new QRegExp ( " \\ bHEVC \\ s+encoder \\ s+version \\ s+( \\ d) \\ .( \\ d+) \\ b " , Qt : : CaseInsensitive ) ;
2014-02-24 14:57:30 +01:00
}
2017-01-07 21:54:06 +01:00
void X265Encoder : : checkVersion_parseLine ( const QString & line , const 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 17:39:36 +01:00
2014-02-24 14:57:30 +01:00
if ( ( offset = patterns [ 0 ] - > lastIndexIn ( line ) ) > = 0 )
{
2014-05-05 18:20:58 +02:00
unsigned int temp [ 3 ] ;
2016-10-21 11:59:41 +02:00
if ( MUtils : : regexp_parse_uint32 ( * patterns [ 0 ] , temp , 3 ) )
2014-05-05 18:20:58 +02:00
{
2016-10-21 11:59:41 +02:00
core = ( 10 * temp [ 0 ] ) + temp [ 1 ] ;
build = temp [ 2 ] ;
2014-05-05 18:20:58 +02:00
}
2014-02-24 14:57:30 +01:00
}
2014-06-04 19:36:20 +02:00
else if ( ( offset = patterns [ 1 ] - > lastIndexIn ( line ) ) > = 0 )
{
unsigned int temp [ 2 ] ;
2016-10-21 11:59:41 +02:00
if ( MUtils : : regexp_parse_uint32 ( * patterns [ 0 ] , temp , 2 ) )
2014-06-04 19:36:20 +02:00
{
core = ( 10 * temp [ 0 ] ) + temp [ 1 ] ;
}
build = 0 ;
}
2014-02-26 17:39:36 +01:00
if ( ! line . isEmpty ( ) )
{
log ( line ) ;
}
2014-02-24 14:57:30 +01:00
}
2014-02-26 17:39:36 +01:00
QString X265Encoder : : 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 ) ;
2014-05-05 18:20:58 +02:00
2014-05-06 00:22:18 +02:00
return tr ( " x265 version: %1.%2+%3 " ) . arg ( QString : : number ( core / 10 ) , QString : : number ( core % 10 ) , QString : : number ( build ) ) ;
2014-02-24 14:57:30 +01:00
}
bool X265Encoder : : 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-02-24 14:57:30 +01:00
2014-05-06 00:22:18 +02:00
if ( ( core < VERSION_X265_MINIMUM_VER ) | | ( ( core = = VERSION_X265_MINIMUM_VER ) & & ( build < VERSION_X265_MINIMUM_REV ) ) )
2014-02-24 14:57:30 +01:00
{
2016-10-21 11:59:41 +02:00
log ( tr ( " \n ERROR: Your version of x265 is too old! (Minimum required revision is %1.%2+%3) " ) . arg ( QString : : number ( VERSION_X265_MINIMUM_VER / 10 ) , QString : : number ( VERSION_X265_MINIMUM_VER % 10 ) , QString : : number ( VERSION_X265_MINIMUM_REV ) ) ) ;
2014-02-24 14:57:30 +01:00
return false ;
}
2014-05-06 00:22:18 +02:00
else if ( core > VERSION_X265_MINIMUM_VER )
2014-04-15 22:12:02 +02:00
{
log ( tr ( " \n WARNING: Your version of x265 is newer than the latest tested version, take care! " ) ) ;
2014-05-06 00:22:18 +02:00
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 ) ) ) ;
2014-04-15 22:12:02 +02:00
}
2014-02-24 14:57:30 +01:00
return true ;
}
2014-02-24 23:13:42 +01:00
2014-02-25 23:52:10 +01:00
// ------------------------------------------------------------
// Encoding Functions
// ------------------------------------------------------------
2016-05-08 18:33:48 +02:00
void X265Encoder : : buildCommandLine ( QStringList & cmdLine , const bool & usePipe , const ClipInfo & clipInfo , const QString & indexFile , const int & pass , const QString & passLogFile )
2014-02-24 23:13:42 +01:00
{
double crf_int = 0.0 , crf_frc = 0.0 ;
2017-07-15 20:38:23 +02:00
cmdLine < < " -D " ;
switch ( m_options - > encVariant ( ) )
{
case 0 :
cmdLine < < QString : : number ( 8 ) ;
break ;
case 1 :
cmdLine < < QString : : number ( 10 ) ;
break ;
case 2 :
cmdLine < < QString : : number ( 12 ) ;
break ;
default :
MUTILS_THROW ( " Unknown encoder variant! " ) ;
}
2014-02-24 23:13:42 +01:00
switch ( m_options - > rcMode ( ) )
{
2016-05-08 15:33:09 +02:00
case 0 :
2014-02-24 23:13:42 +01:00
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 ;
2016-05-24 20:03:25 +02:00
case 1 :
cmdLine < < " --qp " < < QString : : number ( qRound ( m_options - > quantizer ( ) ) ) ;
break ;
2016-05-08 15:33:09 +02:00
case 2 :
case 3 :
2014-02-24 23:13:42 +01:00
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 23:13:42 +01:00
break ;
}
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 23:13:42 +01:00
2015-08-02 22:13:14 +02:00
const QString tune = m_options - > tune ( ) . simplified ( ) . toLower ( ) ;
if ( ! tune . isEmpty ( ) )
2014-02-24 23:13:42 +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 23:13:42 +01:00
}
2015-08-02 22:13:14 +02:00
const QString profile = m_options - > profile ( ) . simplified ( ) . toLower ( ) ;
if ( ! profile . isEmpty ( ) )
2014-02-24 23:13:42 +01:00
{
2015-08-02 22:13:14 +02:00
if ( profile . compare ( QString : : fromLatin1 ( OptionsModel : : PROFILE_UNRESTRICTED ) , Qt : : CaseInsensitive ) ! = 0 )
2014-02-24 23:13:42 +01:00
{
2015-08-02 22:13:14 +02:00
cmdLine < < " --profile " < < profile ;
2014-02-24 23:13:42 +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 )
{
2016-05-08 18:33:48 +02:00
if ( clipInfo . getFrameCount ( ) < 1 )
{
MUTILS_THROW ( " Frames not set! " ) ;
}
cmdLine < < " --frames " < < QString : : number ( clipInfo . getFrameCount ( ) ) ;
2014-02-26 17:39:36 +01:00
cmdLine < < " --y4m " < < " - " ;
2014-02-24 23:13:42 +01:00
}
else
{
cmdLine < < QDir : : toNativeSeparators ( m_sourceFile ) ;
}
}
void X265Encoder : : runEncodingPass_init ( QList < QRegExp * > & patterns )
{
patterns < < new QRegExp ( " \\ [( \\ d+) \\ .( \\ d+)% \\ ].+frames " ) ; //regExpProgress
patterns < < new QRegExp ( " indexing.+ \\ [( \\ d+) \\ .( \\ d+)% \\ ] " ) ; //regExpIndexing
patterns < < new QRegExp ( " ^( \\ d+) frames: " ) ; //regExpFrameCnt
patterns < < new QRegExp ( " \\ [ \\ s*( \\ d+) \\ .( \\ d+)% \\ ] \\ s+( \\ d+)/( \\ d+) \\ s( \\ d+).( \\ d+) \\ s( \\ d+).( \\ d+) \\ s+( \\ d+):( \\ d+):( \\ d+) \\ s+( \\ d+):( \\ d+):( \\ d+) " ) ; //regExpModified
}
2017-01-07 21:54:06 +01:00
void X265Encoder : : runEncodingPass_parseLine ( const QString & line , const QList < QRegExp * > & patterns , const ClipInfo & clipInfo , const int & pass , double & last_progress , double & size_estimate )
2014-02-24 23:13:42 +01:00
{
int offset = - 1 ;
if ( ( offset = patterns [ 0 ] - > lastIndexIn ( line ) ) > = 0 )
{
2014-05-14 17:17:40 +02:00
X265_UPDATE_PROGRESS ( patterns [ 0 ] ) ;
2014-02-24 23:13:42 +01:00
}
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 )
{
2014-05-14 17:17:40 +02:00
X265_UPDATE_PROGRESS ( patterns [ 3 ] ) ;
2014-02-24 23:13:42 +01:00
}
else if ( ! line . isEmpty ( ) )
{
log ( line ) ;
}
}