1 /*
2     SPDX-FileCopyrightText: 2003-2009 Sebastian Trueg <trueg@k3b.org>
3     SPDX-FileCopyrightText: 2010 Michal Malek <michalm@jabster.pl>
4     SPDX-FileCopyrightText: 1998-2009 Sebastian Trueg <trueg@k3b.org>
5 
6     SPDX-License-Identifier: GPL-2.0-or-later
7 */
8 
9 #include "k3bexternalencoder.h"
10 #include "k3bexternalencodercommand.h"
11 #include "k3bplugin_i18n.h"
12 
13 #include <config-k3b.h>
14 
15 #include "k3bcore.h"
16 #include "k3bprocess.h"
17 
18 #include <KConfig>
19 
20 #include <QDebug>
21 #include <QFile>
22 #include <QList>
23 #include <QRegExp>
24 #include <QStandardPaths>
25 
26 #include <sys/types.h>
27 
28 
29 K_PLUGIN_CLASS_WITH_JSON(K3bExternalEncoder, "k3bexternalencoder.json")
30 
31 
32 Q_DECLARE_METATYPE( QProcess::ExitStatus )
33 
34 
35 static const unsigned char s_riffHeader[] =
36 {
37     0x52, 0x49, 0x46, 0x46, // 0  "RIFF"
38     0x00, 0x00, 0x00, 0x00, // 4  wavSize
39     0x57, 0x41, 0x56, 0x45, // 8  "WAVE"
40     0x66, 0x6d, 0x74, 0x20, // 12 "fmt "
41     0x10, 0x00, 0x00, 0x00, // 16
42     0x01, 0x00, 0x02, 0x00, // 20
43     0x44, 0xac, 0x00, 0x00, // 24
44     0x10, 0xb1, 0x02, 0x00, // 28
45     0x04, 0x00, 0x10, 0x00, // 32
46     0x64, 0x61, 0x74, 0x61, // 36 "data"
47     0x00, 0x00, 0x00, 0x00  // 40 byteCount
48 };
49 
50 
51 
52 
53 
commandByExtension(const QString & extension)54 static K3bExternalEncoderCommand commandByExtension( const QString& extension )
55 {
56     QList<K3bExternalEncoderCommand> cmds( K3bExternalEncoderCommand::readCommands() );
57     for( QList<K3bExternalEncoderCommand>::iterator it = cmds.begin(); it != cmds.end(); ++it )
58         if( (*it).extension == extension )
59             return *it;
60 
61     qDebug() << "(K3bExternalEncoder) could not find command for extension " << extension;
62 
63     return K3bExternalEncoderCommand();
64 }
65 
66 
67 class K3bExternalEncoder::Private
68 {
69 public:
70     K3b::Process* process;
71     QString fileName;
72     K3b::Msf length;
73 
74     K3bExternalEncoderCommand cmd;
75 
76     bool initialized;
77 };
78 
79 
K3bExternalEncoder(QObject * parent,const QVariantList &)80 K3bExternalEncoder::K3bExternalEncoder( QObject* parent, const QVariantList& )
81     : K3b::AudioEncoder( parent ),
82       d( new Private() )
83 {
84     d->process = 0;
85     d->initialized = false;
86 
87     qRegisterMetaType<QProcess::ExitStatus>();
88 }
89 
90 
~K3bExternalEncoder()91 K3bExternalEncoder::~K3bExternalEncoder()
92 {
93     if( d->process ) {
94         disconnect( d->process );
95         d->process->deleteLater();
96     }
97     delete d;
98 }
99 
100 
finishEncoderInternal()101 void K3bExternalEncoder::finishEncoderInternal()
102 {
103     if( d->process && d->process->state() == QProcess::Running ) {
104         d->process->closeWriteChannel();
105 
106         // this is kind of evil...
107         // but we need to be sure the process exited when this method returns
108         d->process->waitForFinished(-1);
109     }
110     d->initialized = false;
111 }
112 
113 
slotExternalProgramFinished(int exitCode,QProcess::ExitStatus exitStatus)114 void K3bExternalEncoder::slotExternalProgramFinished( int exitCode, QProcess::ExitStatus exitStatus )
115 {
116     if( (exitStatus != QProcess::NormalExit) || (exitCode != 0) )
117         qDebug() << "(K3bExternalEncoder) program exited with error.";
118 }
119 
120 
openFile(const QString & ext,const QString & filename,const K3b::Msf & length,const MetaData & metaData)121 bool K3bExternalEncoder::openFile( const QString& ext, const QString& filename, const K3b::Msf& length, const MetaData& metaData )
122 {
123     d->fileName = filename;
124     d->length = length;
125 
126     // delete existing files as some programs (like flac for example) might refuse to overwrite files
127     if( QFile::exists( filename ) )
128         QFile::remove( filename );
129 
130     return initEncoderInternal( ext, length, metaData );
131 }
132 
133 
isOpen() const134 bool K3bExternalEncoder::isOpen() const
135 {
136     return d->initialized;
137 }
138 
139 
closeFile()140 void K3bExternalEncoder::closeFile()
141 {
142     finishEncoderInternal();
143 }
144 
145 
initEncoderInternal(const QString & extension,const K3b::Msf &,const MetaData & metaData)146 bool K3bExternalEncoder::initEncoderInternal( const QString& extension, const K3b::Msf& /*length*/, const MetaData& metaData )
147 {
148     // find the correct command
149     d->cmd = commandByExtension( extension );
150 
151     if( !d->cmd.command.isEmpty() ) {
152         // create the commandline
153         QStringList params = d->cmd.command.split( ' ' );
154         for( QStringList::iterator it = params.begin(); it != params.end(); ++it ) {
155             (*it).replace( "%f", d->fileName );
156             (*it).replace( "%a", metaData[META_TRACK_ARTIST].toString() );
157             (*it).replace( "%t", metaData[META_TRACK_TITLE].toString() );
158             (*it).replace( "%c", metaData[META_TRACK_COMMENT].toString() );
159             (*it).replace( "%y", metaData[META_YEAR].toString() );
160             (*it).replace( "%m", metaData[META_ALBUM_TITLE].toString() );
161             (*it).replace( "%r", metaData[META_ALBUM_ARTIST].toString() );
162             (*it).replace( "%x", metaData[META_ALBUM_COMMENT].toString() );
163             (*it).replace( "%n", metaData[META_TRACK_NUMBER].toString() );
164             (*it).replace( "%g", metaData[META_GENRE].toString() );
165         }
166 
167 
168         qDebug() << "***** external parameters:";
169         qDebug() << params.join( " " ) << flush;
170 
171         // set one general error message
172         setLastError( i18n("Command failed: %1", params.join( " " ) ) );
173 
174         // always create a new process since we are called in a separate thread
175         if( d->process ) {
176             disconnect( d->process );
177             d->process->deleteLater();
178         }
179         d->process = new K3b::Process();
180         d->process->setSplitStdout( true );
181         connect( d->process, SIGNAL(finished(int,QProcess::ExitStatus)),
182                 this, SLOT(slotExternalProgramFinished(int,QProcess::ExitStatus)) );
183         connect( d->process, SIGNAL(stderrLine(QString)),
184                  this, SLOT(slotExternalProgramOutput(QString)) );
185         connect( d->process, SIGNAL(stdoutLine(QString)),
186                  this, SLOT(slotExternalProgramOutput(QString)) );
187 
188         d->process->setProgram( params );
189         d->process->start( KProcess::SeparateChannels );
190 
191         if( d->process->waitForStarted() ) {
192             if( d->cmd.writeWaveHeader )
193                 d->initialized = writeWaveHeader();
194             else
195                 d->initialized = true;
196         }
197         else {
198             QString commandName = d->cmd.command.section( QRegExp("\\s+"), 0 );
199             if( !QStandardPaths::findExecutable( commandName ).isEmpty() )
200                 setLastError( i18n("Could not find program '%1'",commandName) );
201 
202             d->initialized = false;
203         }
204 
205     }
206     else {
207         setLastError( i18n("Invalid command: the command is empty.") );
208         d->initialized = false;
209     }
210 
211     return d->initialized;
212 }
213 
214 
writeWaveHeader()215 bool K3bExternalEncoder::writeWaveHeader()
216 {
217     qDebug() << "(K3bExternalEncoder) writing wave header";
218 
219     // write the RIFF thing
220     if( d->process->write( (const char*) s_riffHeader, 4 ) != 4 ) {
221         qDebug() << "(K3bExternalEncoder) failed to write riff header.";
222         return false;
223     }
224 
225     // write the wave size
226     qint32 dataSize( d->length.audioBytes() );
227     qint32 wavSize( dataSize + 44 - 8 );
228     char c[4];
229 
230     c[0] = (wavSize   >> 0 ) & 0xff;
231     c[1] = (wavSize   >> 8 ) & 0xff;
232     c[2] = (wavSize   >> 16) & 0xff;
233     c[3] = (wavSize   >> 24) & 0xff;
234 
235     if( d->process->write( c, 4 ) != 4 ) {
236         qDebug() << "(K3bExternalEncoder) failed to write wave size.";
237         return false;
238     }
239 
240     // write static part of the header
241     if( d->process->write( (const char*) s_riffHeader + 8, 32 ) != 32 ) {
242         qDebug() << "(K3bExternalEncoder) failed to write wave header.";
243         return false;
244     }
245 
246     c[0] = (dataSize   >> 0 ) & 0xff;
247     c[1] = (dataSize   >> 8 ) & 0xff;
248     c[2] = (dataSize   >> 16) & 0xff;
249     c[3] = (dataSize   >> 24) & 0xff;
250 
251     if( d->process->write( c, 4 ) != 4 ) {
252         qDebug() << "(K3bExternalEncoder) failed to write data size.";
253         return false;
254     }
255 
256     return d->process->waitForBytesWritten( -1 );
257 }
258 
259 
encodeInternal(const char * data,qint64 len)260 qint64 K3bExternalEncoder::encodeInternal( const char* data, qint64 len )
261 {
262     if( !d->initialized )
263         return -1;
264 
265     if( d->process->state() == QProcess::Running ) {
266 
267         long written = 0;
268 
269         if( d->cmd.swapByteOrder ) {
270             char* buffer = new char[len];
271             for( unsigned int i = 0; i < len-1; i+=2 ) {
272                 buffer[i] = data[i+1];
273                 buffer[i+1] = data[i];
274             }
275 
276             written = d->process->write( buffer, len );
277             delete [] buffer;
278         }
279         else
280             written = d->process->write( data, len );
281 
282         d->process->waitForBytesWritten( -1 );
283 
284         return written;
285     }
286     else
287         return -1;
288 }
289 
290 
slotExternalProgramOutput(const QString & line)291 void K3bExternalEncoder::slotExternalProgramOutput( const QString& line )
292 {
293     qDebug() << "(" << d->cmd.name << ") " << line;
294 }
295 
296 
extensions() const297 QStringList K3bExternalEncoder::extensions() const
298 {
299     QStringList el;
300     QList<K3bExternalEncoderCommand> cmds( K3bExternalEncoderCommand::readCommands() );
301     for( QList<K3bExternalEncoderCommand>::iterator it = cmds.begin(); it != cmds.end(); ++it )
302         el.append( (*it).extension );
303 
304     return el;
305 }
306 
307 
fileTypeComment(const QString & ext) const308 QString K3bExternalEncoder::fileTypeComment( const QString& ext ) const
309 {
310     return commandByExtension( ext ).name;
311 }
312 
313 #include "k3bexternalencoder.moc"
314