diff --git a/src/libs/metadata/impl/AvFormatParser.cpp b/src/libs/metadata/impl/AvFormatParser.cpp index c959ae97..8b3938f6 100644 --- a/src/libs/metadata/impl/AvFormatParser.cpp +++ b/src/libs/metadata/impl/AvFormatParser.cpp @@ -64,24 +64,6 @@ findFirstValueOfAs(const Av::IAudioFile::MetadataMap& metadataMap, std::initiali return res; } - -static -std::optional -getRelease(const Av::IAudioFile::MetadataMap& metadataMap) -{ - std::optional res; - - std::optional releaseName {findFirstValueOfAs(metadataMap, {"ALBUM"})}; - if (!releaseName) - return res; - - res.emplace(); - res->name = *releaseName; - res->mbid = findFirstValueOfAs(metadataMap, {"MUSICBRAINZ ALBUM ID", "MUSICBRAINZ_ALBUMID", "MUSICBRAINZ/ALBUM ID"}); - - return res; -} - static std::vector getReleaseArtists(const Av::IAudioFile::MetadataMap& metadataMap) @@ -126,6 +108,75 @@ getArtists(const Av::IAudioFile::MetadataMap& metadataMap) return artists; } +static +std::optional +getRelease(const Av::IAudioFile::MetadataMap& metadataMap) +{ + std::optional res; + + std::optional releaseName {findFirstValueOfAs(metadataMap, {"ALBUM", "TALB", "WM/ALBUMTITLE"})}; + if (!releaseName) + return res; + + res.emplace(); + res->name = std::move(*releaseName); + res->mbid = findFirstValueOfAs(metadataMap, {"MUSICBRAINZ ALBUM ID", "MUSICBRAINZ_ALBUMID", "MUSICBRAINZ/ALBUM ID"}); + res->artists = getReleaseArtists(metadataMap); + res->mediumCount = findFirstValueOfAs(metadataMap, {"TOTALDISCS", "DISCTOTAL"}); + if (!res->mediumCount) + { + // mediumCount may be encoded as position/count + if (const auto value {findFirstValueOfAs(metadataMap, {"TPOS", "DISC", "DISK", "DISCNUMBER", "WM/PARTOFSET"})}) + { + // Expecting 'Number/Total' + const std::vector strings {StringUtils::splitString(*value, "/") }; + if (strings.size() == 2) + res->mediumCount = StringUtils::readAs(strings[1]); + } + } + + return res; +} + +static +std::optional +getMedium(const Av::IAudioFile::MetadataMap& metadataMap) +{ + std::optional res; + res.emplace(); + + res->type = findFirstValueOfAs(metadataMap, {"TMED", "MEDIA", "WM/MEDIA"}).value_or(""); + res->name = findFirstValueOfAs(metadataMap, {"TSST", "DISCSUBTITLE", "SETSUBTITLE"}).value_or(""); + res->trackCount = findFirstValueOfAs(metadataMap, {"TOTALTRACKS", "TRACKTOTAL"}); + if (!res->trackCount) + { + // totalTracks may be encoded as "position/count" + if (const auto value {findFirstValueOfAs(metadataMap, {"TRCK", "TRACK", "TRACKNUMBER", "TRKN", "WM/TRACKNUMBER"})}) + { + // Expecting 'Number/Total' + const std::vector strings {StringUtils::splitString(*value, "/") }; + if (strings.size() == 2) + res->trackCount = StringUtils::readAs(strings[1]); + } + } + + // position may be encoded in TPOS/DISC/DISK as "position/count". Expecting 'Number[/Total]' + res->position = findFirstValueOfAs(metadataMap, {"TPOS", "DISC", "DISK", "DISCNUMBER", "WM/PARTOFSET"}); + res->release = getRelease(metadataMap); + + if (res->type.empty() + && res->name.empty() + && !res->trackCount + && !res->position + && !res->release + && !res->replayGain) + { + res.reset(); + } + + return res; +} + std::optional AvFormatParser::parse(const std::filesystem::path& p, bool debug) { @@ -154,16 +205,7 @@ AvFormatParser::parse(const std::filesystem::path& p, bool debug) const Av::IAudioFile::MetadataMap metadataMap {mediaFile->getMetaData()}; track.artists = getArtists(metadataMap); - track.release = getRelease(metadataMap); - if (track.release) - track.release->artists = getReleaseArtists(metadataMap); - - auto getOrCreateMedium = [&]() -> Medium& - { - if (!track.medium) - track.medium.emplace(); - return *track.medium; - }; + track.medium = getMedium(metadataMap); for (const auto& [tag, value] : metadataMap) { @@ -175,30 +217,11 @@ AvFormatParser::parse(const std::filesystem::path& p, bool debug) else if (tag == "TRACK") { // Expecting 'Number/Total' - const std::vector strings {StringUtils::splitString(value, "/") }; - if (strings.size() > 0) - { - track.position = StringUtils::readAs(strings[0]); - - if (strings.size() > 1) - getOrCreateMedium().trackCount = StringUtils::readAs(strings[1]); - } - } - else if (tag == "DISC") - { - // Expecting 'Number/Total' - const std::vector strings {StringUtils::splitString(value, "/")}; - if (strings.size() > 0) - { - getOrCreateMedium().position = StringUtils::readAs(strings[0]); - - if (strings.size() > 1 && track.release) - track.release->mediumCount = StringUtils::readAs(strings[1]); - } + track.position = StringUtils::readAs(value); } else if (tag == "DATE" || tag == "YEAR" - || tag == "WM/Year") + || tag == "WM/YEAR") { track.date = Utils::parseDate(value); } @@ -221,16 +244,6 @@ AvFormatParser::parse(const std::filesystem::path& p, bool debug) { track.recordingMBID = UUID::fromString(value); } - else if (tag == "MEDIA") - { - getOrCreateMedium().type = value; - } - else if (tag == "TSST" - || tag == "DISCSUBTITLE" - || tag == "SETSUBTITLE") - { - getOrCreateMedium().name = value; - } else if (_clusterTypeNames.find(tag) != _clusterTypeNames.end()) { const std::vector clusterNames {StringUtils::splitString(value, "/,;")}; diff --git a/src/libs/metadata/impl/TagLibParser.cpp b/src/libs/metadata/impl/TagLibParser.cpp index a3caa7f3..b9144658 100644 --- a/src/libs/metadata/impl/TagLibParser.cpp +++ b/src/libs/metadata/impl/TagLibParser.cpp @@ -81,6 +81,18 @@ getPropertyValuesFirstMatchAs(const TagMap& tags, const std::vector +std::optional +getPropertyValueFirstMatchAs(const TagMap& tags, const std::vector& keys) +{ + std::optional res; + std::vector values {getPropertyValuesFirstMatchAs(tags, keys)}; + if (!values.empty()) + res = std::move(values.front()); + + return res; +} + template std::vector getPropertyValuesAs(const TagMap& tags, const std::string& key) @@ -88,6 +100,13 @@ getPropertyValuesAs(const TagMap& tags, const std::string& key) return getPropertyValuesFirstMatchAs(tags, {key}); } +template +std::optional +getPropertyValueAs(const TagMap& tags, const std::string& key) +{ + return getPropertyValueFirstMatchAs(tags, {key}); +} + static std::vector splitAndTrimString(std::string_view str, std::string_view delimiters) @@ -185,20 +204,70 @@ getRelease(const TagMap& tags) { std::optional release; - std::vector releaseName {getPropertyValuesAs(tags, "ALBUM")}; - if (releaseName.empty()) + auto releaseName {getPropertyValueAs(tags, "ALBUM")}; + if (!releaseName) return release; - const std::vector releaseMBID {getPropertyValuesFirstMatchAs(tags, {"MUSICBRAINZ_ALBUMID", "MUSICBRAINZ ALBUM ID", "MUSICBRAINZ/ALBUM ID"})}; - release.emplace(); - release->name = std::move(releaseName.front()); - if (!releaseMBID.empty()) - release->mbid = releaseMBID.front(); + release->name = std::move(*releaseName); + release->mbid = getPropertyValueFirstMatchAs(tags, {"MUSICBRAINZ_ALBUMID", "MUSICBRAINZ ALBUM ID", "MUSICBRAINZ/ALBUM ID"}); + release->artists = getArtists(tags, {"ALBUMARTISTS", "ALBUMARTIST"}, {"ALBUMARTISTSSORT", "ALBUMARTISTSORT"}, {"MUSICBRAINZ_ALBUMARTISTID", "MUSICBRAINZ ALBUM ARTIST ID", "MUSICBRAINZ/ALBUM ARTIST ID"}); + release->mediumCount = getPropertyValueAs(tags, "DISCTOTAL"); + if (!release->mediumCount) + { + // mediumCount may be encoded as "position/count" + if (const auto value {getPropertyValueAs(tags, "DISCNUMBER")}) + { + // Expecting 'Number/Total' + const std::vector strings {StringUtils::splitString(*value, "/") }; + if (strings.size() == 2) + release->mediumCount = StringUtils::readAs(strings[1]); + } + } return release; } +static +std::optional +getMedium(const TagMap& tags) +{ + std::optional medium; + medium.emplace(); + + medium->type = getPropertyValueAs(tags, "MEDIA").value_or(""); + medium->name = getPropertyValueFirstMatchAs(tags, {"DISCSUBTITLE", "SETSUBTITLE"}).value_or(""); + medium->trackCount = getPropertyValueAs(tags, "TRACKTOTAL"); + if (!medium->trackCount) + { + // totalTracks may be encoded as "position/count" + if (const auto value {getPropertyValueAs(tags, "TRACKNUMBER")}) + { + // Expecting 'Number/Total' + const std::vector strings {StringUtils::splitString(*value, "/") }; + if (strings.size() == 2) + medium->trackCount = StringUtils::readAs(strings[1]); + } + + } + // Expecting 'Number[/Total]' + medium->position = getPropertyValueAs(tags, "DISCNUMBER"); + medium->release = getRelease(tags); + medium->replayGain = getPropertyValueAs(tags, "REPLAYGAIN_ALBUM_GAIN"); + + if (medium->type.empty() + && medium->name.empty() + && !medium->trackCount + && !medium->position + && !medium->release + && !medium->replayGain) + { + medium.reset(); + } + + return medium; +} + static TagLib::AudioProperties::ReadStyle readStyleToTagLibReadStyle(ParserReadStyle readStyle) @@ -227,13 +296,6 @@ TagLibParser::processTag(Track& track, const std::string& tag, const std::vector if (tag.empty() || values.empty()) return; - auto getOrCreateMedium = [&]() -> Medium& - { - if (!track.medium) - track.medium.emplace(); - return *track.medium; - }; - std::string_view value {values.front()}; if (tag == "TITLE") @@ -250,41 +312,10 @@ TagLibParser::processTag(Track& track, const std::string& tag, const std::vector track.recordingMBID = UUID::fromString(value); else if (tag == "ACOUSTID_ID") track.acoustID = UUID::fromString(value); - else if (tag == "TRACKTOTAL") - { - getOrCreateMedium().trackCount = StringUtils::readAs(value); - } else if (tag == "TRACKNUMBER") { // Expecting 'Number/Total' - std::vector strings {splitAndTrimString(value, "/")}; - - if (!strings.empty()) - { - track.position = StringUtils::readAs(strings[0]); - - // Lower priority than TRACKTOTAL - if (strings.size() > 1 && !getOrCreateMedium().trackCount) - getOrCreateMedium().trackCount = StringUtils::readAs(strings[1]); - } - } - else if (tag == "DISCTOTAL") - { - if (track.release) - track.release->mediumCount = StringUtils::readAs(value); - } - else if (tag == "DISCNUMBER") - { - // Expecting 'Number/Total' - std::vector strings {StringUtils::splitString(value, "/")}; - if (!strings.empty()) - { - getOrCreateMedium().position = StringUtils::readAs(strings[0]); - - // Lower priority than DISCTOTAL - if (strings.size() > 1 && track.release && !track.release->mediumCount) - track.release->mediumCount = StringUtils::readAs(strings[1]); - } + track.position = StringUtils::readAs(value); } else if (tag == "DATE") { @@ -314,14 +345,8 @@ TagLibParser::processTag(Track& track, const std::string& tag, const std::vector track.copyright = value; else if (tag == "COPYRIGHTURL") track.copyrightURL = value; - else if (tag == "REPLAYGAIN_ALBUM_GAIN") - getOrCreateMedium().replayGain = StringUtils::readAs(value); else if (tag == "REPLAYGAIN_TRACK_GAIN") track.replayGain = StringUtils::readAs(value); - else if (tag == "DISCSUBTITLE" || tag == "SETSUBTITLE") - getOrCreateMedium().name = value; - else if (tag == "MEDIA") - getOrCreateMedium().type = value; else if (_clusterTypeNames.find(tag) != _clusterTypeNames.end()) { std::set clusterNames; @@ -395,7 +420,7 @@ TagLibParser::parse(const std::filesystem::path& p, bool debug) track.duration = std::chrono::milliseconds {properties->lengthInMilliseconds()}; MetaData::AudioStream audioStream {static_cast(properties->bitrate() * 1000)}; - track.audioStreams = {std::move(audioStream)}; + track.audioStreams = {audioStream}; } TagMap tags {constructTagMap(f.file()->properties())}; @@ -492,9 +517,7 @@ TagLibParser::parse(const std::filesystem::path& p, bool debug) track.hasCover = true; } - track.release = getRelease(tags); - if (track.release) - track.release->artists = getArtists(tags, {"ALBUMARTISTS", "ALBUMARTIST"}, {"ALBUMARTISTSSORT", "ALBUMARTISTSORT"}, {"MUSICBRAINZ_ALBUMARTISTID", "MUSICBRAINZ ALBUM ARTIST ID", "MUSICBRAINZ/ALBUM ARTIST ID"}); + track.medium = getMedium(tags); track.artists = getArtists(tags, {"ARTISTS", "ARTIST"}, {"ARTISTSORT"}, {"MUSICBRAINZ_ARTISTID", "MUSICBRAINZ ARTIST ID", "MUSICBRAINZ/ARTIST ID"}); track.conductorArtists = getArtists(tags, {"CONDUCTORS", "CONDUCTOR"}, {"CONDUCTORSSORT", "CONDUCTORSORT"}, {}); track.composerArtists = getArtists(tags, {"COMPOSERS", "COMPOSER"}, {"COMPOSERSSORT", "COMPOSERSORT"}, {}); diff --git a/src/libs/metadata/impl/Utils.hpp b/src/libs/metadata/impl/Utils.hpp index 1268eb4c..5d89e2d9 100644 --- a/src/libs/metadata/impl/Utils.hpp +++ b/src/libs/metadata/impl/Utils.hpp @@ -38,5 +38,6 @@ namespace MetaData::Utils // format is "artist name (role)" PerformerArtist extractPerformerAndRole(std::string_view entry); + } diff --git a/src/libs/metadata/include/metadata/IParser.hpp b/src/libs/metadata/include/metadata/IParser.hpp index f23c5573..765d8ac6 100644 --- a/src/libs/metadata/include/metadata/IParser.hpp +++ b/src/libs/metadata/include/metadata/IParser.hpp @@ -35,6 +35,8 @@ namespace MetaData { using Tags = std::map /* names */>; + // Very simplified version of https://musicbrainz.org/doc/MusicBrainz_Database/Schema + struct Artist { std::optional mbid; @@ -59,6 +61,7 @@ namespace MetaData { std::string type; std::string name; + std::optional release; std::optional position; // in release std::optional trackCount; std::optional replayGain; @@ -76,7 +79,6 @@ namespace MetaData std::string title; std::optional medium; std::optional position; // in medium - std::optional release; Tags tags; std::chrono::milliseconds duration; Wt::WDate date; diff --git a/src/libs/services/scanner/impl/ScanStepScanFiles.cpp b/src/libs/services/scanner/impl/ScanStepScanFiles.cpp index ea567356..011fea52 100644 --- a/src/libs/services/scanner/impl/ScanStepScanFiles.cpp +++ b/src/libs/services/scanner/impl/ScanStepScanFiles.cpp @@ -393,9 +393,9 @@ namespace Scanner for (const Artist::pointer& artist : getOrCreateArtists(dbSession, trackInfo->artists, false)) track.modify()->addArtistLink(TrackArtistLink::create(dbSession, track, artist, TrackArtistLinkType::Artist)); - if (trackInfo->release) + if (trackInfo->medium && trackInfo->medium->release) { - for (const Artist::pointer& releaseArtist : getOrCreateArtists(dbSession, trackInfo->release->artists, false)) + for (const Artist::pointer& releaseArtist : getOrCreateArtists(dbSession, trackInfo->medium->release->artists, false)) track.modify()->addArtistLink(TrackArtistLink::create(dbSession, track, releaseArtist, TrackArtistLinkType::ReleaseArtist)); } @@ -426,8 +426,8 @@ namespace Scanner track.modify()->addArtistLink(TrackArtistLink::create(dbSession, track, remixer, TrackArtistLinkType::Remixer)); track.modify()->setScanVersion(_settings.scanVersion); - if (trackInfo->release) - track.modify()->setRelease(getOrCreateRelease(dbSession, *trackInfo->release)); + if (trackInfo->medium && trackInfo->medium->release) + track.modify()->setRelease(getOrCreateRelease(dbSession, *trackInfo->medium->release)); else track.modify()->setRelease({}); track.modify()->setTotalTrack(trackInfo->medium ? trackInfo->medium->trackCount : std::nullopt); diff --git a/src/tools/metadata/LmsMetadata.cpp b/src/tools/metadata/LmsMetadata.cpp index d0f372a9..81e77b18 100644 --- a/src/tools/metadata/LmsMetadata.cpp +++ b/src/tools/metadata/LmsMetadata.cpp @@ -67,7 +67,8 @@ std::ostream& operator<<(std::ostream& os, const MetaData::Medium& medium) { if (!medium.name.empty()) - os << medium.name << std::endl; + os << medium.name; + os << std::endl; if (medium.position) os << "\tPosition: " << *medium.position << std::endl; @@ -81,6 +82,9 @@ operator<<(std::ostream& os, const MetaData::Medium& medium) if (medium.replayGain) std::cout << "\tReplay gain: " << *medium.replayGain << std::endl; + if (medium.release) + std::cout << "Release: " << *medium.release << std::endl; + return os; } @@ -101,7 +105,7 @@ void parse(MetaData::IParser& parser, const std::filesystem::path& file) std::cout << "Parsing time: " << std::fixed << std::setprecision(2) << std::chrono::duration_cast(end - start).count() / 1000. << "ms" << std::endl; - std::cout << "Track metadata:" << std::endl; + std::cout << "Parsed metadata:" << std::endl; for (const Artist& artist : track->artists) std::cout << "Artist: " << artist << std::endl; @@ -134,9 +138,6 @@ void parse(MetaData::IParser& parser, const std::filesystem::path& file) for (const Artist& artist : track->remixerArtists) std::cout << "Remixer: " << artist << std::endl; - if (track->release) - std::cout << "Release: " << *track->release; - if (track->medium) std::cout << "Medium: " << *track->medium;