diff --git a/src/sources/metadatasourcetaglib.cpp b/src/sources/metadatasourcetaglib.cpp index 1038c6e729d..05bfe608386 100644 --- a/src/sources/metadatasourcetaglib.cpp +++ b/src/sources/metadatasourcetaglib.cpp @@ -8,13 +8,10 @@ #include #include -#include #include #if (TAGLIB_HAS_OPUSFILE) #include #endif -#include -#include namespace mixxx { @@ -122,187 +119,164 @@ MetadataSourceTagLib::importTrackMetadataAndCoverImage( switch (m_fileType) { case taglib::FileType::MP3: { TagLib::MPEG::File file(TAGLIB_FILENAME_FROM_QSTRING(m_fileName)); - if (taglib::readAudioProperties(pTrackMetadata, file)) { - const TagLib::ID3v2::Tag* pID3v2Tag = - taglib::hasID3v2Tag(file) ? file.ID3v2Tag() : nullptr; - if (pID3v2Tag) { - taglib::importTrackMetadataFromID3v2Tag(pTrackMetadata, *pID3v2Tag); - taglib::importCoverImageFromID3v2Tag(pCoverImage, *pID3v2Tag); + if (!taglib::readAudioProperties(pTrackMetadata, file)) { + break; + } + // ID3v2 tag takes precedence over APE tag + if (taglib::hasID3v2Tag(file)) { + const TagLib::ID3v2::Tag* pTag = file.ID3v2Tag(); + DEBUG_ASSERT(pTag); + taglib::importTrackMetadataFromID3v2Tag(pTrackMetadata, *pTag); + taglib::importCoverImageFromID3v2Tag(pCoverImage, *pTag); + return afterImport(ImportResult::Succeeded); + } else if (taglib::hasAPETag(file)) { + const TagLib::APE::Tag* pTag = file.APETag(); + DEBUG_ASSERT(pTag); + taglib::importTrackMetadataFromAPETag(pTrackMetadata, *pTag); + taglib::importCoverImageFromAPETag(pCoverImage, *pTag); + return afterImport(ImportResult::Succeeded); + } else if (taglib::hasID3v1Tag(file)) { + // Note (TagLib 1.1.11): TagLib::MPEG::File::tag() returns a + // valid pointer even if neither an ID3v2 nor an ID3v1 tag is + // present! + // See also: https://bugs.launchpad.net/mixxx/+bug/1865957 + const TagLib::Tag* pTag = file.tag(); + if (pTag) { + taglib::importTrackMetadataFromTag(pTrackMetadata, *pTag); return afterImport(ImportResult::Succeeded); - } else { - const TagLib::APE::Tag* pAPETag = - taglib::hasAPETag(file) ? file.APETag() : nullptr; - if (pAPETag) { - taglib::importTrackMetadataFromAPETag(pTrackMetadata, *pAPETag); - taglib::importCoverImageFromAPETag(pCoverImage, *pAPETag); - return afterImport(ImportResult::Succeeded); - } else { - // fallback - const TagLib::Tag* pTag(file.tag()); - if (pTag) { - taglib::importTrackMetadataFromTag(pTrackMetadata, *pTag); - return afterImport(ImportResult::Succeeded); - } - } } } break; } case taglib::FileType::MP4: { TagLib::MP4::File file(TAGLIB_FILENAME_FROM_QSTRING(m_fileName)); - if (taglib::readAudioProperties(pTrackMetadata, file)) { - const TagLib::MP4::Tag* pMP4Tag = file.tag(); - if (pMP4Tag) { - taglib::importTrackMetadataFromMP4Tag(pTrackMetadata, *pMP4Tag); - taglib::importCoverImageFromMP4Tag(pCoverImage, *pMP4Tag); - return afterImport(ImportResult::Succeeded); - } else { - // fallback - const TagLib::Tag* pTag(file.tag()); - if (pTag) { - taglib::importTrackMetadataFromTag(pTrackMetadata, *pTag); - return afterImport(ImportResult::Succeeded); - } - } + if (!taglib::readAudioProperties(pTrackMetadata, file)) { + break; + } + if (taglib::hasMP4Tag(file)) { + const TagLib::MP4::Tag* pTag = file.tag(); + DEBUG_ASSERT(pTag); + taglib::importTrackMetadataFromMP4Tag(pTrackMetadata, *pTag); + taglib::importCoverImageFromMP4Tag(pCoverImage, *pTag); + return afterImport(ImportResult::Succeeded); } break; } case taglib::FileType::FLAC: { TagLib::FLAC::File file(TAGLIB_FILENAME_FROM_QSTRING(m_fileName)); - // Read cover art directly from the file first. Will be - // overwritten with cover art contained in on of the tags. - if (pCoverImage != nullptr) { + if (!taglib::readAudioProperties(pTrackMetadata, file)) { + break; + } + bool importSucceeded = false; + bool coverImageImported = false; + // VorbisComment tag takes precedence over ID3v2 tag + if (taglib::hasXiphComment(file)) { + TagLib::Ogg::XiphComment* pTag = file.xiphComment(); + DEBUG_ASSERT(pTag); + taglib::importTrackMetadataFromVorbisCommentTag(pTrackMetadata, *pTag); + coverImageImported = taglib::importCoverImageFromVorbisCommentTag(pCoverImage, *pTag); + importSucceeded = true; + } else if (taglib::hasID3v2Tag(file)) { + const TagLib::ID3v2::Tag* pTag = file.ID3v2Tag(); + DEBUG_ASSERT(pTag); + taglib::importTrackMetadataFromID3v2Tag(pTrackMetadata, *pTag); + coverImageImported = taglib::importCoverImageFromID3v2Tag(pCoverImage, *pTag); + importSucceeded = true; + } + // Only import cover images from picture list as a fallback if file tags + // are available but no cover image has been found yet! Otherwise until + // file tags have been successfully imported once, Mixxx would retry to + // import the missing file tags over and over again when loading the + // cover image. + if (pCoverImage && // cover image is requested + importSucceeded && + !coverImageImported) { // no cover image found in file tags + // Read cover art directly from the file as a fallback *pCoverImage = taglib::importCoverImageFromVorbisCommentPictureList(file.pictureList()); } - if (taglib::readAudioProperties(pTrackMetadata, file)) { - // VorbisComment tag takes precedence over ID3v2 tag - TagLib::Ogg::XiphComment* pXiphComment = - taglib::hasXiphComment(file) ? file.xiphComment() : nullptr; - if (pXiphComment) { - taglib::importTrackMetadataFromVorbisCommentTag(pTrackMetadata, *pXiphComment); - taglib::importCoverImageFromVorbisCommentTag(pCoverImage, *pXiphComment); - return afterImport(ImportResult::Succeeded); - } else { - const TagLib::ID3v2::Tag* pID3v2Tag = - taglib::hasID3v2Tag(file) ? file.ID3v2Tag() : nullptr; - if (pID3v2Tag) { - taglib::importTrackMetadataFromID3v2Tag(pTrackMetadata, *pID3v2Tag); - taglib::importCoverImageFromID3v2Tag(pCoverImage, *pID3v2Tag); - return afterImport(ImportResult::Succeeded); - } else { - // fallback - const TagLib::Tag* pTag(file.tag()); - if (pTag) { - taglib::importTrackMetadataFromTag(pTrackMetadata, *pTag); - return afterImport(ImportResult::Succeeded); - } - } - } + if (importSucceeded) { + return afterImport(ImportResult::Succeeded); } break; } case taglib::FileType::OGG: { TagLib::Ogg::Vorbis::File file(TAGLIB_FILENAME_FROM_QSTRING(m_fileName)); - if (taglib::readAudioProperties(pTrackMetadata, file)) { - TagLib::Ogg::XiphComment* pXiphComment = file.tag(); - if (pXiphComment) { - taglib::importTrackMetadataFromVorbisCommentTag(pTrackMetadata, *pXiphComment); - taglib::importCoverImageFromVorbisCommentTag(pCoverImage, *pXiphComment); - return afterImport(ImportResult::Succeeded); - } else { - // fallback - const TagLib::Tag* pTag(file.tag()); - if (pTag) { - taglib::importTrackMetadataFromTag(pTrackMetadata, *pTag); - return afterImport(ImportResult::Succeeded); - } - } + if (!taglib::readAudioProperties(pTrackMetadata, file)) { + break; + } + TagLib::Ogg::XiphComment* pTag = file.tag(); + if (pTag) { + taglib::importTrackMetadataFromVorbisCommentTag(pTrackMetadata, *pTag); + taglib::importCoverImageFromVorbisCommentTag(pCoverImage, *pTag); + return afterImport(ImportResult::Succeeded); } break; } #if (TAGLIB_HAS_OPUSFILE) case taglib::FileType::OPUS: { TagLib::Ogg::Opus::File file(TAGLIB_FILENAME_FROM_QSTRING(m_fileName)); - if (taglib::readAudioProperties(pTrackMetadata, file)) { - TagLib::Ogg::XiphComment* pXiphComment = file.tag(); - if (pXiphComment) { - taglib::importTrackMetadataFromVorbisCommentTag(pTrackMetadata, *pXiphComment); - taglib::importCoverImageFromVorbisCommentTag(pCoverImage, *pXiphComment); - return afterImport(ImportResult::Succeeded); - } else { - // fallback - const TagLib::Tag* pTag(file.tag()); - if (pTag) { - taglib::importTrackMetadataFromTag(pTrackMetadata, *pTag); - return afterImport(ImportResult::Succeeded); - } - } + if (!taglib::readAudioProperties(pTrackMetadata, file)) { + break; + } + TagLib::Ogg::XiphComment* pTag = file.tag(); + if (pTag) { + taglib::importTrackMetadataFromVorbisCommentTag(pTrackMetadata, *pTag); + taglib::importCoverImageFromVorbisCommentTag(pCoverImage, *pTag); + return afterImport(ImportResult::Succeeded); } break; } #endif // TAGLIB_HAS_OPUSFILE case taglib::FileType::WV: { TagLib::WavPack::File file(TAGLIB_FILENAME_FROM_QSTRING(m_fileName)); - if (taglib::readAudioProperties(pTrackMetadata, file)) { - const TagLib::APE::Tag* pAPETag = - taglib::hasAPETag(file) ? file.APETag() : nullptr; - if (pAPETag) { - taglib::importTrackMetadataFromAPETag(pTrackMetadata, *pAPETag); - taglib::importCoverImageFromAPETag(pCoverImage, *pAPETag); - return afterImport(ImportResult::Succeeded); - } else { - // fallback - const TagLib::Tag* pTag(file.tag()); - if (pTag) { - taglib::importTrackMetadataFromTag(pTrackMetadata, *pTag); - return afterImport(ImportResult::Succeeded); - } - } + if (!taglib::readAudioProperties(pTrackMetadata, file)) { + break; + } + if (taglib::hasAPETag(file)) { + const TagLib::APE::Tag* pTag = file.APETag(); + DEBUG_ASSERT(pTag); + taglib::importTrackMetadataFromAPETag(pTrackMetadata, *pTag); + taglib::importCoverImageFromAPETag(pCoverImage, *pTag); + return afterImport(ImportResult::Succeeded); } break; } case taglib::FileType::WAV: { TagLib::RIFF::WAV::File file(TAGLIB_FILENAME_FROM_QSTRING(m_fileName)); - if (taglib::readAudioProperties(pTrackMetadata, file)) { + if (!taglib::readAudioProperties(pTrackMetadata, file)) { + break; + } + if (taglib::hasID3v2Tag(file)) { #if (TAGLIB_HAS_WAV_ID3V2TAG) - const TagLib::ID3v2::Tag* pID3v2Tag = - file.hasID3v2Tag() ? file.ID3v2Tag() : nullptr; + const TagLib::ID3v2::Tag* pTag = file.ID3v2Tag(); #else - const TagLib::ID3v2::Tag* pID3v2Tag = file.tag(); + const TagLib::ID3v2::Tag* pTag = file.tag(); #endif - if (pID3v2Tag) { - taglib::importTrackMetadataFromID3v2Tag(pTrackMetadata, *pID3v2Tag); - taglib::importCoverImageFromID3v2Tag(pCoverImage, *pID3v2Tag); - return afterImport(ImportResult::Succeeded); - } else { - // fallback - const TagLib::RIFF::Info::Tag* pTag = file.InfoTag(); - if (pTag) { - taglib::importTrackMetadataFromRIFFTag(pTrackMetadata, *pTag); - return afterImport(ImportResult::Succeeded); - } - } + DEBUG_ASSERT(pTag); + taglib::importTrackMetadataFromID3v2Tag(pTrackMetadata, *pTag); + taglib::importCoverImageFromID3v2Tag(pCoverImage, *pTag); + return afterImport(ImportResult::Succeeded); + } else if (file.hasInfoTag()) { + const TagLib::RIFF::Info::Tag* pTag = file.InfoTag(); + DEBUG_ASSERT(pTag); + taglib::importTrackMetadataFromRIFFTag(pTrackMetadata, *pTag); + return afterImport(ImportResult::Succeeded); } break; } case taglib::FileType::AIFF: { AiffFile file(TAGLIB_FILENAME_FROM_QSTRING(m_fileName)); - if (taglib::readAudioProperties(pTrackMetadata, file)) { -#if (TAGLIB_HAS_AIFF_HAS_ID3V2TAG) - const TagLib::ID3v2::Tag* pID3v2Tag = file.hasID3v2Tag() ? file.tag() : nullptr; -#else - const TagLib::ID3v2::Tag* pID3v2Tag = file.tag(); -#endif - if (pID3v2Tag) { - taglib::importTrackMetadataFromID3v2Tag(pTrackMetadata, *pID3v2Tag); - taglib::importCoverImageFromID3v2Tag(pCoverImage, *pID3v2Tag); - return afterImport(ImportResult::Succeeded); - } else { - // fallback - if (file.importTrackMetadataFromTextChunks(pTrackMetadata)) { - return afterImport(ImportResult::Succeeded); - } - } + if (!taglib::readAudioProperties(pTrackMetadata, file)) { + break; + } + if (taglib::hasID3v2Tag(file)) { + const TagLib::ID3v2::Tag* pTag = file.tag(); + DEBUG_ASSERT(pTag); + taglib::importTrackMetadataFromID3v2Tag(pTrackMetadata, *pTag); + taglib::importCoverImageFromID3v2Tag(pCoverImage, *pTag); + return afterImport(ImportResult::Succeeded); + } else if (file.importTrackMetadataFromTextChunks(pTrackMetadata)) { + return afterImport(ImportResult::Succeeded); } break; } @@ -314,12 +288,10 @@ MetadataSourceTagLib::importTrackMetadataAndCoverImage( return afterImport(ImportResult::Failed); } - if (kLogger.debugEnabled()) { - kLogger.debug() - << "No track metadata or cover art found" - << "in file" << m_fileName - << "with type" << m_fileType; - } + kLogger.info() + << "No track metadata or cover art found" + << "in file" << m_fileName + << "with type" << m_fileType; return afterImport(ImportResult::Unavailable); } diff --git a/src/track/trackmetadatataglib.cpp b/src/track/trackmetadatataglib.cpp index d34e35c82ad..d70673fee28 100644 --- a/src/track/trackmetadatataglib.cpp +++ b/src/track/trackmetadatataglib.cpp @@ -1,5 +1,7 @@ #include "track/trackmetadatataglib.h" +#include + #include "track/tracknumbers.h" #include "util/assert.h" @@ -17,6 +19,10 @@ #define TAGLIB_HAS_TAG_CHECK \ (TAGLIB_MAJOR_VERSION > 1) || ((TAGLIB_MAJOR_VERSION == 1) && (TAGLIB_MINOR_VERSION >= 9)) +// TagLib has support for MP4::File::hasMP4Tag() and MP4::Tag::isEmpty() version 1.10 +#define TAGLIB_HAS_MP4TAG_CHECK_AND_IS_EMPTY \ + (TAGLIB_MAJOR_VERSION > 1) || ((TAGLIB_MAJOR_VERSION == 1) && (TAGLIB_MINOR_VERSION >= 10)) + // TagLib has support for length in milliseconds since version 1.10 #define TAGLIB_HAS_LENGTH_IN_MILLISECONDS \ (TAGLIB_MAJOR_VERSION > 1) || ((TAGLIB_MAJOR_VERSION == 1) && (TAGLIB_MINOR_VERSION >= 10)) @@ -161,6 +167,33 @@ bool hasAPETag(TagLib::WavPack::File& file) { #endif } +bool hasID3v2Tag(TagLib::RIFF::WAV::File& file) { +#if (TAGLIB_HAS_WAV_ID3V2TAG) + return file.hasID3v2Tag(); +#else + return file.tag() != nullptr; +#endif +} + +bool hasID3v2Tag(TagLib::RIFF::AIFF::File& file) { +#if (TAGLIB_HAS_AIFF_HAS_ID3V2TAG) + return file.hasID3v2Tag(); +#else + return file.tag() != nullptr; +#endif +} + +bool hasMP4Tag(TagLib::MP4::File& file) { + // Note (TagLib 1.11.1): For MP4 files without file tags + // TagLib still reports that the MP4 tag exists. Additionally + // we need to check that the tag itself is not empty. +#if (TAGLIB_HAS_MP4TAG_CHECK_AND_IS_EMPTY) + return file.hasMP4Tag() && !file.tag()->isEmpty(); +#else + return file.tag() != nullptr; +#endif +} + namespace { // Preferred picture types for cover art sorted by priority @@ -1135,9 +1168,9 @@ const T* downcastID3v2Frame(TagLib::ID3v2::Frame* frame) { return downcastFrame; } -void importCoverImageFromID3v2Tag(QImage* pCoverArt, const TagLib::ID3v2::Tag& tag) { +bool importCoverImageFromID3v2Tag(QImage* pCoverArt, const TagLib::ID3v2::Tag& tag) { if (!pCoverArt) { - return; // nothing to do + return false; // nothing to do } const auto iterAPIC = tag.frameListMap().find("APIC"); @@ -1145,7 +1178,7 @@ void importCoverImageFromID3v2Tag(QImage* pCoverArt, const TagLib::ID3v2::Tag& t if (kLogger.debugEnabled()) { kLogger.debug() << "No cover art: None or empty list of ID3v2 APIC frames"; } - return; // abort + return false; // abort } const TagLib::ID3v2::FrameList pFrames = iterAPIC->second; @@ -1162,7 +1195,7 @@ void importCoverImageFromID3v2Tag(QImage* pCoverArt, const TagLib::ID3v2::Tag& t continue; } else { *pCoverArt = image; - return; // success + return true; // success } } } @@ -1181,15 +1214,20 @@ void importCoverImageFromID3v2Tag(QImage* pCoverArt, const TagLib::ID3v2::Tag& t continue; } else { *pCoverArt = image; - return; // success + return true; // success } } } + + if (kLogger.debugEnabled()) { + kLogger.debug() << "No cover art found in ID3v2 tag"; + } + return false; } -void importCoverImageFromAPETag(QImage* pCoverArt, const TagLib::APE::Tag& tag) { +bool importCoverImageFromAPETag(QImage* pCoverArt, const TagLib::APE::Tag& tag) { if (!pCoverArt) { - return; // nothing to do + return false; // nothing to do } if (tag.itemListMap().contains("COVER ART (FRONT)")) { @@ -1205,14 +1243,20 @@ void importCoverImageFromAPETag(QImage* pCoverArt, const TagLib::APE::Tag& tag) << "Failed to load image from APE tag"; } else { *pCoverArt = image; // success + return true; } } } + + if (kLogger.debugEnabled()) { + kLogger.debug() << "No cover art found in APE tag"; + } + return false; } -void importCoverImageFromVorbisCommentTag(QImage* pCoverArt, TagLib::Ogg::XiphComment& tag) { +bool importCoverImageFromVorbisCommentTag(QImage* pCoverArt, TagLib::Ogg::XiphComment& tag) { if (!pCoverArt) { - return; // nothing to do + return false; // nothing to do } #if (TAGLIB_HAS_VORBIS_COMMENT_PICTURES) @@ -1220,7 +1264,7 @@ void importCoverImageFromVorbisCommentTag(QImage* pCoverArt, TagLib::Ogg::XiphCo importCoverImageFromVorbisCommentPictureList(tag.pictureList()); if (!image.isNull()) { *pCoverArt = image; - return; // done + return false; // done } #endif @@ -1254,7 +1298,7 @@ void importCoverImageFromVorbisCommentTag(QImage* pCoverArt, TagLib::Ogg::XiphCo continue; } else { *pCoverArt = image; - return; // done + return true; // done } } else { kLogger.warning() @@ -1282,18 +1326,20 @@ void importCoverImageFromVorbisCommentTag(QImage* pCoverArt, TagLib::Ogg::XiphCo continue; } else { *pCoverArt = image; - return; // done + return true; // done } } } + if (kLogger.debugEnabled()) { kLogger.debug() << "No cover art found in VorbisComment tag"; } + return false; } -void importCoverImageFromMP4Tag(QImage* pCoverArt, const TagLib::MP4::Tag& tag) { +bool importCoverImageFromMP4Tag(QImage* pCoverArt, const TagLib::MP4::Tag& tag) { if (!pCoverArt) { - return; // nothing to do + return false; // nothing to do } if (getItemListMap(tag).contains("covr")) { @@ -1307,10 +1353,15 @@ void importCoverImageFromMP4Tag(QImage* pCoverArt, const TagLib::MP4::Tag& tag) continue; } else { *pCoverArt = image; - return; // done + return true; // done } } } + + if (kLogger.debugEnabled()) { + kLogger.debug() << "No cover art found in MP4 tag"; + } + return false; } void importTrackMetadataFromTag( diff --git a/src/track/trackmetadatataglib.h b/src/track/trackmetadatataglib.h index 9251c093499..b15838b51e6 100644 --- a/src/track/trackmetadatataglib.h +++ b/src/track/trackmetadatataglib.h @@ -6,8 +6,11 @@ #include #include +#include #include +#include #include +#include #include // TagLib has support for the Ogg Opus file format since version 1.9 @@ -53,10 +56,10 @@ bool readAudioProperties( TrackMetadata* pTrackMetadata, const TagLib::File& file); -void importCoverImageFromID3v2Tag(QImage* pCoverArt, const TagLib::ID3v2::Tag& tag); -void importCoverImageFromAPETag(QImage* pCoverArt, const TagLib::APE::Tag& tag); -void importCoverImageFromVorbisCommentTag(QImage* pCoverArt, TagLib::Ogg::XiphComment& tag); -void importCoverImageFromMP4Tag(QImage* pCoverArt, const TagLib::MP4::Tag& tag); +bool importCoverImageFromID3v2Tag(QImage* pCoverArt, const TagLib::ID3v2::Tag& tag); +bool importCoverImageFromAPETag(QImage* pCoverArt, const TagLib::APE::Tag& tag); +bool importCoverImageFromVorbisCommentTag(QImage* pCoverArt, TagLib::Ogg::XiphComment& tag); +bool importCoverImageFromMP4Tag(QImage* pCoverArt, const TagLib::MP4::Tag& tag); QImage importCoverImageFromVorbisCommentPictureList(const TagLib::List& pictures); @@ -141,6 +144,9 @@ bool hasAPETag(TagLib::MPEG::File& file); bool hasID3v2Tag(TagLib::FLAC::File& file); bool hasXiphComment(TagLib::FLAC::File& file); bool hasAPETag(TagLib::WavPack::File& file); +bool hasID3v2Tag(TagLib::RIFF::WAV::File& file); +bool hasID3v2Tag(TagLib::RIFF::AIFF::File& file); +bool hasMP4Tag(TagLib::MP4::File& file); } // namespace taglib