Added performers. fixes #278
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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}};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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 << "'";
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user