Added performers. fixes #278

This commit is contained in:
emeric
2022-12-14 20:07:54 +01:00
parent 495005357c
commit 998def846d
27 changed files with 451 additions and 96 deletions
+52 -12
View File
@@ -132,6 +132,48 @@ getArtists(const TagLib::PropertyMap& properties,
return artists;
}
static
PerformerContainer
getPerformerArtists(const TagLib::PropertyMap& properties,
const std::vector<std::string_view>& artistTagNames)
{
PerformerContainer performers;
// picard stores like this: (see https://picard-docs.musicbrainz.org/en/appendices/tag_mapping.html#performer)
// PERFORMER: artist (role)
if (const std::vector<std::string> artistNames {getPropertyValuesFirstMatchAs<std::string>(properties, artistTagNames)}; !artistNames.empty())
{
for (std::string_view entry : artistNames)
{
Utils::PerformerArtist performer {Utils::extractPerformerAndRole(entry)};
StringUtils::capitalize(performer.role);
performers[performer.role].push_back(std::move(performer.artist));
}
}
// PERFORMER:role (MP3)
else
{
for (const auto& [key, values] : properties)
{
if (key.startsWith("PERFORMER"))
{
std::string performerStr {key.to8Bit(true)};
std::string role;
if (const std::size_t rolePos {performerStr.find(':')}; rolePos != std::string::npos)
{
role = StringUtils::stringToLower(performerStr.substr(rolePos + 1, performerStr.size() - rolePos + 1));
StringUtils::capitalize(role);
}
for (const auto& value : values)
performers[role].push_back(Artist {value.to8Bit(true)});
}
}
}
return performers;
}
static
std::optional<Album>
getAlbum(const TagLib::PropertyMap& properties)
@@ -140,7 +182,7 @@ getAlbum(const TagLib::PropertyMap& properties)
if (albumName.empty())
return std::nullopt;
const std::vector<UUID> albumMBID {getPropertyValuesFirstMatchAs<UUID>(properties, {"MUSICBRAINZ_ALBUMID", "MUSICBRAINZ ALBUM ID"})};
const std::vector<UUID> albumMBID {getPropertyValuesFirstMatchAs<UUID>(properties, {"MUSICBRAINZ_ALBUMID", "MUSICBRAINZ ALBUM ID", "MUSICBRAINZ/ALBUM ID"})};
if (albumMBID.empty())
return Album {std::move(albumName.front()), {}};
@@ -187,12 +229,14 @@ TagLibParser::processTag(Track& track, const std::string& tag, const TagLib::Str
if (tag == "TITLE")
track.title = value;
else if (tag == "MUSICBRAINZ_RELEASETRACKID"
|| tag == "MUSICBRAINZ RELEASE TRACK ID")
|| tag == "MUSICBRAINZ RELEASE TRACK ID"
|| tag == "MUSICBRAINZ/RELEASE TRACK ID")
{
track.trackMBID = UUID::fromString(value);
}
else if (tag == "MUSICBRAINZ_TRACKID"
|| tag == "MUSICBRAINZ TRACK ID")
|| tag == "MUSICBRAINZ TRACK ID"
|| tag == "MUSICBRAINZ/TRACK ID")
track.recordingMBID = UUID::fromString(value);
else if (tag == "ACOUSTID_ID")
track.acoustID = UUID::fromString(value);
@@ -416,23 +460,19 @@ TagLibParser::parse(const std::filesystem::path& p, bool debug)
track.hasCover = true;
}
for (const auto& property : properties)
{
const std::string tag {property.first.upper().to8Bit(true)};
const TagLib::StringList& values {property.second};
processTag(track, tag, values, debug);
}
for (const auto& [tag, values] : properties)
processTag(track, tag.upper().to8Bit(true), values, debug);
track.album = getAlbum(properties);
track.artists = getArtists(properties, {"ARTISTS", "ARTIST"}, {"ARTISTSORT"}, {"MUSICBRAINZ_ARTISTID", "MUSICBRAINZ ARTIST ID"});
track.albumArtists = getArtists(properties, {"ALBUMARTISTS", "ALBUMARTIST"}, {"ALBUMARTISTSSORT", "ALBUMARTISTSORT"}, {"MUSICBRAINZ_ALBUMARTISTID", "MUSICBRAINZ ALBUM ARTIST ID"});
track.artists = getArtists(properties, {"ARTISTS", "ARTIST"}, {"ARTISTSORT"}, {"MUSICBRAINZ_ARTISTID", "MUSICBRAINZ ARTIST ID", "MUSICBRAINZ/ARTIST ID"});
track.albumArtists = getArtists(properties, {"ALBUMARTISTS", "ALBUMARTIST"}, {"ALBUMARTISTSSORT", "ALBUMARTISTSORT"}, {"MUSICBRAINZ_ALBUMARTISTID", "MUSICBRAINZ ALBUM ARTIST ID", "MUSICBRAINZ/ALBUM ARTIST ID"});
track.conductorArtists = getArtists(properties, {"CONDUCTORS", "CONDUCTOR"}, {"CONDUCTORSSORT", "CONDUCTORSORT"}, {});
track.composerArtists = getArtists(properties, {"COMPOSERS", "COMPOSER"}, {"COMPOSERSSORT", "COMPOSERSORT"}, {});
track.lyricistArtists = getArtists(properties, {"LYRICISTS", "LYRICIST"}, {"LYRICISTSSORT", "LYRICISTSORT"}, {});
track.mixerArtists = getArtists(properties, {"MIXERS", "MIXER"}, {"MIXERSSORT", "MIXERSORT"}, {});
track.producerArtists = getArtists(properties, {"PRODUCERS", "PRODUCER"}, {"PRODUCERSSORT", "PRODUCERSORT"}, {});
track.remixerArtists = getArtists(properties, {"REMIXERS", "REMIXER", "ModifiedBy"}, {"REMIXERSSORT", "REMIXERSORT"}, {});
track.performerArtists = getPerformerArtists(properties, {"PERFORMERS", "PERFORMER"});
return track;
}
+46
View File
@@ -71,5 +71,51 @@ namespace MetaData::Utils
throw LmsException {"Unknown read style"};
}
PerformerArtist
extractPerformerAndRole(std::string_view entry)
{
std::string_view artistName;
std::string_view role;
std::size_t roleBegin {};
std::size_t roleEnd {};
std::size_t count {};
for (std::size_t i {}; i < entry.size(); ++i)
{
std::size_t currentIndex {entry.size() - i - 1};
const char c {entry[currentIndex]};
if (std::isspace(c))
continue;
if (c == ')')
{
if (count++ == 0)
roleEnd = currentIndex;
}
else if (c == '(')
{
if (count == 0)
break;
if (--count == 0)
{
roleBegin = currentIndex + 1;
role = StringUtils::stringTrim(entry.substr(roleBegin, roleEnd - roleBegin));
artistName = StringUtils::stringTrim(entry.substr(0, currentIndex));
break;
}
}
else if (count == 0)
break;
}
if (!roleEnd || !roleBegin)
artistName = StringUtils::stringTrim(entry);
return PerformerArtist {Artist {artistName}, std::string {role}};
}
}
+10
View File
@@ -18,6 +18,7 @@
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#include <string_view>
#include <Wt/WDate.h>
#include "metadata/IParser.hpp"
@@ -26,5 +27,14 @@ namespace MetaData::Utils
{
Wt::WDate parseDate(const std::string& dateStr);
std::string_view readStyleToString(ParserReadStyle readStyle);
struct PerformerArtist
{
Artist artist;
std::string role;
};
// format is "artist name (role)"
PerformerArtist extractPerformerAndRole(std::string_view entry);
}
@@ -24,6 +24,7 @@
#include <map>
#include <optional>
#include <set>
#include <string>
#include <string_view>
#include <vector>
@@ -44,6 +45,8 @@ namespace MetaData
Artist(std::string_view _name, std::optional<std::string> _sortName, std::optional<UUID> _musicBrainzArtistID) : name {_name}, sortName {_sortName}, musicBrainzArtistID {_musicBrainzArtistID} {}
};
using PerformerContainer = std::map<std::string /*role*/, std::vector<Artist>>;
struct Album
{
std::string name;
@@ -83,6 +86,7 @@ namespace MetaData
std::vector<Artist> composerArtists;
std::vector<Artist> lyricistArtists;
std::vector<Artist> mixerArtists;
PerformerContainer performerArtists;
std::vector<Artist> producerArtists;
std::vector<Artist> remixerArtists;
};
+44 -1
View File
@@ -62,7 +62,6 @@ TEST(MetaData, parseDate)
{ "1995-05/09", Wt::WDate {} }, // invalid mixup separators
};
for (const TestCase& testCase : testCases)
{
const Wt::WDate parsed {parseDate(testCase.str)};
@@ -72,3 +71,47 @@ TEST(MetaData, parseDate)
EXPECT_EQ(parsed.day(), testCase.result.day()) << " str was '" << testCase.str << "'";
}
}
TEST(MetaData, extractPerformerAndRole)
{
using namespace MetaData::Utils;
struct TestCase
{
std::string str;
std::string expectedArtistName;
std::string expectedRole;
} testCases []
{
{ "", "", "" },
{ "(myrole)", "", "myrole" },
{ "(my role)", "", "my role" },
{ " ( my role ) ", "", "my role" },
{ " (()) ", "", "()" },
{ ")", ")", "" },
{ "(", "(", "" },
{ "artist name (my role)", "artist name", "my role" },
{ "artist name ()", "artist name", "" },
{ "artist name ( )", "artist name", "" },
{ "artist (subname) name", "artist (subname) name", "" },
{ " artist name ( my role )", "artist name", "my role" },
{ "artist name (artist subname) (my role)", "artist name (artist subname)", "my role" },
{ "artist name", "artist name", "" },
{ " artist name ", "artist name", "" },
{ "artist name (", "artist name (", "" },
{ "artist name )", "artist name )", "" },
{ "artist name (()", "artist name (", "" },
{ "artist name (())", "artist name", "()" },
{ "artist name ( () )", "artist name", "()" },
{ "artist name (drums (drum set))", "artist name", "drums (drum set)" },
{ "artist name ( drums (drum set) )", "artist name", "drums (drum set)" },
};
for (const TestCase& testCase : testCases)
{
PerformerArtist performer {extractPerformerAndRole(testCase.str)};
EXPECT_EQ(performer.artist.name, testCase.expectedArtistName) << " str was '" << testCase.str << "'";
EXPECT_EQ(performer.role, testCase.expectedRole) << " str was '" << testCase.str << "'";
}
}