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