KFileMetaData

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

KDE's Doxygen guidelines are available online.