From 4ba7aff628c684f1cc17c8b67191f5cfaeb3e3ad Mon Sep 17 00:00:00 2001 From: emeric Date: Tue, 4 Apr 2023 23:22:27 +0200 Subject: [PATCH] Added support for release type in metadata parser --- src/libs/metadata/impl/TagLibParser.cpp | 11 +++- src/libs/metadata/impl/Utils.cpp | 45 +++++++++++++++ src/libs/metadata/impl/Utils.hpp | 10 +++- .../metadata/include/metadata/IParser.hpp | 28 +++++++++ src/libs/metadata/test/Utils.cpp | 57 +++++++++++++++++++ src/libs/utils/include/utils/EnumSet.hpp | 12 ++++ src/tools/metadata/LmsMetadata.cpp | 47 +++++++++++++++ 7 files changed, 207 insertions(+), 3 deletions(-) diff --git a/src/libs/metadata/impl/TagLibParser.cpp b/src/libs/metadata/impl/TagLibParser.cpp index 4e0553ad..3001e05f 100644 --- a/src/libs/metadata/impl/TagLibParser.cpp +++ b/src/libs/metadata/impl/TagLibParser.cpp @@ -225,6 +225,13 @@ getRelease(const TagMap& tags) } } + release->primaryType = getPropertyValueFirstMatchAs(tags, {"MUSICBRAINZ_ALBUMTYPE", "RELEASETYPE", "MUSICBRAINZ ALBUM TYPE", "MUSICBRAINZ/ALBUM TYPE"}); + if (release->primaryType) + { + const auto secondaryTypes {getPropertyValuesFirstMatchAs(tags, {"MUSICBRAINZ_ALBUMTYPE", "RELEASETYPE", "MUSICBRAINZ ALBUM TYPE", "MUSICBRAINZ/ALBUM TYPE"})}; + release->secondaryTypes.assign(std::cbegin(secondaryTypes), std::cend(secondaryTypes)); + } + return release; } @@ -445,7 +452,7 @@ TagLibParser::parse(const std::filesystem::path& p, bool debug) for (const auto& [name, attributeList] : tag->attributeListMap()) { - std::string strName {name.to8Bit(true)}; + std::string strName {StringUtils::stringToUpper(name.to8Bit(true))}; if (strName.find("WM/") == 0 || tags.find(strName) != std::cend(tags)) continue; @@ -461,7 +468,7 @@ TagLibParser::parse(const std::filesystem::path& p, bool debug) if (debug) std::cout << "ASF property: '" << name << "'" << std::endl; - tags[strName] = std::move(attributes); + tags.emplace(strName, std::move(attributes)); } } } diff --git a/src/libs/metadata/impl/Utils.cpp b/src/libs/metadata/impl/Utils.cpp index b8ae6810..774de175 100644 --- a/src/libs/metadata/impl/Utils.cpp +++ b/src/libs/metadata/impl/Utils.cpp @@ -119,3 +119,48 @@ namespace MetaData::Utils } } +namespace StringUtils +{ + static bool iequals(std::string_view a, std::string_view b) + { + return std::equal(std::cbegin(a), std::cend(a), + std::cbegin(b), std::cend(b), + [](char a, char b) { return tolower(a) == tolower(b);} + ); + } + + template<> + std::optional readAs(std::string_view str) + { + str = stringTrim(str); + + if (iequals(str, "album")) + return MetaData::Release::PrimaryType::Album; + else if (iequals(str, "single")) + return MetaData::Release::PrimaryType::Single; + else if (iequals(str, "EP")) + return MetaData::Release::PrimaryType::EP; + else if (iequals(str, "broadcast")) + return MetaData::Release::PrimaryType::Broadcast; + else if (iequals(str, "other")) + return MetaData::Release::PrimaryType::Other; + + return std::nullopt; + } + + template<> + std::optional readAs(std::string_view str) + { + str = stringTrim(str); + + if (iequals(str, "compilation")) + return MetaData::Release::SecondaryType::Compilation; + else if (iequals(str, "soundtrack")) + return MetaData::Release::SecondaryType::Soundtrack; + else if (iequals(str, "live")) + return MetaData::Release::SecondaryType::Live; + + return std::nullopt; + } +} + diff --git a/src/libs/metadata/impl/Utils.hpp b/src/libs/metadata/impl/Utils.hpp index 5d89e2d9..8c80cc14 100644 --- a/src/libs/metadata/impl/Utils.hpp +++ b/src/libs/metadata/impl/Utils.hpp @@ -38,6 +38,14 @@ namespace MetaData::Utils // format is "artist name (role)" PerformerArtist extractPerformerAndRole(std::string_view entry); - +} + +namespace StringUtils +{ + template<> + std::optional readAs(std::string_view str); + + template<> + std::optional readAs(std::string_view str); } diff --git a/src/libs/metadata/include/metadata/IParser.hpp b/src/libs/metadata/include/metadata/IParser.hpp index 765d8ac6..15b72282 100644 --- a/src/libs/metadata/include/metadata/IParser.hpp +++ b/src/libs/metadata/include/metadata/IParser.hpp @@ -29,6 +29,7 @@ #include #include +#include "utils/EnumSet.hpp" #include "utils/UUID.hpp" namespace MetaData @@ -51,10 +52,37 @@ namespace MetaData struct Release { + // see https://musicbrainz.org/doc/Release_Group/Type + enum class PrimaryType + { + Album, + Single, + EP, + Broadcast, + Other + }; + + enum class SecondaryType + { + Compilation, + Soundtrack, + Spokenword, + Interview, + Audiobook, + AudioDrama, + Live, + Remix, + DJMix, + Mixtape_Street, + Demo, + }; + std::optional mbid; std::string name; std::vector artists; std::optional mediumCount; + std::optional primaryType; + EnumSet secondaryTypes; }; struct Medium diff --git a/src/libs/metadata/test/Utils.cpp b/src/libs/metadata/test/Utils.cpp index 41b9ee9e..1cc0e69c 100644 --- a/src/libs/metadata/test/Utils.cpp +++ b/src/libs/metadata/test/Utils.cpp @@ -115,3 +115,60 @@ TEST(MetaData, extractPerformerAndRole) EXPECT_EQ(performer.role, testCase.expectedRole) << " str was '" << testCase.str << "'"; } } + +TEST(MetaData, primaryReleaseTypes) +{ + using namespace MetaData; + + struct TestCase + { + std::string str; + std::optional result; + } testCases [] + { + { "", std::nullopt }, + { "album", Release::PrimaryType::Album }, + { "Album", Release::PrimaryType::Album }, + { " Album", Release::PrimaryType::Album }, + { "Album ", Release::PrimaryType::Album }, + { "ep", Release::PrimaryType::EP }, + { " ep ", Release::PrimaryType::EP }, + { "broadcast", Release::PrimaryType::Broadcast }, + { "single", Release::PrimaryType::Single }, + { "other", Release::PrimaryType::Other }, + }; + + for (const TestCase& testCase : testCases) + { + std::optional parsed {StringUtils::readAs(testCase.str)}; + + EXPECT_EQ(parsed, testCase.result) << " str was '" << testCase.str << "'"; + } +} + +TEST(MetaData, secondaryReleaseTypes) +{ + using namespace MetaData; + + struct TestCase + { + std::string str; + std::optional result; + } testCases [] + { + { "", std::nullopt }, + { "compilation", Release::SecondaryType::Compilation }, + { " compilation ", Release::SecondaryType::Compilation }, + { "soundtrack", Release::SecondaryType::Soundtrack }, + { "live", Release::SecondaryType::Live }, + { "demo", Release::SecondaryType::Demo }, + }; + + for (const TestCase& testCase : testCases) + { + std::optional parsed {StringUtils::readAs(testCase.str)}; + + EXPECT_EQ(parsed, testCase.result) << " str was '" << testCase.str << "'"; + } +} + diff --git a/src/libs/utils/include/utils/EnumSet.hpp b/src/libs/utils/include/utils/EnumSet.hpp index 2d4361c6..f09ae1e2 100644 --- a/src/libs/utils/include/utils/EnumSet.hpp +++ b/src/libs/utils/include/utils/EnumSet.hpp @@ -44,6 +44,13 @@ class EnumSet template constexpr EnumSet(It begin, It end) { + assign(begin, end); + } + + template + constexpr void assign(It begin, It end) + { + clear(); for (It it {begin}; it != end; ++it) insert(*it); } @@ -71,6 +78,11 @@ class EnumSet return _bitfield & (underlying_type{ 1 } << static_cast(value)); } + constexpr void clear() + { + _bitfield = 0; + } + class iterator { public: diff --git a/src/tools/metadata/LmsMetadata.cpp b/src/tools/metadata/LmsMetadata.cpp index 81e77b18..9ac1a459 100644 --- a/src/tools/metadata/LmsMetadata.cpp +++ b/src/tools/metadata/LmsMetadata.cpp @@ -44,6 +44,46 @@ operator<<(std::ostream& os, const MetaData::Artist& artist) return os; } +static +std::ostream& +operator<<(std::ostream& os, MetaData::Release::PrimaryType type) +{ + switch (type) + { + case MetaData::Release::PrimaryType::Album: os << "Album"; break; + case MetaData::Release::PrimaryType::Single: os << "Single"; break; + case MetaData::Release::PrimaryType::EP: os << "EP"; break; + case MetaData::Release::PrimaryType::Broadcast: os << "Broadcast"; break; + case MetaData::Release::PrimaryType::Other: os << "Other"; break; + default: + os << "??"; + } + return os; +} + +static +std::ostream& +operator<<(std::ostream& os, MetaData::Release::SecondaryType type) +{ + switch (type) + { + case MetaData::Release::SecondaryType::Compilation: os << "Compilation"; break; + case MetaData::Release::SecondaryType::Soundtrack: os << "Soundtrack"; break; + case MetaData::Release::SecondaryType::Spokenword: os << "Spokenword"; break; + case MetaData::Release::SecondaryType::Interview: os << "Interview"; break; + case MetaData::Release::SecondaryType::Audiobook: os << "Audiobook"; break; + case MetaData::Release::SecondaryType::AudioDrama: os << "Audio drama"; break; + case MetaData::Release::SecondaryType::Live: os << "Live"; break; + case MetaData::Release::SecondaryType::Remix: os << "Remix"; break; + case MetaData::Release::SecondaryType::DJMix: os << "DJ-mix"; break; + case MetaData::Release::SecondaryType::Mixtape_Street: os << "Mixtape/Street"; break; + case MetaData::Release::SecondaryType::Demo: os << "Mixtape/Demo"; break; + default: + os << "??"; + } + return os; +} + static std::ostream& operator<<(std::ostream& os, const MetaData::Release& release) @@ -59,6 +99,13 @@ operator<<(std::ostream& os, const MetaData::Release& release) for (const MetaData::Artist& artist : release.artists) std::cout << "\tRelease artist: " << artist << std::endl; + if (release.primaryType) + { + std::cout << "\tPrimary type: " << *release.primaryType << std::endl; + for (MetaData::Release::SecondaryType type : release.secondaryTypes) + std::cout << "\tSecondary type:" << type << std::endl; + } + return os; }