KFileMetaData

taglibextractor.cpp
1 /*
2  SPDX-FileCopyrightText: 2012 Vishesh Handa <[email protected]>
3 
4  SPDX-License-Identifier: LGPL-2.1-or-later
5 */
6 
7 
8 #include "taglibextractor.h"
9 #include "embeddedimagedata.h"
10 #include "kfilemetadata_debug.h"
11 
12 // Taglib includes
13 #include <taglib.h>
14 #include <tag.h>
15 #include <tfilestream.h>
16 #include <tpropertymap.h>
17 #include <aifffile.h>
18 #include <apefile.h>
19 #include <apetag.h>
20 #include <asffile.h>
21 #include <flacfile.h>
22 #include <mp4file.h>
23 #include <mpcfile.h>
24 #include <mpegfile.h>
25 #include <oggfile.h>
26 #include <opusfile.h>
27 #include <speexfile.h>
28 #include <vorbisfile.h>
29 #include <wavfile.h>
30 #include <wavpackfile.h>
31 #include <asftag.h>
32 #include <asfattribute.h>
33 #include <asfpicture.h>
34 #include <id3v2tag.h>
35 #include <mp4tag.h>
36 #include <popularimeterframe.h>
37 
38 using namespace KFileMetaData;
39 
40 namespace {
41 
42 const QStringList supportedMimeTypes = {
43  QStringLiteral("audio/flac"),
44  QStringLiteral("audio/mp4"),
45  QStringLiteral("audio/mpeg"),
46  QStringLiteral("audio/mpeg3"),
47  QStringLiteral("audio/ogg"),
48  QStringLiteral("audio/opus"),
49  QStringLiteral("audio/wav"),
50  QStringLiteral("audio/vnd.audible.aax"),
51  QStringLiteral("audio/x-aiff"),
52  QStringLiteral("audio/x-aifc"),
53  QStringLiteral("audio/x-ape"),
54  QStringLiteral("audio/x-mpeg"),
55  QStringLiteral("audio/x-ms-wma"),
56  QStringLiteral("audio/x-musepack"),
57  QStringLiteral("audio/x-opus+ogg"),
58  QStringLiteral("audio/x-speex+ogg"),
59  QStringLiteral("audio/x-vorbis+ogg"),
60  QStringLiteral("audio/x-wav"),
61  QStringLiteral("audio/x-wavpack"),
62 };
63 
64 void extractAudioProperties(TagLib::File* file, ExtractionResult* result)
65 {
66  TagLib::AudioProperties* audioProp = file->audioProperties();
67  if (audioProp && (result->inputFlags() & ExtractionResult::ExtractMetaData)) {
68  if (audioProp->length()) {
69  // What about the xml duration?
70  result->add(Property::Duration, audioProp->length());
71  }
72 
73  if (audioProp->bitrate()) {
74  result->add(Property::BitRate, audioProp->bitrate() * 1000);
75  }
76 
77  if (audioProp->channels()) {
78  result->add(Property::Channels, audioProp->channels());
79  }
80 
81  if (audioProp->sampleRate()) {
82  result->add(Property::SampleRate, audioProp->sampleRate());
83  }
84  }
85 }
86 
87 void readGenericProperties(const TagLib::PropertyMap &savedProperties, ExtractionResult* result)
88 {
89  if (!(result->inputFlags() & ExtractionResult::ExtractMetaData) || savedProperties.isEmpty()) {
90  return;
91  }
92 
93  if (savedProperties.contains("TITLE")) {
94  result->add(Property::Title, TStringToQString(savedProperties["TITLE"].toString()).trimmed());
95  }
96  if (savedProperties.contains("ALBUM")) {
97  result->add(Property::Album, TStringToQString(savedProperties["ALBUM"].toString()).trimmed());
98  }
99  if (savedProperties.contains("COMMENT")) {
100  result->add(Property::Comment, TStringToQString(savedProperties["COMMENT"].toString()).trimmed());
101  }
102  if (savedProperties.contains("TRACKNUMBER")) {
103  result->add(Property::TrackNumber, savedProperties["TRACKNUMBER"].toString().toInt());
104  }
105  if (savedProperties.contains("DATE")) {
106  result->add(Property::ReleaseYear, savedProperties["DATE"].toString().toInt());
107  }
108  if (savedProperties.contains("OPUS")) {
109  result->add(Property::Opus, savedProperties["OPUS"].toString().toInt());
110  }
111  if (savedProperties.contains("DISCNUMBER")) {
112  result->add(Property::DiscNumber, savedProperties["DISCNUMBER"].toString().toInt());
113  }
114  if (savedProperties.contains("RATING")) {
115  /*
116  * There is no standard regarding ratings. Mimic MediaMonkey's behavior
117  * with a range of 0 to 100 (stored in steps of 10) and make it compatible
118  * with baloo rating with a range from 0 to 10
119  */
120  result->add(Property::Rating, savedProperties["RATING"].toString().toInt() / 10);
121  }
122  if (savedProperties.contains("LOCATION")) {
123  result->add(Property::Location, TStringToQString(savedProperties["LOCATION"].toString()).trimmed());
124  }
125  if (savedProperties.contains("LANGUAGE")) {
126  result->add(Property::Language, TStringToQString(savedProperties["LANGUAGE"].toString()).trimmed());
127  }
128  if (savedProperties.contains("LICENSE")) {
129  result->add(Property::License, TStringToQString(savedProperties["LICENSE"].toString()).trimmed());
130  }
131  if (savedProperties.contains("PUBLISHER")) {
132  result->add(Property::Publisher, TStringToQString(savedProperties["PUBLISHER"].toString()).trimmed());
133  }
134  if (savedProperties.contains("COPYRIGHT")) {
135  result->add(Property::Copyright, TStringToQString(savedProperties["COPYRIGHT"].toString()).trimmed());
136  }
137  if (savedProperties.contains("LABEL")) {
138  result->add(Property::Label, TStringToQString(savedProperties["LABEL"].toString()).trimmed());
139  }
140  if (savedProperties.contains("ENSEMBLE")) {
141  result->add(Property::Ensemble, TStringToQString(savedProperties["ENSEMBLE"].toString()).trimmed());
142  }
143  if (savedProperties.contains("COMPILATION")) {
144  result->add(Property::Compilation, TStringToQString(savedProperties["COMPILATION"].toString()).trimmed());
145  }
146  if (savedProperties.contains("LYRICS")) {
147  result->add(Property::Lyrics, TStringToQString(savedProperties["LYRICS"].toString()).trimmed().replace(QLatin1Char('\r'), QLatin1Char('\n'))); // Convert old Mac line endings
148  }
149  if (savedProperties.contains("ARTIST")) {
150  const auto artists = savedProperties["ARTIST"];
151  for (const auto& artist : artists) {
152  result->add(Property::Artist, TStringToQString(artist).trimmed());
153  }
154  }
155  if (savedProperties.contains("GENRE")) {
156  const auto genres = savedProperties["GENRE"];
157  for (const auto& genre : genres) {
158  result->add(Property::Genre, TStringToQString(genre).trimmed());
159  }
160  }
161  if (savedProperties.contains("ALBUMARTIST")) {
162  const auto albumArtists = savedProperties["ALBUMARTIST"];
163  for (const auto& albumArtist : albumArtists) {
164  result->add(Property::AlbumArtist, TStringToQString(albumArtist).trimmed());
165  }
166  }
167  if (savedProperties.contains("COMPOSER")) {
168  const auto composers = savedProperties["COMPOSER"];
169  for (const auto& composer : composers) {
170  result->add(Property::Composer, TStringToQString(composer).trimmed());
171  }
172  }
173  if (savedProperties.contains("LYRICIST")) {
174  const auto lyricists = savedProperties["LYRICIST"];
175  for (const auto& lyricist : lyricists) {
176  result->add(Property::Lyricist, TStringToQString(lyricist).trimmed());
177  }
178  }
179  if (savedProperties.contains("CONDUCTOR")) {
180  const auto conductors = savedProperties["CONDUCTOR"];
181  for (const auto& conductor : conductors) {
182  result->add(Property::Conductor, TStringToQString(conductor).trimmed());
183  }
184  }
185  if (savedProperties.contains("ARRANGER")) {
186  const auto arrangers = savedProperties["ARRANGER"];
187  for (const auto& arranger : arrangers) {
188  result->add(Property::Arranger, TStringToQString(arranger).trimmed());
189  }
190  }
191  if (savedProperties.contains("PERFORMER")) {
192  const auto performers = savedProperties["PERFORMER"];
193  for (const auto& performer : performers) {
194  result->add(Property::Performer, TStringToQString(performer).trimmed());
195  }
196  }
197  if (savedProperties.contains("AUTHOR")) {
198  const auto authors = savedProperties["AUTHOR"];
199  for (const auto& author: authors) {
200  result->add(Property::Author, TStringToQString(author).trimmed());
201  }
202  }
203 
204  if (savedProperties.contains("REPLAYGAIN_TRACK_GAIN")) {
205  auto trackGainString = TStringToQString(savedProperties["REPLAYGAIN_TRACK_GAIN"].toString(";")).trimmed();
206  // remove " dB" suffix
207  if (trackGainString.endsWith(QStringLiteral(" dB"), Qt::CaseInsensitive)) {
208  trackGainString.chop(3);
209  }
210  bool success = false;
211  double replayGainTrackGain = trackGainString.toDouble(&success);
212  if (success) {
213  result->add(Property::ReplayGainTrackGain, replayGainTrackGain);
214  }
215  }
216  if (savedProperties.contains("REPLAYGAIN_ALBUM_GAIN")) {
217  auto albumGainString = TStringToQString(savedProperties["REPLAYGAIN_ALBUM_GAIN"].toString(";")).trimmed();
218  // remove " dB" suffix
219  if (albumGainString.endsWith(QStringLiteral(" dB"), Qt::CaseInsensitive)) {
220  albumGainString.chop(3);
221  }
222  bool success = false;
223  double replayGainAlbumGain = albumGainString.toDouble(&success);
224  if (success) {
225  result->add(Property::ReplayGainAlbumGain, replayGainAlbumGain);
226  }
227  }
228  if (savedProperties.contains("REPLAYGAIN_TRACK_PEAK")) {
229  auto trackPeakString = TStringToQString(savedProperties["REPLAYGAIN_TRACK_PEAK"].toString(";")).trimmed();
230  bool success = false;
231  double replayGainTrackPeak = trackPeakString.toDouble(&success);
232  if (success) {
233  result->add(Property::ReplayGainTrackPeak, replayGainTrackPeak);
234  }
235  }
236  if (savedProperties.contains("REPLAYGAIN_ALBUM_PEAK")) {
237  auto albumPeakString = TStringToQString(savedProperties["REPLAYGAIN_ALBUM_PEAK"].toString(";")).trimmed();
238  bool success = false;
239  double replayGainAlbumPeak = albumPeakString.toDouble(&success);
240  if (success) {
241  result->add(Property::ReplayGainAlbumPeak, replayGainAlbumPeak);
242  }
243  }
244 }
245 
246 void extractId3Tags(TagLib::ID3v2::Tag* Id3Tags, ExtractionResult* result)
247 {
248  if (!(result->inputFlags() & ExtractionResult::ExtractMetaData) || Id3Tags->isEmpty()) {
249  return;
250  }
251 
252  TagLib::ID3v2::FrameList lstID3v2;
253 
254  /*
255  * Publisher.
256  * Special handling because TagLib::PropertyMap maps "TPUB" to "LABEL"
257  * Insert manually for Publisher.
258  */
259  lstID3v2 = Id3Tags->frameListMap()["TPUB"];
260  if (!lstID3v2.isEmpty()) {
261  result->add(Property::Publisher, TStringToQString(lstID3v2.front()->toString()));
262  }
263 
264  // Compilation.
265  lstID3v2 = Id3Tags->frameListMap()["TCMP"];
266  if (!lstID3v2.isEmpty()) {
267  result->add(Property::Compilation, TStringToQString(lstID3v2.front()->toString()));
268  }
269 
270  /*
271  * Rating.
272  * There is no standard regarding ratings. Most of the implementations match
273  * a 5 stars rating to a range of 0-255 for MP3.
274  * Map it to baloo rating with a range of 0 - 10.
275  */
276  lstID3v2 = Id3Tags->frameListMap()["POPM"];
277  if (!lstID3v2.isEmpty()) {
278  TagLib::ID3v2::PopularimeterFrame *ratingFrame = static_cast<TagLib::ID3v2::PopularimeterFrame *>(lstID3v2.front());
279  int rating = ratingFrame->rating();
280  if (rating == 0) {
281  rating = 0;
282  } else if (rating == 1) {
283  TagLib::String ratingProvider = ratingFrame->email();
284  if (ratingProvider == "[email protected]" || ratingProvider == "org.kde.kfilemetadata") {
285  rating = 1;
286  } else {
287  rating = 2;
288  }
289  } else if (rating >= 1 && rating <= 255) {
290  rating = static_cast<int>(0.032 * rating + 2);
291  }
292  result->add(Property::Rating, rating);
293  }
294 }
295 
296 template<typename ImageType>
297 EmbeddedImageData::ImageType mapTaglibType(const ImageType type)
298 {
299  switch (type) {
300  case ImageType::FrontCover:
301  return EmbeddedImageData::FrontCover;
302  case ImageType::Other:
303  return EmbeddedImageData::Other;
304  case ImageType::FileIcon:
305  return EmbeddedImageData::FileIcon;
306  case ImageType::OtherFileIcon:
307  return EmbeddedImageData::OtherFileIcon;
308  case ImageType::BackCover:
309  return EmbeddedImageData::BackCover;
310  case ImageType::LeafletPage:
311  return EmbeddedImageData::LeafletPage;
312  case ImageType::Media:
313  return EmbeddedImageData::Media;
314  case ImageType::LeadArtist:
315  return EmbeddedImageData::LeadArtist;
316  case ImageType::Artist:
317  return EmbeddedImageData::Artist;
318  case ImageType::Conductor:
319  return EmbeddedImageData::Conductor;
320  case ImageType::Band:
321  return EmbeddedImageData::Band;
322  case ImageType::Composer:
323  return EmbeddedImageData::Composer;
324  case ImageType::Lyricist:
325  return EmbeddedImageData::Lyricist;
326  case ImageType::RecordingLocation:
327  return EmbeddedImageData::RecordingLocation;
328  case ImageType::DuringRecording:
329  return EmbeddedImageData::DuringRecording;
330  case ImageType::DuringPerformance:
331  return EmbeddedImageData::DuringPerformance;
332  case ImageType::MovieScreenCapture:
333  return EmbeddedImageData::MovieScreenCapture;
334  case ImageType::ColouredFish:
335  return EmbeddedImageData::ColouredFish;
336  case ImageType::Illustration:
337  return EmbeddedImageData::Illustration;
338  case ImageType::BandLogo:
339  return EmbeddedImageData::BandLogo;
340  case ImageType::PublisherLogo:
341  return EmbeddedImageData::PublisherLogo;
342  default:
343  return EmbeddedImageData::Unknown;
344  }
345 }
346 
348 extractId3Cover(const TagLib::ID3v2::Tag* Id3Tags,
349  const EmbeddedImageData::ImageTypes types)
350 {
352  if (!types || Id3Tags->isEmpty()) {
353  return images;
354  }
355 
356  // Attached Picture.
357  TagLib::ID3v2::FrameList lstID3v2 = Id3Tags->frameListMap()["APIC"];
358 
359  using PictureFrame = TagLib::ID3v2::AttachedPictureFrame;
360  for (const auto& frame : std::as_const(lstID3v2)) {
361  const auto *coverFrame = static_cast<PictureFrame *>(frame);
362  const auto imageType = mapTaglibType<PictureFrame::Type>(coverFrame->type());
363  if (types & imageType) {
364  const auto& picture = coverFrame->picture();
365  images.insert(imageType, QByteArray(picture.data(), picture.size()));
366  }
367  }
368  return images;
369 }
370 
372 extractFlacCover(const TagLib::List<TagLib::FLAC::Picture *> picList,
373  const EmbeddedImageData::ImageTypes types)
374 {
376  if (!types || picList.isEmpty()) {
377  return images;
378  }
379 
380  for (const auto& picture : std::as_const(picList)) {
381  const auto imageType = mapTaglibType<TagLib::FLAC::Picture::Type>(picture->type());
382  if (types & imageType) {
383  images.insert(imageType, QByteArray(picture->data().data(), picture->data().size()));
384  }
385  }
386  return images;
387 }
388 
389 void extractMp4Tags(TagLib::MP4::Tag* mp4Tags, ExtractionResult* result)
390 {
391  if (!(result->inputFlags() & ExtractionResult::ExtractMetaData) || mp4Tags->isEmpty()) {
392  return;
393  }
394 
395  TagLib::MP4::ItemListMap allTags = mp4Tags->itemListMap();
396 
397  /*
398  * There is no standard regarding ratings. Mimic MediaMonkey's behavior
399  * with a range of 0 to 100 (stored in steps of 10) and make it compatible
400  * with baloo rating with a range from 0 to 10.
401  */
402  TagLib::MP4::ItemListMap::Iterator itRating = allTags.find("rate");
403  if (itRating != allTags.end()) {
404  result->add(Property::Rating, itRating->second.toStringList().toString().toInt() / 10);
405  }
406 }
407 
409 extractMp4Cover(const TagLib::MP4::Tag* mp4Tags,
410  const EmbeddedImageData::ImageTypes types)
411 {
413  TagLib::MP4::Item coverArtItem = mp4Tags->item("covr");
414  if (!(types & EmbeddedImageData::FrontCover) || !coverArtItem.isValid()) {
415  return images;
416  }
417 
418  const TagLib::MP4::CoverArtList coverArtList = coverArtItem.toCoverArtList();
419  if (!coverArtList.isEmpty()) {
420  const TagLib::MP4::CoverArt& cover = coverArtList.front();
421  images.insert(EmbeddedImageData::FrontCover, QByteArray(cover.data().data(), cover.data().size()));
422  }
423  return images;
424 }
425 
427 extractApeCover(const TagLib::APE::Tag* apeTags,
428  const EmbeddedImageData::ImageTypes types)
429 {
431  if (!(types & EmbeddedImageData::FrontCover) || apeTags->isEmpty()) {
432  return images;
433  }
434 
435  TagLib::APE::ItemListMap lstApe = apeTags->itemListMap();
436  TagLib::APE::ItemListMap::ConstIterator itApe;
437 
438  /* The cover art tag for APEv2 tags starts with the filename as string
439  * with zero termination followed by the actual picture data */
440  itApe = lstApe.find("COVER ART (FRONT)");
441  if (itApe != lstApe.end()) {
442  const auto& picture = (*itApe).second.binaryData();
443  int position = picture.find('\0');
444  if (position >= 0) {
445  position += 1;
446  images.insert(EmbeddedImageData::FrontCover, QByteArray(picture.data() + position, picture.size() - position));
447  }
448  }
449  return images;
450 }
451 
452 void extractAsfTags(TagLib::ASF::Tag* asfTags, ExtractionResult* result)
453 {
454  if (!(result->inputFlags() & ExtractionResult::ExtractMetaData) || asfTags->isEmpty()) {
455  return;
456  }
457 
458  TagLib::ASF::AttributeList lstASF = asfTags->attribute("WM/SharedUserRating");
459  if (!lstASF.isEmpty()) {
460  int rating = lstASF.front().toString().toInt();
461  /*
462  * Map the rating values of WMP to Baloo rating.
463  * 0->0, 1->2, 25->4, 50->6, 75->8, 99->10
464  */
465  if (rating == 0) {
466  rating = 0;
467  } else if (rating == 1) {
468  rating = 2;
469  } else {
470  rating = static_cast<int>(0.09 * rating + 2);
471  }
472  result->add(Property::Rating, rating);
473  }
474 
475  lstASF = asfTags->attribute("Author");
476  if (!lstASF.isEmpty()) {
477  const auto attribute = lstASF.front();
478  result->add(Property::Author, TStringToQString(attribute.toString()).trimmed());
479  }
480 
481  // Lyricist is called "WRITER" for wma/asf files
482  lstASF = asfTags->attribute("WM/Writer");
483  if (!lstASF.isEmpty()) {
484  const auto attribute = lstASF.front();
485  result->add(Property::Lyricist, TStringToQString(attribute.toString()).trimmed());
486  }
487 
488  /*
489  * TagLib exports "WM/PUBLISHER" as "LABEL" in the PropertyMap,
490  * add it manually to Publisher.
491  */
492  lstASF = asfTags->attribute("WM/Publisher");
493  if (!lstASF.isEmpty()) {
494  const auto attribute = lstASF.front();
495  result->add(Property::Publisher, TStringToQString(attribute.toString()).trimmed());
496  }
497 }
498 
500 extractAsfCover(const TagLib::ASF::Tag* asfTags,
501  const EmbeddedImageData::ImageTypes types)
502 {
504  if (!types || asfTags->isEmpty()) {
505  return images;
506  }
507 
508  // Attached Picture.
509  TagLib::ASF::AttributeList lstASF = asfTags->attribute("WM/Picture");
510 
511  using Picture = TagLib::ASF::Picture;
512  for (const auto& attribute: std::as_const(lstASF)) {
513  Picture picture = attribute.toPicture();
514  const auto imageType = mapTaglibType<Picture::Type>(picture.type());
515  if (types & imageType) {
516  const auto& pictureData = picture.picture();
517  images.insert(imageType, QByteArray(pictureData.data(), pictureData.size()));
518  }
519  }
520  return images;
521 }
522 
523 } // anonymous namespace
524 
525 TagLibExtractor::TagLibExtractor(QObject* parent)
526  : ExtractorPlugin(parent)
527 {
528 }
529 
530 QStringList TagLibExtractor::mimetypes() const
531 {
532  return supportedMimeTypes;
533 }
534 
535 void TagLibExtractor::extract(ExtractionResult* result)
536 {
537  const QString fileUrl = result->inputUrl();
538  const QString mimeType = getSupportedMimeType(result->inputMimetype());
539 
540  // Open the file readonly. Important if we're sandboxed.
541 #if defined Q_OS_WINDOWS
542  TagLib::FileStream stream(fileUrl.toLocal8Bit().constData(), true);
543 #else
544  TagLib::FileStream stream(fileUrl.toUtf8().constData(), true);
545 #endif
546  if (!stream.isOpen()) {
547  qCWarning(KFILEMETADATA_LOG) << "Unable to open file readonly: " << fileUrl;
548  return;
549  }
550 
551  const EmbeddedImageData::ImageTypes imageTypes{
552  result->inputFlags() & ExtractionResult::ExtractImageData ? EmbeddedImageData::AllImages : 0
553  };
554 
555  if (mimeType == QLatin1String("audio/mpeg") || mimeType == QLatin1String("audio/mpeg3")
556  || mimeType == QLatin1String("audio/x-mpeg")) {
557  TagLib::MPEG::File file(&stream, TagLib::ID3v2::FrameFactory::instance(), true);
558  if (file.isValid()) {
559  extractAudioProperties(&file, result);
560  readGenericProperties(file.properties(), result);
561  if (file.hasID3v2Tag()) {
562  result->addImageData(extractId3Cover(file.ID3v2Tag(), imageTypes));
563  extractId3Tags(file.ID3v2Tag(), result);
564  }
565  }
566  } else if (mimeType == QLatin1String("audio/x-aiff") || mimeType == QLatin1String("audio/x-aifc")) {
567  TagLib::RIFF::AIFF::File file(&stream, true);
568  if (file.isValid()) {
569  extractAudioProperties(&file, result);
570  readGenericProperties(file.properties(), result);
571  if (file.hasID3v2Tag()) {
572  result->addImageData(extractId3Cover(file.tag(), imageTypes));
573  extractId3Tags(file.tag(), result);
574  }
575  }
576  } else if (mimeType == QLatin1String("audio/wav") || mimeType == QLatin1String("audio/x-wav")) {
577  TagLib::RIFF::WAV::File file(&stream, true);
578  if (file.isValid()) {
579  extractAudioProperties(&file, result);
580  readGenericProperties(file.properties(), result);
581  if (file.hasID3v2Tag()) {
582  result->addImageData(extractId3Cover(file.tag(), imageTypes));
583  extractId3Tags(file.tag(), result);
584  }
585  }
586  } else if (mimeType == QLatin1String("audio/x-musepack")) {
587  TagLib::MPC::File file(&stream, true);
588  if (file.isValid()) {
589  extractAudioProperties(&file, result);
590  readGenericProperties(file.properties(), result);
591  if (file.APETag()) {
592  result->addImageData(extractApeCover(file.APETag(), imageTypes));
593  }
594  }
595  } else if (mimeType == QLatin1String("audio/x-ape")) {
596  TagLib::APE::File file(&stream, true);
597  if (file.isValid()) {
598  extractAudioProperties(&file, result);
599  readGenericProperties(file.properties(), result);
600  if (file.APETag()) {
601  result->addImageData(extractApeCover(file.APETag(), imageTypes));
602  }
603  }
604  } else if (mimeType == QLatin1String("audio/x-wavpack")) {
605  TagLib::WavPack::File file(&stream, true);
606  if (file.isValid()) {
607  extractAudioProperties(&file, result);
608  readGenericProperties(file.properties(), result);
609  if (file.APETag()) {
610  result->addImageData(extractApeCover(file.APETag(), imageTypes));
611  }
612  }
613  } else if ((mimeType == QLatin1String("audio/mp4")) ||
614  (mimeType == QLatin1String("audio/vnd.audible.aax"))) {
615  TagLib::MP4::File file(&stream, true);
616  if (file.isValid()) {
617  extractAudioProperties(&file, result);
618  readGenericProperties(file.properties(), result);
619  extractMp4Tags(file.tag(), result);
620  result->addImageData(extractMp4Cover(file.tag(), imageTypes));
621  }
622  } else if (mimeType == QLatin1String("audio/flac")) {
623  TagLib::FLAC::File file(&stream, TagLib::ID3v2::FrameFactory::instance(), true);
624  if (file.isValid()) {
625  extractAudioProperties(&file, result);
626  readGenericProperties(file.properties(), result);
627  result->addImageData(extractFlacCover(file.pictureList(), imageTypes));
628  }
629  } else if (mimeType == QLatin1String("audio/ogg") || mimeType == QLatin1String("audio/x-vorbis+ogg")) {
630  TagLib::Ogg::Vorbis::File file(&stream, true);
631  if (file.isValid()) {
632  extractAudioProperties(&file, result);
633  readGenericProperties(file.properties(), result);
634  if (file.tag()) {
635  result->addImageData(extractFlacCover(file.tag()->pictureList(), imageTypes));
636  }
637  }
638  } else if (mimeType == QLatin1String("audio/opus") || mimeType == QLatin1String("audio/x-opus+ogg")) {
639  TagLib::Ogg::Opus::File file(&stream, true);
640  if (file.isValid()) {
641  extractAudioProperties(&file, result);
642  readGenericProperties(file.properties(), result);
643  if (file.tag()) {
644  result->addImageData(extractFlacCover(file.tag()->pictureList(), imageTypes));
645  }
646  }
647  } else if (mimeType == QLatin1String("audio/x-speex+ogg")) {
648  TagLib::Ogg::Speex::File file(&stream, true);
649  // Workaround for buggy taglib:
650  // isValid() returns true for invalid files, but XiphComment* tag() returns a nullptr
651  if (file.isValid() && file.tag()) {
652  extractAudioProperties(&file, result);
653  readGenericProperties(file.properties(), result);
654  result->addImageData(extractFlacCover(file.tag()->pictureList(), imageTypes));
655  }
656  } else if (mimeType == QLatin1String("audio/x-ms-wma")) {
657  TagLib::ASF::File file(&stream, true);
658  if (file.isValid()) {
659  extractAudioProperties(&file, result);
660  readGenericProperties(file.properties(), result);
661  extractAsfTags(file.tag(), result);
662  TagLib::ASF::Tag* asfTags = dynamic_cast<TagLib::ASF::Tag*>(file.tag());
663  if (asfTags) {
664  result->addImageData(extractAsfCover(asfTags, imageTypes));
665  }
666  }
667  }
668 
669  result->addType(Type::Audio);
670 }
671 
672 // TAG information (incomplete).
673 // https://xiph.org/vorbis/doc/v-comment.html
674 // https://help.mp3tag.de/main_tags.html
675 // http://id3.org/
676 // https://www.legroom.net/2009/05/09/ogg-vorbis-and-flac-comment-field-recommendations
677 // https://kodi.wiki/view/Music_tagging#Tags_Kodi_reads
678 // https://wiki.hydrogenaud.io/index.php?title=Tag_Mapping
679 // https://picard.musicbrainz.org/docs/mappings/
680 // -- FLAC/OGG --
681 // Artist: ARTIST, PERFORMER
682 // Album artist: ALBUMARTIST
683 // Composer: COMPOSER
684 // Lyricist: LYRICIST
685 // Conductor: CONDUCTOR
686 // Disc number: DISCNUMBER
687 // Total discs: TOTALDISCS, DISCTOTAL
688 // Track number: TRACKNUMBER
689 // Total tracks: TOTALTRACKS, TRACKTOTAL
690 // Genre: GENRE
691 // -- ID3v2 --
692 // Artist: TPE1
693 // Album artist: TPE2
694 // Composer: TCOM
695 // Lyricist: TEXT
696 // Conductor: TPE3
697 // Disc number[/total dics]: TPOS
698 // Track number[/total tracks]: TRCK
699 // Genre: TCON
virtual void addType(Type::Type type)=0
This function is called by the plugins.
The ExtractionResult class is where all the data extracted by the indexer is saved....
CaseInsensitive
QString inputMimetype() const
The input mimetype.
KCALUTILS_EXPORT QString mimeType()
QString inputUrl() const
The input url which the plugins will use to locate the file.
QMap::iterator insert(const Key &key, const T &value)
QMap::iterator find(const Key &key)
char * toString(const T &value)
Flags inputFlags() const
The flags which the extraction plugin should considering following when extracting metadata from the ...
QByteArray toUtf8() const const
QAction * replace(const QObject *recvr, const char *slot, QObject *parent)
void addImageData(QMap< EmbeddedImageData::ImageType, QByteArray > &&images)
This function is called by the plugins.
const char * constData() const const
virtual void add(Property::Property property, const QVariant &value)=0
This function is called by the plugins when they wish to add a key value pair which should be indexed...
The ExtractorPlugin is the base class for all file metadata extractors. It is responsible for extract...
QByteArray toLocal8Bit() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2022 The KDE developers.
Generated on Thu May 26 2022 03:46:07 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.