From bd6a08facbc0498b82aa007fe75bdb0c00400018 Mon Sep 17 00:00:00 2001 From: emeric Date: Mon, 23 Mar 2020 20:16:12 +0100 Subject: [PATCH] WMA: better metadata parsing. fixes #40 --- src/libs/database/impl/Session.cpp | 10 +- src/libs/metadata/impl/TagLibParser.cpp | 262 ++++++++++-------- .../include/metadata/TagLibParser.hpp | 9 +- 3 files changed, 160 insertions(+), 121 deletions(-) diff --git a/src/libs/database/impl/Session.cpp b/src/libs/database/impl/Session.cpp index 35eeb7df..63586f17 100644 --- a/src/libs/database/impl/Session.cpp +++ b/src/libs/database/impl/Session.cpp @@ -40,7 +40,7 @@ namespace Database { -#define LMS_DATABASE_VERSION 13 +#define LMS_DATABASE_VERSION 14 using Version = std::size_t; @@ -153,7 +153,13 @@ CREATE TABLE IF NOT EXISTS "track_bookmark" ( } else if (version == 12) { - // Artist and release that have a baddly parsed name but a MBID had no chance to updat the name + // Artist and release that have a badly parsed name but a MBID had no chance to updat the name + // Just increment the scan version of the settings to make the next scheduled scan rescan everything + ScanSettings::get(*this).modify()->incScanVersion(); + } + else if (version == 13) + { + // Always store UUID in lower case + better WMA parsing // Just increment the scan version of the settings to make the next scheduled scan rescan everything ScanSettings::get(*this).modify()->incScanVersion(); } diff --git a/src/libs/metadata/impl/TagLibParser.cpp b/src/libs/metadata/impl/TagLibParser.cpp index 78949ce8..fee48f25 100644 --- a/src/libs/metadata/impl/TagLibParser.cpp +++ b/src/libs/metadata/impl/TagLibParser.cpp @@ -154,12 +154,118 @@ getAlbum(const TagLib::PropertyMap& properties) return Album {std::move(albumName.front()), albumMBID.front()}; } +void +TagLibParser::processTag(Track& track, const std::string& tag, const TagLib::StringList& values, bool debug) +{ + + // TODO validate MBID format + if (debug) + { + std::vector strs; + std::transform(values.begin(), values.end(), std::back_inserter(strs), [](const auto& value) { return value.to8Bit(true); }); + + std::cout << "[" << tag << "] = " << StringUtils::joinStrings(strs, "*SEP*") << std::endl; + } + + if (tag.empty() || values.isEmpty() || values.front().isEmpty()) + return; + + std::string value {StringUtils::stringTrim(values.front().to8Bit(true))}; + + if (tag == "TITLE") + track.title = value; + else if (tag == "MUSICBRAINZ_RELEASETRACKID" + || tag == "MUSICBRAINZ RELEASE TRACK ID") + { + track.musicBrainzTrackID = UUID::fromString(value); + } + else if (tag == "MUSICBRAINZ_TRACKID" + || tag == "MUSICBRAINZ TRACK ID") + track.musicBrainzRecordID = UUID::fromString(value); + else if (tag == "ACOUSTID_ID") + track.acoustID = UUID::fromString(value); + else if (tag == "TRACKTOTAL") + { + auto totalTrack = StringUtils::readAs(value); + if (totalTrack) + track.totalTrack = totalTrack; + } + else if (tag == "TRACKNUMBER") + { + // Expecting 'Number/Total' + std::vector strings {splitAndTrimString(value, "/")}; + + if (!strings.empty()) + { + track.trackNumber = StringUtils::readAs(strings[0]); + + // Lower priority than TRACKTOTAL + if (strings.size() > 1 && !track.totalTrack) + track.totalTrack = StringUtils::readAs(strings[1]); + } + } + else if (tag == "DISCTOTAL") + { + auto totalDisc = StringUtils::readAs(value); + if (totalDisc) + track.totalDisc = totalDisc; + } + else if (tag == "DISCNUMBER") + { + // Expecting 'Number/Total' + std::vector strings {StringUtils::splitString(value, "/")}; + + if (!strings.empty()) + { + track.discNumber = StringUtils::readAs(strings[0]); + + // Lower priority than DISCTOTAL + if (strings.size() > 1 && !track.totalDisc) + track.totalDisc = StringUtils::readAs(strings[1]); + } + } + else if (tag == "DATE") + track.year = StringUtils::readAs(value); + else if (tag == "ORIGINALDATE" && !track.originalYear) + { + // Lower priority than ORIGINALYEAR + track.originalYear = StringUtils::readAs(value); + } + else if (tag == "ORIGINALYEAR") + { + // Higher priority than ORIGINALDATE + auto originalYear = StringUtils::readAs(value); + if (originalYear) + track.originalYear = originalYear; + } + else if (tag == "METADATA_BLOCK_PICTURE") + track.hasCover = true; + else if (tag == "COPYRIGHT") + track.copyright = value; + else if (tag == "COPYRIGHTURL") + track.copyrightURL = value; + else if (_clusterTypeNames.find(tag) != _clusterTypeNames.end()) + { + std::set clusterNames; + for (const auto& valueList : values) + { + auto values = splitAndTrimString(valueList.to8Bit(true), "/,;"); + + for (const auto& value : values) + clusterNames.insert(value); + } + + if (!clusterNames.empty()) + track.clusters[tag] = clusterNames; + } +} + std::optional TagLibParser::parse(const std::filesystem::path& p, bool debug) { TagLib::FileRef f {p.string().c_str(), - true, // read audio properties - TagLib::AudioProperties::Fast}; + true, // read audio properties + TagLib::AudioProperties::Fast}; if (f.isNull()) { @@ -184,14 +290,40 @@ TagLibParser::parse(const std::filesystem::path& p, bool debug) track.audioStreams = {std::move(audioStream)}; } + TagLib::PropertyMap properties {f.file()->properties()}; + // Not that good embedded pictures handling // WMA if (TagLib::ASF::File* asfFile {dynamic_cast(f.file())}) { const TagLib::ASF::Tag* tag {asfFile->tag()}; - if (tag && tag->attributeListMap().contains("WM/Picture")) - track.hasCover = true; + if (tag) + { + if (tag->attributeListMap().contains("WM/Picture")) + track.hasCover = true; + + for (const auto& [name, attributeList] : tag->attributeListMap()) + { + if (name.to8Bit().find("WM/") == 0 || properties.contains(name)) + continue; + + TagLib::StringList stringAttributeList; + for (const auto& attribute : attributeList) + { + if (attribute.type() == TagLib::ASF::Attribute::AttributeTypes::UnicodeType) + stringAttributeList.append(attribute.toString()); + } + + if (!stringAttributeList.isEmpty()) + { + if (debug) + std::cout << "Property: '" << name << "'" << std::endl; + + properties.insert(name, stringAttributeList); + } + } + } } // MP3 else if (TagLib::MPEG::File* mp3File {dynamic_cast(f.file())}) @@ -209,124 +341,18 @@ TagLibParser::parse(const std::filesystem::path& p, bool debug) track.hasCover = true; } - if (f.tag()) + for(const auto& property : properties) { - MetaData::Clusters clusters; - const TagLib::PropertyMap& properties {f.file()->properties()}; - - for(const auto& property : properties) - { - const std::string tag {property.first.upper().to8Bit(true)}; - const TagLib::StringList& values {property.second}; - - // TODO validate MBID format - if (debug) - { - std::vector strs; - std::transform(values.begin(), values.end(), std::back_inserter(strs), [](const auto& value) { return value.to8Bit(true); }); - - std::cout << "[" << tag << "] = " << StringUtils::joinStrings(strs, "*SEP*") << std::endl; - } - - if (tag.empty() || values.isEmpty() || values.front().isEmpty()) - continue; - - std::string value {StringUtils::stringTrim(values.front().to8Bit(true))}; - - if (tag == "TITLE") - track.title = value; - else if (tag == "MUSICBRAINZ_RELEASETRACKID" - || tag == "MUSICBRAINZ RELEASE TRACK ID") - { - track.musicBrainzTrackID = UUID::fromString(value); - } - else if (tag == "MUSICBRAINZ_TRACKID" - || tag == "MUSICBRAINZ TRACK ID") - track.musicBrainzRecordID = UUID::fromString(value); - else if (tag == "ACOUSTID_ID") - track.acoustID = UUID::fromString(value); - else if (tag == "TRACKTOTAL") - { - auto totalTrack = StringUtils::readAs(value); - if (totalTrack) - track.totalTrack = totalTrack; - } - else if (tag == "TRACKNUMBER") - { - // Expecting 'Number/Total' - std::vector strings {splitAndTrimString(value, "/")}; - - if (!strings.empty()) - { - track.trackNumber = StringUtils::readAs(strings[0]); - - // Lower priority than TRACKTOTAL - if (strings.size() > 1 && !track.totalTrack) - track.totalTrack = StringUtils::readAs(strings[1]); - } - } - else if (tag == "DISCTOTAL") - { - auto totalDisc = StringUtils::readAs(value); - if (totalDisc) - track.totalDisc = totalDisc; - } - else if (tag == "DISCNUMBER") - { - // Expecting 'Number/Total' - std::vector strings {StringUtils::splitString(value, "/")}; - - if (!strings.empty()) - { - track.discNumber = StringUtils::readAs(strings[0]); - - // Lower priority than DISCTOTAL - if (strings.size() > 1 && !track.totalDisc) - track.totalDisc = StringUtils::readAs(strings[1]); - } - } - else if (tag == "DATE") - track.year = StringUtils::readAs(value); - else if (tag == "ORIGINALDATE" && !track.originalYear) - { - // Lower priority than ORIGINALYEAR - track.originalYear = StringUtils::readAs(value); - } - else if (tag == "ORIGINALYEAR") - { - // Higher priority than ORIGINALDATE - auto originalYear = StringUtils::readAs(value); - if (originalYear) - track.originalYear = originalYear; - } - else if (tag == "METADATA_BLOCK_PICTURE") - track.hasCover = true; - else if (tag == "COPYRIGHT") - track.copyright = value; - else if (tag == "COPYRIGHTURL") - track.copyrightURL = value; - else if (_clusterTypeNames.find(tag) != _clusterTypeNames.end()) - { - std::set clusterNames; - for (const auto& valueList : values) - { - auto values = splitAndTrimString(valueList.to8Bit(true), "/,;"); - - for (const auto& value : values) - clusterNames.insert(value); - } - - if (!clusterNames.empty()) - track.clusters[tag] = clusterNames; - } - } - - track.artists = getArtists(properties); - track.albumArtists = getAlbumArtists(properties); - track.album = getAlbum(properties); + const std::string tag {property.first.upper().to8Bit(true)}; + const TagLib::StringList& values {property.second}; + processTag(track, tag, values, debug); } + track.artists = getArtists(properties); + track.albumArtists = getAlbumArtists(properties); + track.album = getAlbum(properties); + return track; } diff --git a/src/libs/metadata/include/metadata/TagLibParser.hpp b/src/libs/metadata/include/metadata/TagLibParser.hpp index 1c055b76..ee298f8d 100644 --- a/src/libs/metadata/include/metadata/TagLibParser.hpp +++ b/src/libs/metadata/include/metadata/TagLibParser.hpp @@ -21,14 +21,21 @@ #include "metadata/IParser.hpp" +namespace TagLib +{ + class StringList; +} + namespace MetaData { // Parse that makes use of AvFormat class TagLibParser : public IParser { - public: + private: std::optional parse(const std::filesystem::path& p, bool debug = false) override; + + void processTag(Track& track, const std::string& tag, const TagLib::StringList& values, bool debug); }; } // namespace MetaData