File indexing completed on 2025-03-16 12:49:37
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"