8#include "taglibextractor.h"
9#include "embeddedimagedata.h"
10#include "kfilemetadata_debug.h"
19#include <tfilestream.h>
20#include <tpropertymap.h>
30#include <oggflacfile.h>
33#include <vorbisfile.h>
35#include <wavpackfile.h>
37#include <asfattribute.h>
38#include <asfpicture.h>
41#include <popularimeterframe.h>
42#include <attachedpictureframe.h>
49 QStringLiteral(
"audio/flac"),
50 QStringLiteral(
"audio/mp4"),
51 QStringLiteral(
"audio/mpeg"),
52 QStringLiteral(
"audio/ogg"),
53 QStringLiteral(
"audio/wav"),
54 QStringLiteral(
"audio/vnd.audible.aax"),
55 QStringLiteral(
"audio/vnd.audible.aaxc"),
56 QStringLiteral(
"audio/vnd.wave"),
57 QStringLiteral(
"audio/x-aiff"),
58 QStringLiteral(
"audio/x-aifc"),
59 QStringLiteral(
"audio/x-ape"),
60 QStringLiteral(
"audio/x-flac+ogg"),
61 QStringLiteral(
"audio/x-ms-wma"),
62 QStringLiteral(
"audio/x-musepack"),
63 QStringLiteral(
"audio/x-opus+ogg"),
64 QStringLiteral(
"audio/x-speex+ogg"),
65 QStringLiteral(
"audio/x-vorbis+ogg"),
66 QStringLiteral(
"audio/x-wav"),
67 QStringLiteral(
"audio/x-wavpack"),
68 QStringLiteral(
"audio/x-mod"),
69 QStringLiteral(
"audio/x-s3m"),
70 QStringLiteral(
"audio/x-xm"),
71 QStringLiteral(
"audio/x-it"),
76 TagLib::AudioProperties* audioProp = file->audioProperties();
77 if (audioProp && (result->
inputFlags() & ExtractionResult::ExtractMetaData)) {
78 if (audioProp->lengthInSeconds()) {
80 result->
add(Property::Duration, audioProp->lengthInSeconds());
83 if (audioProp->bitrate()) {
84 result->
add(Property::BitRate, audioProp->bitrate() * 1000);
87 if (audioProp->channels()) {
88 result->
add(Property::Channels, audioProp->channels());
91 if (audioProp->sampleRate()) {
92 result->
add(Property::SampleRate, audioProp->sampleRate());
97void readGenericProperties(
const TagLib::PropertyMap &savedProperties,
ExtractionResult* result)
99 if (!(result->
inputFlags() & ExtractionResult::ExtractMetaData) || savedProperties.isEmpty()) {
103 if (savedProperties.contains(
"TITLE")) {
104 result->
add(Property::Title, TStringToQString(savedProperties[
"TITLE"].
toString()).trimmed());
106 if (savedProperties.contains(
"ALBUM")) {
107 result->
add(Property::Album, TStringToQString(savedProperties[
"ALBUM"].
toString()).trimmed());
109 if (savedProperties.contains(
"COMMENT")) {
110 result->
add(Property::Comment, TStringToQString(savedProperties[
"COMMENT"].
toString()).trimmed());
112 if (savedProperties.contains(
"TRACKNUMBER")) {
113 result->
add(Property::TrackNumber, savedProperties[
"TRACKNUMBER"].
toString().toInt());
115 if (savedProperties.contains(
"DATE")) {
116 result->
add(Property::ReleaseYear, savedProperties[
"DATE"].
toString().toInt());
118 if (savedProperties.contains(
"OPUS")) {
119 result->
add(Property::Opus, savedProperties[
"OPUS"].
toString().toInt());
121 if (savedProperties.contains(
"DISCNUMBER")) {
122 result->
add(Property::DiscNumber, savedProperties[
"DISCNUMBER"].
toString().toInt());
124 if (savedProperties.contains(
"RATING")) {
130 result->
add(Property::Rating, savedProperties[
"RATING"].
toString().toInt() / 10);
132 if (savedProperties.contains(
"LOCATION")) {
133 result->
add(Property::Location, TStringToQString(savedProperties[
"LOCATION"].
toString()).trimmed());
135 if (savedProperties.contains(
"LANGUAGE")) {
136 result->
add(Property::Language, TStringToQString(savedProperties[
"LANGUAGE"].
toString()).trimmed());
138 if (savedProperties.contains(
"LICENSE")) {
139 result->
add(Property::License, TStringToQString(savedProperties[
"LICENSE"].
toString()).trimmed());
141 if (savedProperties.contains(
"PUBLISHER")) {
142 result->
add(Property::Publisher, TStringToQString(savedProperties[
"PUBLISHER"].
toString()).trimmed());
144 if (savedProperties.contains(
"COPYRIGHT")) {
145 result->
add(Property::Copyright, TStringToQString(savedProperties[
"COPYRIGHT"].
toString()).trimmed());
147 if (savedProperties.contains(
"LABEL")) {
148 result->
add(Property::Label, TStringToQString(savedProperties[
"LABEL"].
toString()).trimmed());
150 if (savedProperties.contains(
"ENSEMBLE")) {
151 result->
add(Property::Ensemble, TStringToQString(savedProperties[
"ENSEMBLE"].
toString()).trimmed());
153 if (savedProperties.contains(
"COMPILATION")) {
154 result->
add(Property::Compilation, TStringToQString(savedProperties[
"COMPILATION"].
toString()).trimmed());
156 if (savedProperties.contains(
"LYRICS")) {
159 if (savedProperties.contains(
"ARTIST")) {
160 const auto artists = savedProperties[
"ARTIST"];
161 for (
const auto& artist : artists) {
162 result->
add(Property::Artist, TStringToQString(artist).trimmed());
165 if (savedProperties.contains(
"GENRE")) {
166 const auto genres = savedProperties[
"GENRE"];
167 for (
const auto& genre : genres) {
168 result->
add(Property::Genre, TStringToQString(genre).trimmed());
171 if (savedProperties.contains(
"ALBUMARTIST")) {
172 const auto albumArtists = savedProperties[
"ALBUMARTIST"];
173 for (
const auto& albumArtist : albumArtists) {
174 result->
add(Property::AlbumArtist, TStringToQString(albumArtist).trimmed());
177 if (savedProperties.contains(
"COMPOSER")) {
178 const auto composers = savedProperties[
"COMPOSER"];
179 for (
const auto& composer : composers) {
180 result->
add(Property::Composer, TStringToQString(composer).trimmed());
183 if (savedProperties.contains(
"LYRICIST")) {
184 const auto lyricists = savedProperties[
"LYRICIST"];
185 for (
const auto& lyricist : lyricists) {
186 result->
add(Property::Lyricist, TStringToQString(lyricist).trimmed());
189 if (savedProperties.contains(
"CONDUCTOR")) {
190 const auto conductors = savedProperties[
"CONDUCTOR"];
191 for (
const auto& conductor : conductors) {
192 result->
add(Property::Conductor, TStringToQString(conductor).trimmed());
195 if (savedProperties.contains(
"ARRANGER")) {
196 const auto arrangers = savedProperties[
"ARRANGER"];
197 for (
const auto& arranger : arrangers) {
198 result->
add(Property::Arranger, TStringToQString(arranger).trimmed());
201 if (savedProperties.contains(
"PERFORMER")) {
202 const auto performers = savedProperties[
"PERFORMER"];
203 for (
const auto& performer : performers) {
204 result->
add(Property::Performer, TStringToQString(performer).trimmed());
207 if (savedProperties.contains(
"AUTHOR")) {
208 const auto authors = savedProperties[
"AUTHOR"];
209 for (
const auto& author: authors) {
210 result->
add(Property::Author, TStringToQString(author).trimmed());
214 if (savedProperties.contains(
"REPLAYGAIN_TRACK_GAIN")) {
215 auto trackGainString = TStringToQString(savedProperties[
"REPLAYGAIN_TRACK_GAIN"].
toString(
";")).trimmed();
218 trackGainString.chop(3);
220 bool success =
false;
221 double replayGainTrackGain = trackGainString.toDouble(&success);
223 result->
add(Property::ReplayGainTrackGain, replayGainTrackGain);
226 if (savedProperties.contains(
"REPLAYGAIN_ALBUM_GAIN")) {
227 auto albumGainString = TStringToQString(savedProperties[
"REPLAYGAIN_ALBUM_GAIN"].
toString(
";")).trimmed();
230 albumGainString.chop(3);
232 bool success =
false;
233 double replayGainAlbumGain = albumGainString.toDouble(&success);
235 result->
add(Property::ReplayGainAlbumGain, replayGainAlbumGain);
238 if (savedProperties.contains(
"REPLAYGAIN_TRACK_PEAK")) {
239 auto trackPeakString = TStringToQString(savedProperties[
"REPLAYGAIN_TRACK_PEAK"].
toString(
";")).trimmed();
240 bool success =
false;
241 double replayGainTrackPeak = trackPeakString.toDouble(&success);
243 result->
add(Property::ReplayGainTrackPeak, replayGainTrackPeak);
246 if (savedProperties.contains(
"REPLAYGAIN_ALBUM_PEAK")) {
247 auto albumPeakString = TStringToQString(savedProperties[
"REPLAYGAIN_ALBUM_PEAK"].
toString(
";")).trimmed();
248 bool success =
false;
249 double replayGainAlbumPeak = albumPeakString.toDouble(&success);
251 result->
add(Property::ReplayGainAlbumPeak, replayGainAlbumPeak);
254 if (savedProperties.contains(
"TRACKERNAME")) {
255 result->
add(Property::Generator, TStringToQString(savedProperties[
"TRACKERNAME"].
toString()).trimmed());
261 if (!(result->
inputFlags() & ExtractionResult::ExtractMetaData) || Id3Tags->isEmpty()) {
265 TagLib::ID3v2::FrameList lstID3v2;
272 lstID3v2 = Id3Tags->frameListMap()[
"TPUB"];
273 if (!lstID3v2.isEmpty()) {
274 result->
add(Property::Publisher, TStringToQString(lstID3v2.front()->toString()));
278 lstID3v2 = Id3Tags->frameListMap()[
"TCMP"];
279 if (!lstID3v2.isEmpty()) {
280 result->
add(Property::Compilation, TStringToQString(lstID3v2.front()->toString()));
289 lstID3v2 = Id3Tags->frameListMap()[
"POPM"];
290 if (!lstID3v2.isEmpty()) {
291 TagLib::ID3v2::PopularimeterFrame *ratingFrame =
static_cast<TagLib::ID3v2::PopularimeterFrame *
>(lstID3v2.front());
292 int rating = ratingFrame->rating();
295 }
else if (rating == 1) {
296 TagLib::String ratingProvider = ratingFrame->email();
297 if (ratingProvider ==
"no@email" || ratingProvider ==
"org.kde.kfilemetadata") {
302 }
else if (rating >= 1 && rating <= 255) {
303 rating =
static_cast<int>(0.032 * rating + 2);
305 result->
add(Property::Rating, rating);
309template<
typename ImageType>
313 case ImageType::FrontCover:
314 return EmbeddedImageData::FrontCover;
315 case ImageType::Other:
316 return EmbeddedImageData::Other;
317 case ImageType::FileIcon:
318 return EmbeddedImageData::FileIcon;
319 case ImageType::OtherFileIcon:
320 return EmbeddedImageData::OtherFileIcon;
321 case ImageType::BackCover:
322 return EmbeddedImageData::BackCover;
323 case ImageType::LeafletPage:
324 return EmbeddedImageData::LeafletPage;
325 case ImageType::Media:
326 return EmbeddedImageData::Media;
327 case ImageType::LeadArtist:
328 return EmbeddedImageData::LeadArtist;
329 case ImageType::Artist:
330 return EmbeddedImageData::Artist;
331 case ImageType::Conductor:
332 return EmbeddedImageData::Conductor;
333 case ImageType::Band:
334 return EmbeddedImageData::Band;
335 case ImageType::Composer:
336 return EmbeddedImageData::Composer;
337 case ImageType::Lyricist:
338 return EmbeddedImageData::Lyricist;
339 case ImageType::RecordingLocation:
340 return EmbeddedImageData::RecordingLocation;
341 case ImageType::DuringRecording:
342 return EmbeddedImageData::DuringRecording;
343 case ImageType::DuringPerformance:
344 return EmbeddedImageData::DuringPerformance;
345 case ImageType::MovieScreenCapture:
346 return EmbeddedImageData::MovieScreenCapture;
347 case ImageType::ColouredFish:
348 return EmbeddedImageData::ColouredFish;
349 case ImageType::Illustration:
350 return EmbeddedImageData::Illustration;
351 case ImageType::BandLogo:
352 return EmbeddedImageData::BandLogo;
353 case ImageType::PublisherLogo:
354 return EmbeddedImageData::PublisherLogo;
356 return EmbeddedImageData::Unknown;
361extractId3Cover(
const TagLib::ID3v2::Tag* Id3Tags,
365 if (!types || Id3Tags->isEmpty()) {
370 TagLib::ID3v2::FrameList lstID3v2 = Id3Tags->frameListMap()[
"APIC"];
372 using PictureFrame = TagLib::ID3v2::AttachedPictureFrame;
373 for (
const auto& frame : std::as_const(lstID3v2)) {
374 const auto *coverFrame =
static_cast<PictureFrame *
>(frame);
375 const auto imageType = mapTaglibType<PictureFrame::Type>(coverFrame->type());
376 if (types & imageType) {
377 const auto& picture = coverFrame->picture();
385extractFlacCover(
const TagLib::List<TagLib::FLAC::Picture *> picList,
389 if (!types || picList.isEmpty()) {
393 for (
const auto& picture : std::as_const(picList)) {
394 const auto imageType = mapTaglibType<TagLib::FLAC::Picture::Type>(picture->type());
395 if (types & imageType) {
396 images.
insert(imageType,
QByteArray(picture->data().data(), picture->data().size()));
404 if (!(result->
inputFlags() & ExtractionResult::ExtractMetaData) || mp4Tags->isEmpty()) {
408 auto ratingItem = mp4Tags->item(
"rate");
415 if (ratingItem.isValid()) {
416 result->
add(Property::Rating, ratingItem.toStringList().toString().toInt() / 10);
421extractMp4Cover(
const TagLib::MP4::Tag* mp4Tags,
425 TagLib::MP4::Item coverArtItem = mp4Tags->item(
"covr");
426 if (!(types & EmbeddedImageData::FrontCover) || !coverArtItem.isValid()) {
430 const TagLib::MP4::CoverArtList coverArtList = coverArtItem.toCoverArtList();
431 if (!coverArtList.isEmpty()) {
432 const TagLib::MP4::CoverArt& cover = coverArtList.front();
433 images.
insert(EmbeddedImageData::FrontCover,
QByteArray(cover.data().data(), cover.data().size()));
439extractApeCover(
const TagLib::APE::Tag* apeTags,
443 if (!(types & EmbeddedImageData::FrontCover) || apeTags->isEmpty()) {
447 TagLib::APE::ItemListMap lstApe = apeTags->itemListMap();
448 TagLib::APE::ItemListMap::ConstIterator itApe;
452 itApe = lstApe.
find(
"COVER ART (FRONT)");
453 if (itApe != lstApe.end()) {
454 const auto& picture = (*itApe).second.binaryData();
455 int position = picture.find(
'\0');
458 images.
insert(EmbeddedImageData::FrontCover,
QByteArray(picture.data() + position, picture.size() - position));
466 if (!(result->
inputFlags() & ExtractionResult::ExtractMetaData) || asfTags->isEmpty()) {
470 TagLib::ASF::AttributeList lstASF = asfTags->attribute(
"WM/SharedUserRating");
471 if (!lstASF.isEmpty()) {
472 int rating = lstASF.front().toString().toInt();
479 }
else if (rating == 1) {
482 rating =
static_cast<int>(0.09 * rating + 2);
484 result->
add(Property::Rating, rating);
487 lstASF = asfTags->attribute(
"Author");
488 if (!lstASF.isEmpty()) {
489 const auto attribute = lstASF.front();
490 result->
add(Property::Author, TStringToQString(attribute.toString()).trimmed());
494 lstASF = asfTags->attribute(
"WM/Writer");
495 if (!lstASF.isEmpty()) {
496 const auto attribute = lstASF.front();
497 result->
add(Property::Lyricist, TStringToQString(attribute.toString()).trimmed());
504 lstASF = asfTags->attribute(
"WM/Publisher");
505 if (!lstASF.isEmpty()) {
506 const auto attribute = lstASF.front();
507 result->
add(Property::Publisher, TStringToQString(attribute.toString()).trimmed());
512extractAsfCover(
const TagLib::ASF::Tag* asfTags,
516 if (!types || asfTags->isEmpty()) {
521 TagLib::ASF::AttributeList lstASF = asfTags->attribute(
"WM/Picture");
523 using Picture = TagLib::ASF::Picture;
524 for (
const auto& attribute: std::as_const(lstASF)) {
525 Picture picture = attribute.toPicture();
526 const auto imageType = mapTaglibType<Picture::Type>(picture.type());
527 if (types & imageType) {
528 const auto& pictureData = picture.picture();
529 images.
insert(imageType,
QByteArray(pictureData.data(), pictureData.size()));
537TagLibExtractor::TagLibExtractor(
QObject* parent)
544 return supportedMimeTypes;
553#if defined Q_OS_WINDOWS
558 if (!stream.isOpen()) {
559 qCWarning(KFILEMETADATA_LOG) <<
"Unable to open file readonly: " << fileUrl;
568#if TAGLIB_MAJOR_VERSION >= 2
569 TagLib::MPEG::File file(&stream,
true);
571 TagLib::MPEG::File file(&stream, TagLib::ID3v2::FrameFactory::instance(),
true);
573 if (file.isValid()) {
574 extractAudioProperties(&file, result);
575 readGenericProperties(file.properties(), result);
576 if (file.hasID3v2Tag()) {
577 result->
addImageData(extractId3Cover(file.ID3v2Tag(), imageTypes));
578 extractId3Tags(file.ID3v2Tag(), result);
582 TagLib::RIFF::AIFF::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.tag(), imageTypes));
588 extractId3Tags(file.tag(), result);
594 TagLib::RIFF::WAV::File file(&stream,
true);
595 if (file.isValid()) {
596 extractAudioProperties(&file, result);
597 readGenericProperties(file.properties(), result);
598 if (file.hasID3v2Tag()) {
599 result->
addImageData(extractId3Cover(file.ID3v2Tag(), imageTypes));
600 extractId3Tags(file.ID3v2Tag(), result);
604 TagLib::MPC::File file(&stream,
true);
605 if (file.isValid()) {
606 extractAudioProperties(&file, result);
607 readGenericProperties(file.properties(), result);
609 result->
addImageData(extractApeCover(file.APETag(), imageTypes));
613 TagLib::APE::File file(&stream,
true);
614 if (file.isValid()) {
615 extractAudioProperties(&file, result);
616 readGenericProperties(file.properties(), result);
618 result->
addImageData(extractApeCover(file.APETag(), imageTypes));
622 TagLib::WavPack::File file(&stream,
true);
623 if (file.isValid()) {
624 extractAudioProperties(&file, result);
625 readGenericProperties(file.properties(), result);
627 result->
addImageData(extractApeCover(file.APETag(), imageTypes));
633 TagLib::MP4::File file(&stream,
true);
634 if (file.isValid()) {
635 extractAudioProperties(&file, result);
636 readGenericProperties(file.properties(), result);
637 extractMp4Tags(file.tag(), result);
638 result->
addImageData(extractMp4Cover(file.tag(), imageTypes));
641#if TAGLIB_MAJOR_VERSION >= 2
642 TagLib::FLAC::File file(&stream,
true);
644 TagLib::FLAC::File file(&stream, TagLib::ID3v2::FrameFactory::instance(),
true);
646 if (file.isValid()) {
647 extractAudioProperties(&file, result);
648 readGenericProperties(file.properties(), result);
649 result->
addImageData(extractFlacCover(file.pictureList(), imageTypes));
652 TagLib::Ogg::FLAC::File file(&stream,
true);
653 if (file.isValid()) {
654 extractAudioProperties(&file, result);
655 readGenericProperties(file.properties(), result);
657 result->
addImageData(extractFlacCover(file.tag()->pictureList(), imageTypes));
661 TagLib::Ogg::Vorbis::File file(&stream,
true);
662 if (file.isValid()) {
663 extractAudioProperties(&file, result);
664 readGenericProperties(file.properties(), result);
666 result->
addImageData(extractFlacCover(file.tag()->pictureList(), imageTypes));
670 TagLib::Ogg::Opus::File file(&stream,
true);
671 if (file.isValid()) {
672 extractAudioProperties(&file, result);
673 readGenericProperties(file.properties(), result);
675 result->
addImageData(extractFlacCover(file.tag()->pictureList(), imageTypes));
679 TagLib::Ogg::Speex::File file(&stream,
true);
682 if (file.isValid() && file.tag()) {
683 extractAudioProperties(&file, result);
684 readGenericProperties(file.properties(), result);
685 result->
addImageData(extractFlacCover(file.tag()->pictureList(), imageTypes));
688 TagLib::ASF::File file(&stream,
true);
689 if (file.isValid()) {
690 extractAudioProperties(&file, result);
691 readGenericProperties(file.properties(), result);
692 extractAsfTags(file.tag(), result);
693 TagLib::ASF::Tag* asfTags =
dynamic_cast<TagLib::ASF::Tag*
>(file.tag());
695 result->
addImageData(extractAsfCover(asfTags, imageTypes));
699 TagLib::Mod::File file(&stream);
700 if (file.isValid()) {
701 extractAudioProperties(&file, result);
702 readGenericProperties(file.properties(), result);
705 TagLib::S3M::File file(&stream);
706 if (file.isValid()) {
707 extractAudioProperties(&file, result);
708 readGenericProperties(file.properties(), result);
711 TagLib::XM::File file(&stream);
712 if (file.isValid()) {
713 extractAudioProperties(&file, result);
714 readGenericProperties(file.properties(), result);
717 TagLib::IT::File file(&stream);
718 if (file.isValid()) {
719 extractAudioProperties(&file, result);
720 readGenericProperties(file.properties(), result);
756#include "moc_taglibextractor.cpp"
char * toString(const EngineQuery &query)
KCALUTILS_EXPORT QString mimeType()
const QList< QKeySequence > & replace()
const char * constData() const const
iterator find(const Key &key)
iterator insert(const Key &key, const T &value)
QByteArray toLocal8Bit() const const
QByteArray toUtf8() const const