File indexing completed on 2024-05-12 15:37:07

0001 /*
0002     SPDX-FileCopyrightText: 2012 Vishesh Handa <me@vhanda.in>
0003 
0004     SPDX-License-Identifier: LGPL-2.1-or-later
0005 */
0006 
0007 
0008 #include "taglibextractor.h"
0009 #include "embeddedimagedata.h"
0010 #include "kfilemetadata_debug.h"
0011 
0012 // Taglib includes
0013 #include <taglib.h>
0014 #include <tag.h>
0015 #include <tfilestream.h>
0016 #include <tpropertymap.h>
0017 #include <aifffile.h>
0018 #include <apefile.h>
0019 #include <apetag.h>
0020 #include <asffile.h>
0021 #include <flacfile.h>
0022 #include <mp4file.h>
0023 #include <mpcfile.h>
0024 #include <mpegfile.h>
0025 #include <oggfile.h>
0026 #include <oggflacfile.h>
0027 #include <opusfile.h>
0028 #include <speexfile.h>
0029 #include <vorbisfile.h>
0030 #include <wavfile.h>
0031 #include <wavpackfile.h>
0032 #include <asftag.h>
0033 #include <asfattribute.h>
0034 #include <asfpicture.h>
0035 #include <id3v2tag.h>
0036 #include <mp4tag.h>
0037 #include <popularimeterframe.h>
0038 #include <attachedpictureframe.h>
0039 
0040 using namespace KFileMetaData;
0041 
0042 namespace {
0043 
0044 const QStringList supportedMimeTypes = {
0045     QStringLiteral("audio/flac"),
0046     QStringLiteral("audio/mp4"),
0047     QStringLiteral("audio/mpeg"),
0048     QStringLiteral("audio/mpeg3"),
0049     QStringLiteral("audio/ogg"),
0050     QStringLiteral("audio/opus"),
0051     QStringLiteral("audio/wav"),
0052     QStringLiteral("audio/vnd.audible.aax"),
0053     QStringLiteral("audio/vnd.wave"),
0054     QStringLiteral("audio/x-aiff"),
0055     QStringLiteral("audio/x-aifc"),
0056     QStringLiteral("audio/x-ape"),
0057     QStringLiteral("audio/x-flac+ogg"),
0058     QStringLiteral("audio/x-mpeg"),
0059     QStringLiteral("audio/x-ms-wma"),
0060     QStringLiteral("audio/x-musepack"),
0061     QStringLiteral("audio/x-opus+ogg"),
0062     QStringLiteral("audio/x-speex+ogg"),
0063     QStringLiteral("audio/x-vorbis+ogg"),
0064     QStringLiteral("audio/x-wav"),
0065     QStringLiteral("audio/x-wavpack"),
0066 };
0067 
0068 void extractAudioProperties(TagLib::File* file, ExtractionResult* result)
0069 {
0070     TagLib::AudioProperties* audioProp = file->audioProperties();
0071     if (audioProp && (result->inputFlags() & ExtractionResult::ExtractMetaData)) {
0072         if (audioProp->length()) {
0073             // What about the xml duration?
0074             result->add(Property::Duration, audioProp->length());
0075         }
0076 
0077         if (audioProp->bitrate()) {
0078             result->add(Property::BitRate, audioProp->bitrate() * 1000);
0079         }
0080 
0081         if (audioProp->channels()) {
0082             result->add(Property::Channels, audioProp->channels());
0083         }
0084 
0085         if (audioProp->sampleRate()) {
0086             result->add(Property::SampleRate, audioProp->sampleRate());
0087         }
0088     }
0089 }
0090 
0091 void readGenericProperties(const TagLib::PropertyMap &savedProperties, ExtractionResult* result)
0092 {
0093     if (!(result->inputFlags() & ExtractionResult::ExtractMetaData) || savedProperties.isEmpty()) {
0094         return;
0095     }
0096 
0097     if (savedProperties.contains("TITLE")) {
0098         result->add(Property::Title, TStringToQString(savedProperties["TITLE"].toString()).trimmed());
0099     }
0100     if (savedProperties.contains("ALBUM")) {
0101         result->add(Property::Album, TStringToQString(savedProperties["ALBUM"].toString()).trimmed());
0102     }
0103     if (savedProperties.contains("COMMENT")) {
0104         result->add(Property::Comment, TStringToQString(savedProperties["COMMENT"].toString()).trimmed());
0105     }
0106     if (savedProperties.contains("TRACKNUMBER")) {
0107         result->add(Property::TrackNumber, savedProperties["TRACKNUMBER"].toString().toInt());
0108     }
0109     if (savedProperties.contains("DATE")) {
0110         result->add(Property::ReleaseYear, savedProperties["DATE"].toString().toInt());
0111     }
0112     if (savedProperties.contains("OPUS")) {
0113         result->add(Property::Opus, savedProperties["OPUS"].toString().toInt());
0114     }
0115     if (savedProperties.contains("DISCNUMBER")) {
0116         result->add(Property::DiscNumber, savedProperties["DISCNUMBER"].toString().toInt());
0117     }
0118     if (savedProperties.contains("RATING")) {
0119         /*
0120          * There is no standard regarding ratings. Mimic MediaMonkey's behavior
0121          * with a range of 0 to 100 (stored in steps of 10) and make it compatible
0122          * with baloo rating with a range from 0 to 10
0123          */
0124         result->add(Property::Rating, savedProperties["RATING"].toString().toInt() / 10);
0125     }
0126     if (savedProperties.contains("LOCATION")) {
0127         result->add(Property::Location, TStringToQString(savedProperties["LOCATION"].toString()).trimmed());
0128     }
0129     if (savedProperties.contains("LANGUAGE")) {
0130         result->add(Property::Language, TStringToQString(savedProperties["LANGUAGE"].toString()).trimmed());
0131     }
0132     if (savedProperties.contains("LICENSE")) {
0133         result->add(Property::License, TStringToQString(savedProperties["LICENSE"].toString()).trimmed());
0134     }
0135     if (savedProperties.contains("PUBLISHER")) {
0136         result->add(Property::Publisher, TStringToQString(savedProperties["PUBLISHER"].toString()).trimmed());
0137     }
0138     if (savedProperties.contains("COPYRIGHT")) {
0139         result->add(Property::Copyright, TStringToQString(savedProperties["COPYRIGHT"].toString()).trimmed());
0140     }
0141     if (savedProperties.contains("LABEL")) {
0142         result->add(Property::Label, TStringToQString(savedProperties["LABEL"].toString()).trimmed());
0143     }
0144     if (savedProperties.contains("ENSEMBLE")) {
0145         result->add(Property::Ensemble, TStringToQString(savedProperties["ENSEMBLE"].toString()).trimmed());
0146     }
0147     if (savedProperties.contains("COMPILATION")) {
0148         result->add(Property::Compilation, TStringToQString(savedProperties["COMPILATION"].toString()).trimmed());
0149     }
0150     if (savedProperties.contains("LYRICS")) {
0151         result->add(Property::Lyrics, TStringToQString(savedProperties["LYRICS"].toString()).trimmed().replace(QLatin1Char('\r'), QLatin1Char('\n'))); // Convert old Mac line endings
0152     }
0153     if (savedProperties.contains("ARTIST")) {
0154         const auto artists = savedProperties["ARTIST"];
0155         for (const auto& artist : artists) {
0156             result->add(Property::Artist, TStringToQString(artist).trimmed());
0157         }
0158     }
0159     if (savedProperties.contains("GENRE")) {
0160         const auto genres = savedProperties["GENRE"];
0161         for (const auto& genre : genres) {
0162             result->add(Property::Genre, TStringToQString(genre).trimmed());
0163         }
0164     }
0165     if (savedProperties.contains("ALBUMARTIST")) {
0166         const auto albumArtists = savedProperties["ALBUMARTIST"];
0167         for (const auto& albumArtist : albumArtists) {
0168             result->add(Property::AlbumArtist, TStringToQString(albumArtist).trimmed());
0169         }
0170     }
0171     if (savedProperties.contains("COMPOSER")) {
0172         const auto composers = savedProperties["COMPOSER"];
0173         for (const auto& composer : composers) {
0174             result->add(Property::Composer, TStringToQString(composer).trimmed());
0175         }
0176     }
0177     if (savedProperties.contains("LYRICIST")) {
0178         const auto lyricists = savedProperties["LYRICIST"];
0179         for (const auto& lyricist : lyricists) {
0180             result->add(Property::Lyricist, TStringToQString(lyricist).trimmed());
0181         }
0182     }
0183     if (savedProperties.contains("CONDUCTOR")) {
0184         const auto conductors = savedProperties["CONDUCTOR"];
0185         for (const auto& conductor : conductors) {
0186             result->add(Property::Conductor, TStringToQString(conductor).trimmed());
0187         }
0188     }
0189     if (savedProperties.contains("ARRANGER")) {
0190         const auto arrangers = savedProperties["ARRANGER"];
0191         for (const auto& arranger : arrangers) {
0192             result->add(Property::Arranger, TStringToQString(arranger).trimmed());
0193         }
0194     }
0195     if (savedProperties.contains("PERFORMER")) {
0196         const auto performers = savedProperties["PERFORMER"];
0197         for (const auto& performer : performers) {
0198             result->add(Property::Performer, TStringToQString(performer).trimmed());
0199         }
0200     }
0201     if (savedProperties.contains("AUTHOR")) {
0202         const auto authors = savedProperties["AUTHOR"];
0203         for (const auto& author: authors) {
0204             result->add(Property::Author, TStringToQString(author).trimmed());
0205         }
0206     }
0207 
0208     if (savedProperties.contains("REPLAYGAIN_TRACK_GAIN")) {
0209         auto trackGainString = TStringToQString(savedProperties["REPLAYGAIN_TRACK_GAIN"].toString(";")).trimmed();
0210         // remove " dB" suffix
0211         if (trackGainString.endsWith(QStringLiteral(" dB"), Qt::CaseInsensitive)) {
0212             trackGainString.chop(3);
0213         }
0214         bool success = false;
0215         double replayGainTrackGain = trackGainString.toDouble(&success);
0216         if (success) {
0217             result->add(Property::ReplayGainTrackGain, replayGainTrackGain);
0218         }
0219     }
0220     if (savedProperties.contains("REPLAYGAIN_ALBUM_GAIN")) {
0221         auto albumGainString = TStringToQString(savedProperties["REPLAYGAIN_ALBUM_GAIN"].toString(";")).trimmed();
0222         // remove " dB" suffix
0223         if (albumGainString.endsWith(QStringLiteral(" dB"), Qt::CaseInsensitive)) {
0224             albumGainString.chop(3);
0225         }
0226         bool success = false;
0227         double replayGainAlbumGain = albumGainString.toDouble(&success);
0228         if (success) {
0229             result->add(Property::ReplayGainAlbumGain, replayGainAlbumGain);
0230         }
0231     }
0232     if (savedProperties.contains("REPLAYGAIN_TRACK_PEAK")) {
0233         auto trackPeakString = TStringToQString(savedProperties["REPLAYGAIN_TRACK_PEAK"].toString(";")).trimmed();
0234         bool success = false;
0235         double replayGainTrackPeak = trackPeakString.toDouble(&success);
0236         if (success) {
0237             result->add(Property::ReplayGainTrackPeak, replayGainTrackPeak);
0238         }
0239     }
0240     if (savedProperties.contains("REPLAYGAIN_ALBUM_PEAK")) {
0241         auto albumPeakString = TStringToQString(savedProperties["REPLAYGAIN_ALBUM_PEAK"].toString(";")).trimmed();
0242         bool success = false;
0243         double replayGainAlbumPeak = albumPeakString.toDouble(&success);
0244         if (success) {
0245             result->add(Property::ReplayGainAlbumPeak, replayGainAlbumPeak);
0246         }
0247     }
0248 }
0249 
0250 void extractId3Tags(TagLib::ID3v2::Tag* Id3Tags, ExtractionResult* result)
0251 {
0252     if (!(result->inputFlags() & ExtractionResult::ExtractMetaData) || Id3Tags->isEmpty()) {
0253         return;
0254     }
0255 
0256     TagLib::ID3v2::FrameList lstID3v2;
0257 
0258     /*
0259      * Publisher.
0260      * Special handling because TagLib::PropertyMap maps "TPUB" to "LABEL"
0261      * Insert manually for Publisher.
0262      */
0263     lstID3v2 = Id3Tags->frameListMap()["TPUB"];
0264     if (!lstID3v2.isEmpty()) {
0265         result->add(Property::Publisher, TStringToQString(lstID3v2.front()->toString()));
0266     }
0267 
0268     // Compilation.
0269     lstID3v2 = Id3Tags->frameListMap()["TCMP"];
0270     if (!lstID3v2.isEmpty()) {
0271         result->add(Property::Compilation, TStringToQString(lstID3v2.front()->toString()));
0272     }
0273 
0274     /*
0275      * Rating.
0276      * There is no standard regarding ratings. Most of the implementations match
0277      * a 5 stars rating to a range of 0-255 for MP3.
0278      * Map it to baloo rating with a range of 0 - 10.
0279      */
0280     lstID3v2 = Id3Tags->frameListMap()["POPM"];
0281     if (!lstID3v2.isEmpty()) {
0282         TagLib::ID3v2::PopularimeterFrame *ratingFrame = static_cast<TagLib::ID3v2::PopularimeterFrame *>(lstID3v2.front());
0283         int rating = ratingFrame->rating();
0284         if (rating == 0) {
0285             rating = 0;
0286         } else if (rating == 1) {
0287             TagLib::String ratingProvider = ratingFrame->email();
0288             if (ratingProvider == "no@email" || ratingProvider == "org.kde.kfilemetadata") {
0289                 rating = 1;
0290             } else {
0291                 rating = 2;
0292             }
0293         } else if (rating >= 1 && rating <= 255) {
0294             rating = static_cast<int>(0.032 * rating + 2);
0295         }
0296         result->add(Property::Rating, rating);
0297     }
0298 }
0299 
0300 template<typename ImageType>
0301 EmbeddedImageData::ImageType mapTaglibType(const ImageType type)
0302 {
0303     switch (type) {
0304         case ImageType::FrontCover:
0305             return EmbeddedImageData::FrontCover;
0306          case ImageType::Other:
0307              return EmbeddedImageData::Other;
0308          case ImageType::FileIcon:
0309               return EmbeddedImageData::FileIcon;
0310          case ImageType::OtherFileIcon:
0311               return EmbeddedImageData::OtherFileIcon;
0312          case ImageType::BackCover:
0313               return EmbeddedImageData::BackCover;
0314          case ImageType::LeafletPage:
0315               return EmbeddedImageData::LeafletPage;
0316          case ImageType::Media:
0317               return EmbeddedImageData::Media;
0318          case ImageType::LeadArtist:
0319               return EmbeddedImageData::LeadArtist;
0320          case ImageType::Artist:
0321               return EmbeddedImageData::Artist;
0322          case ImageType::Conductor:
0323               return EmbeddedImageData::Conductor;
0324          case ImageType::Band:
0325               return EmbeddedImageData::Band;
0326          case ImageType::Composer:
0327               return EmbeddedImageData::Composer;
0328          case ImageType::Lyricist:
0329               return EmbeddedImageData::Lyricist;
0330          case ImageType::RecordingLocation:
0331               return EmbeddedImageData::RecordingLocation;
0332          case ImageType::DuringRecording:
0333               return EmbeddedImageData::DuringRecording;
0334          case ImageType::DuringPerformance:
0335               return EmbeddedImageData::DuringPerformance;
0336          case ImageType::MovieScreenCapture:
0337               return EmbeddedImageData::MovieScreenCapture;
0338          case ImageType::ColouredFish:
0339               return EmbeddedImageData::ColouredFish;
0340          case ImageType::Illustration:
0341               return EmbeddedImageData::Illustration;
0342          case ImageType::BandLogo:
0343               return EmbeddedImageData::BandLogo;
0344          case ImageType::PublisherLogo:
0345               return EmbeddedImageData::PublisherLogo;
0346          default:
0347             return EmbeddedImageData::Unknown;
0348     }
0349 }
0350 
0351 QMap<EmbeddedImageData::ImageType, QByteArray>
0352 extractId3Cover(const TagLib::ID3v2::Tag* Id3Tags,
0353                 const EmbeddedImageData::ImageTypes types)
0354 {
0355     QMap<EmbeddedImageData::ImageType, QByteArray> images;
0356     if (!types || Id3Tags->isEmpty()) {
0357         return images;
0358     }
0359 
0360     // Attached Picture.
0361     TagLib::ID3v2::FrameList lstID3v2 = Id3Tags->frameListMap()["APIC"];
0362 
0363     using PictureFrame = TagLib::ID3v2::AttachedPictureFrame;
0364     for (const auto& frame : std::as_const(lstID3v2)) {
0365         const auto *coverFrame = static_cast<PictureFrame *>(frame);
0366         const auto imageType = mapTaglibType<PictureFrame::Type>(coverFrame->type());
0367         if (types & imageType) {
0368             const auto& picture = coverFrame->picture();
0369             images.insert(imageType, QByteArray(picture.data(), picture.size()));
0370         }
0371     }
0372     return images;
0373 }
0374 
0375 QMap<EmbeddedImageData::ImageType, QByteArray>
0376 extractFlacCover(const TagLib::List<TagLib::FLAC::Picture *> picList,
0377                  const EmbeddedImageData::ImageTypes types)
0378 {
0379     QMap<EmbeddedImageData::ImageType, QByteArray> images;
0380     if (!types || picList.isEmpty()) {
0381         return images;
0382     }
0383 
0384     for (const auto& picture : std::as_const(picList)) {
0385         const auto imageType = mapTaglibType<TagLib::FLAC::Picture::Type>(picture->type());
0386         if (types & imageType) {
0387             images.insert(imageType, QByteArray(picture->data().data(), picture->data().size()));
0388         }
0389     }
0390     return images;
0391 }
0392 
0393 void extractMp4Tags(TagLib::MP4::Tag* mp4Tags, ExtractionResult* result)
0394 {
0395     if (!(result->inputFlags() & ExtractionResult::ExtractMetaData) || mp4Tags->isEmpty()) {
0396         return;
0397     }
0398 
0399     auto ratingItem = mp4Tags->item("rate");
0400 
0401     /*
0402      * There is no standard regarding ratings. Mimic MediaMonkey's behavior
0403      * with a range of 0 to 100 (stored in steps of 10) and make it compatible
0404      * with baloo rating with a range from 0 to 10.
0405      */
0406     if (ratingItem.isValid()) {
0407         result->add(Property::Rating, ratingItem.toStringList().toString().toInt() / 10);
0408     }
0409 }
0410 
0411 QMap<EmbeddedImageData::ImageType, QByteArray>
0412 extractMp4Cover(const TagLib::MP4::Tag* mp4Tags,
0413                 const EmbeddedImageData::ImageTypes types)
0414 {
0415     QMap<EmbeddedImageData::ImageType, QByteArray> images;
0416     TagLib::MP4::Item coverArtItem = mp4Tags->item("covr");
0417     if (!(types & EmbeddedImageData::FrontCover) || !coverArtItem.isValid()) {
0418         return images;
0419     }
0420 
0421     const TagLib::MP4::CoverArtList coverArtList = coverArtItem.toCoverArtList();
0422     if (!coverArtList.isEmpty()) {
0423         const TagLib::MP4::CoverArt& cover = coverArtList.front();
0424         images.insert(EmbeddedImageData::FrontCover, QByteArray(cover.data().data(), cover.data().size()));
0425     }
0426     return images;
0427 }
0428 
0429 QMap<EmbeddedImageData::ImageType, QByteArray>
0430 extractApeCover(const TagLib::APE::Tag* apeTags,
0431                 const EmbeddedImageData::ImageTypes types)
0432 {
0433     QMap<EmbeddedImageData::ImageType, QByteArray> images;
0434     if (!(types & EmbeddedImageData::FrontCover) || apeTags->isEmpty()) {
0435         return images;
0436     }
0437 
0438     TagLib::APE::ItemListMap lstApe = apeTags->itemListMap();
0439     TagLib::APE::ItemListMap::ConstIterator itApe;
0440 
0441     /* The cover art tag for APEv2 tags starts with the filename as string
0442      * with zero termination followed by the actual picture data */
0443     itApe = lstApe.find("COVER ART (FRONT)");
0444     if (itApe != lstApe.end()) {
0445         const auto& picture = (*itApe).second.binaryData();
0446         int position = picture.find('\0');
0447         if (position >= 0) {
0448             position += 1;
0449             images.insert(EmbeddedImageData::FrontCover, QByteArray(picture.data() + position, picture.size() - position));
0450         }
0451     }
0452     return images;
0453 }
0454 
0455 void extractAsfTags(TagLib::ASF::Tag* asfTags, ExtractionResult* result)
0456 {
0457     if (!(result->inputFlags() & ExtractionResult::ExtractMetaData) || asfTags->isEmpty()) {
0458         return;
0459     }
0460 
0461     TagLib::ASF::AttributeList lstASF = asfTags->attribute("WM/SharedUserRating");
0462     if (!lstASF.isEmpty()) {
0463         int rating = lstASF.front().toString().toInt();
0464         /*
0465          * Map the rating values of WMP to Baloo rating.
0466          * 0->0, 1->2, 25->4, 50->6, 75->8, 99->10
0467          */
0468         if (rating == 0) {
0469             rating = 0;
0470         } else if (rating == 1) {
0471             rating = 2;
0472         } else {
0473             rating = static_cast<int>(0.09 * rating + 2);
0474         }
0475         result->add(Property::Rating, rating);
0476     }
0477 
0478     lstASF = asfTags->attribute("Author");
0479     if (!lstASF.isEmpty()) {
0480         const auto attribute = lstASF.front();
0481         result->add(Property::Author, TStringToQString(attribute.toString()).trimmed());
0482     }
0483 
0484     // Lyricist is called "WRITER" for wma/asf files
0485     lstASF = asfTags->attribute("WM/Writer");
0486     if (!lstASF.isEmpty()) {
0487         const auto attribute = lstASF.front();
0488         result->add(Property::Lyricist, TStringToQString(attribute.toString()).trimmed());
0489     }
0490 
0491     /*
0492      * TagLib exports "WM/PUBLISHER" as "LABEL" in the PropertyMap,
0493      * add it manually to Publisher.
0494      */
0495     lstASF = asfTags->attribute("WM/Publisher");
0496     if (!lstASF.isEmpty()) {
0497         const auto attribute = lstASF.front();
0498         result->add(Property::Publisher, TStringToQString(attribute.toString()).trimmed());
0499     }
0500 }
0501 
0502 QMap<EmbeddedImageData::ImageType, QByteArray>
0503 extractAsfCover(const TagLib::ASF::Tag* asfTags,
0504                 const EmbeddedImageData::ImageTypes types)
0505 {
0506     QMap<EmbeddedImageData::ImageType, QByteArray> images;
0507     if (!types || asfTags->isEmpty()) {
0508         return images;
0509     }
0510 
0511     // Attached Picture.
0512     TagLib::ASF::AttributeList lstASF = asfTags->attribute("WM/Picture");
0513 
0514     using Picture = TagLib::ASF::Picture;
0515     for (const auto& attribute: std::as_const(lstASF)) {
0516         Picture picture = attribute.toPicture();
0517         const auto imageType = mapTaglibType<Picture::Type>(picture.type());
0518         if (types & imageType) {
0519             const auto& pictureData = picture.picture();
0520             images.insert(imageType, QByteArray(pictureData.data(), pictureData.size()));
0521         }
0522     }
0523     return images;
0524 }
0525 
0526 } // anonymous namespace
0527 
0528 TagLibExtractor::TagLibExtractor(QObject* parent)
0529     : ExtractorPlugin(parent)
0530 {
0531 }
0532 
0533 QStringList TagLibExtractor::mimetypes() const
0534 {
0535     return supportedMimeTypes;
0536 }
0537 
0538 void TagLibExtractor::extract(ExtractionResult* result)
0539 {
0540     const QString fileUrl = result->inputUrl();
0541     const QString mimeType = getSupportedMimeType(result->inputMimetype());
0542 
0543     // Open the file readonly. Important if we're sandboxed.
0544 #if defined Q_OS_WINDOWS
0545     TagLib::FileStream stream(fileUrl.toLocal8Bit().constData(), true);
0546 #else
0547     TagLib::FileStream stream(fileUrl.toUtf8().constData(), true);
0548 #endif
0549     if (!stream.isOpen()) {
0550         qCWarning(KFILEMETADATA_LOG) << "Unable to open file readonly: " << fileUrl;
0551         return;
0552     }
0553 
0554     const EmbeddedImageData::ImageTypes imageTypes{
0555         result->inputFlags() & ExtractionResult::ExtractImageData ? EmbeddedImageData::AllImages : 0
0556     };
0557 
0558     if (mimeType == QLatin1String("audio/mpeg") || mimeType == QLatin1String("audio/mpeg3")
0559             || mimeType == QLatin1String("audio/x-mpeg")) {
0560         TagLib::MPEG::File file(&stream, TagLib::ID3v2::FrameFactory::instance(), true);
0561         if (file.isValid()) {
0562             extractAudioProperties(&file, result);
0563             readGenericProperties(file.properties(), result);
0564             if (file.hasID3v2Tag()) {
0565                 result->addImageData(extractId3Cover(file.ID3v2Tag(), imageTypes));
0566                 extractId3Tags(file.ID3v2Tag(), result);
0567             }
0568         }
0569     } else if (mimeType == QLatin1String("audio/x-aiff") || mimeType == QLatin1String("audio/x-aifc")) {
0570         TagLib::RIFF::AIFF::File file(&stream, true);
0571         if (file.isValid()) {
0572             extractAudioProperties(&file, result);
0573             readGenericProperties(file.properties(), result);
0574             if (file.hasID3v2Tag()) {
0575                 result->addImageData(extractId3Cover(file.tag(), imageTypes));
0576                 extractId3Tags(file.tag(), result);
0577             }
0578         }
0579     } else if (mimeType == QLatin1String("audio/wav") ||
0580                mimeType == QLatin1String("audio/vnd.wave") ||
0581                mimeType == QLatin1String("audio/x-wav")) {
0582         TagLib::RIFF::WAV::File file(&stream, true);
0583         if (file.isValid()) {
0584             extractAudioProperties(&file, result);
0585             readGenericProperties(file.properties(), result);
0586             if (file.hasID3v2Tag()) {
0587                 result->addImageData(extractId3Cover(file.ID3v2Tag(), imageTypes));
0588                 extractId3Tags(file.ID3v2Tag(), result);
0589             }
0590         }
0591     } else if (mimeType == QLatin1String("audio/x-musepack")) {
0592         TagLib::MPC::File file(&stream, true);
0593         if (file.isValid()) {
0594             extractAudioProperties(&file, result);
0595             readGenericProperties(file.properties(), result);
0596             if (file.APETag()) {
0597                 result->addImageData(extractApeCover(file.APETag(), imageTypes));
0598             }
0599         }
0600     } else if (mimeType == QLatin1String("audio/x-ape")) {
0601         TagLib::APE::File file(&stream, true);
0602         if (file.isValid()) {
0603             extractAudioProperties(&file, result);
0604             readGenericProperties(file.properties(), result);
0605             if (file.APETag()) {
0606                 result->addImageData(extractApeCover(file.APETag(), imageTypes));
0607             }
0608         }
0609     } else if (mimeType == QLatin1String("audio/x-wavpack")) {
0610         TagLib::WavPack::File file(&stream, true);
0611         if (file.isValid()) {
0612             extractAudioProperties(&file, result);
0613             readGenericProperties(file.properties(), result);
0614             if (file.APETag()) {
0615                 result->addImageData(extractApeCover(file.APETag(), imageTypes));
0616             }
0617         }
0618     } else if ((mimeType == QLatin1String("audio/mp4")) ||
0619                (mimeType == QLatin1String("audio/vnd.audible.aax"))) {
0620         TagLib::MP4::File file(&stream, true);
0621         if (file.isValid()) {
0622             extractAudioProperties(&file, result);
0623             readGenericProperties(file.properties(), result);
0624             extractMp4Tags(file.tag(), result);
0625             result->addImageData(extractMp4Cover(file.tag(), imageTypes));
0626         }
0627     } else if (mimeType == QLatin1String("audio/flac")) {
0628         TagLib::FLAC::File file(&stream, TagLib::ID3v2::FrameFactory::instance(), true);
0629         if (file.isValid()) {
0630             extractAudioProperties(&file, result);
0631             readGenericProperties(file.properties(), result);
0632             result->addImageData(extractFlacCover(file.pictureList(), imageTypes));
0633         }
0634     } else if (mimeType == QLatin1String("audio/x-flac+ogg")) {
0635         TagLib::Ogg::FLAC::File file(&stream, true);
0636         if (file.isValid()) {
0637             extractAudioProperties(&file, result);
0638             readGenericProperties(file.properties(), result);
0639             if (file.tag()) {
0640                 result->addImageData(extractFlacCover(file.tag()->pictureList(), imageTypes));
0641             }
0642         }
0643     } else if (mimeType == QLatin1String("audio/ogg") || mimeType == QLatin1String("audio/x-vorbis+ogg")) {
0644         TagLib::Ogg::Vorbis::File file(&stream, true);
0645         if (file.isValid()) {
0646             extractAudioProperties(&file, result);
0647             readGenericProperties(file.properties(), result);
0648             if (file.tag()) {
0649                 result->addImageData(extractFlacCover(file.tag()->pictureList(), imageTypes));
0650             }
0651         }
0652     } else if (mimeType == QLatin1String("audio/opus") || mimeType == QLatin1String("audio/x-opus+ogg")) {
0653         TagLib::Ogg::Opus::File file(&stream, true);
0654         if (file.isValid()) {
0655             extractAudioProperties(&file, result);
0656             readGenericProperties(file.properties(), result);
0657             if (file.tag()) {
0658                 result->addImageData(extractFlacCover(file.tag()->pictureList(), imageTypes));
0659             }
0660         }
0661     } else if (mimeType == QLatin1String("audio/x-speex+ogg")) {
0662         TagLib::Ogg::Speex::File file(&stream, true);
0663         // Workaround for buggy taglib:
0664         // isValid() returns true for invalid files, but XiphComment* tag() returns a nullptr
0665         if (file.isValid() && file.tag()) {
0666             extractAudioProperties(&file, result);
0667             readGenericProperties(file.properties(), result);
0668             result->addImageData(extractFlacCover(file.tag()->pictureList(), imageTypes));
0669         }
0670     } else if (mimeType == QLatin1String("audio/x-ms-wma")) {
0671         TagLib::ASF::File file(&stream, true);
0672         if (file.isValid()) {
0673             extractAudioProperties(&file, result);
0674             readGenericProperties(file.properties(), result);
0675             extractAsfTags(file.tag(), result);
0676             TagLib::ASF::Tag* asfTags = dynamic_cast<TagLib::ASF::Tag*>(file.tag());
0677             if (asfTags) {
0678                 result->addImageData(extractAsfCover(asfTags, imageTypes));
0679             }
0680         }
0681     }
0682 
0683     result->addType(Type::Audio);
0684 }
0685 
0686 // TAG information (incomplete).
0687 // https://xiph.org/vorbis/doc/v-comment.html
0688 // https://help.mp3tag.de/main_tags.html
0689 // http://id3.org/
0690 // https://www.legroom.net/2009/05/09/ogg-vorbis-and-flac-comment-field-recommendations
0691 // https://kodi.wiki/view/Music_tagging#Tags_Kodi_reads
0692 // https://wiki.hydrogenaud.io/index.php?title=Tag_Mapping
0693 // https://picard.musicbrainz.org/docs/mappings/
0694 // -- FLAC/OGG --
0695 // Artist:          ARTIST, PERFORMER
0696 // Album artist:    ALBUMARTIST
0697 // Composer:        COMPOSER
0698 // Lyricist:        LYRICIST
0699 // Conductor:       CONDUCTOR
0700 // Disc number:     DISCNUMBER
0701 // Total discs:     TOTALDISCS, DISCTOTAL
0702 // Track number:    TRACKNUMBER
0703 // Total tracks:    TOTALTRACKS, TRACKTOTAL
0704 // Genre: GENRE
0705 // -- ID3v2 --
0706 // Artist:                          TPE1
0707 // Album artist:                    TPE2
0708 // Composer:                        TCOM
0709 // Lyricist:                        TEXT
0710 // Conductor:                       TPE3
0711 // Disc number[/total dics]:        TPOS
0712 // Track number[/total tracks]:     TRCK
0713 // Genre:                           TCON
0714 
0715 #include "moc_taglibextractor.cpp"