File indexing completed on 2024-05-05 04:49:24
0001 /**************************************************************************************** 0002 * Copyright (c) 2010 Téo Mrnjavac <teo@kde.org> * 0003 * * 0004 * This program is free software; you can redistribute it and/or modify it under * 0005 * the terms of the GNU General Public License as published by the Free Software * 0006 * Foundation; either version 2 of the License, or (at your option) any later * 0007 * version. * 0008 * * 0009 * This program is distributed in the hope that it will be useful, but WITHOUT ANY * 0010 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * 0011 * PARTICULAR PURPOSE. See the GNU General Public License for more details. * 0012 * * 0013 * You should have received a copy of the GNU General Public License along with * 0014 * this program. If not, see <http://www.gnu.org/licenses/>. * 0015 ****************************************************************************************/ 0016 0017 #include "TranscodingJob.h" 0018 0019 #include "core/support/Debug.h" 0020 #include "core/transcoding/TranscodingController.h" 0021 0022 #include <KProcess> 0023 0024 #include <QFile> 0025 #include <QTimer> 0026 0027 namespace Transcoding 0028 { 0029 0030 Job::Job( const QUrl &src, 0031 const QUrl &dest, 0032 const Transcoding::Configuration &configuration, 0033 QObject *parent ) 0034 : KJob( parent ) 0035 , m_src( src ) 0036 , m_dest( dest ) 0037 , m_configuration( configuration ) 0038 , m_duration( -1 ) 0039 { 0040 init(); 0041 } 0042 0043 Job::Job( QUrl &src, 0044 const Transcoding::Configuration &configuration, 0045 QObject *parent ) 0046 : KJob( parent ) 0047 , m_src( src ) 0048 , m_dest( src ) 0049 , m_configuration( configuration ) 0050 , m_duration( -1 ) 0051 { 0052 QString fileExtension = Amarok::Components::transcodingController()->format( configuration.encoder() )->fileExtension(); 0053 if( !( fileExtension.isEmpty() ) ) 0054 { 0055 QString destPath = src.path(); 0056 destPath.truncate( destPath.lastIndexOf( QLatin1Char('.') ) + 1 ); 0057 destPath.append( fileExtension ); 0058 m_dest.setPath( destPath ); 0059 } 0060 init(); 0061 } 0062 0063 void 0064 Job::init() 0065 { 0066 m_transcoder = new KProcess( this ); 0067 0068 m_transcoder->setOutputChannelMode( KProcess::MergedChannels ); 0069 0070 //First the executable... 0071 m_transcoder->setProgram( QStringLiteral("ffmpeg") ); 0072 //... prevent ffmpeg from being interactive when destination file already exists. We 0073 // would use -n to exit immediately, but libav's ffmpeg doesn't support it, so we 0074 // check for destination file existence manually and pass -y (overwrite) to avoid 0075 // race condition 0076 *m_transcoder << QStringLiteral( "-y" ); 0077 //... then we'd have the infile configuration followed by "-i" and the infile path... 0078 *m_transcoder << QStringLiteral( "-i" ) 0079 << m_src.path(); 0080 //... and finally, outfile configuration followed by the outfile path. 0081 const Transcoding::Format *format = Amarok::Components::transcodingController()->format( m_configuration.encoder() ); 0082 *m_transcoder << format->ffmpegParameters( m_configuration ) 0083 << m_dest.path(); 0084 0085 connect( m_transcoder, &KProcess::readyRead, 0086 this, &Job::processOutput ); 0087 connect( m_transcoder, QOverload<int, QProcess::ExitStatus>::of(&KProcess::finished), 0088 this, &Job::transcoderDone ); 0089 } 0090 0091 void 0092 Job::start() 0093 { 0094 DEBUG_BLOCK 0095 if( QFile::exists( m_dest.path() ) ) 0096 { 0097 debug() << "Not starting ffmpeg encoder, file already exists:" << m_dest.path(); 0098 QTimer::singleShot( 0, this, &Job::transcoderDoneDefault ); 0099 } 0100 else 0101 { 0102 QString commandline = QStringLiteral( "'" ) + m_transcoder->program().join(QStringLiteral("' '")) + QStringLiteral( "'" ); 0103 debug()<< "Calling" << commandline.toLocal8Bit().constData(); 0104 m_transcoder->start(); 0105 } 0106 } 0107 0108 void 0109 Job::transcoderDone( int exitCode, QProcess::ExitStatus exitStatus ) //SLOT 0110 { 0111 if( exitCode == 0 && exitStatus == QProcess::NormalExit ) 0112 debug() << "YAY, transcoding done!"; 0113 else 0114 { 0115 debug() << "NAY, transcoding fail!"; 0116 setError( KJob::UserDefinedError ); 0117 setErrorText( QStringLiteral( "Calling `" ) + m_transcoder->program().join(QStringLiteral(" ")) + "` failed" ); 0118 } 0119 emitResult(); 0120 } 0121 0122 void 0123 Job::transcoderDoneDefault() 0124 { 0125 transcoderDone( -1, QProcess::CrashExit ); 0126 } 0127 0128 void 0129 Job::processOutput() 0130 { 0131 QString output = QString::fromLocal8Bit( m_transcoder->readAllStandardOutput().data() ); 0132 if( output.simplified().isEmpty() ) 0133 return; 0134 foreach( const QString &line, output.split( QChar( '\n' ) ) ) 0135 debug() << "ffmpeg:" << line.toLocal8Bit().constData(); 0136 0137 if( m_duration == -1 ) 0138 { 0139 m_duration = computeDuration( output ); 0140 if( m_duration >= 0 ) 0141 setTotalAmount( KJob::Bytes, m_duration ); //Nothing better than bytes I can think of 0142 } 0143 0144 qint64 progress = computeProgress( output ); 0145 if( progress > -1 ) 0146 setProcessedAmount( KJob::Bytes, progress ); 0147 } 0148 0149 inline qint64 0150 Job::computeDuration( const QString &output ) 0151 { 0152 //We match something like "Duration: 00:04:33.60" 0153 QRegExp matchDuration( "Duration: (\\d{2,}):(\\d{2}):(\\d{2})\\.(\\d{2})" ); 0154 0155 if( output.contains( matchDuration ) ) 0156 { 0157 //duration is in csec 0158 qint64 duration = matchDuration.cap( 1 ).toLong() * 60 * 60 * 100 + 0159 matchDuration.cap( 2 ).toInt() * 60 * 100 + 0160 matchDuration.cap( 3 ).toInt() * 100 + 0161 matchDuration.cap( 4 ).toInt(); 0162 return duration; 0163 } 0164 else 0165 return -1; 0166 } 0167 0168 inline qint64 0169 Job::computeProgress( const QString &output ) 0170 { 0171 //Output is like size= 323kB time=18.10 bitrate= 146.0kbits/s 0172 //We're going to use the "time" column, which counts the elapsed time in seconds. 0173 QRegExp matchTime( "time=(\\d+)\\.(\\d{2})" ); 0174 0175 if( output.contains( matchTime ) ) 0176 { 0177 qint64 time = matchTime.cap( 1 ).toLong() * 100 + 0178 matchTime.cap( 2 ).toInt(); 0179 return time; 0180 } 0181 else 0182 return -1; 0183 } 0184 0185 } //namespace Transcoding