1
2 #include "convert.h"
3 #include "convertitem.h"
4 #include "config.h"
5 #include "core/codecplugin.h"
6 #include "core/conversionoptions.h"
7 #include "filelist.h"
8 #include "global.h"
9 #include "logger.h"
10 #include "outputdirectory.h"
11
12 #include <kio/jobclasses.h>
13 #include <kio/job.h>
14
15 #include <KLocale>
16 #include <KMessageBox>
17 #include <QFile>
18 #include <QFileInfo>
19
20
Convert(Config * _config,FileList * _fileList,Logger * _logger,QObject * parent)21 Convert::Convert( Config *_config, FileList *_fileList, Logger *_logger, QObject *parent )
22 : QObject( parent ),
23 config( _config ),
24 fileList( _fileList ),
25 logger( _logger )
26 {
27 connect( &updateTimer, SIGNAL(timeout()), this, SLOT(updateProgress()) );
28
29 QList<CodecPlugin*> codecPlugins = config->pluginLoader()->getAllCodecPlugins();
30 for( int i=0; i<codecPlugins.size(); i++ )
31 {
32 connect( codecPlugins.at(i), SIGNAL(jobFinished(int,int)), this, SLOT(pluginProcessFinished(int,int)) );
33 connect( codecPlugins.at(i), SIGNAL(log(int,const QString&)), this, SLOT(pluginLog(int,const QString&)) );
34 }
35 QList<FilterPlugin*> filterPlugins = config->pluginLoader()->getAllFilterPlugins();
36 for( int i=0; i<filterPlugins.size(); i++ )
37 {
38 connect( filterPlugins.at(i), SIGNAL(jobFinished(int,int)), this, SLOT(pluginProcessFinished(int,int)) );
39 connect( filterPlugins.at(i), SIGNAL(log(int,const QString&)), this, SLOT(pluginLog(int,const QString&)) );
40 }
41 QList<ReplayGainPlugin*> replaygainPlugins = config->pluginLoader()->getAllReplayGainPlugins();
42 for( int i=0; i<replaygainPlugins.size(); i++ )
43 {
44 connect( replaygainPlugins.at(i), SIGNAL(jobFinished(int,int)), this, SLOT(pluginProcessFinished(int,int)) );
45 connect( replaygainPlugins.at(i), SIGNAL(log(int,const QString&)), this, SLOT(pluginLog(int,const QString&)) );
46 }
47 QList<RipperPlugin*> ripperPlugins = config->pluginLoader()->getAllRipperPlugins();
48 for( int i=0; i<ripperPlugins.size(); i++ )
49 {
50 connect( ripperPlugins.at(i), SIGNAL(jobFinished(int,int)), this, SLOT(pluginProcessFinished(int,int)) );
51 connect( ripperPlugins.at(i), SIGNAL(log(int,const QString&)), this, SLOT(pluginLog(int,const QString&)) );
52 }
53 }
54
~Convert()55 Convert::~Convert()
56 {}
57
cleanUp()58 void Convert::cleanUp()
59 {
60 // TODO clean up
61 }
62
get(ConvertItem * item)63 void Convert::get( ConvertItem *item )
64 {
65 if( item->take > 0 )
66 remove( item, FileListItem::Failed );
67
68 logger->log( item->logID, i18n("Getting file") );
69 item->state = ConvertItem::get;
70
71 item->tempInputUrl = item->generateTempUrl( "download", item->inputUrl.fileName().mid(item->inputUrl.fileName().lastIndexOf(".")+1) );
72
73 if( !updateTimer.isActive() )
74 updateTimer.start( ConfigUpdateDelay );
75
76 logger->log( item->logID, i18n("Copying \"%1\" to \"%2\"",item->inputUrl.pathOrUrl(),item->tempInputUrl.toLocalFile()) );
77
78 item->kioCopyJob = KIO::file_copy( item->inputUrl, item->tempInputUrl, -1 , KIO::HideProgressInfo );
79 connect( item->kioCopyJob.data(), SIGNAL(result(KJob*)), this, SLOT(kioJobFinished(KJob*)) );
80 connect( item->kioCopyJob.data(), SIGNAL(percent(KJob*,unsigned long)), this, SLOT(kioJobProgress(KJob*,unsigned long)) );
81 }
82
convert(ConvertItem * item)83 void Convert::convert( ConvertItem *item )
84 {
85 if( !item )
86 return;
87
88 const ConversionOptions *conversionOptions = config->conversionOptionsManager()->getConversionOptions( item->fileListItem->conversionOptionsId );
89 if( !conversionOptions )
90 return;
91
92 KUrl inputUrl;
93 if( !item->tempInputUrl.toLocalFile().isEmpty() )
94 inputUrl = item->tempInputUrl;
95 else
96 inputUrl = item->inputUrl;
97
98 if( !item->fileListItem->tags )
99 item->fileListItem->tags = config->tagEngine()->readTags( inputUrl );
100
101 if( item->fileListItem->tags && item->fileListItem->tags->isEncrypted )
102 {
103 logger->log( item->logID, "<br><span style=\"color:#C00000\">" + i18n("File is encrypted, conversion not possible") + "</span>" );
104 remove( item, FileListItem::Encrypted );
105 return;
106 }
107
108 if( item->outputUrl.isEmpty() )
109 {
110 // item->outputUrl = !item->fileListItem->outputUrl.url().isEmpty() ? item->fileListItem->outputUrl : OutputDirectory::calcPath( item->fileListItem, config, usedOutputNames.values() );
111 item->outputUrl = OutputDirectory::calcPath( item->fileListItem, config, usedOutputNames.values() );
112 if( QFile::exists(item->outputUrl.toLocalFile()) )
113 {
114 logger->log( item->logID, "\tOutput file already exists" );
115 item->outputUrl = KUrl();
116 remove( item, FileListItem::Skipped );
117 return;
118 }
119 if( OutputDirectory::makePath(item->outputUrl) == KUrl() )
120 {
121 logger->log( item->logID, "\t" + i18n("Cannot create output directory \"%1\"",item->outputUrl.toLocalFile()) );
122 item->outputUrl = KUrl();
123 remove( item, FileListItem::CantWriteOutput );
124 return;
125 }
126 fileList->updateItem( item->fileListItem );
127 }
128 usedOutputNames.insert( item->logID, item->outputUrl.toLocalFile() );
129
130 if( config->data.general.copyIfSameCodec && item->fileListItem->codecName == conversionOptions->codecName )
131 {
132 item->state = ConvertItem::convert;
133 logger->log( item->logID, i18n("Copying \"%1\" to \"%2\"",inputUrl.pathOrUrl(),item->outputUrl.toLocalFile()) );
134
135 item->kioCopyJob = KIO::file_copy( item->inputUrl, item->outputUrl, -1 , KIO::HideProgressInfo );
136 connect( item->kioCopyJob.data(), SIGNAL(result(KJob*)), this, SLOT(kioJobFinished(KJob*)) );
137 // connect( item->kioCopyJob.data(), SIGNAL(percent(KJob*,unsigned long)), this, SLOT(kioJobProgress(KJob*,unsigned long)) );
138
139 return;
140 }
141
142 if( item->take > item->conversionPipes.count() - 1 )
143 {
144 logger->log( item->logID, "\t" + i18n("No more backends left to try :(") );
145 remove( item, FileListItem::Failed );
146 return;
147 }
148
149 if( item->take > 0 )
150 item->updateTimes();
151
152 item->conversionPipesStep = -1;
153
154 if( !updateTimer.isActive() )
155 updateTimer.start( ConfigUpdateDelay );
156
157 if( item->conversionPipes.at(item->take).trunks.count() == 1 ) // conversion can be done by one plugin alone
158 {
159 logger->log( item->logID, i18n("Converting") );
160 item->state = ConvertItem::convert;
161 item->conversionPipesStep = 0;
162 item->backendPlugin = item->conversionPipes.at(item->take).trunks.at(0).plugin;
163 if( item->backendPlugin->type() == "codec" || item->backendPlugin->type() == "filter" )
164 {
165 bool useInternalReplayGain = false;
166 if( item->conversionPipes.at(item->take).trunks.at(0).data.hasInternalReplayGain && item->mode & ConvertItem::replaygain )
167 {
168 foreach( const Config::CodecData& codecData, config->data.backends.codecs )
169 {
170 if( codecData.codecName == item->conversionPipes.at(item->take).trunks.at(0).codecTo )
171 {
172 if( codecData.replaygain.first() == i18n("Try internal") )
173 useInternalReplayGain = true;
174
175 break;
176 }
177 }
178 }
179 if( useInternalReplayGain )
180 {
181 item->internalReplayGainUsed = true;
182 }
183 item->backendID = qobject_cast<CodecPlugin*>(item->backendPlugin)->convert( inputUrl, item->outputUrl, item->conversionPipes.at(item->take).trunks.at(0).codecFrom, item->conversionPipes.at(item->take).trunks.at(0).codecTo, conversionOptions, item->fileListItem->tags, useInternalReplayGain );
184 }
185 else if( item->backendPlugin->type() == "ripper" )
186 {
187 item->backendID = qobject_cast<RipperPlugin*>(item->backendPlugin)->rip( item->fileListItem->device, item->fileListItem->track, item->fileListItem->tracks, item->outputUrl );
188 }
189
190 if( item->backendID < 100 )
191 {
192 switch( item->backendID )
193 {
194 case BackendPlugin::BackendNeedsConfiguration:
195 {
196 logger->log( item->logID, "\t" + i18n("Conversion failed. At least one of the used backends needs to be configured properly.") );
197 remove( item, FileListItem::BackendNeedsConfiguration );
198 break;
199 }
200 case BackendPlugin::FeatureNotSupported:
201 {
202 logger->log( item->logID, "\t" + i18n("Conversion failed. The preferred plugin lacks support for a necessary feature.") );
203 executeSameStep( item );
204 break;
205 }
206 case BackendPlugin::UnknownError:
207 {
208 logger->log( item->logID, "\t" + i18n("Conversion failed. Unknown Error.") );
209 executeSameStep( item );
210 break;
211 }
212 }
213 }
214 else if( item->backendPlugin->type() == "ripper" )
215 {
216 item->fileListItem->state = FileListItem::Ripping;
217 }
218 }
219 else // conversion needs two plugins or more
220 {
221 bool usePipes = false;
222 bool useInternalReplayGain = false;
223 QStringList commandList;
224 if( config->data.advanced.usePipes )
225 {
226 usePipes = true;
227
228 const int stepCount = item->conversionPipes.at(item->take).trunks.count() - 1;
229 int step = 0;
230 foreach( const ConversionPipeTrunk& trunk, item->conversionPipes.at(item->take).trunks )
231 {
232 BackendPlugin *plugin = trunk.plugin;
233 QStringList command;
234 const KUrl inUrl = ( step == 0 ) ? inputUrl : KUrl();
235 const KUrl outUrl = ( step == stepCount ) ? item->outputUrl : KUrl();
236 if( plugin->type() == "codec" || plugin->type() == "filter" )
237 {
238 if( step == stepCount && trunk.data.hasInternalReplayGain && item->mode & ConvertItem::replaygain )
239 {
240 foreach( const Config::CodecData& codecData, config->data.backends.codecs )
241 {
242 if( codecData.codecName == trunk.codecTo )
243 {
244 if( codecData.replaygain.first() == i18n("Try internal") )
245 useInternalReplayGain = true;
246
247 break;
248 }
249 }
250 }
251 command = qobject_cast<CodecPlugin*>(plugin)->convertCommand( inUrl, outUrl, item->conversionPipes.at(item->take).trunks.at(step).codecFrom, item->conversionPipes.at(item->take).trunks.at(step).codecTo, conversionOptions, item->fileListItem->tags, useInternalReplayGain );
252 }
253 else if( plugin->type() == "ripper" )
254 {
255 command = qobject_cast<RipperPlugin*>(plugin)->ripCommand( item->fileListItem->device, item->fileListItem->track, item->fileListItem->tracks, outUrl );
256 }
257 if( command.isEmpty() )
258 {
259 usePipes = false;
260 break;
261 }
262 commandList.append( command.join(" ") );
263 step++;
264 }
265 }
266 if( usePipes )
267 {
268 // both plugins support pipes
269 const QString command = commandList.join(" | ");
270
271 if( useInternalReplayGain )
272 item->internalReplayGainUsed = true;
273
274 logger->log( item->logID, i18n("Converting") );
275 item->state = ConvertItem::convert;
276 // merge all conversion times into one since we are doing everything in one step
277 float time = 0.0f;
278 foreach( const float t, item->convertTimes )
279 {
280 time += t;
281 }
282 item->convertTimes.clear();
283 item->convertTimes.append( time );
284 item->conversionPipesStep = 0;
285 logger->log( item->logID, "<pre>\t<span style=\"color:#DC6300\">" + command + "</span></pre>" );
286 item->process = new KProcess();
287 item->process.data()->setOutputChannelMode( KProcess::MergedChannels );
288 connect( item->process.data(), SIGNAL(readyRead()), this, SLOT(processOutput()) );
289 connect( item->process.data(), SIGNAL(finished(int,QProcess::ExitStatus)), this, SLOT(processExit(int,QProcess::ExitStatus)) );
290 item->process.data()->clearProgram();
291 item->process.data()->setShellCommand( command );
292 item->process.data()->start();
293 }
294 else
295 {
296 // at least on plugins doesn't support pipes
297
298 // estimated size of the wav file
299 int estimatedFileSize = 0;
300 if( item->conversionPipes.at(item->take).trunks.first().plugin->type() == "ripper" && item->fileListItem->tags )
301 {
302 estimatedFileSize = item->fileListItem->tags->length * 44100*16*2/8/1024/1024;
303 }
304 else
305 {
306 QFileInfo fileInfo( inputUrl.toLocalFile() );
307 estimatedFileSize = fileInfo.size();
308 if( config->pluginLoader()->isCodecLossless(item->fileListItem->codecName) )
309 {
310 estimatedFileSize *= 1.4;
311 }
312 else if( item->fileListItem->codecName == "midi" || item->fileListItem->codecName == "mod" )
313 {
314 estimatedFileSize *= 1000;
315 }
316 else
317 {
318 estimatedFileSize *= 10;
319 }
320 estimatedFileSize /= 1024*1024;
321 }
322 const bool useSharedMemory = config->data.advanced.useSharedMemoryForTempFiles && estimatedFileSize > 0 && estimatedFileSize < config->data.advanced.maxSizeForSharedMemoryTempFiles;
323
324 for( int i=0; i<item->conversionPipes.at(item->take).trunks.count()-1; i++ )
325 {
326 item->tempConvertUrls += item->generateTempUrl( "convert", config->pluginLoader()->codecExtensions(item->conversionPipes.at(item->take).trunks.at(i).codecTo).first(), useSharedMemory );
327 }
328
329 convertNextBackend( item );
330 }
331 }
332 }
333
convertNextBackend(ConvertItem * item)334 void Convert::convertNextBackend( ConvertItem *item )
335 {
336 if( !item )
337 return;
338
339 const ConversionOptions *conversionOptions = config->conversionOptionsManager()->getConversionOptions( item->fileListItem->conversionOptionsId );
340 if( !conversionOptions )
341 return;
342
343 const int stepCount = item->conversionPipes.at(item->take).trunks.count() - 1;
344 const int step = ++item->conversionPipesStep;
345
346 if( step > stepCount )
347 {
348 executeNextStep( item );
349 }
350
351 BackendPlugin *plugin = item->conversionPipes.at(item->take).trunks.at(step).plugin;
352 if( !plugin )
353 return;
354
355 KUrl inputUrl;
356 if( !item->tempInputUrl.toLocalFile().isEmpty() )
357 inputUrl = item->tempInputUrl;
358 else
359 inputUrl = item->inputUrl;
360
361 const KUrl inUrl = ( step == 0 ) ? inputUrl : item->tempConvertUrls.at(step - 1);
362 const KUrl outUrl = ( step == stepCount ) ? item->outputUrl : item->tempConvertUrls.at(step);
363
364 if( step == 0 )
365 {
366 if( plugin->type() == "ripper" )
367 {
368 item->state = ConvertItem::rip;
369 logger->log( item->logID, i18n("Ripping") );
370 }
371 else if( plugin->type() == "codec" || ( plugin->type() == "filter" && item->conversionPipes.at(item->take).trunks.at(step).codecFrom != "wav" ) )
372 {
373 item->state = ConvertItem::decode;
374 logger->log( item->logID, i18n("Decoding") );
375 }
376 else if( plugin->type() == "filter" )
377 {
378 item->state = ConvertItem::filter;
379 logger->log( item->logID, i18n("Applying filter") );
380 }
381 }
382 else if( step == stepCount )
383 {
384 if( plugin->type() == "codec" || ( plugin->type() == "filter" && item->conversionPipes.at(item->take).trunks.at(step).codecTo != "wav" ) )
385 {
386 item->state = ConvertItem::encode;
387 logger->log( item->logID, i18n("Encoding") );
388 }
389 else if( plugin->type() == "filter" )
390 {
391 item->state = ConvertItem::filter;
392 logger->log( item->logID, i18n("Applying filter") );
393 }
394 }
395 else
396 {
397 item->state = ConvertItem::filter;
398 logger->log( item->logID, i18n("Applying filter") );
399 }
400
401 item->backendPlugin = plugin;
402 if( plugin->type() == "codec" || plugin->type() == "filter" )
403 {
404 bool useInternalReplayGain = false;
405 if( step == stepCount && item->conversionPipes.at(item->take).trunks.at(step).data.hasInternalReplayGain && item->mode & ConvertItem::replaygain )
406 {
407 foreach( const Config::CodecData& codecData, config->data.backends.codecs )
408 {
409 if( codecData.codecName == item->conversionPipes.at(item->take).trunks.at(step).codecTo )
410 {
411 if( codecData.replaygain.first() == i18n("Try internal") )
412 useInternalReplayGain = true;
413
414 break;
415 }
416 }
417 }
418 if( useInternalReplayGain )
419 {
420 item->internalReplayGainUsed = true;
421 }
422 item->backendID = qobject_cast<CodecPlugin*>(plugin)->convert( inUrl, outUrl, item->conversionPipes.at(item->take).trunks.at(step).codecFrom, item->conversionPipes.at(item->take).trunks.at(step).codecTo, conversionOptions, item->fileListItem->tags, useInternalReplayGain );
423 }
424 else if( plugin->type() == "ripper" )
425 {
426 item->backendID = qobject_cast<RipperPlugin*>(plugin)->rip( item->fileListItem->device, item->fileListItem->track, item->fileListItem->tracks, outUrl );
427 }
428
429 if( item->backendID < 100 )
430 {
431 switch( item->backendID )
432 {
433 case BackendPlugin::BackendNeedsConfiguration:
434 {
435 logger->log( item->logID, "\t" + i18n("Conversion failed. At least one of the used backends needs to be configured properly.") );
436 remove( item, FileListItem::BackendNeedsConfiguration );
437 break;
438 }
439 case BackendPlugin::FeatureNotSupported:
440 {
441 logger->log( item->logID, "\t" + i18n("Conversion failed. The preferred plugin lacks support for a necessary feature.") );
442 executeSameStep( item );
443 break;
444 }
445 case BackendPlugin::UnknownError:
446 {
447 logger->log( item->logID, "\t" + i18n("Conversion failed. Unknown Error.") );
448 executeSameStep( item );
449 break;
450 }
451 }
452 }
453 else if( item->backendPlugin->type() == "ripper" )
454 {
455 item->fileListItem->state = FileListItem::Ripping;
456 }
457 }
458
replaygain(ConvertItem * item)459 void Convert::replaygain( ConvertItem *item )
460 {
461 if( !item )
462 return;
463
464 const QString albumName = item->fileListItem->tags ? item->fileListItem->tags->album : "";
465
466 if( fileList->waitForAlbumGain(item->fileListItem) )
467 {
468 logger->log( item->logID, i18n("Skipping Replay Gain, Album Gain will be calculated later") );
469 item->state = ConvertItem::replaygain;
470
471 if( !albumGainItems[albumName].contains(item) )
472 {
473 albumGainItems[albumName].append( item );
474 executeNextStep( item );
475 }
476 else
477 {
478 logger->log( item->logID, "\t" + i18n("No more backends left to try :(") );
479 albumGainItems[albumName].removeAll( item );
480 remove( item, FileListItem::Failed );
481 }
482 return;
483 }
484
485 QList<ConvertItem*> albumItems;
486 if( !albumName.isEmpty() )
487 albumItems = albumGainItems[albumName];
488
489 albumItems.append( item );
490
491 logger->log( item->logID, i18n("Applying Replay Gain") );
492
493 if( item->take > item->replaygainPipes.count() - 1 )
494 {
495 logger->log( item->logID, "\t" + i18n("No more backends left to try :(") );
496 remove( item, FileListItem::Failed );
497 return;
498 }
499
500 item->state = ConvertItem::replaygain;
501 item->backendPlugin = item->replaygainPipes.at(item->take).plugin;
502
503 KUrl::List urlList;
504 QStringList directories;
505 foreach( ConvertItem *albumItem, albumItems )
506 {
507 urlList.append( albumItem->outputUrl );
508 directories.append( albumItem->outputUrl.directory() );
509 if( albumItem != item )
510 {
511 albumItem->fileListItem->state = FileListItem::ApplyingAlbumGain;
512 fileList->updateItem( albumItem->fileListItem );
513 }
514 }
515
516 if( item->backendPlugin->name() == "Vorbis Gain" )
517 {
518 bool waitForVorbisGainFinish = false;
519
520 foreach( const QString directory, directories )
521 {
522 if( activeVorbisGainDirectories.contains(directory) )
523 {
524 waitForVorbisGainFinish = true;
525 break;
526 }
527 }
528
529 if( waitForVorbisGainFinish )
530 {
531 logger->log( item->logID, i18n("Waiting for running Vorbis Gain process to finish") );
532 item->state = ConvertItem::wait_replaygain;
533 item->fileListItem->state = FileListItem::WaitingForAlbumGain;
534 fileList->updateItem( item->fileListItem );
535 emit finished( 0, FileListItem::Failed, false ); // send signal to FileList; trigger call of FileList::convertNextItem()
536 return;
537 }
538 else
539 {
540 activeVorbisGainDirectories.append( directories );
541 }
542 }
543
544 item->backendID = qobject_cast<ReplayGainPlugin*>(item->backendPlugin)->apply( urlList );
545
546 if( item->backendID < 100 )
547 {
548 switch( item->backendID )
549 {
550 case BackendPlugin::BackendNeedsConfiguration:
551 {
552 logger->log( item->logID, "\t" + i18n("Conversion failed. At least one of the used backends needs to be configured properly.") );
553 remove( item, FileListItem::BackendNeedsConfiguration );
554 break;
555 }
556 case BackendPlugin::FeatureNotSupported:
557 {
558 logger->log( item->logID, "\t" + i18n("Conversion failed. The preferred plugin lacks support for a necessary feature.") );
559 executeSameStep( item );
560 break;
561 }
562 case BackendPlugin::UnknownError:
563 {
564 logger->log( item->logID, "\t" + i18n("Conversion failed. Unknown Error.") );
565 executeSameStep( item );
566 break;
567 }
568 }
569 }
570
571 if( !updateTimer.isActive() )
572 updateTimer.start( ConfigUpdateDelay );
573 }
574
writeTags(ConvertItem * item)575 void Convert::writeTags( ConvertItem *item )
576 {
577 if( !item || !item->fileListItem || !item->fileListItem->tags )
578 return;
579
580 logger->log( item->logID, i18n("Writing tags") );
581 // item->state = ConvertItem::write_tags;
582 // item->fileListItem->setText( 0, i18n("Writing tags")+"... 00 %" );
583
584 config->tagEngine()->writeTags( item->outputUrl, item->fileListItem->tags );
585
586 KUrl inputUrl;
587 if( !item->tempInputUrl.toLocalFile().isEmpty() )
588 inputUrl = item->tempInputUrl;
589 else
590 inputUrl = item->inputUrl;
591
592 if( !(item->fileListItem->tags->tagsRead & TagData::Covers) )
593 {
594 item->fileListItem->tags->covers = config->tagEngine()->readCovers( inputUrl );
595 item->fileListItem->tags->tagsRead = TagData::TagsRead(item->fileListItem->tags->tagsRead | TagData::Covers);
596 }
597
598 const bool success = config->tagEngine()->writeCovers( item->outputUrl, item->fileListItem->tags->covers );
599 if( config->data.coverArt.writeCovers == 0 || ( config->data.coverArt.writeCovers == 1 && !success ) )
600 {
601 config->tagEngine()->writeCoversToDirectory( item->outputUrl.directory(), item->fileListItem->tags );
602 }
603 }
604
605 // void Convert::executeUserScript( ConvertItem *item )
606 // {
607 // logger->log( item->logID, i18n("Running user script") );
608 // item->state = ConvertItem::execute_userscript;
609 //
610 // KUrl source( item->fileListItem->options.filePathName );
611 // KUrl destination( item->outputFilePathName );
612 //
613 // item->fileListItem->setText( 0, i18n("Running user script")+"... 00 %" );
614 //
615 // item->convertProcess->clearProgram();
616 //
617 // QString userscript = locate( "data", "soundkonverter/userscript.sh" );
618 // if( userscript == "" ) executeNextStep( item );
619 //
620 // *(item->convertProcess) << userscript;
621 // *(item->convertProcess) << source.path();
622 // *(item->convertProcess) << destination.path();
623 //
624 // logger->log( item->logID, userscript + " \"" + source.path() + "\" \"" + destination.path() + "\"" );
625 //
626 // // /* item->convertProcess->setPriority( config->data.general.priority );
627 // // item->convertProcess->start( KProcess::NotifyOnExit, KProcess::AllOutput );*/
628 // }
629
executeNextStep(ConvertItem * item)630 void Convert::executeNextStep( ConvertItem *item )
631 {
632 logger->log( item->logID, "<br>" + i18n("Executing next step") );
633
634 item->lastTake = item->take;
635 item->take = 0;
636 item->progress = 0.0f;
637
638 switch( item->state )
639 {
640 case ConvertItem::initial:
641 {
642 if( item->mode & ConvertItem::get )
643 get( item );
644 else if( item->mode & ConvertItem::convert )
645 convert( item );
646 else if( item->mode & ConvertItem::replaygain )
647 replaygain( item );
648 else
649 remove( item, FileListItem::Succeeded );
650 break;
651 }
652 case ConvertItem::get:
653 {
654 if( item->mode & ConvertItem::convert )
655 convert( item );
656 else
657 remove( item, FileListItem::Succeeded );
658 break;
659 }
660 case ConvertItem::convert:
661 case ConvertItem::encode:
662 {
663 if( item->mode & ConvertItem::replaygain )
664 replaygain( item );
665 else
666 remove( item, FileListItem::Succeeded );
667 break;
668 }
669 case ConvertItem::rip:
670 case ConvertItem::decode:
671 case ConvertItem::filter:
672 {
673 item->take = item->lastTake;
674 convertNextBackend( item );
675 break;
676 }
677 case ConvertItem::wait_replaygain:
678 {
679 replaygain( item );
680 break;
681 }
682 case ConvertItem::replaygain:
683 {
684 remove( item, FileListItem::Succeeded );
685 break;
686 }
687 }
688 }
689
executeSameStep(ConvertItem * item)690 void Convert::executeSameStep( ConvertItem *item )
691 {
692 item->take++;
693 item->updateTimes();
694 item->progress = 0.0f;
695
696 if( item->internalReplayGainUsed )
697 {
698 item->mode = ConvertItem::Mode( item->mode | ConvertItem::replaygain );
699 item->internalReplayGainUsed = false;
700 }
701
702 switch( item->state )
703 {
704 case ConvertItem::initial:
705 {
706 break;
707 }
708 case ConvertItem::get:
709 {
710 get( item );
711 return;
712 }
713 case ConvertItem::rip:
714 case ConvertItem::convert:
715 case ConvertItem::decode:
716 case ConvertItem::filter:
717 case ConvertItem::encode:
718 {
719 // remove temp/failed files
720 foreach( const KUrl& url, item->tempConvertUrls )
721 {
722 if( QFile::exists(url.toLocalFile()) )
723 {
724 QFile::remove( url.toLocalFile() );
725 }
726 }
727 if( QFile::exists(item->outputUrl.toLocalFile()) )
728 {
729 QFile::remove( item->outputUrl.toLocalFile() );
730 }
731 convert( item );
732 return;
733 }
734 case ConvertItem::wait_replaygain:
735 case ConvertItem::replaygain:
736 {
737 replaygain( item );
738 return;
739 }
740 }
741
742 remove( item, FileListItem::Failed ); // shouldn't be possible
743 }
744
kioJobProgress(KJob * job,unsigned long percent)745 void Convert::kioJobProgress( KJob *job, unsigned long percent )
746 {
747 // search the item list for our item
748 foreach( ConvertItem *item, items )
749 {
750 if( item->kioCopyJob.data() == job )
751 {
752 item->progress = (float)percent;
753 }
754 }
755 }
756
kioJobFinished(KJob * job)757 void Convert::kioJobFinished( KJob *job )
758 {
759 // search the item list for our item
760 foreach( ConvertItem *item, items )
761 {
762 if( item->kioCopyJob.data() == job )
763 {
764 item->kioCopyJob.data()->deleteLater();
765
766 if( job->error() == 0 ) // copy was successful
767 {
768 float fileTime;
769 switch( item->state )
770 {
771 case ConvertItem::get:
772 {
773 fileTime = item->getTime;
774 break;
775 }
776 case ConvertItem::convert:
777 {
778 fileTime = 0.0f;
779 foreach( const float t, item->convertTimes )
780 {
781 fileTime += t;
782 }
783 break;
784 }
785 default:
786 {
787 fileTime = 0.0f;
788 }
789 }
790 if( item->state == ConvertItem::get )
791 {
792 if( !item->fileListItem->tags )
793 {
794 item->fileListItem->tags = config->tagEngine()->readTags( item->tempInputUrl );
795 if( item->fileListItem->tags )
796 {
797 logger->log( item->logID, i18n("Read tags successfully") );
798 }
799 else
800 {
801 logger->log( item->logID, i18n("Unable to read tags") );
802 }
803 }
804 }
805 item->finishedTime += fileTime;
806 executeNextStep( item );
807 }
808 else
809 {
810 // remove temp/failed files
811 KUrl url;
812 if( item->state == ConvertItem::get )
813 {
814 url = item->tempInputUrl;
815 }
816 else if( item->state == ConvertItem::convert )
817 {
818 url = item->outputUrl;
819 }
820
821 if( QFile::exists(url.toLocalFile()) )
822 {
823 QFile::remove(url.toLocalFile());
824 logger->log( item->logID, i18nc("removing file","Removing: %1",url.toLocalFile()) );
825 }
826 if( QFile::exists(url.toLocalFile()+".part") )
827 {
828 QFile::remove(url.toLocalFile()+".part");
829 logger->log( item->logID, i18nc("removing file","Removing: %1",url.toLocalFile()+".part") );
830 }
831
832 if( job->error() == 1 )
833 {
834 remove( item, FileListItem::StoppedByUser );
835 }
836 else
837 {
838 logger->log( item->logID, i18n("An error occurred. Error code: %1 (%2)",job->error(),job->errorString()) );
839 remove( item, FileListItem::Failed );
840 }
841 }
842 }
843 }
844 }
845
processOutput()846 void Convert::processOutput()
847 {
848 foreach( ConvertItem *item, items )
849 {
850 if( item->process.data() == QObject::sender() )
851 {
852 const QString output = item->process.data()->readAllStandardOutput().data();
853
854 bool logOutput = true;
855 foreach( const ConversionPipeTrunk& trunk, item->conversionPipes.at(item->take).trunks )
856 {
857 const float progress = trunk.plugin->parseOutput( output );
858
859 if( progress > item->progress )
860 {
861 item->progress = progress;
862 logOutput = false;
863 }
864 }
865
866 if( logOutput && !output.simplified().isEmpty() )
867 logger->log( item->logID, "<pre>\t<span style=\"color:#C00000\">" + output.trimmed().replace("\n","<br>\t") + "</span></pre>" );
868
869 break;
870 }
871 }
872 }
873
processExit(int exitCode,QProcess::ExitStatus exitStatus)874 void Convert::processExit( int exitCode, QProcess::ExitStatus exitStatus )
875 {
876 Q_UNUSED(exitStatus)
877
878 if( QObject::sender() == 0 )
879 {
880 logger->log( 1000, QString("Error: processExit was called from a null sender. Exit code: %1").arg(exitCode) );
881 KMessageBox::error( 0, QString("Error: processExit was called from a null sender. Exit code: %1").arg(exitCode) );
882 return;
883 }
884
885 // search the item list for our item
886 foreach( ConvertItem *item, items )
887 {
888 if( item->process.data() == QObject::sender() )
889 {
890 item->process.data()->deleteLater(); // NOTE crash discovered here - probably fixed by using deleteLater
891
892 if( item->killed )
893 {
894 remove( item, FileListItem::StoppedByUser );
895 return;
896 }
897
898 if( exitCode == 0 )
899 {
900 float fileTime;
901 switch( item->state )
902 {
903 case ConvertItem::initial:
904 case ConvertItem::get:
905 case ConvertItem::wait_replaygain:
906 {
907 fileTime = 0.0f;
908 break;
909 }
910 case ConvertItem::convert:
911 case ConvertItem::rip:
912 case ConvertItem::decode:
913 case ConvertItem::filter:
914 case ConvertItem::encode:
915 {
916 fileTime = item->convertTimes.at(item->conversionPipesStep);
917 break;
918 }
919 case ConvertItem::replaygain:
920 {
921 fileTime = item->replaygainTime;
922 break;
923 }
924 }
925 item->finishedTime += fileTime;
926
927 if( item->state == ConvertItem::rip )
928 {
929 item->fileListItem->state = FileListItem::Converting;
930 emit rippingFinished( item->fileListItem->device );
931 }
932
933 if( item->internalReplayGainUsed )
934 {
935 item->mode = ConvertItem::Mode( item->mode ^ ConvertItem::replaygain );
936 }
937
938 switch( item->state )
939 {
940 case ConvertItem::rip:
941 case ConvertItem::decode:
942 case ConvertItem::filter:
943 {
944 convertNextBackend( item );
945 break;
946 }
947 default:
948 {
949 executeNextStep( item );
950 }
951 }
952 }
953 else
954 {
955 logger->log( item->logID, "\t" + i18n("Conversion failed. Exit code: %1",exitCode) );
956 executeSameStep( item );
957 }
958 }
959 }
960 }
961
pluginProcessFinished(int id,int exitCode)962 void Convert::pluginProcessFinished( int id, int exitCode )
963 {
964 if( QObject::sender() == 0 )
965 {
966 logger->log( 1000, QString("Error: pluginProcessFinished was called from a null sender. Exit code: %1").arg(exitCode) );
967 KMessageBox::error( 0, QString("Error: pluginProcessFinished was called from a null sender. Exit code: %1").arg(exitCode) );
968 return;
969 }
970
971 foreach( ConvertItem *item, items )
972 {
973 if( item->backendPlugin && item->backendPlugin == QObject::sender() && item->backendID == id )
974 {
975 item->backendID = -1;
976
977 if( item->backendPlugin->name() == "Vorbis Gain" )
978 {
979 activeVorbisGainDirectories.removeAll( item->outputUrl.directory() );
980 foreach( ConvertItem *nextItem, items )
981 {
982 if( nextItem->state == ConvertItem::wait_replaygain && nextItem->backendPlugin->name() == "Vorbis Gain" )
983 {
984 executeNextStep( nextItem );
985 break;
986 }
987 }
988 }
989
990 if( item->killed )
991 {
992 remove( item, FileListItem::StoppedByUser );
993 return;
994 }
995
996 if( exitCode == 0 )
997 {
998 float fileTime;
999 switch( item->state )
1000 {
1001 case ConvertItem::initial:
1002 case ConvertItem::get:
1003 case ConvertItem::wait_replaygain:
1004 {
1005 fileTime = 0.0f;
1006 break;
1007 }
1008 case ConvertItem::convert:
1009 case ConvertItem::rip:
1010 case ConvertItem::decode:
1011 case ConvertItem::filter:
1012 case ConvertItem::encode:
1013 {
1014 fileTime = item->convertTimes.at(item->conversionPipesStep);
1015 break;
1016 }
1017 case ConvertItem::replaygain:
1018 {
1019 fileTime = item->replaygainTime;
1020 break;
1021 }
1022 }
1023 item->finishedTime += fileTime;
1024
1025 if( item->state == ConvertItem::rip )
1026 {
1027 item->fileListItem->state = FileListItem::Converting;
1028 emit rippingFinished( item->fileListItem->device );
1029 }
1030
1031 if( item->internalReplayGainUsed )
1032 {
1033 item->mode = ConvertItem::Mode( item->mode ^ ConvertItem::replaygain );
1034 }
1035
1036 executeNextStep( item );
1037 }
1038 else
1039 {
1040 logger->log( item->logID, "\t" + i18n("Conversion failed. Exit code: %1",exitCode) );
1041 executeSameStep( item );
1042 }
1043 }
1044 }
1045 }
1046
pluginLog(int id,const QString & message)1047 void Convert::pluginLog( int id, const QString& message )
1048 {
1049 // log all cached logs that can be logged now
1050 for( int i=0; i<pluginLogQueue.size(); i++ )
1051 {
1052 for( int j=0; j<items.size(); j++ )
1053 {
1054 if( items.at(j)->backendPlugin && items.at(j)->backendPlugin == pluginLogQueue.at(i).plugin && items.at(j)->backendID == pluginLogQueue.at(i).id )
1055 {
1056 for( int k=0; k<pluginLogQueue.at(i).messages.size(); k++ )
1057 {
1058 logger->log( items.at(j)->logID, pluginLogQueue.at(i).messages.at(k) );
1059 }
1060 pluginLogQueue.removeAt(i);
1061 i--;
1062 break;
1063 }
1064 }
1065 }
1066
1067 if( message.trimmed().isEmpty() )
1068 return;
1069
1070 // search the matching process
1071 for( int i=0; i<items.size(); i++ )
1072 {
1073 if( items.at(i)->backendPlugin && items.at(i)->backendPlugin == QObject::sender() && items.at(i)->backendID == id )
1074 {
1075 logger->log( items.at(i)->logID, message );
1076 return;
1077 }
1078 }
1079
1080 // if the process can't be found; add to the log queue
1081 for( int i=0; i<pluginLogQueue.size(); i++ )
1082 {
1083 if( pluginLogQueue.at(i).plugin == QObject::sender() && pluginLogQueue.at(i).id == id )
1084 {
1085 pluginLogQueue[i].messages += message;
1086 return;
1087 }
1088 }
1089
1090 // no existing item in the log queue; create new log queue item
1091 LogQueue newLog;
1092 newLog.plugin = qobject_cast<BackendPlugin*>(QObject::sender());
1093 newLog.id = id;
1094 newLog.messages = QStringList(message);
1095 pluginLogQueue += newLog;
1096
1097 // logger->log( 1000, qobject_cast<BackendPlugin*>(QObject::sender())->name() + ": " + message.trimmed().replace("\n","\n\t") );
1098 }
1099
add(FileListItem * fileListItem)1100 void Convert::add( FileListItem *fileListItem )
1101 {
1102 KUrl fileName;
1103 if( fileListItem->track >= 0 )
1104 {
1105 if( fileListItem->tags )
1106 {
1107 fileName = KUrl( i18nc("identificator for the logger","CD track %1: %2 - %3",QString().sprintf("%02i",fileListItem->tags->track),fileListItem->tags->artist,fileListItem->tags->title) );
1108 }
1109 else // shouldn't be possible
1110 {
1111 fileName = KUrl( i18nc("identificator for the logger","CD track %1",fileListItem->track) );
1112 }
1113 }
1114 else
1115 {
1116 fileName = fileListItem->url;
1117 }
1118 logger->log( 1000, i18n("Adding new item to conversion list: '%1'",fileName.pathOrUrl()) );
1119
1120 ConvertItem *newItem = new ConvertItem( fileListItem );
1121 items.append( newItem );
1122
1123 newItem->progressedTime.start();
1124
1125 // register at the logger
1126 newItem->logID = logger->registerProcess( fileName.pathOrUrl() );
1127 logger->log( 1000, "\t" + i18n("Got log ID: %1",QString::number(newItem->logID)) );
1128
1129 // TODO remove redundancy, logID is needed in the fileListItem
1130 newItem->fileListItem->logId = newItem->logID;
1131
1132 // logger->log( newItem->logID, "Mime Type: " + newItem->fileListItem->mimeType );
1133 // if( newItem->fileListItem->tags ) logger->log( newItem->logID, i18n("Tags successfully read") );
1134 // else logger->log( newItem->logID, i18n("Reading tags failed") );
1135
1136 if( fileListItem->tags && fileListItem->tags->isEncrypted )
1137 {
1138 logger->log( newItem->logID, "<br><span style=\"color:#C00000\">" + i18n("File is encrypted, conversion not possible") + "</span>" );
1139 remove( newItem, FileListItem::Encrypted );
1140 return;
1141 }
1142
1143 // set some variables to default values
1144 newItem->mode = (ConvertItem::Mode)0x0000;
1145 newItem->state = (ConvertItem::Mode)0x0000;
1146
1147 newItem->inputUrl = fileListItem->url;
1148
1149 const ConversionOptions *conversionOptions = config->conversionOptionsManager()->getConversionOptions(fileListItem->conversionOptionsId);
1150 if( !conversionOptions )
1151 {
1152 logger->log( 1000, "Convert::add(...) no ConversionOptions found" );
1153 remove( newItem, FileListItem::Failed );
1154 return;
1155 }
1156
1157 if( fileListItem->track >= 0 )
1158 {
1159 logger->log( newItem->logID, "\t" + i18n("Track number: %1, device: %2",QString::number(fileListItem->track),fileListItem->device) );
1160 }
1161
1162 newItem->conversionPipes = config->pluginLoader()->getConversionPipes( fileListItem->codecName, conversionOptions->codecName, conversionOptions->filterOptions, conversionOptions->pluginName );
1163
1164 logger->log( newItem->logID, "\t" + i18n("Possible conversion strategies:") );
1165 for( int i=0; i<newItem->conversionPipes.size(); i++ )
1166 {
1167 QStringList pipe_str;
1168
1169 for( int j=0; j<newItem->conversionPipes.at(i).trunks.size(); j++ )
1170 {
1171 pipe_str += QString("%1 %2 %3 (%4)").arg(newItem->conversionPipes.at(i).trunks.at(j).codecFrom).arg(QChar(10141)).arg(newItem->conversionPipes.at(i).trunks.at(j).codecTo).arg(newItem->conversionPipes.at(i).trunks.at(j).plugin->name());
1172 }
1173
1174 logger->log( newItem->logID, "\t\t" + pipe_str.join(", ") );
1175 }
1176
1177 if( conversionOptions->outputDirectoryMode != OutputDirectory::Source )
1178 logger->log( newItem->logID, i18n("File system type: %1",conversionOptions->outputFilesystem) );
1179
1180 newItem->mode = ConvertItem::Mode( newItem->mode | ConvertItem::convert );
1181
1182 if( conversionOptions->replaygain )
1183 {
1184 newItem->replaygainPipes = config->pluginLoader()->getReplayGainPipes( conversionOptions->codecName );
1185 newItem->mode = ConvertItem::Mode( newItem->mode | ConvertItem::replaygain );
1186 }
1187
1188 if( !newItem->inputUrl.isLocalFile() && fileListItem->track == -1 )
1189 newItem->mode = ConvertItem::Mode( newItem->mode | ConvertItem::get );
1190 // if( (!newItem->inputUrl.isLocalFile() && item->track == -1) || newItem->inputUrl.url().toAscii() != newItem->inputUrl.url() )
1191 // newItem->mode = ConvertItem::Mode( newItem->mode | ConvertItem::get );
1192
1193 newItem->updateTimes();
1194
1195 // (visual) feedback
1196 fileListItem->state = FileListItem::Converting;
1197
1198 // and start
1199 executeNextStep( newItem );
1200 }
1201
remove(ConvertItem * item,FileListItem::ReturnCode returnCode)1202 void Convert::remove( ConvertItem *item, FileListItem::ReturnCode returnCode )
1203 {
1204 // TODO "remove" (re-add) the times to the progress indicator
1205 //emit uncountTime( item->getTime + item->getCorrectionTime + item->ripTime +
1206 // item->decodeTime + item->encodeTime + item->replaygainTime );
1207
1208 QString exitMessage;
1209
1210 bool waitForAlbumGain = false;
1211 const QString albumName = item->fileListItem->tags ? item->fileListItem->tags->album : "";
1212 if( !albumName.isEmpty() )
1213 waitForAlbumGain = albumGainItems[albumName].contains( item );
1214
1215 QFileInfo outputFileInfo( item->outputUrl.toLocalFile() );
1216 float fileRatio = outputFileInfo.size();
1217 if( item->fileListItem->track > 0 )
1218 {
1219 fileRatio /= item->fileListItem->length*4*44100;
1220 }
1221 else if( !item->fileListItem->local )
1222 {
1223 QFileInfo inputFileInfo( item->tempInputUrl.toLocalFile() );
1224 fileRatio /= inputFileInfo.size();
1225 }
1226 else
1227 {
1228 QFileInfo inputFileInfo( item->inputUrl.toLocalFile() );
1229 fileRatio /= inputFileInfo.size();
1230 }
1231 const ConversionOptions *conversionOptions = config->conversionOptionsManager()->getConversionOptions( item->fileListItem->conversionOptionsId );
1232 if( fileRatio < 0.01 && outputFileInfo.size() < 100000 && returnCode != FileListItem::StoppedByUser && ( !conversionOptions || !config->pluginLoader()->isCodecInferiorQuality(conversionOptions->codecName) ) )
1233 {
1234 exitMessage = i18n("An error occurred, the output file size is less than one percent of the input file size");
1235
1236 if( returnCode == FileListItem::Succeeded || returnCode == FileListItem::SucceededWithProblems )
1237 {
1238 logger->log( item->logID, i18n("Conversion failed, trying again. Exit code: -2 (%1)",exitMessage) );
1239 item->take = item->lastTake;
1240 logger->log( item->logID, i18n("Removing partially converted output file") );
1241 QFile::remove( item->outputUrl.toLocalFile() );
1242 executeSameStep( item );
1243 return;
1244 }
1245 }
1246
1247 if( returnCode == FileListItem::Succeeded && item->lastTake > 0 )
1248 returnCode = FileListItem::SucceededWithProblems;
1249
1250 switch( returnCode )
1251 {
1252 case FileListItem::Succeeded:
1253 exitMessage = i18nc("Conversion exit status","Normal exit");
1254 break;
1255 case FileListItem::SucceededWithProblems:
1256 exitMessage = i18nc("Conversion exit status","Succeeded but problems occurred");
1257 break;
1258 case FileListItem::StoppedByUser:
1259 exitMessage = i18nc("Conversion exit status","Aborted by the user");
1260 break;
1261 case FileListItem::BackendNeedsConfiguration:
1262 exitMessage = i18nc("Conversion exit status","Backend needs configuration");
1263 break;
1264 case FileListItem::DiscFull:
1265 exitMessage = i18nc("Conversion exit status","Not enough space on the output device");
1266 break;
1267 case FileListItem::CantWriteOutput:
1268 exitMessage = i18nc("Conversion exit status","Cannot write to output directory, please check permissions");
1269 break;
1270 case FileListItem::Skipped:
1271 exitMessage = i18nc("Conversion exit status","File already exists");
1272 break;
1273 case FileListItem::Encrypted:
1274 exitMessage = i18nc("Conversion exit status","File is encrypted");
1275 break;
1276 case FileListItem::Failed:
1277 exitMessage = i18nc("Conversion exit status","An error occurred");
1278 break;
1279 }
1280
1281 if( returnCode == FileListItem::Succeeded || returnCode == FileListItem::SucceededWithProblems )
1282 writeTags( item );
1283
1284 if( !waitForAlbumGain && !item->fileListItem->notifyCommand.isEmpty() && ( !config->data.general.waitForAlbumGain || !conversionOptions || !conversionOptions->replaygain ) )
1285 {
1286 QList<ConvertItem*> albumItems;
1287 if( !albumName.isEmpty() )
1288 albumItems = albumGainItems[albumName];
1289
1290 albumItems.append( item );
1291
1292 foreach( const ConvertItem *albumItem, albumItems )
1293 {
1294 QString command = albumItem->fileListItem->notifyCommand;
1295 // command.replace( "%u", albumItem->fileListItem->url );
1296 command.replace( "%i", albumItem->inputUrl.toLocalFile() );
1297 command.replace( "%o", albumItem->outputUrl.toLocalFile() );
1298 logger->log( item->logID, i18n("Executing command: \"%1\"",command) );
1299
1300 QProcess::startDetached( command );
1301 }
1302 }
1303
1304 // remove temp/failed files
1305 if( QFile::exists(item->tempInputUrl.toLocalFile()) )
1306 {
1307 QFile::remove(item->tempInputUrl.toLocalFile());
1308 }
1309 foreach( const KUrl& url, item->tempConvertUrls )
1310 {
1311 if( QFile::exists(url.toLocalFile()) )
1312 {
1313 QFile::remove( url.toLocalFile() );
1314 }
1315 }
1316 if( returnCode != FileListItem::Succeeded && returnCode != FileListItem::SucceededWithProblems && returnCode != FileListItem::Skipped && QFile::exists(item->outputUrl.toLocalFile()) )
1317 {
1318 logger->log( item->logID, i18n("Removing partially converted output file") );
1319 QFile::remove(item->outputUrl.toLocalFile());
1320 }
1321
1322 usedOutputNames.remove( item->logID );
1323
1324 logger->log( item->logID, "<br>" + i18n("Removing file from conversion list. Exit code %1 (%2)",returnCode,exitMessage) );
1325
1326 logger->log( item->logID, "\t" + i18n("Conversion time") + ": " + Global::prettyNumber(item->progressedTime.elapsed(),"ms") );
1327 logger->log( item->logID, "\t" + i18n("Output file size") + ": " + Global::prettyNumber(outputFileInfo.size(),"B") );
1328 logger->log( item->logID, "\t" + i18n("File size ratio") + ": " + Global::prettyNumber(fileRatio*100,"%") );
1329
1330 emit timeFinished( item->fileListItem->length );
1331
1332 if( item->process.data() )
1333 item->process.data()->deleteLater();
1334 if( item->kioCopyJob.data() )
1335 item->kioCopyJob.data()->deleteLater();
1336
1337 if( !waitForAlbumGain && !albumName.isEmpty() )
1338 {
1339 QList<ConvertItem*> albumItems = albumGainItems[albumName];
1340
1341 foreach( const ConvertItem *albumItem, albumItems )
1342 {
1343 emit finished( albumItem->fileListItem, FileListItem::Succeeded ); // send signal to FileList
1344 }
1345 qDeleteAll( albumItems );
1346
1347 albumGainItems.remove( albumName );
1348 }
1349 emit finished( item->fileListItem, returnCode, waitForAlbumGain ); // send signal to FileList
1350 emit finishedProcess( item->logID, returnCode == FileListItem::Succeeded, waitForAlbumGain ); // send signal to Logger
1351
1352 items.removeAll( item );
1353
1354 if( !waitForAlbumGain )
1355 delete item;
1356
1357 if( items.size() == 0 && albumGainItems.size() == 0 )
1358 updateTimer.stop();
1359 }
1360
kill(FileListItem * fileListItem)1361 void Convert::kill( FileListItem *fileListItem )
1362 {
1363 for( int i=0; i<items.size(); i++ )
1364 {
1365 if( items.at(i)->fileListItem == fileListItem )
1366 {
1367 items.at(i)->killed = true;
1368
1369 if( items.at(i)->backendID != -1 && items.at(i)->backendPlugin )
1370 {
1371 items.at(i)->backendPlugin->kill( items.at(i)->backendID );
1372 }
1373 else if( items.at(i)->process.data() != 0 )
1374 {
1375 items.at(i)->process.data()->kill();
1376 }
1377 else if( items.at(i)->kioCopyJob.data() != 0 )
1378 {
1379 items.at(i)->kioCopyJob.data()->kill( KJob::EmitResult );
1380 }
1381 }
1382 }
1383 }
1384
itemRemoved(FileListItem * fileListItem)1385 void Convert::itemRemoved( FileListItem *fileListItem )
1386 {
1387 if( !fileListItem )
1388 return;
1389
1390 const QString albumName = fileListItem->tags ? fileListItem->tags->album : "";
1391
1392 if( !albumName.isEmpty() )
1393 {
1394 QList<ConvertItem*> albumItems = albumGainItems[albumName];
1395
1396 foreach( ConvertItem *albumItem, albumItems )
1397 {
1398 if( albumItem->fileListItem == fileListItem )
1399 {
1400 albumGainItems[albumName].removeAll( albumItem );
1401 break;
1402 }
1403 }
1404 }
1405 }
1406
updateProgress()1407 void Convert::updateProgress()
1408 {
1409 float time = 0.0f;
1410 QString fileProgressString;
1411
1412 // trigger flushing of the logger cache
1413 pluginLog( 0, "" );
1414
1415 foreach( ConvertItem *item, items )
1416 {
1417 float fileProgress = 0.0f;
1418 bool logProgress = true;
1419
1420 if( item->backendID != -1 && item->backendPlugin )
1421 {
1422 fileProgress = item->backendPlugin->progress( item->backendID );
1423 }
1424 else
1425 {
1426 fileProgress = item->progress;
1427 }
1428
1429 if( fileProgress >= 0 )
1430 {
1431 fileProgressString = Global::prettyNumber(fileProgress,"%");
1432 }
1433 else
1434 {
1435 fileProgressString = i18nc("The conversion progress can't be determined","Unknown");
1436 fileProgress = 0; // make it a valid value so the calculations below work
1437 }
1438
1439 float fileTime = 0.0f;
1440
1441 switch( item->state )
1442 {
1443 case ConvertItem::initial:
1444 {
1445 break;
1446 }
1447 case ConvertItem::get:
1448 {
1449 fileTime = item->getTime;
1450 item->fileListItem->setText( 0, i18n("Getting file")+"... "+fileProgressString );
1451 break;
1452 }
1453 case ConvertItem::convert:
1454 {
1455 fileTime = item->convertTimes.at(item->conversionPipesStep);
1456 item->fileListItem->setText( 0, i18n("Converting")+"... "+fileProgressString );
1457 break;
1458 }
1459 case ConvertItem::rip:
1460 {
1461 fileTime = item->convertTimes.at(item->conversionPipesStep);
1462 item->fileListItem->setText( 0, i18n("Ripping")+"... "+fileProgressString );
1463 break;
1464 }
1465 case ConvertItem::decode:
1466 {
1467 fileTime = item->convertTimes.at(item->conversionPipesStep);
1468 item->fileListItem->setText( 0, i18n("Decoding")+"... "+fileProgressString );
1469 break;
1470 }
1471 case ConvertItem::filter:
1472 {
1473 fileTime = item->convertTimes.at(item->conversionPipesStep);
1474 item->fileListItem->setText( 0, i18n("Filter")+"... "+fileProgressString );
1475 break;
1476 }
1477 case ConvertItem::encode:
1478 {
1479 fileTime = item->convertTimes.at(item->conversionPipesStep);
1480 item->fileListItem->setText( 0, i18n("Encoding")+"... "+fileProgressString );
1481 break;
1482 }
1483 case ConvertItem::wait_replaygain:
1484 {
1485 item->fileListItem->setText( 0, i18n("Waiting for Replay Gain") );
1486 logProgress = false;
1487 break;
1488 }
1489 case ConvertItem::replaygain:
1490 {
1491 const QString albumName = item->fileListItem->tags ? item->fileListItem->tags->album : "";
1492 QList<ConvertItem*> albumItems;
1493 if( !albumName.isEmpty() )
1494 albumItems = albumGainItems[albumName];
1495 if( !albumItems.contains(item) )
1496 albumItems.append( item );
1497 foreach( const ConvertItem *albumItem, albumItems )
1498 {
1499 fileTime += albumItem->replaygainTime;
1500 }
1501 item->fileListItem->setText( 0, i18n("Replay Gain")+"... "+fileProgressString );
1502 break;
1503 }
1504 }
1505 time += item->finishedTime + fileProgress * fileTime / 100.0f;
1506
1507 if( logProgress )
1508 {
1509 logger->log( item->logID, "<pre>\t<span style=\"color:#585858\">" + i18n("Progress: %1",fileProgress) + "</span></pre>" );
1510 }
1511 }
1512
1513 emit updateTime( time );
1514 }
1515
1516