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