1 //
2 // C++ Implementation: pluginloader
3 //
4 // Description:
5 //
6 //
7 // Author: Daniel Faust <hessijames@gmail.com>, (C) 2007
8 //
9 // Copyright: See COPYING file that comes with this distribution
10 //
11 //
12 #include "pluginloader.h"
13 #include "logger.h"
14 #include "config.h"
15 
16 #include <QSet>
17 #include <QFile>
18 
19 #include <KServiceTypeTrader>
20 #include <KMimeType>
21 
22 
moreThanConversionPipe(const ConversionPipe & pipe1,const ConversionPipe & pipe2)23 bool moreThanConversionPipe( const ConversionPipe& pipe1, const ConversionPipe& pipe2 )
24 {
25     int rating1 = 0;
26     int rating2 = 0;
27 
28     int minimumRating1 = 0;
29     foreach( const ConversionPipeTrunk& trunk, pipe1.trunks )
30     {
31         if( minimumRating1 == 0 || trunk.rating < minimumRating1 )
32             minimumRating1 = trunk.rating;
33 
34         rating1 -= 10;
35     }
36     rating1 += minimumRating1;
37 
38     int minimumRating2 = 0;
39     foreach( const ConversionPipeTrunk& trunk, pipe2.trunks )
40     {
41         if( minimumRating2 == 0 || trunk.rating < minimumRating2 )
42             minimumRating2 = trunk.rating;
43 
44         rating2 -= 10;
45     }
46     rating2 += minimumRating2;
47 
48     return rating1 > rating2;
49 }
50 
moreThanReplayGainPipe(const ReplayGainPipe & pipe1,const ReplayGainPipe & pipe2)51 bool moreThanReplayGainPipe( const ReplayGainPipe& pipe1, const ReplayGainPipe& pipe2 )
52 {
53     return pipe1.rating > pipe2.rating;
54 }
55 
56 
PluginLoader(Logger * _logger,Config * _config)57 PluginLoader::PluginLoader( Logger *_logger, Config *_config )
58     : QObject( _config ),
59     logger( _logger ),
60     config( _config )
61 {
62     atomPath += "moov";
63     atomPath += "trak";
64     atomPath += "mdia";
65     atomPath += "minf";
66     atomPath += "stbl";
67     atomPath += "stsd";
68 }
69 
~PluginLoader()70 PluginLoader::~PluginLoader()
71 {
72     qDeleteAll( codecPlugins );
73     qDeleteAll( filterPlugins );
74     qDeleteAll( replaygainPlugins );
75     qDeleteAll( ripperPlugins );
76 }
77 
addFormatInfo(const QString & codecName,BackendPlugin * plugin)78 void PluginLoader::addFormatInfo( const QString& codecName, BackendPlugin *plugin )
79 {
80     BackendPlugin::FormatInfo info = plugin->formatInfo( codecName );
81 
82     for( int i=0; i<formatInfos.count(); i++ )
83     {
84         if( formatInfos.at(i).codecName == codecName )
85         {
86             if( formatInfos.at(i).description.isEmpty() )
87                 formatInfos[i].description = info.description;
88 
89             for( int j=0; j < info.mimeTypes.count(); j++ )
90             {
91                 if( !formatInfos.at(i).mimeTypes.contains(info.mimeTypes.at(j)) )
92                     formatInfos[i].mimeTypes.append( info.mimeTypes.at(j) );
93             }
94             for( int j=0; j < info.extensions.count(); j++ )
95             {
96                 if( !formatInfos.at(i).extensions.contains(info.extensions.at(j)) )
97                     formatInfos[i].extensions.append( info.extensions.at(j) );
98             }
99 
100             if( formatInfos.at(i).lossless != info.lossless )
101                 logger->log( 1000, "<span style=\"color:red\">Disturbing Error: Plugin " + plugin->name() + " says " + codecName + " was " + (info.lossless?"lossless":"lossy") + " but it is already registed as " + (!info.lossless?"lossless":"lossy") + "</span>" );
102 
103             return;
104         }
105     }
106 
107     formatInfos.append( info );
108 }
109 
load()110 void PluginLoader::load()
111 {
112     QTime overallTime;
113     overallTime.start();
114     QTime createInstanceTime;
115 
116     int createInstanceTimeSum = 0;
117 
118     KService::List offers;
119 
120     logger->log( 1000, "\nloading plugins ..." );
121 
122     offers = KServiceTypeTrader::self()->query("soundKonverter/CodecPlugin");
123 
124     if( !offers.isEmpty() )
125     {
126         for( int i=0; i<offers.size(); i++ )
127         {
128             createInstanceTime.start();
129             QVariantList allArgs;
130             allArgs << offers.at(i)->storageId() << "";
131             QString error;
132             CodecPlugin *plugin = offers.at(i).data()->createInstance<CodecPlugin>(0, allArgs, &error );
133             if( plugin )
134             {
135                 logger->log( 1000, "\tloading plugin: " + plugin->name() );
136                 createInstanceTimeSum += createInstanceTime.elapsed();
137                 codecPlugins.append( plugin );
138                 plugin->scanForBackends();
139                 QMap<QString,int> encodeCodecs;
140                 QMap<QString,int> decodeCodecs;
141                 QList<ConversionPipeTrunk> codecTable = plugin->codecTable();
142                 for( int j = 0; j < codecTable.count(); j++ )
143                 {
144                     codecTable[j].plugin = plugin;
145                     conversionPipeTrunks.append( codecTable.at(j) );
146                     if( codecTable.at(j).codecTo != "wav" )
147                         encodeCodecs[codecTable.at(j).codecTo] += codecTable.at(j).enabled;
148                     if( codecTable.at(j).codecFrom != "wav" )
149                         decodeCodecs[codecTable.at(j).codecFrom] += codecTable.at(j).enabled;
150                     addFormatInfo( codecTable.at(j).codecFrom, plugin );
151                     addFormatInfo( codecTable.at(j).codecTo, plugin );
152                 }
153                 if( encodeCodecs.count() > 0 )
154                 {
155                     logger->log( 1000, "\t\tencode:" );
156                     for( int j=0; j<encodeCodecs.count(); j++ )
157                     {
158                         QString spaces;
159                         spaces.fill( ' ', 12 - encodeCodecs.keys().at(j).length() );
160                         logger->log( 1000, "<pre>\t\t\t" + QString("%1%2(%3)").arg(encodeCodecs.keys().at(j)).arg(spaces).arg(encodeCodecs.values().at(j) ? "<span style=\"color:green\">enabled</span>" : "<span style=\"color:red\">disabled</span>") + "</pre>" );
161                     }
162                 }
163                 if( decodeCodecs.count() > 0 )
164                 {
165                     logger->log( 1000, "\t\tdecode:" );
166                     for( int j=0; j<decodeCodecs.count(); j++ )
167                     {
168                         QString spaces;
169                         spaces.fill( ' ', 12 - decodeCodecs.keys().at(j).length() );
170                         logger->log( 1000, "<pre>\t\t\t" + QString("%1%2(%3)").arg(decodeCodecs.keys().at(j)).arg(spaces).arg(decodeCodecs.values().at(j) ? "<span style=\"color:green\">enabled</span>" : "<span style=\"color:red\">disabled</span>") + "</pre>" );
171                     }
172                 }
173                 logger->log( 1000, "" );
174             }
175             else
176             {
177                 logger->log( 1000, "<pre>\t<span style=\"color:red\">failed to load plugin: " + offers.at(i)->library() + "</span></pre>" );
178             }
179         }
180     }
181 
182     offers = KServiceTypeTrader::self()->query("soundKonverter/FilterPlugin");
183 
184     if( !offers.isEmpty() )
185     {
186         for( int i=0; i<offers.size(); i++ )
187         {
188             createInstanceTime.start();
189             QVariantList allArgs;
190             allArgs << offers.at(i)->storageId() << "";
191             QString error;
192 
193             FilterPlugin *plugin = offers.at(i).data()->createInstance<FilterPlugin>(0, allArgs, &error );
194             if( plugin )
195             {
196                 logger->log( 1000, "\tloading plugin: " + plugin->name() );
197                 createInstanceTimeSum += createInstanceTime.elapsed();
198                 filterPlugins.append( plugin );
199                 plugin->scanForBackends();
200                 QMap<QString,int> encodeCodecs;
201                 QMap<QString,int> decodeCodecs;
202                 QList<ConversionPipeTrunk> codecTable = plugin->codecTable();
203                 for( int j = 0; j < codecTable.count(); j++ )
204                 {
205                     codecTable[j].plugin = plugin;
206                     filterPipeTrunks.append( codecTable.at(j) );
207                     if( codecTable.at(j).codecTo != "wav" )
208                         encodeCodecs[codecTable.at(j).codecTo] += codecTable.at(j).enabled;
209                     if( codecTable.at(j).codecFrom != "wav" )
210                         decodeCodecs[codecTable.at(j).codecFrom] += codecTable.at(j).enabled;
211                     addFormatInfo( codecTable.at(j).codecFrom, plugin );
212                     addFormatInfo( codecTable.at(j).codecTo, plugin );
213                 }
214                 if( encodeCodecs.count() > 0 )
215                 {
216                     logger->log( 1000, "\t\tencode:" );
217                     for( int j=0; j<encodeCodecs.count(); j++ )
218                     {
219                         QString spaces;
220                         spaces.fill( ' ', 12 - encodeCodecs.keys().at(j).length() );
221                         logger->log( 1000, "<pre>\t\t\t" + QString("%1%2(%3)").arg(encodeCodecs.keys().at(j)).arg(spaces).arg(encodeCodecs.values().at(j) ? "<span style=\"color:green\">enabled</span>" : "<span style=\"color:red\">disabled</span>") + "</pre>" );
222                     }
223                 }
224                 if( decodeCodecs.count() > 0 )
225                 {
226                     logger->log( 1000, "\t\tdecode:" );
227                     for( int j=0; j<decodeCodecs.count(); j++ )
228                     {
229                         QString spaces;
230                         spaces.fill( ' ', 12 - decodeCodecs.keys().at(j).length() );
231                         logger->log( 1000, "<pre>\t\t\t" + QString("%1%2(%3)").arg(decodeCodecs.keys().at(j)).arg(spaces).arg(decodeCodecs.values().at(j) ? "<span style=\"color:green\">enabled</span>" : "<span style=\"color:red\">disabled</span>") + "</pre>" );
232                     }
233                 }
234                 // TODO filters
235                 logger->log( 1000, "" );
236             }
237             else
238             {
239                 logger->log( 1000, "<pre>\t<span style=\"color:red\">failed to load plugin: " + offers.at(i)->library() + "</span></pre>" );
240             }
241         }
242     }
243 
244     offers = KServiceTypeTrader::self()->query("soundKonverter/ReplayGainPlugin");
245 
246     if( !offers.isEmpty() )
247     {
248         for( int i=0; i<offers.size(); i++ )
249         {
250             createInstanceTime.start();
251             QVariantList allArgs;
252             allArgs << offers.at(i)->storageId() << "";
253             QString error;
254 
255             ReplayGainPlugin *plugin = offers.at(i).data()->createInstance<ReplayGainPlugin>(0, allArgs, &error );
256             if( plugin )
257             {
258                 logger->log( 1000, "\tloading plugin: " + plugin->name() );
259                 createInstanceTimeSum += createInstanceTime.elapsed();
260                 replaygainPlugins.append( plugin );
261                 plugin->scanForBackends();
262                 QList<ReplayGainPipe> codecTable = plugin->codecTable();
263                 for( int j = 0; j < codecTable.count(); j++ )
264                 {
265                     codecTable[j].plugin = plugin;
266                     replaygainPipes.append( codecTable.at(j) );
267                     QString spaces;
268                     spaces.fill( ' ', 12 - codecTable.at(j).codecName.length() );
269                     logger->log( 1000, "<pre>\t\t\t" + QString("%1%2(%3)").arg(codecTable.at(j).codecName).arg(spaces).arg(codecTable.at(j).enabled ? "<span style=\"color:green\">enabled</span>" : "<span style=\"color:red\">disabled</span>") + "</pre>" );
270                     addFormatInfo( codecTable.at(j).codecName, plugin );
271                 }
272                 logger->log( 1000, "" );
273             }
274             else
275             {
276                 logger->log( 1000, "<pre>\t<span style=\"color:red\">failed to load plugin: " + offers.at(i)->library() + "</span></pre>" );
277             }
278         }
279     }
280 
281     offers = KServiceTypeTrader::self()->query("soundKonverter/RipperPlugin");
282 
283     if( !offers.isEmpty() )
284     {
285         for( int i=0; i<offers.size(); i++ )
286         {
287             createInstanceTime.start();
288             QVariantList allArgs;
289             allArgs << offers.at(i)->storageId() << "";
290             QString error;
291             RipperPlugin *plugin = offers.at(i).data()->createInstance<RipperPlugin>(0, allArgs, &error );
292             if( plugin )
293             {
294                 logger->log( 1000, "\tloading plugin: " + plugin->name() );
295                 createInstanceTimeSum += createInstanceTime.elapsed();
296                 ripperPlugins.append( plugin );
297                 plugin->scanForBackends();
298                 QList<ConversionPipeTrunk> codecTable = plugin->codecTable();
299                 for( int j = 0; j < codecTable.count(); j++ )
300                 {
301                     codecTable[j].plugin = plugin;
302                     conversionPipeTrunks.append( codecTable.at(j) );
303                     QString spaces;
304                     spaces.fill( ' ', 12 - codecTable.at(j).codecTo.length() );
305                     logger->log( 1000, "<pre>\t\t\t" + QString("%1%2(%3, %4)").arg(codecTable.at(j).codecTo).arg(spaces).arg(codecTable.at(j).enabled ? "<span style=\"color:green\">enabled</span>" : "<span style=\"color:red\">disabled</span>").arg(codecTable.at(j).data.canRipEntireCd ? "<span style=\"color:green\">can rip to single file</span>" : "<span style=\"color:red\">can't rip to single file</span>") + "</pre>" );
306                 }
307                 logger->log( 1000, "" );
308             }
309             else
310             {
311                 logger->log( 1000, "<pre>\t<span style=\"color:red\">failed to load plugin: " + offers.at(i)->library() + "</span></pre>" );
312             }
313         }
314     }
315 
316     conversionFilterPipeTrunks = conversionPipeTrunks + filterPipeTrunks;
317 
318     logger->log( 1000, QString("... all plugins loaded (took %1 ms, creating instances: %2 ms)").arg(overallTime.elapsed()).arg(createInstanceTimeSum) + "\n" );
319 }
320 
formatList(Possibilities possibilities,CompressionType compressionType)321 QStringList PluginLoader::formatList( Possibilities possibilities, CompressionType compressionType )
322 {
323     QSet<QString> set;
324     QStringList list;
325 
326     foreach( const ConversionPipeTrunk& trunk, conversionFilterPipeTrunks )
327     {
328         if( !trunk.enabled )
329             continue;
330 
331         if( possibilities & Encode )
332         {
333             const QString codec = trunk.codecTo;
334             const bool isLossless = isCodecLossless(codec);
335             const bool isInferiorQuality = isCodecInferiorQuality(codec);
336 //             const bool isHybrid = isCodecHybrid(codec);
337             if( ( ( compressionType & Lossy && !isLossless ) || ( compressionType & Lossless && isLossless ) ) &&
338                 ( ( compressionType & InferiorQuality && isInferiorQuality ) || !isInferiorQuality ) )
339                 set += codec;
340 //             if( compressionType & Hybrid && isCodecHybrid(codec) && ( compressionType & InferiorQuality && isInferiorQuality || !isInferiorQuality ) )
341 //                 set += codec;
342         }
343         if( possibilities & Decode )
344         {
345             const QString codec = trunk.codecFrom;
346             const bool isLossless = isCodecLossless(codec);
347             const bool isInferiorQuality = isCodecInferiorQuality(codec);
348 //             const bool isHybrid = isCodecHybrid(codec);
349             if( ( ( compressionType & Lossy && !isLossless ) || ( compressionType & Lossless && isLossless ) ) &&
350                 ( ( compressionType & InferiorQuality && isInferiorQuality ) || !isInferiorQuality ) )
351                 set += codec;
352 //             if( compressionType & Hybrid && isCodecHybrid(codec) && ( compressionType & InferiorQuality && isInferiorQuality || !isInferiorQuality ) )
353 //                 set += codec;
354         }
355     }
356 
357     if( possibilities & ReplayGain )
358     {
359         for( int i=0; i<replaygainPipes.count(); i++ )
360         {
361             if( !replaygainPipes.at(i).enabled )
362                 continue;
363 
364             set += replaygainPipes.at(i).codecName;
365         }
366     }
367 
368     list = set.toList();
369     list.sort();
370 
371     QStringList importantCodecs;
372     importantCodecs += "ogg vorbis";
373     importantCodecs += "mp3";
374     importantCodecs += "flac";
375     importantCodecs += "m4a/aac";
376     importantCodecs += "m4a/alac";
377     importantCodecs += "wma";
378     int listIterator = 0;
379     for( int i=0; i<importantCodecs.count(); i++ )
380     {
381         if( list.contains(importantCodecs.at(i)) )
382         {
383             list.move( list.indexOf(importantCodecs.at(i)), listIterator++ );
384         }
385     }
386     if( list.contains("wav") )
387     {
388         list.move( list.indexOf("wav"), list.count()-1 );
389     }
390 
391     return list;
392 }
393 
encodersForCodec(const QString & codecName)394 QList<CodecPlugin*> PluginLoader::encodersForCodec( const QString& codecName )
395 {
396     QSet<CodecPlugin*> encoders;
397 
398     foreach( const ConversionPipeTrunk& pipeTrunk, conversionFilterPipeTrunks )
399     {
400         if( pipeTrunk.codecTo == codecName && pipeTrunk.enabled )
401         {
402             encoders += qobject_cast<CodecPlugin*>(pipeTrunk.plugin);
403         }
404     }
405 
406     return encoders.toList();
407 }
408 
409 // QList<CodecPlugin*> PluginLoader::decodersForCodec( const QString& codecName )
410 // {
411 //     QSet<CodecPlugin*> decoders;
412 //
413 //     for( int i=0; i<conversionPipeTrunks.count(); i++ )
414 //     {
415 //         if( conversionPipeTrunks.at(i).codecFrom == codecName && conversionPipeTrunks.at(i).enabled && conversionPipeTrunks.at(i).plugin->type() == "codec" )
416 //         {
417 //             decoders += qobject_cast<CodecPlugin*>(conversionPipeTrunks.at(i).plugin);
418 //         }
419 //     }
420 //
421 //     for( int i=0; i<filterPipeTrunks.count(); i++ )
422 //     {
423 //         if( filterPipeTrunks.at(i).codecFrom == codecName && filterPipeTrunks.at(i).enabled && filterPipeTrunks.at(i).plugin->type() == "filter" )
424 //         {
425 //             decoders += qobject_cast<CodecPlugin*>(filterPipeTrunks.at(i).plugin);
426 //         }
427 //     }
428 //
429 //     return decoders.toList();
430 // }
431 
432 // QList<ReplayGainPlugin*> PluginLoader::replaygainForCodec( const QString& codecName )
433 // {
434 //     QSet<ReplayGainPlugin*> replaygain;
435 //
436 //     for( int i=0; i<replaygainPipes.count(); i++ )
437 //     {
438 //         if( replaygainPipes.at(i).codecName == codecName && replaygainPipes.at(i).enabled )
439 //         {
440 //             replaygain += replaygainPipes.at(i).plugin;
441 //         }
442 //     }
443 //
444 //     return replaygain.toList();
445 // }
446 
backendPluginByName(const QString & name)447 BackendPlugin *PluginLoader::backendPluginByName( const QString& name )
448 {
449     for( int i=0; i<codecPlugins.count(); i++ )
450     {
451         if( codecPlugins.at(i)->name() == name )
452         {
453             return codecPlugins.at(i);
454         }
455     }
456     for( int i=0; i<filterPlugins.count(); i++ )
457     {
458         if( filterPlugins.at(i)->name() == name )
459         {
460             return filterPlugins.at(i);
461         }
462     }
463     for( int i=0; i<replaygainPlugins.count(); i++ )
464     {
465         if( replaygainPlugins.at(i)->name() == name )
466         {
467             return replaygainPlugins.at(i);
468         }
469     }
470     for( int i=0; i<ripperPlugins.count(); i++ )
471     {
472         if( ripperPlugins.at(i)->name() == name )
473         {
474             return ripperPlugins.at(i);
475         }
476     }
477 
478     return 0;
479 }
480 
getConversionPipes(const QString & codecFrom,const QString & codecTo,QList<FilterOptions * > filterOptions,const QString & preferredPlugin)481 QList<ConversionPipe> PluginLoader::getConversionPipes( const QString& codecFrom, const QString& codecTo, QList<FilterOptions*> filterOptions, const QString& preferredPlugin )
482 {
483     QList<ConversionPipe> list;
484 
485     QStringList decoders;
486     QStringList encoders;
487     // get the lists of decoders and encoders ordered by the user in the config dialog
488     for( int i=0; i<config->data.backends.codecs.count(); i++ )
489     {
490         if( config->data.backends.codecs.at(i).codecName == codecFrom )
491         {
492             decoders = config->data.backends.codecs.at(i).decoders;
493         }
494         if( config->data.backends.codecs.at(i).codecName == codecTo && codecTo != "wav" )
495         {
496             encoders = config->data.backends.codecs.at(i).encoders;
497         }
498     }
499     // prepend the preferred plugin
500     if( !preferredPlugin.isEmpty() )
501     {
502         encoders.removeAll( preferredPlugin );
503         encoders.prepend( preferredPlugin );
504     }
505 
506     QList<FilterPlugin*> filterPlugins;
507     foreach( const FilterOptions *filter, filterOptions )
508     {
509         filterPlugins.append( qobject_cast<FilterPlugin*>(backendPluginByName(filter->pluginName)) );
510     }
511 
512     // build all possible pipes
513     for( int i=0; i<conversionFilterPipeTrunks.count(); i++ )
514     {
515         if( conversionFilterPipeTrunks.at(i).codecFrom == codecFrom && conversionFilterPipeTrunks.at(i).enabled )
516         {
517             if( codecFrom == "wav" && conversionFilterPipeTrunks.at(i).codecTo == codecTo )
518             {
519                 ConversionPipe newPipe;
520 
521                 foreach( FilterPlugin *plugin, filterPlugins )
522                 {
523                     if( plugin == qobject_cast<FilterPlugin*>(conversionFilterPipeTrunks.at(i).plugin) )
524                         continue;
525 
526                     foreach( const ConversionPipeTrunk& trunk, filterPipeTrunks )
527                     {
528                         if( trunk.plugin == plugin && trunk.codecFrom == "wav" && trunk.codecTo == "wav" && trunk.enabled )
529                         {
530                             newPipe.trunks += trunk;
531                             break;
532                         }
533                     }
534                 }
535                 newPipe.trunks += conversionFilterPipeTrunks.at(i);
536 
537                 if( encoders.indexOf(newPipe.trunks.last().plugin->name()) != -1 )
538                 {
539                     // add rating depending on the position in the list ordered by the user, encoders do count much
540                     const int rating = ( encoders.count() - encoders.indexOf(newPipe.trunks.last().plugin->name()) ) * 1000000;
541                     for( int i=0; i<newPipe.trunks.count(); i++ )
542                     {
543                         newPipe.trunks[i].rating += rating;
544                     }
545                 }
546 
547                 list += newPipe;
548             }
549             else if( codecTo == "wav" && conversionFilterPipeTrunks.at(i).codecTo == codecTo )
550             {
551                 ConversionPipe newPipe;
552 
553                 newPipe.trunks += conversionFilterPipeTrunks.at(i);
554                 foreach( FilterPlugin *plugin, filterPlugins )
555                 {
556                     if( plugin == qobject_cast<FilterPlugin*>(conversionFilterPipeTrunks.at(i).plugin) )
557                         continue;
558 
559                     foreach( const ConversionPipeTrunk& trunk, filterPipeTrunks )
560                     {
561                         if( trunk.plugin == plugin && trunk.codecFrom == "wav" && trunk.codecTo == "wav" && trunk.enabled )
562                         {
563                             newPipe.trunks += trunk;
564                             break;
565                         }
566                     }
567                 }
568 
569                 if( decoders.indexOf(newPipe.trunks.first().plugin->name()) != -1 )
570                 {
571                     // add rating depending on the position in the list ordered by the user, decoders don't count much
572                     const int rating = ( decoders.count() - decoders.indexOf(newPipe.trunks.first().plugin->name()) ) * 1000;
573                     for( int i=0; i<newPipe.trunks.count(); i++ )
574                     {
575                         newPipe.trunks[i].rating += rating;
576                     }
577                 }
578 
579                 list += newPipe;
580             }
581             else if( conversionFilterPipeTrunks.at(i).codecTo == codecTo )
582             {
583                 if( filterPlugins.count() == 0 || ( filterPlugins.count() == 1 && filterPlugins.first() == conversionFilterPipeTrunks.at(i).plugin ) )
584                 {
585                     ConversionPipe newPipe;
586 
587                     newPipe.trunks += conversionFilterPipeTrunks.at(i);
588                     if( decoders.indexOf(newPipe.trunks.first().plugin->name()) != -1 )
589                     {
590                         // add rating depending on the position in the list ordered by the user, decoders don't count much
591                         newPipe.trunks[0].rating += ( decoders.count() - decoders.indexOf(newPipe.trunks.first().plugin->name()) ) * 1000;
592                     }
593                     if( encoders.indexOf(newPipe.trunks.first().plugin->name()) != -1 )
594                     {
595                         // add rating depending on the position in the list ordered by the user, encoders do count much
596                         newPipe.trunks[0].rating += ( encoders.count() - encoders.indexOf(newPipe.trunks.first().plugin->name()) ) * 1000000;
597                     }
598 
599                     list += newPipe;
600                 }
601             }
602             else if( conversionFilterPipeTrunks.at(i).codecTo == "wav" )
603             {
604                 if( conversionFilterPipeTrunks.at(i).codecFrom == conversionFilterPipeTrunks.at(i).codecTo )
605                     continue;
606 
607                 for( int j = 0; j < conversionFilterPipeTrunks.count(); j++ )
608                 {
609                     if( i == j )
610                         continue;
611 
612                     if( conversionFilterPipeTrunks.at(j).codecFrom == conversionFilterPipeTrunks.at(j).codecTo )
613                         continue;
614 
615                     if( conversionFilterPipeTrunks.at(j).codecFrom == conversionFilterPipeTrunks.at(i).codecTo && conversionFilterPipeTrunks.at(j).codecTo == codecTo && conversionFilterPipeTrunks.at(j).enabled )
616                     {
617                         ConversionPipe newPipe;
618 
619                         newPipe.trunks += conversionFilterPipeTrunks.at(i);
620                         foreach( FilterPlugin *plugin, filterPlugins )
621                         {
622                             if( plugin == qobject_cast<FilterPlugin*>(conversionFilterPipeTrunks.at(i).plugin) )
623                                 continue;
624 
625                             if( plugin == qobject_cast<FilterPlugin*>(conversionFilterPipeTrunks.at(j).plugin) )
626                                 continue;
627 
628                             foreach( const ConversionPipeTrunk& trunk, filterPipeTrunks )
629                             {
630                                 if( trunk.plugin == plugin && trunk.codecFrom == "wav" && trunk.codecTo == "wav" && trunk.enabled )
631                                 {
632                                     newPipe.trunks += trunk;
633                                     break;
634                                 }
635                             }
636                         }
637                         newPipe.trunks += conversionFilterPipeTrunks.at(j);
638 
639                         if( decoders.indexOf(newPipe.trunks.first().plugin->name()) != -1 )
640                         {
641                             // add rating depending on the position in the list ordered by the user, decoders don't count much
642                             const int rating = ( decoders.count() - decoders.indexOf(newPipe.trunks.first().plugin->name()) ) * 1000;
643                             for( int i=0; i<newPipe.trunks.count(); i++ )
644                             {
645                                 newPipe.trunks[i].rating += rating;
646                             }
647                         }
648                         if( encoders.indexOf(newPipe.trunks.last().plugin->name()) != -1 )
649                         {
650                             // add rating depending on the position in the list ordered by the user, encoders do count much
651                             const int rating = ( encoders.count() - encoders.indexOf(newPipe.trunks.last().plugin->name()) ) * 1000000;
652                             for( int i=0; i<newPipe.trunks.count(); i++ )
653                             {
654                                 newPipe.trunks[i].rating += rating;
655                             }
656                         }
657 
658                         list += newPipe;
659                     }
660                 }
661             }
662         }
663     }
664 
665     qSort( list.begin(), list.end(), moreThanConversionPipe );
666 
667     return list;
668 }
669 
getReplayGainPipes(const QString & codecName,const QString & preferredPlugin)670 QList<ReplayGainPipe> PluginLoader::getReplayGainPipes( const QString& codecName, const QString& preferredPlugin )
671 {
672     QList<ReplayGainPipe> list;
673 
674     QStringList backends;
675     // get the lists of decoders and encoders ordered by the user in the config dialog
676     for( int i=0; i<config->data.backends.codecs.count(); i++ )
677     {
678         if( config->data.backends.codecs.at(i).codecName == codecName )
679         {
680             backends = config->data.backends.codecs.at(i).replaygain;
681         }
682     }
683     // prepend the preferred plugin
684     backends.removeAll( preferredPlugin );
685     backends.prepend( preferredPlugin );
686 
687     for( int i=0; i<replaygainPipes.count(); i++ )
688     {
689         if( replaygainPipes.at(i).codecName == codecName && replaygainPipes.at(i).enabled )
690         {
691             ReplayGainPipe newPipe = replaygainPipes.at(i);
692             if( backends.indexOf(newPipe.plugin->name()) != -1 )
693             {
694                 // add rating depending on the position in the list ordered by the user
695                 newPipe.rating += ( backends.count() - backends.indexOf(newPipe.plugin->name()) ) * 1000;
696             }
697             list += newPipe;
698         }
699     }
700 
701     qSort( list.begin(), list.end(), moreThanReplayGainPipe );
702 
703     return list;
704 }
705 
getCodecFromM4aFile(QFile * file)706 QString PluginLoader::getCodecFromM4aFile( QFile *file )
707 {
708     int atomPathDepth = 0;
709 
710     qint64 maxPos = file->size();
711 
712     while( !file->atEnd() )
713     {
714         const QByteArray length = file->read(4);
715         const QByteArray name = file->read(4);
716 
717         if( atomPathDepth == 6 && name == "mp4a" )
718         {
719             // It could be something other than aac but lets assume it's aac for now
720             return "m4a/aac";
721         }
722         else if( atomPathDepth == 6 && name == "alac" )
723         {
724             return "m4a/alac";
725         }
726         else if( length.size() == 4 )
727         {
728             qint64 int_length = ((static_cast<qint64>(length.at(0)) & 0xFF) << 24) +
729                                 ((static_cast<qint64>(length.at(1)) & 0xFF) << 16) +
730                                 ((static_cast<qint64>(length.at(2)) & 0xFF) << 8) +
731                                 ((static_cast<qint64>(length.at(3)) & 0xFF) );
732 
733             if( int_length == 0 ) // Meaning: continues until end of file.
734                 return "";
735 
736             if( int_length == 1 ) // Meaning: length is 64 bits
737             {
738                 const QByteArray l = file->read(8);
739                 int_length    = ((static_cast<qint64>(l.at(0)) & 0xFF) << 56) +
740                                 ((static_cast<qint64>(l.at(1)) & 0xFF) << 48) +
741                                 ((static_cast<qint64>(l.at(2)) & 0xFF) << 40) +
742                                 ((static_cast<qint64>(l.at(3)) & 0xFF) << 32) +
743                                 ((static_cast<qint64>(l.at(4)) & 0xFF) << 24) +
744                                 ((static_cast<qint64>(l.at(5)) & 0xFF) << 16) +
745                                 ((static_cast<qint64>(l.at(6)) & 0xFF) << 8) +
746                                 ((static_cast<qint64>(l.at(7)) & 0xFF) );
747             }
748 
749             if( name == atomPath.at(atomPathDepth) )
750             {
751                 atomPathDepth++;
752                 maxPos = file->pos() - 8 + int_length;
753 
754                 if( atomPathDepth == 6 )
755                     file->seek( file->pos() + 8 ); // Skip 'stsd' header
756             }
757             else
758             {
759                 if( file->pos() - 8 + int_length > maxPos )
760                     return "";
761 
762                 file->seek( file->pos() - 8 + int_length );
763             }
764         }
765         else
766         {
767             return "";
768         }
769     }
770 
771     return "";
772 }
773 
getCodecFromFile(const KUrl & filename,QString * mimeType,bool checkM4a)774 QString PluginLoader::getCodecFromFile( const KUrl& filename, QString *mimeType, bool checkM4a )
775 {
776     QString codec = "";
777     short rating = 0;
778     const QString mime = KMimeType::findByUrl(filename)->name();
779 
780     if( mimeType )
781         *mimeType = mime;
782 
783     if( mime == "inode/directory" )
784         return codec;
785 
786     const QString extension = filename.url().mid( filename.url().lastIndexOf(".") + 1 ).toLower();
787 
788     foreach( const BackendPlugin::FormatInfo& info, formatInfos )
789     {
790         short newRating = info.priority;
791 
792         if( info.mimeTypes.contains(mime) )
793             newRating += 100 - info.mimeTypes.indexOf(mime);
794 
795         if( info.extensions.contains(extension) )
796             newRating += 100 - info.extensions.indexOf(extension);
797 
798         // special treatment for the mp4 family
799         if( ( mime == "audio/mp4" || mime == "audio/x-m4a" ) && extension == "aac" && info.codecName == "m4a/aac" )
800             newRating = 300;
801         else if( ( mime == "audio/mp4" || mime == "audio/x-m4a" ) && extension == "alac" && info.codecName == "m4a/alac" )
802             newRating = 300;
803 
804         if( newRating == info.priority )
805             continue;
806 
807         if( newRating == 300 )
808         {
809             return info.codecName;
810         }
811         else if( newRating > rating )
812         {
813             rating = newRating;
814             codec = info.codecName;
815         }
816     }
817 
818     // special treatment for the mp4 family
819     if( checkM4a && ( mime == "audio/mp4" || mime == "audio/x-m4a" ) && filename.isLocalFile() )
820     {
821         QFile file( filename.toLocalFile() );
822         if( file.open(QIODevice::ReadOnly) )
823         {
824             const QString newCodec = getCodecFromM4aFile( &file );
825 
826             file.close();
827 
828             if( !newCodec.isEmpty() )
829                 return newCodec;
830         }
831     }
832 
833     // get codec from a plugin - not used at the moment
834     // QList<BackendPlugin*> allPlugins;
835     // foreach( CodecPlugin *plugin, codecPlugins )
836     //     allPlugins.append( plugin );
837     // foreach( FilterPlugin *plugin, filterPlugins )
838     //     allPlugins.append( plugin );
839     // foreach( ReplayGainPlugin *plugin, replaygainPlugins )
840     //     allPlugins.append( plugin );
841     //
842     // foreach( BackendPlugin *plugin, allPlugins )
843     // {
844     //     short newRating = 0;
845     //     const QString newCodec = plugin->getCodecFromFile( filename, mime, &newRating );
846     //     if( !newCodec.isEmpty() && newRating == 300 )
847     //     {
848     //         return newCodec;
849     //     }
850     //     else if( !newCodec.isEmpty() && newRating > rating )
851     //     {
852     //         rating = newRating;
853     //         codec = newCodec;
854     //     }
855     // }
856 
857     return codec;
858 }
859 
canDecode(const QString & codecName,QStringList * errorList)860 bool PluginLoader::canDecode( const QString& codecName, QStringList *errorList )
861 {
862     if( codecName.isEmpty() )
863         return false;
864 
865     for( int i=0; i<conversionFilterPipeTrunks.size(); i++ )
866     {
867         if( conversionFilterPipeTrunks.at(i).codecFrom == codecName && conversionFilterPipeTrunks.at(i).enabled )
868         {
869             return true;
870         }
871     }
872 
873     if( errorList )
874     {
875         for( int i=0; i<conversionFilterPipeTrunks.size(); i++ )
876         {
877             if( conversionFilterPipeTrunks.at(i).codecFrom == codecName )
878             {
879                 if( !conversionFilterPipeTrunks.at(i).problemInfo.isEmpty() && !errorList->contains(conversionFilterPipeTrunks.at(i).problemInfo) )
880                 {
881                       errorList->append( conversionFilterPipeTrunks.at(i).problemInfo );
882                 }
883             }
884         }
885     }
886 
887     return false;
888 }
889 
canReplayGain(const QString & codecName,CodecPlugin * plugin,QStringList * errorList)890 bool PluginLoader::canReplayGain( const QString& codecName, CodecPlugin *plugin, QStringList *errorList )
891 {
892     if( codecName.isEmpty() )
893         return false;
894 
895     for( int i=0; i<replaygainPipes.count(); i++ )
896     {
897         if( replaygainPipes.at(i).codecName == codecName && replaygainPipes.at(i).enabled )
898         {
899             return true;
900         }
901     }
902     if( plugin )
903     {
904         for( int i=0; i<conversionFilterPipeTrunks.size(); i++ )
905         {
906             if( conversionFilterPipeTrunks.at(i).plugin == plugin && conversionFilterPipeTrunks.at(i).codecTo == codecName && conversionFilterPipeTrunks.at(i).enabled && conversionFilterPipeTrunks.at(i).data.hasInternalReplayGain )
907             {
908                 return true;
909             }
910         }
911     }
912 
913     if( errorList )
914     {
915         // internal replaygain are not inlcuded in the error list
916         for( int i=0; i<replaygainPipes.size(); i++ )
917         {
918             if( replaygainPipes.at(i).codecName == codecName )
919             {
920                 if( !replaygainPipes.at(i).problemInfo.isEmpty() && !errorList->contains(replaygainPipes.at(i).problemInfo) )
921                 {
922                       errorList->append( replaygainPipes.at(i).problemInfo );
923                 }
924             }
925         }
926     }
927 
928     return false;
929 }
930 
canRipEntireCd(QStringList * errorList)931 bool PluginLoader::canRipEntireCd( QStringList *errorList )
932 {
933     for( int i=0; i<conversionFilterPipeTrunks.count(); i++ )
934     {
935         if( conversionFilterPipeTrunks.at(i).plugin->type() == "ripper" && conversionFilterPipeTrunks.at(i).data.canRipEntireCd && conversionFilterPipeTrunks.at(i).enabled )
936         {
937             return true;
938         }
939     }
940 
941     if( errorList )
942     {
943         for( int i=0; i<conversionFilterPipeTrunks.size(); i++ )
944         {
945             if( conversionFilterPipeTrunks.at(i).plugin->type() == "ripper" && conversionFilterPipeTrunks.at(i).data.canRipEntireCd )
946             {
947                 if( !conversionFilterPipeTrunks.at(i).problemInfo.isEmpty() && !errorList->contains(conversionFilterPipeTrunks.at(i).problemInfo) )
948                 {
949                       errorList->append( conversionFilterPipeTrunks.at(i).problemInfo );
950                 }
951             }
952         }
953     }
954 
955     return false;
956 }
957 
decodeProblems(bool detailed)958 QMap<QString,QStringList> PluginLoader::decodeProblems( bool detailed )
959 {
960     QMap<QString,QStringList> problems;
961     QStringList errorList;
962     QStringList enabledCodecs;
963 
964     if( !detailed )
965     {
966         for( int i=0; i<conversionFilterPipeTrunks.size(); i++ )
967         {
968             if( conversionFilterPipeTrunks.at(i).enabled )
969             {
970                 enabledCodecs += conversionFilterPipeTrunks.at(i).codecFrom;
971             }
972         }
973     }
974 
975     for( int i=0; i<conversionFilterPipeTrunks.size(); i++ )
976     {
977         if( !conversionFilterPipeTrunks.at(i).enabled && !conversionFilterPipeTrunks.at(i).problemInfo.isEmpty() && !problems.value(conversionFilterPipeTrunks.at(i).codecFrom).contains(conversionFilterPipeTrunks.at(i).problemInfo) && !enabledCodecs.contains(conversionFilterPipeTrunks.at(i).codecFrom) )
978         {
979             problems[conversionFilterPipeTrunks.at(i).codecFrom] += conversionFilterPipeTrunks.at(i).problemInfo;
980         }
981     }
982 
983     return problems;
984 }
985 
encodeProblems(bool detailed)986 QMap<QString,QStringList> PluginLoader::encodeProblems( bool detailed )
987 {
988     QMap<QString,QStringList> problems;
989     QStringList errorList;
990     QStringList enabledCodecs;
991 
992     if( !detailed )
993     {
994         for( int i=0; i<conversionFilterPipeTrunks.size(); i++ )
995         {
996             if( conversionFilterPipeTrunks.at(i).enabled )
997             {
998                 enabledCodecs += conversionFilterPipeTrunks.at(i).codecTo;
999             }
1000         }
1001     }
1002 
1003     for( int i=0; i<conversionFilterPipeTrunks.size(); i++ )
1004     {
1005         if( !conversionFilterPipeTrunks.at(i).enabled && !conversionFilterPipeTrunks.at(i).problemInfo.isEmpty() && !problems.value(conversionFilterPipeTrunks.at(i).codecTo).contains(conversionFilterPipeTrunks.at(i).problemInfo) && !enabledCodecs.contains(conversionFilterPipeTrunks.at(i).codecTo) )
1006         {
1007               problems[conversionFilterPipeTrunks.at(i).codecTo] += conversionFilterPipeTrunks.at(i).problemInfo;
1008         }
1009     }
1010 
1011     return problems;
1012 }
1013 
replaygainProblems(bool detailed)1014 QMap<QString,QStringList> PluginLoader::replaygainProblems( bool detailed )
1015 {
1016     QMap<QString,QStringList> problems;
1017     QStringList errorList;
1018     QStringList enabledCodecs;
1019 
1020     if( !detailed )
1021     {
1022         for( int i=0; i<replaygainPipes.size(); i++ )
1023         {
1024             if( replaygainPipes.at(i).enabled )
1025             {
1026                 enabledCodecs += replaygainPipes.at(i).codecName;
1027             }
1028         }
1029     }
1030 
1031     for( int i=0; i<replaygainPipes.size(); i++ )
1032     {
1033         if( !replaygainPipes.at(i).enabled && !replaygainPipes.at(i).problemInfo.isEmpty() && !problems.value(replaygainPipes.at(i).codecName).contains(replaygainPipes.at(i).problemInfo) && !enabledCodecs.contains(replaygainPipes.at(i).codecName) )
1034         {
1035               problems[replaygainPipes.at(i).codecName] += replaygainPipes.at(i).problemInfo;
1036         }
1037     }
1038 
1039     return problems;
1040 }
1041 
pluginDecodeProblems(const QString & pluginName,const QString & codecName)1042 QString PluginLoader::pluginDecodeProblems( const QString& pluginName, const QString& codecName )
1043 {
1044     for( int i=0; i<conversionFilterPipeTrunks.size(); i++ )
1045     {
1046         if( !conversionFilterPipeTrunks.at(i).enabled && !conversionFilterPipeTrunks.at(i).problemInfo.isEmpty() && conversionFilterPipeTrunks.at(i).plugin->name() == pluginName && conversionFilterPipeTrunks.at(i).codecFrom == codecName )
1047         {
1048               return conversionFilterPipeTrunks.at(i).problemInfo;
1049         }
1050     }
1051 
1052     return QString();
1053 }
1054 
pluginEncodeProblems(const QString & pluginName,const QString & codecName)1055 QString PluginLoader::pluginEncodeProblems( const QString& pluginName, const QString& codecName )
1056 {
1057     for( int i=0; i<conversionFilterPipeTrunks.size(); i++ )
1058     {
1059         if( !conversionFilterPipeTrunks.at(i).enabled && !conversionFilterPipeTrunks.at(i).problemInfo.isEmpty() && conversionFilterPipeTrunks.at(i).plugin->name() == pluginName && conversionFilterPipeTrunks.at(i).codecTo == codecName )
1060         {
1061               return conversionFilterPipeTrunks.at(i).problemInfo;
1062         }
1063     }
1064 
1065     return QString();
1066 }
1067 
pluginReplayGainProblems(const QString & pluginName,const QString & codecName)1068 QString PluginLoader::pluginReplayGainProblems( const QString& pluginName, const QString& codecName )
1069 {
1070     for( int i=0; i<replaygainPipes.size(); i++ )
1071     {
1072         if( !replaygainPipes.at(i).enabled && !replaygainPipes.at(i).problemInfo.isEmpty() && replaygainPipes.at(i).plugin->name() == pluginName && replaygainPipes.at(i).codecName == codecName )
1073         {
1074               return replaygainPipes.at(i).problemInfo;
1075         }
1076     }
1077 
1078     return QString();
1079 }
1080 
isCodecLossless(const QString & codecName)1081 bool PluginLoader::isCodecLossless( const QString& codecName )
1082 {
1083     for( int i=0; i<formatInfos.count(); i++ )
1084     {
1085         if( formatInfos.at(i).codecName == codecName )
1086         {
1087             return formatInfos.at(i).lossless;
1088         }
1089     }
1090     return false;
1091 }
1092 
isCodecInferiorQuality(const QString & codecName)1093 bool PluginLoader::isCodecInferiorQuality( const QString& codecName )
1094 {
1095     for( int i=0; i<formatInfos.count(); i++ )
1096     {
1097         if( formatInfos.at(i).codecName == codecName )
1098         {
1099             return formatInfos.at(i).inferiorQuality;
1100         }
1101     }
1102     return false;
1103 }
1104 
isCodecHybrid(const QString & codecName)1105 bool PluginLoader::isCodecHybrid( const QString& codecName )
1106 {
1107     Q_UNUSED(codecName)
1108 
1109     return false;
1110 }
1111 
hasCodecInternalReplayGain(const QString & codecName)1112 bool PluginLoader::hasCodecInternalReplayGain( const QString& codecName )
1113 {
1114     for( int i=0; i<conversionPipeTrunks.count(); i++ )
1115     {
1116         if( conversionPipeTrunks.at(i).codecTo == codecName && conversionPipeTrunks.at(i).plugin->type() == "codec" && conversionPipeTrunks.at(i).data.hasInternalReplayGain )
1117         {
1118             return true;
1119         }
1120     }
1121     return false;
1122 }
1123 
codecExtensions(const QString & codecName)1124 QStringList PluginLoader::codecExtensions( const QString& codecName )
1125 {
1126     for( int i=0; i<formatInfos.count(); i++ )
1127     {
1128         if( formatInfos.at(i).codecName == codecName )
1129         {
1130             QStringList extensions = formatInfos.at(i).extensions;
1131 
1132             if( codecName == "ogg vorbis" && config->data.general.preferredOggVorbisExtension == "oga" )
1133             {
1134                 extensions.removeAll( "oga" );
1135                 extensions.prepend( "oga" );
1136             }
1137 
1138             return extensions;
1139         }
1140     }
1141     return QStringList();
1142 }
1143 
codecMimeTypes(const QString & codecName)1144 QStringList PluginLoader::codecMimeTypes( const QString& codecName )
1145 {
1146     for( int i=0; i<formatInfos.count(); i++ )
1147     {
1148         if( formatInfos.at(i).codecName == codecName )
1149         {
1150             return formatInfos.at(i).mimeTypes;
1151         }
1152     }
1153     return QStringList();
1154 }
1155 
codecDescription(const QString & codecName)1156 QString PluginLoader::codecDescription( const QString& codecName )
1157 {
1158     for( int i=0; i<formatInfos.count(); i++ )
1159     {
1160         if( formatInfos.at(i).codecName == codecName )
1161         {
1162             return formatInfos.at(i).description;
1163         }
1164     }
1165     return "";
1166 }
1167 
1168