2010-11-18 00:32:46 +01:00
///////////////////////////////////////////////////////////////////////////////
// LameXP - Audio Encoder Front-End
2022-06-19 13:50:46 +02:00
// Copyright (C) 2004-2022 LoRd_MuldeR <MuldeR2@GMX.de>
2010-11-18 00:32:46 +01:00
//
// This program is free software; you can redistribute it and/or modify
2020-03-28 15:31:01 +01:00
// it under the terms of the GNU GENERAL PUBLIC LICENSE as published by
2010-11-18 00:32:46 +01:00
// the Free Software Foundation; either version 2 of the License, or
2020-03-28 15:31:01 +01:00
// (at your option) any later version; always including the non-optional
// LAMEXP GNU GENERAL PUBLIC LICENSE ADDENDUM. See "License.txt" file!
2010-11-18 00:32:46 +01:00
//
// 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_Process.h"
2014-11-25 02:14:42 +01:00
//Internal
2010-11-18 00:32:46 +01:00
# include "Global.h"
2010-11-19 21:11:54 +01:00
# include "Model_AudioFile.h"
2010-11-18 00:32:46 +01:00
# include "Model_Progress.h"
2010-11-20 02:14:22 +01:00
# include "Encoder_Abstract.h"
2010-12-01 23:14:47 +01:00
# include "Decoder_Abstract.h"
2010-12-19 00:50:22 +01:00
# include "Filter_Abstract.h"
# include "Filter_Downmix.h"
2011-05-06 17:51:49 +02:00
# include "Filter_Resample.h"
2011-12-22 00:06:34 +01:00
# include "Tool_WaveProperties.h"
2010-12-01 23:14:47 +01:00
# include "Registry_Decoder.h"
2010-11-20 02:14:22 +01:00
# include "Model_Settings.h"
2010-11-18 00:32:46 +01:00
2014-11-25 02:14:42 +01:00
//MUtils
# include <MUtils/Global.h>
2014-11-25 18:23:03 +01:00
# include <MUtils/OSSupport.h>
2014-11-25 02:14:42 +01:00
# include <MUtils/Version.h>
//Qt
2010-11-19 21:11:54 +01:00
# include <QUuid>
# include <QFileInfo>
2010-11-20 02:14:22 +01:00
# include <QDir>
2010-11-20 22:14:10 +01:00
# include <QMutex>
# include <QMutexLocker>
2011-03-20 23:32:11 +01:00
# include <QDate>
2013-10-09 16:11:58 +02:00
# include <QThreadPool>
2010-11-19 21:11:54 +01:00
2014-11-25 02:14:42 +01:00
//CRT
2010-11-18 00:32:46 +01:00
# include <limits.h>
# include <time.h>
2011-05-06 17:51:49 +02:00
# include <stdlib.h>
# define DIFF(X,Y) ((X > Y) ? (X-Y) : (Y-X))
2013-10-12 22:55:41 +02:00
# define IS_WAVE(X) ((X.containerType().compare("Wave", Qt::CaseInsensitive) == 0) && (X.audioType().compare("PCM", Qt::CaseInsensitive) == 0))
2011-08-05 15:41:19 +02:00
# define STRDEF(STR,DEF) ((!STR.isEmpty()) ? STR : DEF)
2010-11-18 00:32:46 +01:00
////////////////////////////////////////////////////////////
// Constructor
////////////////////////////////////////////////////////////
2011-02-25 22:03:39 +01:00
ProcessThread : : ProcessThread ( const AudioFileModel & audioFile , const QString & outputDirectory , const QString & tempDirectory , AbstractEncoder * encoder , const bool prependRelativeSourcePath )
2010-11-19 21:11:54 +01:00
:
m_audioFile ( audioFile ) ,
2010-11-20 02:14:22 +01:00
m_outputDirectory ( outputDirectory ) ,
2011-02-25 22:03:39 +01:00
m_tempDirectory ( tempDirectory ) ,
2010-11-20 02:14:22 +01:00
m_encoder ( encoder ) ,
2010-11-19 21:11:54 +01:00
m_jobId ( QUuid : : createUuid ( ) ) ,
2010-12-14 01:25:13 +01:00
m_prependRelativeSourcePath ( prependRelativeSourcePath ) ,
2011-08-04 23:26:38 +02:00
m_renamePattern ( " <BaseName> " ) ,
2014-05-30 16:52:34 +02:00
m_overwriteMode ( OverwriteMode_KeepBoth ) ,
2015-11-07 21:39:33 +01:00
m_keepDateTime ( false ) ,
2013-10-11 20:43:31 +02:00
m_initialized ( - 1 ) ,
2011-12-22 00:06:34 +01:00
m_propDetect ( new WaveProperties ( ) )
2010-11-18 00:32:46 +01:00
{
2010-11-20 02:14:22 +01:00
connect ( m_encoder , SIGNAL ( statusUpdated ( int ) ) , this , SLOT ( handleUpdate ( int ) ) , Qt : : DirectConnection ) ;
2010-11-22 21:45:00 +01:00
connect ( m_encoder , SIGNAL ( messageLogged ( QString ) ) , this , SLOT ( handleMessage ( QString ) ) , Qt : : DirectConnection ) ;
2010-12-01 23:14:47 +01:00
2011-12-22 00:06:34 +01:00
connect ( m_propDetect , SIGNAL ( statusUpdated ( int ) ) , this , SLOT ( handleUpdate ( int ) ) , Qt : : DirectConnection ) ;
connect ( m_propDetect , SIGNAL ( messageLogged ( QString ) ) , this , SLOT ( handleMessage ( QString ) ) , Qt : : DirectConnection ) ;
2010-12-01 23:14:47 +01:00
m_currentStep = UnknownStep ;
2010-11-18 00:32:46 +01:00
}
ProcessThread : : ~ ProcessThread ( void )
{
2010-12-01 23:14:47 +01:00
while ( ! m_tempFiles . isEmpty ( ) )
{
2014-11-25 02:14:42 +01:00
MUtils : : remove_file ( m_tempFiles . takeFirst ( ) ) ;
2010-12-01 23:14:47 +01:00
}
2011-01-25 23:12:56 +01:00
while ( ! m_filters . isEmpty ( ) )
{
delete m_filters . takeFirst ( ) ;
}
2014-11-25 02:14:42 +01:00
MUTILS_DELETE ( m_encoder ) ;
MUTILS_DELETE ( m_propDetect ) ;
2013-10-09 03:14:38 +02:00
emit processFinished ( ) ;
2010-11-18 00:32:46 +01:00
}
2013-10-09 16:11:58 +02:00
////////////////////////////////////////////////////////////
// Init Function
////////////////////////////////////////////////////////////
2013-10-11 20:43:31 +02:00
bool ProcessThread : : init ( void )
2013-10-09 16:11:58 +02:00
{
2017-04-18 21:05:28 +02:00
if ( m_initialized . testAndSetOrdered ( ( - 1 ) , 0 ) )
2013-10-09 16:11:58 +02:00
{
//Initialize job status
qDebug ( " Process thread %s has started. " , m_jobId . toString ( ) . toLatin1 ( ) . constData ( ) ) ;
emit processStateInitialized ( m_jobId , QFileInfo ( m_audioFile . filePath ( ) ) . fileName ( ) , tr ( " Starting... " ) , ProgressModel : : JobRunning ) ;
//Initialize log
2014-12-04 00:02:42 +01:00
handleMessage ( QString ( ) . sprintf ( " LameXP v%u.%02u (Build #%u), compiled on %s at %s " , lamexp_version_major ( ) , lamexp_version_minor ( ) , lamexp_version_build ( ) , MUTILS_UTF8 ( MUtils : : Version : : app_build_date ( ) . toString ( Qt : : ISODate ) ) , MUTILS_UTF8 ( MUtils : : Version : : app_build_time ( ) . toString ( Qt : : ISODate ) ) ) ) ;
2013-10-09 16:11:58 +02:00
handleMessage ( " \n ------------------------------- \n " ) ;
2013-10-12 20:34:59 +02:00
return true ;
2013-10-11 20:43:31 +02:00
}
2013-10-12 20:34:59 +02:00
qWarning ( " [ProcessThread::init] Job %s already initialialized, skipping! " , m_jobId . toString ( ) . toLatin1 ( ) . constData ( ) ) ;
return false ;
2013-10-11 20:43:31 +02:00
}
2015-05-10 19:52:07 +02:00
bool ProcessThread : : start ( QThreadPool * const pool )
2013-10-11 20:43:31 +02:00
{
//Make sure object was initialized correctly
2017-04-18 21:05:28 +02:00
if ( m_initialized < 0 )
2013-10-11 20:43:31 +02:00
{
2014-11-30 21:32:23 +01:00
MUTILS_THROW ( " Object not initialized yet! " ) ;
2013-10-11 20:43:31 +02:00
}
2013-10-09 16:11:58 +02:00
2017-04-18 21:05:28 +02:00
if ( m_initialized . testAndSetOrdered ( 0 , 1 ) )
2013-10-11 20:43:31 +02:00
{
2013-10-09 16:11:58 +02:00
m_outFileName . clear ( ) ;
2017-04-18 21:05:28 +02:00
m_aborted . fetchAndStoreOrdered ( 0 ) ;
2013-10-12 20:34:59 +02:00
bool bSuccess = false ;
//Generate output file name
2013-10-09 16:11:58 +02:00
switch ( generateOutFileName ( m_outFileName ) )
{
case 1 :
//File name generated successfully :-)
2013-10-12 20:34:59 +02:00
bSuccess = true ;
2013-10-09 16:11:58 +02:00
pool - > start ( this ) ;
break ;
case - 1 :
//File name already exists -> skipping!
emit processStateChanged ( m_jobId , tr ( " Skipped. " ) , ProgressModel : : JobSkipped ) ;
emit processStateFinished ( m_jobId , m_outFileName , - 1 ) ;
break ;
default :
//File name could not be generated
emit processStateChanged ( m_jobId , tr ( " Not found! " ) , ProgressModel : : JobFailed ) ;
emit processStateFinished ( m_jobId , m_outFileName , 0 ) ;
break ;
}
2013-10-12 20:34:59 +02:00
return bSuccess ;
2013-10-09 16:11:58 +02:00
}
2013-10-12 20:34:59 +02:00
qWarning ( " [ProcessThread::start] Job %s already started, skipping! " , m_jobId . toString ( ) . toLatin1 ( ) . constData ( ) ) ;
2013-10-09 16:11:58 +02:00
return false ;
}
2011-05-06 17:51:49 +02:00
////////////////////////////////////////////////////////////
// Thread Entry Point
////////////////////////////////////////////////////////////
2010-11-18 00:32:46 +01:00
void ProcessThread : : run ( )
2010-11-26 00:29:53 +01:00
{
try
{
processFile ( ) ;
}
2013-10-18 21:37:40 +02:00
catch ( const std : : exception & error )
{
2014-11-30 21:32:23 +01:00
MUTILS_PRINT_ERROR ( " \n GURU MEDITATION !!! \n \n Exception error: \n %s \n " , error . what ( ) ) ;
2014-11-25 18:23:03 +01:00
MUtils : : OS : : fatal_exit ( L " Unhandeled C++ exception error, application will exit! " ) ;
2013-10-18 21:37:40 +02:00
}
2010-11-26 00:29:53 +01:00
catch ( . . . )
{
2014-11-30 21:32:23 +01:00
MUTILS_PRINT_ERROR ( " \n GURU MEDITATION !!! \n \n Unknown exception error! \n " ) ;
2014-11-25 18:23:03 +01:00
MUtils : : OS : : fatal_exit ( L " Unhandeled C++ exception error, application will exit! " ) ;
2010-11-26 00:29:53 +01:00
}
}
void ProcessThread : : processFile ( )
2010-11-18 00:32:46 +01:00
{
m_aborted = false ;
2010-12-01 23:14:47 +01:00
bool bSuccess = true ;
2013-10-09 03:14:38 +02:00
2013-10-09 16:11:58 +02:00
//Make sure object was initialized correctly
2013-10-11 20:43:31 +02:00
if ( m_initialized < 1 )
2010-11-20 02:14:22 +01:00
{
2014-11-30 21:32:23 +01:00
MUTILS_THROW ( " Object not initialized yet! " ) ;
2010-11-25 18:09:31 +01:00
}
2011-05-06 17:51:49 +02:00
2010-12-01 23:14:47 +01:00
QString sourceFile = m_audioFile . filePath ( ) ;
2015-10-25 16:19:45 +01:00
//-----------------------------------------------------
// Decode source file
//-----------------------------------------------------
2013-10-12 22:55:41 +02:00
const AudioFileModel_TechInfo & formatInfo = m_audioFile . techInfo ( ) ;
if ( ! m_filters . isEmpty ( ) | | ! m_encoder - > isFormatSupported ( formatInfo . containerType ( ) , formatInfo . containerProfile ( ) , formatInfo . audioType ( ) , formatInfo . audioProfile ( ) , formatInfo . audioVersion ( ) ) )
2010-11-25 18:09:31 +01:00
{
2010-12-01 23:14:47 +01:00
m_currentStep = DecodingStep ;
2013-10-12 22:55:41 +02:00
AbstractDecoder * decoder = DecoderRegistry : : lookup ( formatInfo . containerType ( ) , formatInfo . containerProfile ( ) , formatInfo . audioType ( ) , formatInfo . audioProfile ( ) , formatInfo . audioVersion ( ) ) ;
2010-12-01 23:14:47 +01:00
if ( decoder )
{
QString tempFile = generateTempFileName ( ) ;
connect ( decoder , SIGNAL ( statusUpdated ( int ) ) , this , SLOT ( handleUpdate ( int ) ) , Qt : : DirectConnection ) ;
connect ( decoder , SIGNAL ( messageLogged ( QString ) ) , this , SLOT ( handleMessage ( QString ) ) , Qt : : DirectConnection ) ;
2017-04-18 21:05:28 +02:00
bSuccess = decoder - > decode ( sourceFile , tempFile , m_aborted ) ;
2014-11-25 02:14:42 +01:00
MUTILS_DELETE ( decoder ) ;
2010-12-01 23:14:47 +01:00
if ( bSuccess )
{
sourceFile = tempFile ;
2013-10-12 22:55:41 +02:00
m_audioFile . techInfo ( ) . setContainerType ( QString : : fromLatin1 ( " Wave " ) ) ;
m_audioFile . techInfo ( ) . setAudioType ( QString : : fromLatin1 ( " PCM " ) ) ;
2012-06-25 21:59:28 +02:00
if ( QFileInfo ( sourceFile ) . size ( ) > = 4294967296 i64 )
{
handleMessage ( tr ( " WARNING: Decoded file size exceeds 4 GB, problems might occur! \n " ) ) ;
}
handleMessage ( " \n ------------------------------- \n " ) ;
2010-12-01 23:14:47 +01:00
}
}
else
{
2013-10-09 16:11:58 +02:00
if ( QFileInfo ( m_outFileName ) . exists ( ) & & ( QFileInfo ( m_outFileName ) . size ( ) < 512 ) ) QFile : : remove ( m_outFileName ) ;
2013-10-12 22:55:41 +02:00
handleMessage ( QString ( " %1 \n %2 \n \n %3 \t %4 \n %5 \t %6 " ) . arg ( tr ( " The format of this file is NOT supported: " ) , m_audioFile . filePath ( ) , tr ( " Container Format: " ) , m_audioFile . containerInfo ( ) , tr ( " Audio Format: " ) , m_audioFile . audioCompressInfo ( ) ) ) ;
2011-01-02 01:09:05 +01:00
emit processStateChanged ( m_jobId , tr ( " Unsupported! " ) , ProgressModel : : JobFailed ) ;
2013-10-09 16:11:58 +02:00
emit processStateFinished ( m_jobId , m_outFileName , 0 ) ;
2010-12-01 23:14:47 +01:00
return ;
}
2011-12-22 18:36:41 +01:00
}
2011-12-21 01:23:21 +01:00
2015-10-25 16:19:45 +01:00
//-----------------------------------------------------
// Update audio properties after decode
//-----------------------------------------------------
2017-04-18 21:05:28 +02:00
if ( bSuccess & & ( ! m_aborted ) & & IS_WAVE ( m_audioFile . techInfo ( ) ) )
2011-12-22 18:36:41 +01:00
{
2012-07-21 19:16:12 +02:00
if ( m_encoder - > supportedSamplerates ( ) | | m_encoder - > supportedBitdepths ( ) | | m_encoder - > supportedChannelCount ( ) | | m_encoder - > needsTimingInfo ( ) | | ! m_filters . isEmpty ( ) )
2011-12-21 01:23:21 +01:00
{
2011-12-22 18:36:41 +01:00
m_currentStep = AnalyzeStep ;
2017-04-18 21:05:28 +02:00
bSuccess = m_propDetect - > detect ( sourceFile , & m_audioFile . techInfo ( ) , m_aborted ) ;
2011-12-22 18:36:41 +01:00
if ( bSuccess )
2011-12-22 00:06:34 +01:00
{
2011-12-22 18:36:41 +01:00
handleMessage ( " \n ------------------------------- \n " ) ;
2011-12-22 00:06:34 +01:00
2011-12-22 18:36:41 +01:00
//Do we need to take care if Stereo downmix?
2016-11-12 16:06:04 +01:00
const unsigned int * const supportedChannelCount = m_encoder - > supportedChannelCount ( ) ;
if ( supportedChannelCount & & supportedChannelCount [ 0 ] )
2011-12-22 00:06:34 +01:00
{
2016-11-12 16:06:04 +01:00
insertDownmixFilter ( supportedChannelCount ) ;
2011-12-22 18:36:41 +01:00
}
//Do we need to take care of downsampling the input?
2016-11-12 16:06:04 +01:00
const unsigned int * const supportedSamplerates = m_encoder - > supportedSamplerates ( ) ;
const unsigned int * const supportedBitdepths = m_encoder - > supportedBitdepths ( ) ;
if ( ( supportedSamplerates & & supportedSamplerates [ 0 ] ) | | ( supportedBitdepths & & supportedBitdepths [ 0 ] ) )
2011-12-22 18:36:41 +01:00
{
2016-11-12 16:06:04 +01:00
insertDownsampleFilter ( supportedSamplerates , supportedBitdepths ) ;
2011-12-22 00:06:34 +01:00
}
}
2011-12-21 01:23:21 +01:00
}
}
2015-10-25 16:19:45 +01:00
//-----------------------------------------------------
// Apply all audio filters
//-----------------------------------------------------
2016-11-12 16:49:29 +01:00
while ( bSuccess & & ( ! m_filters . isEmpty ( ) ) & & ( ! m_aborted ) )
2010-12-19 00:50:22 +01:00
{
2016-11-12 16:49:29 +01:00
QString tempFile = generateTempFileName ( ) ;
AbstractFilter * poFilter = m_filters . takeFirst ( ) ;
m_currentStep = FilteringStep ;
2010-12-19 00:50:22 +01:00
2016-11-12 16:49:29 +01:00
connect ( poFilter , SIGNAL ( statusUpdated ( int ) ) , this , SLOT ( handleUpdate ( int ) ) , Qt : : DirectConnection ) ;
connect ( poFilter , SIGNAL ( messageLogged ( QString ) ) , this , SLOT ( handleMessage ( QString ) ) , Qt : : DirectConnection ) ;
2010-12-19 00:50:22 +01:00
2017-04-18 21:05:28 +02:00
const AbstractFilter : : FilterResult filterResult = poFilter - > apply ( sourceFile , tempFile , & m_audioFile . techInfo ( ) , m_aborted ) ;
2016-11-12 16:49:29 +01:00
switch ( filterResult )
{
case AbstractFilter : : FILTER_SUCCESS :
sourceFile = tempFile ;
break ;
case AbstractFilter : : FILTER_FAILURE :
bSuccess = false ;
break ;
2011-08-07 14:04:17 +02:00
}
2016-11-12 16:49:29 +01:00
handleMessage ( " \n ------------------------------- \n " ) ;
delete poFilter ;
2010-12-19 00:50:22 +01:00
}
2015-10-25 16:19:45 +01:00
//-----------------------------------------------------
// Encode audio file
//-----------------------------------------------------
2017-04-18 21:05:28 +02:00
if ( bSuccess & & ( ! m_aborted ) )
2010-12-01 23:14:47 +01:00
{
m_currentStep = EncodingStep ;
2017-04-18 21:05:28 +02:00
bSuccess = m_encoder - > encode ( sourceFile , m_audioFile . metaInfo ( ) , m_audioFile . techInfo ( ) . duration ( ) , m_audioFile . techInfo ( ) . audioChannels ( ) , m_outFileName , m_aborted ) ;
2010-12-01 23:14:47 +01:00
}
2010-11-20 02:14:22 +01:00
2012-11-11 18:58:08 +01:00
//Clean-up
2017-04-19 23:54:00 +02:00
if ( ( ! bSuccess ) | | MUTILS_BOOLIFY ( m_aborted ) )
2012-11-11 18:58:08 +01:00
{
2013-10-09 16:11:58 +02:00
QFileInfo fileInfo ( m_outFileName ) ;
2015-10-25 16:19:45 +01:00
if ( fileInfo . exists ( ) & & ( fileInfo . size ( ) < 1024 ) )
2012-11-11 18:58:08 +01:00
{
2013-10-09 16:11:58 +02:00
QFile : : remove ( m_outFileName ) ;
2012-11-11 18:58:08 +01:00
}
}
2010-12-01 23:14:47 +01:00
//Make sure output file exists
2012-11-11 18:58:08 +01:00
if ( bSuccess & & ( ! m_aborted ) )
2010-11-18 00:32:46 +01:00
{
2015-10-25 16:19:45 +01:00
const QFileInfo fileInfo ( m_outFileName ) ;
bSuccess = fileInfo . exists ( ) & & fileInfo . isFile ( ) & & ( fileInfo . size ( ) > = 1024 ) ;
2010-11-18 00:32:46 +01:00
}
2015-10-25 16:19:45 +01:00
//-----------------------------------------------------
// Finalize
//-----------------------------------------------------
2015-11-07 21:39:33 +01:00
if ( bSuccess & & ( ! m_aborted ) & & m_keepDateTime )
2015-10-25 16:19:45 +01:00
{
updateFileTime ( m_audioFile . filePath ( ) , m_outFileName ) ;
}
MUtils : : OS : : sleep_ms ( 12 ) ;
2011-11-16 22:56:32 +01:00
2010-12-01 23:14:47 +01:00
//Report result
2017-04-19 23:54:00 +02:00
emit processStateChanged ( m_jobId , ( MUTILS_BOOLIFY ( m_aborted ) ? tr ( " Aborted! " ) : ( bSuccess ? tr ( " Done. " ) : tr ( " Failed! " ) ) ) , ( ( bSuccess & & ( ! m_aborted ) ) ? ProgressModel : : JobComplete : ProgressModel : : JobFailed ) ) ;
2013-10-09 16:11:58 +02:00
emit processStateFinished ( m_jobId , m_outFileName , ( bSuccess ? 1 : 0 ) ) ;
2010-11-20 22:14:10 +01:00
2010-11-18 22:37:35 +01:00
qDebug ( " Process thread is done. " ) ;
2010-11-18 00:32:46 +01:00
}
2010-11-20 02:14:22 +01:00
////////////////////////////////////////////////////////////
// SLOTS
////////////////////////////////////////////////////////////
void ProcessThread : : handleUpdate ( int progress )
{
2013-10-25 17:55:27 +02:00
//qDebug("Progress: %d\n", progress);
2011-11-16 22:56:32 +01:00
2010-12-01 23:14:47 +01:00
switch ( m_currentStep )
{
case EncodingStep :
2011-01-02 01:09:05 +01:00
emit processStateChanged ( m_jobId , QString ( " %1 (%2%) " ).arg(tr( " Encoding " ), QString::number(progress)), ProgressModel::JobRunning) ;
2010-12-19 00:50:22 +01:00
break ;
2011-12-22 00:06:34 +01:00
case AnalyzeStep :
emit processStateChanged ( m_jobId , QString ( " %1 (%2%) " ).arg(tr( " Analyzing " ), QString::number(progress)), ProgressModel::JobRunning) ;
break ;
2010-12-19 00:50:22 +01:00
case FilteringStep :
2011-01-02 01:09:05 +01:00
emit processStateChanged ( m_jobId , QString ( " %1 (%2%) " ).arg(tr( " Filtering " ), QString::number(progress)), ProgressModel::JobRunning) ;
2010-12-01 23:14:47 +01:00
break ;
case DecodingStep :
2011-01-02 01:09:05 +01:00
emit processStateChanged ( m_jobId , QString ( " %1 (%2%) " ).arg(tr( " Decoding " ), QString::number(progress)), ProgressModel::JobRunning) ;
2010-12-01 23:14:47 +01:00
break ;
}
2010-11-20 02:14:22 +01:00
}
2010-11-22 21:45:00 +01:00
void ProcessThread : : handleMessage ( const QString & line )
{
emit processMessageLogged ( m_jobId , line ) ;
}
2010-11-20 02:14:22 +01:00
////////////////////////////////////////////////////////////
// PRIVAE FUNCTIONS
////////////////////////////////////////////////////////////
2012-11-08 21:19:45 +01:00
int ProcessThread : : generateOutFileName ( QString & outFileName )
2010-11-20 02:14:22 +01:00
{
2012-11-08 21:19:45 +01:00
outFileName . clear ( ) ;
2018-04-30 14:35:51 +02:00
/* -------- Check source file -------- */
2012-11-08 21:19:45 +01:00
//Make sure the source file exists
2015-05-09 23:33:07 +02:00
const QFileInfo sourceFile ( m_audioFile . filePath ( ) ) ;
2014-05-28 16:49:58 +02:00
if ( ! ( sourceFile . exists ( ) & & sourceFile . isFile ( ) ) )
2010-11-21 21:51:22 +01:00
{
2011-01-02 01:09:05 +01:00
handleMessage ( QString ( " %1 \n %2 " ) . arg ( tr ( " The source audio file could not be found: " ) , sourceFile . absoluteFilePath ( ) ) ) ;
2012-11-08 21:19:45 +01:00
return 0 ;
2010-11-25 18:09:31 +01:00
}
2012-11-08 21:19:45 +01:00
//Make sure the source file readable
2010-11-25 18:09:31 +01:00
QFile readTest ( sourceFile . canonicalFilePath ( ) ) ;
if ( ! readTest . open ( QIODevice : : ReadOnly ) )
{
2012-11-08 21:19:45 +01:00
handleMessage ( QString ( " %1 \n %2 " ) . arg ( tr ( " The source audio file could not be opened for reading: " ) , QDir : : toNativeSeparators ( readTest . fileName ( ) ) ) ) ;
return 0 ;
2010-11-21 21:51:22 +01:00
}
2010-11-25 18:09:31 +01:00
else
{
readTest . close ( ) ;
}
2010-11-20 02:14:22 +01:00
2018-04-30 14:35:51 +02:00
/* -------- Determine target directory -------- */
2017-04-09 19:17:00 +02:00
QDir targetDir ( MUtils : : clean_file_path ( m_outputDirectory . isEmpty ( ) ? sourceFile . canonicalPath ( ) : m_outputDirectory , false ) ) ;
2010-12-14 01:25:13 +01:00
2012-11-08 21:19:45 +01:00
//Prepend relative source file path?
2010-12-14 01:25:13 +01:00
if ( m_prependRelativeSourcePath & & ! m_outputDirectory . isEmpty ( ) )
{
2017-03-12 21:14:56 +01:00
QDir sourceDir = sourceFile . dir ( ) ;
if ( ! sourceDir . isRoot ( ) )
2010-12-14 01:25:13 +01:00
{
2017-03-12 21:14:56 +01:00
quint32 depth = 0 ;
while ( ( ! sourceDir . isRoot ( ) ) & & ( + + depth < = 0xFF ) )
{
if ( ! sourceDir . cdUp ( ) ) break ;
}
const QString postfix = QFileInfo ( sourceDir . relativeFilePath ( sourceFile . canonicalFilePath ( ) ) ) . path ( ) ;
2017-04-09 19:17:00 +02:00
targetDir . setPath ( MUtils : : clean_file_path ( QString ( " %1/%2 " ) . arg ( targetDir . absolutePath ( ) , postfix ) , false ) ) ;
2010-12-14 01:25:13 +01:00
}
}
2010-11-21 21:51:22 +01:00
2012-11-08 21:19:45 +01:00
//Make sure output directory does exist
2010-11-21 21:51:22 +01:00
if ( ! targetDir . exists ( ) )
{
targetDir . mkpath ( " . " ) ;
if ( ! targetDir . exists ( ) )
{
2012-11-08 21:19:45 +01:00
handleMessage ( QString ( " %1 \n %2 " ) . arg ( tr ( " The target output directory doesn't exist and could NOT be created: " ) , QDir : : toNativeSeparators ( targetDir . absolutePath ( ) ) ) ) ;
return 0 ;
2010-11-21 21:51:22 +01:00
}
}
2012-11-08 21:19:45 +01:00
//Make sure that the output dir is writable
2018-04-30 14:35:51 +02:00
QFile writeTest ( MUtils : : make_temp_file ( targetDir , " ~rw " ) ) ;
2010-11-25 18:09:31 +01:00
if ( ! writeTest . open ( QIODevice : : ReadWrite ) )
{
2012-11-08 21:19:45 +01:00
handleMessage ( QString ( " %1 \n %2 " ) . arg ( tr ( " The target output directory is NOT writable: " ) , QDir : : toNativeSeparators ( targetDir . absolutePath ( ) ) ) ) ;
return 0 ;
2010-11-25 18:09:31 +01:00
}
else
{
2018-04-30 14:35:51 +02:00
writeTest . remove ( ) ; /*clean-up*/
2010-11-25 18:09:31 +01:00
}
2018-04-30 14:35:51 +02:00
/* -------- Generate initial file name and check -------- */
2017-08-15 22:47:09 +02:00
//File extension
const QString fileExt = m_renameFileExt . isEmpty ( ) ? QString : : fromUtf8 ( m_encoder - > toEncoderInfo ( ) - > extension ( ) ) : m_renameFileExt ;
//Generate file name
const QString fileName = MUtils : : clean_file_name ( QString ( " %1.%2 " ) . arg ( applyRegularExpression ( applyRenamePattern ( sourceFile . completeBaseName ( ) , m_audioFile . metaInfo ( ) ) ) , fileExt ) , true ) ;
2011-08-04 23:26:38 +02:00
2012-11-08 21:19:45 +01:00
//Generate full output path
2018-04-30 14:35:51 +02:00
outFileName = targetDir . absoluteFilePath ( fileName ) ;
2012-11-08 21:19:45 +01:00
//Skip file, if target file exists (optional!)
2014-05-30 16:52:34 +02:00
if ( ( m_overwriteMode = = OverwriteMode_SkipExisting ) & & QFileInfo ( outFileName ) . exists ( ) )
2012-11-08 21:19:45 +01:00
{
handleMessage ( QString ( " %1 \n %2 \n " ) . arg ( tr ( " Target output file already exists, going to skip this file: " ) , QDir : : toNativeSeparators ( outFileName ) ) ) ;
handleMessage ( tr ( " If you don't want existing files to be skipped, please change the overwrite mode! " ) ) ;
return - 1 ;
}
//Delete file, if target file exists (optional!)
2018-04-30 14:35:51 +02:00
const QFileInfo origFileName ( outFileName ) ;
if ( ( m_overwriteMode = = OverwriteMode_Overwrite ) & & origFileName . exists ( ) & & origFileName . isFile ( ) )
2012-11-08 21:19:45 +01:00
{
2014-05-30 16:52:34 +02:00
handleMessage ( QString ( " %1 \n %2 \n " ) . arg ( tr ( " Target output file already exists, going to delete existing file: " ) , QDir : : toNativeSeparators ( outFileName ) ) ) ;
2017-08-15 22:47:09 +02:00
bool removed = false ;
2018-04-30 14:35:51 +02:00
if ( sourceFile . canonicalFilePath ( ) . compare ( origFileName . absoluteFilePath ( ) , Qt : : CaseInsensitive ) ! = 0 )
2012-11-08 21:19:45 +01:00
{
2018-04-30 14:35:51 +02:00
removed = MUtils : : remove_file ( outFileName ) ;
2014-05-30 16:52:34 +02:00
}
2017-08-15 22:47:09 +02:00
if ( ! removed )
2014-05-30 16:52:34 +02:00
{
handleMessage ( QString ( " %1 \n " ) . arg ( tr ( " Failed to delete existing target file, will save to another file name! " ) ) ) ;
2012-11-08 21:19:45 +01:00
}
}
2018-04-30 14:35:51 +02:00
/* -------- Generate final non-existing file name -------- */
2012-11-08 21:19:45 +01:00
//Generate final name
2018-04-30 14:35:51 +02:00
outFileName = MUtils : : make_unique_file ( origFileName . absoluteDir ( ) , origFileName . completeBaseName ( ) , origFileName . suffix ( ) , true , true ) ;
if ( ! outFileName . isEmpty ( ) )
2010-11-20 02:14:22 +01:00
{
2018-04-30 14:35:51 +02:00
return 1 ;
2010-11-20 22:14:10 +01:00
}
2018-04-30 14:35:51 +02:00
//Failed to generate
2017-08-15 22:47:09 +02:00
handleMessage ( QString ( " %1 \n " ) . arg ( tr ( " Failed to generate non-existing target file name! " ) ) ) ;
return 0 ;
2010-11-20 02:14:22 +01:00
}
2013-10-12 22:55:41 +02:00
QString ProcessThread : : applyRenamePattern ( const QString & baseName , const AudioFileModel_MetaInfo & metaInfo )
{
QString fileName = m_renamePattern ;
2015-05-09 23:33:07 +02:00
fileName . replace ( " <BaseName> " , STRDEF ( baseName , tr ( " Unknown File Name " ) ) , Qt : : CaseInsensitive ) ;
fileName . replace ( " <TrackNo> " , QString ( ) . sprintf ( " %02d " , metaInfo . position ( ) ) , Qt : : CaseInsensitive ) ;
fileName . replace ( " <Title> " , STRDEF ( metaInfo . title ( ) , tr ( " Unknown Title " ) ) , Qt : : CaseInsensitive ) ;
fileName . replace ( " <Artist> " , STRDEF ( metaInfo . artist ( ) , tr ( " Unknown Artist " ) ) , Qt : : CaseInsensitive ) ;
fileName . replace ( " <Album> " , STRDEF ( metaInfo . album ( ) , tr ( " Unknown Album " ) ) , Qt : : CaseInsensitive ) ;
fileName . replace ( " <Year> " , QString ( ) . sprintf ( " %04d " , metaInfo . year ( ) ) , Qt : : CaseInsensitive ) ;
fileName . replace ( " <Comment> " , STRDEF ( metaInfo . comment ( ) , tr ( " Unknown Comment " ) ) , Qt : : CaseInsensitive ) ;
2013-10-12 22:55:41 +02:00
2017-03-12 21:14:56 +01:00
return fileName . trimmed ( ) . isEmpty ( ) ? baseName : fileName ;
2013-10-12 22:55:41 +02:00
}
2017-03-12 21:14:56 +01:00
QString ProcessThread : : applyRegularExpression ( const QString & baseName )
2015-05-09 23:33:07 +02:00
{
if ( m_renameRegExp_Search . isEmpty ( ) | | m_renameRegExp_Replace . isEmpty ( ) )
{
2017-03-12 21:14:56 +01:00
return baseName ;
2015-05-09 23:33:07 +02:00
}
QRegExp regExp ( m_renameRegExp_Search ) ;
if ( ! regExp . isValid ( ) )
{
qWarning ( " Invalid regular expression detected -> cannot rename! " ) ;
2017-03-12 21:14:56 +01:00
return baseName ;
2015-05-09 23:33:07 +02:00
}
2017-03-12 21:14:56 +01:00
const QString fileName = QString ( baseName ) . replace ( regExp , m_renameRegExp_Replace ) ;
return fileName . trimmed ( ) . isEmpty ( ) ? baseName : fileName ;
2015-05-09 23:33:07 +02:00
}
2010-12-01 23:14:47 +01:00
QString ProcessThread : : generateTempFileName ( void )
{
2015-08-31 22:53:19 +02:00
const QString tempFileName = MUtils : : make_temp_file ( m_tempDirectory , " wav " , true ) ;
if ( tempFileName . isEmpty ( ) )
2010-12-01 23:14:47 +01:00
{
2016-12-19 21:17:41 +01:00
return QString ( " %1/~whoops%2.wav " ) . arg ( m_tempDirectory , QString : : number ( MUtils : : next_rand_u32 ( ) ) ) ;
2010-12-01 23:14:47 +01:00
}
m_tempFiles < < tempFileName ;
return tempFileName ;
}
2016-11-12 16:06:04 +01:00
bool ProcessThread : : insertDownsampleFilter ( const unsigned int * const supportedSamplerates , const unsigned int * const supportedBitdepths )
2011-05-06 17:51:49 +02:00
{
2016-11-12 16:06:04 +01:00
int targetSampleRate = 0 , targetBitDepth = 0 ;
2011-12-22 18:36:41 +01:00
/* Adjust sample rate */
2016-11-12 16:06:04 +01:00
if ( supportedSamplerates & & m_audioFile . techInfo ( ) . audioSamplerate ( ) )
2011-05-06 17:51:49 +02:00
{
2016-11-12 16:06:04 +01:00
const unsigned int inputRate = m_audioFile . techInfo ( ) . audioSamplerate ( ) ;
unsigned int currentDiff = UINT_MAX , minimumDiff = UINT_MAX , bestRate = UINT_MAX ;
//Find the most suitable supported sampling rate
for ( int i = 0 ; supportedSamplerates [ i ] ; i + + )
2011-05-06 17:51:49 +02:00
{
2016-11-12 16:06:04 +01:00
currentDiff = DIFF ( inputRate , supportedSamplerates [ i ] ) ;
if ( ( currentDiff < minimumDiff ) | | ( ( currentDiff = = minimumDiff ) & & ( bestRate < supportedSamplerates [ i ] ) ) )
2011-12-22 18:36:41 +01:00
{
2016-11-12 16:06:04 +01:00
bestRate = supportedSamplerates [ i ] ;
minimumDiff = currentDiff ;
if ( ! ( minimumDiff > 0 ) ) break ;
2011-12-22 18:36:41 +01:00
}
2011-05-06 17:51:49 +02:00
}
2016-11-12 16:06:04 +01:00
if ( bestRate ! = inputRate )
2011-12-22 18:36:41 +01:00
{
2016-11-12 16:06:04 +01:00
targetSampleRate = ( bestRate ! = UINT_MAX ) ? bestRate : supportedSamplerates [ 0 ] ;
2011-12-22 18:36:41 +01:00
}
}
/* Adjust bit depth (word size) */
2016-11-12 16:06:04 +01:00
if ( supportedBitdepths & & m_audioFile . techInfo ( ) . audioBitdepth ( ) )
2011-05-06 17:51:49 +02:00
{
2013-10-12 22:55:41 +02:00
const unsigned int inputBPS = m_audioFile . techInfo ( ) . audioBitdepth ( ) ;
2012-08-22 23:52:55 +02:00
bool bAdjustBitdepth = true ;
2013-10-23 20:56:57 +02:00
//Is the input bit depth supported exactly? (including IEEE Float)
2016-11-12 16:06:04 +01:00
for ( int i = 0 ; supportedBitdepths [ i ] ; i + + )
2011-05-06 17:51:49 +02:00
{
2016-11-12 16:06:04 +01:00
if ( supportedBitdepths [ i ] = = inputBPS ) bAdjustBitdepth = false ;
2012-08-22 23:52:55 +02:00
}
if ( bAdjustBitdepth )
{
unsigned int currentDiff = UINT_MAX , minimumDiff = UINT_MAX , bestBPS = UINT_MAX ;
const unsigned int originalBPS = ( inputBPS = = AudioFileModel : : BITDEPTH_IEEE_FLOAT32 ) ? 32 : inputBPS ;
//Find the most suitable supported bit depth
2016-11-12 16:06:04 +01:00
for ( int i = 0 ; supportedBitdepths [ i ] ; i + + )
2011-05-06 17:51:49 +02:00
{
2016-11-12 16:06:04 +01:00
if ( supportedBitdepths [ i ] = = AudioFileModel : : BITDEPTH_IEEE_FLOAT32 ) continue ;
2012-08-22 23:52:55 +02:00
2016-11-12 16:06:04 +01:00
currentDiff = DIFF ( originalBPS , supportedBitdepths [ i ] ) ;
if ( ( currentDiff < minimumDiff ) | | ( ( currentDiff = = minimumDiff ) & & ( bestBPS < supportedBitdepths [ i ] ) ) )
2012-08-22 23:52:55 +02:00
{
2016-11-12 16:06:04 +01:00
bestBPS = supportedBitdepths [ i ] ;
2012-08-22 23:52:55 +02:00
minimumDiff = currentDiff ;
if ( ! ( minimumDiff > 0 ) ) break ;
}
2011-05-06 17:51:49 +02:00
}
2011-12-22 18:36:41 +01:00
2012-08-22 23:52:55 +02:00
if ( bestBPS ! = originalBPS )
{
2016-11-12 16:06:04 +01:00
targetBitDepth = ( bestBPS ! = UINT_MAX ) ? bestBPS : supportedBitdepths [ 0 ] ;
}
}
}
//Check if downsampling filter is already in the chain
if ( targetSampleRate | | targetBitDepth )
{
for ( int i = 0 ; i < m_filters . count ( ) ; i + + )
{
if ( dynamic_cast < ResampleFilter * > ( m_filters . at ( i ) ) )
{
qWarning ( " Encoder requires downsampling, but user has already set resamling filter! " ) ;
handleMessage ( " WARNING: Encoder may need resampling, but already using resample filter. Encoding *may* fail! \n " ) ;
targetSampleRate = targetBitDepth = 0 ;
2012-08-22 23:52:55 +02:00
}
2011-05-06 17:51:49 +02:00
}
}
2011-12-22 18:36:41 +01:00
/* Insert the filter */
if ( targetSampleRate | | targetBitDepth )
{
m_filters . append ( new ResampleFilter ( targetSampleRate , targetBitDepth ) ) ;
2016-11-12 16:06:04 +01:00
return true ;
2011-12-22 18:36:41 +01:00
}
2016-11-12 16:06:04 +01:00
return false ; /*did not insert the resample filter */
2011-12-21 01:23:21 +01:00
}
2016-11-12 16:06:04 +01:00
bool ProcessThread : : insertDownmixFilter ( const unsigned int * const supportedChannels )
2011-08-06 18:56:09 +02:00
{
2016-11-12 16:06:04 +01:00
//Determine number of channels in source
const unsigned int channels = m_audioFile . techInfo ( ) . audioChannels ( ) ;
bool requiresDownmix = ( channels > 0 ) ;
//Check whether encoder requires downmixing
if ( requiresDownmix )
2011-08-06 18:56:09 +02:00
{
2016-11-12 16:06:04 +01:00
for ( int i = 0 ; supportedChannels [ i ] ; i + + )
2011-08-06 18:56:09 +02:00
{
2016-11-12 16:06:04 +01:00
if ( supportedChannels [ i ] = = channels )
{
requiresDownmix = false ;
break ;
}
2011-08-06 18:56:09 +02:00
}
}
2011-08-27 21:28:20 +02:00
2016-11-12 16:06:04 +01:00
//Check if downmixing filter is already in the chain
if ( requiresDownmix )
2011-08-06 18:56:09 +02:00
{
2016-11-12 16:06:04 +01:00
for ( int i = 0 ; i < m_filters . count ( ) ; i + + )
2011-12-23 00:43:11 +01:00
{
2016-11-12 16:06:04 +01:00
if ( dynamic_cast < DownmixFilter * > ( m_filters . at ( i ) ) )
2011-12-23 00:43:11 +01:00
{
2016-11-12 16:06:04 +01:00
qWarning ( " Encoder requires Stereo downmix, but user has already forced downmix! " ) ;
handleMessage ( " WARNING: Encoder may need downmixning, but already using downmixning filter. Encoding *may* fail! \n " ) ;
2011-12-23 00:43:11 +01:00
requiresDownmix = false ;
break ;
}
}
2016-11-12 16:06:04 +01:00
}
2011-12-23 00:43:11 +01:00
2016-11-12 16:06:04 +01:00
//Now add the downmixing filter, if needed
if ( requiresDownmix )
{
m_filters . append ( new DownmixFilter ( ) ) ;
return true ;
2011-08-06 18:56:09 +02:00
}
2016-11-12 16:06:04 +01:00
return false ; /*did not insert the downmix filter*/
2011-08-06 18:56:09 +02:00
}
2015-10-25 16:19:45 +01:00
bool ProcessThread : : updateFileTime ( const QString & originalFile , const QString & modifiedFile )
{
bool success = false ;
QFileInfo originalFileInfo ( originalFile ) ;
const QDateTime timeCreated = originalFileInfo . created ( ) , timeLastMod = originalFileInfo . lastModified ( ) ;
if ( timeCreated . isValid ( ) & & timeLastMod . isValid ( ) )
{
if ( ! MUtils : : OS : : set_file_time ( modifiedFile , timeCreated , timeLastMod ) )
{
qWarning ( " Failed to update creation/modified time of output file: \" %s \" " , MUTILS_UTF8 ( modifiedFile ) ) ;
}
}
else
{
qWarning ( " Failed to read creation/modified time of source file: \" %s \" " , MUTILS_UTF8 ( originalFile ) ) ;
}
return success ;
}
2011-05-06 17:51:49 +02:00
////////////////////////////////////////////////////////////
// PUBLIC FUNCTIONS
////////////////////////////////////////////////////////////
2011-01-25 23:12:56 +01:00
void ProcessThread : : addFilter ( AbstractFilter * filter )
{
m_filters . append ( filter ) ;
}
2011-08-04 23:26:38 +02:00
void ProcessThread : : setRenamePattern ( const QString & pattern )
{
2015-05-09 23:33:07 +02:00
const QString newPattern = pattern . simplified ( ) ;
2011-08-05 02:33:32 +02:00
if ( ! newPattern . isEmpty ( ) ) m_renamePattern = newPattern ;
2011-08-04 23:26:38 +02:00
}
2015-05-09 23:33:07 +02:00
void ProcessThread : : setRenameRegExp ( const QString & search , const QString & replace )
{
const QString newSearch = search . trimmed ( ) , newReplace = replace . simplified ( ) ;
if ( ( ! newSearch . isEmpty ( ) ) & & ( ! newReplace . isEmpty ( ) ) )
{
m_renameRegExp_Search = newSearch ;
m_renameRegExp_Replace = newReplace ;
}
}
2015-05-10 19:52:07 +02:00
void ProcessThread : : setRenameFileExt ( const QString & fileExtension )
{
2017-04-09 19:17:00 +02:00
m_renameFileExt = MUtils : : clean_file_name ( fileExtension , false ) . simplified ( ) ;
2015-05-10 19:52:07 +02:00
while ( m_renameFileExt . startsWith ( ' . ' ) )
{
m_renameFileExt = m_renameFileExt . mid ( 1 ) . trimmed ( ) ;
}
}
2014-05-30 16:52:34 +02:00
void ProcessThread : : setOverwriteMode ( const bool & bSkipExistingFile , const bool & bReplacesExisting )
2012-11-08 21:19:45 +01:00
{
if ( bSkipExistingFile & & bReplacesExisting )
{
2014-05-30 16:52:34 +02:00
qWarning ( " Inconsistent overwrite flags -> reverting to default! " ) ;
m_overwriteMode = OverwriteMode_KeepBoth ;
}
else
{
m_overwriteMode = OverwriteMode_KeepBoth ;
if ( bSkipExistingFile ) m_overwriteMode = OverwriteMode_SkipExisting ;
if ( bReplacesExisting ) m_overwriteMode = OverwriteMode_Overwrite ;
2012-11-08 21:19:45 +01:00
}
}
2015-11-07 21:39:33 +01:00
void ProcessThread : : setKeepDateTime ( const bool & keepDateTime )
{
m_keepDateTime = keepDateTime ;
}
2010-11-18 00:32:46 +01:00
////////////////////////////////////////////////////////////
// EVENTS
////////////////////////////////////////////////////////////
2011-11-16 22:56:32 +01:00
/*NONE*/