From e9e608cfa09792a64fbda0946c92ec396622a088 Mon Sep 17 00:00:00 2001 From: jacqueline Date: Mon, 23 Dec 2024 16:08:47 +1100 Subject: [PATCH] Add a new track tag + index for multiple artists We still mostly use the singular 'Artist' tag for e.g. displaying a nice name in the now playing screen, but where a track has an 'ARTISTS=' tag, we'll split by semicolon and then use the resulting list to populate an index of tracks by artist --- src/tangara/database/database.cpp | 2 +- src/tangara/database/database.hpp | 2 +- src/tangara/database/index.cpp | 9 +++++ src/tangara/database/index.hpp | 1 + src/tangara/database/tag_parser.cpp | 1 + src/tangara/database/track.cpp | 51 +++++++++++++++++++++++++++++ src/tangara/database/track.hpp | 5 +++ 7 files changed, 69 insertions(+), 2 deletions(-) diff --git a/src/tangara/database/database.cpp b/src/tangara/database/database.cpp index 9851b041..67893b6e 100644 --- a/src/tangara/database/database.cpp +++ b/src/tangara/database/database.cpp @@ -292,7 +292,7 @@ auto Database::setTrackData(TrackId id, const TrackData& data) -> void { auto Database::getIndexes() -> std::vector { // TODO(jacqueline): This probably needs to be async? When we have runtime // configurable indexes, they will need to come from somewhere. - return {kAllTracks, kAllAlbums, kAlbumsByArtist, + return {kAllTracks, kAllAlbums, kAllArtists, kAlbumsByArtist, kTracksByGenre, kPodcasts, kAudiobooks}; } diff --git a/src/tangara/database/database.hpp b/src/tangara/database/database.hpp index e46a123e..9a7e1d4e 100644 --- a/src/tangara/database/database.hpp +++ b/src/tangara/database/database.hpp @@ -38,7 +38,7 @@ namespace database { -const uint8_t kCurrentDbVersion = 8; +const uint8_t kCurrentDbVersion = 9; struct SearchKey; class Record; diff --git a/src/tangara/database/index.cpp b/src/tangara/database/index.cpp index 1cdc0d07..58a7ead4 100644 --- a/src/tangara/database/index.cpp +++ b/src/tangara/database/index.cpp @@ -56,6 +56,13 @@ const IndexInfo kAllAlbums{ .components = {Tag::kAlbum, Tag::kAlbumOrder}, }; +const IndexInfo kAllArtists{ + .id = 7, + .type = MediaType::kMusic, + .name = "All Artists", + .components = {Tag::kAllArtists, Tag::kTitle}, +}; + const IndexInfo kPodcasts{ .id = 5, .type = MediaType::kPodcast, @@ -114,6 +121,8 @@ class Indexer { return "Unknown Album"; case Tag::kAlbumArtist: return track_tags_.artist().value_or("Unknown Artist"); + case Tag::kAllArtists: + return track_tags_.artist().value_or("Unknown Artist"); case Tag::kGenres: return std::pmr::vector{}; case Tag::kDisc: diff --git a/src/tangara/database/index.hpp b/src/tangara/database/index.hpp index e1c6283a..98e57d54 100644 --- a/src/tangara/database/index.hpp +++ b/src/tangara/database/index.hpp @@ -78,6 +78,7 @@ extern const IndexInfo kAlbumsByArtist; extern const IndexInfo kTracksByGenre; extern const IndexInfo kAllTracks; extern const IndexInfo kAllAlbums; +extern const IndexInfo kAllArtists; extern const IndexInfo kPodcasts; extern const IndexInfo kAudiobooks; diff --git a/src/tangara/database/tag_parser.cpp b/src/tangara/database/tag_parser.cpp index 15323a7c..cfe4b8a5 100644 --- a/src/tangara/database/tag_parser.cpp +++ b/src/tangara/database/tag_parser.cpp @@ -170,6 +170,7 @@ OggTagParser::OggTagParser() { nameToTag_["TITLE"] = Tag::kTitle; nameToTag_["ALBUM"] = Tag::kAlbum; nameToTag_["ARTIST"] = Tag::kArtist; + nameToTag_["ARTISTS"] = Tag::kAllArtists; nameToTag_["ALBUMARTIST"] = Tag::kAlbumArtist; nameToTag_["TRACK"] = Tag::kTrack; nameToTag_["TRACKNUMBER"] = Tag::kTrack; diff --git a/src/tangara/database/track.cpp b/src/tangara/database/track.cpp index 49babb6a..f0959c98 100644 --- a/src/tangara/database/track.cpp +++ b/src/tangara/database/track.cpp @@ -20,6 +20,7 @@ namespace database { +static constexpr char kAllArtistDelimiters[] = ";"; static constexpr char kGenreDelimiters[] = ",;"; auto tagName(Tag t) -> std::string { @@ -28,6 +29,8 @@ auto tagName(Tag t) -> std::string { return "title"; case Tag::kArtist: return "artist"; + case Tag::kAllArtists: + return "all_artists"; case Tag::kAlbum: return "album"; case Tag::kAlbumArtist: @@ -111,6 +114,8 @@ auto TrackTags::get(Tag t) const -> TagValue { return valueOrMonostate(title_); case Tag::kArtist: return valueOrMonostate(artist_); + case Tag::kAllArtists: + return allArtists_; case Tag::kAlbum: return valueOrMonostate(album_); case Tag::kAlbumArtist: @@ -135,6 +140,9 @@ auto TrackTags::set(Tag t, std::string_view v) -> void { case Tag::kArtist: artist(v); break; + case Tag::kAllArtists: + allArtists(v); + break; case Tag::kAlbum: album(v); break; @@ -165,6 +173,7 @@ auto TrackTags::allPresent() const -> std::vector { }; add_if_present(Tag::kTitle, title_); add_if_present(Tag::kArtist, artist_); + add_if_present(Tag::kAllArtists, !allArtists_.empty()); add_if_present(Tag::kAlbum, album_); add_if_present(Tag::kAlbumArtist, album_artist_); add_if_present(Tag::kDisc, disc_); @@ -189,6 +198,48 @@ auto TrackTags::artist(std::string_view s) -> void { artist_ = s; } +auto TrackTags::allArtists() const -> std::span { + return allArtists_; +} + +auto TrackTags::allArtists(const std::string_view s) -> void { + allArtists_.clear(); + std::string src = {s.data(), s.size()}; + char* token = std::strtok(src.data(), kAllArtistDelimiters); + + auto trim_and_add = [this](std::string_view s) { + std::string copy = {s.data(), s.size()}; + + // Trim the left + copy.erase(copy.begin(), + std::find_if(copy.begin(), copy.end(), [](unsigned char ch) { + return !std::isspace(ch); + })); + + // Trim the right + copy.erase(std::find_if(copy.rbegin(), copy.rend(), + [](unsigned char ch) { return !std::isspace(ch); }) + .base(), + copy.end()); + + // Ignore empty strings. + if (!copy.empty()) { + allArtists_.push_back({copy.data(), copy.size()}); + } + }; + + if (token == NULL) { + // No delimiters found in the input. Treat this as a single artist. + trim_and_add(s); + } else { + while (token != NULL) { + // Add tokens until no more delimiters found. + trim_and_add(token); + token = std::strtok(NULL, kAllArtistDelimiters); + } + } +} + auto TrackTags::album() const -> const std::optional& { return album_; } diff --git a/src/tangara/database/track.hpp b/src/tangara/database/track.hpp index 65c5cfec..21d6349d 100644 --- a/src/tangara/database/track.hpp +++ b/src/tangara/database/track.hpp @@ -73,6 +73,7 @@ enum class Tag { kTrack = 5, kAlbumOrder = 6, kGenres = 7, + kAllArtists = 8, }; using TagValue = std::variant const std::optional&; auto artist(std::string_view) -> void; + auto allArtists() const -> std::span; + auto allArtists(const std::string_view) -> void; + auto album() const -> const std::optional&; auto album(std::string_view) -> void; @@ -144,6 +148,7 @@ class TrackTags { std::optional title_; std::optional artist_; + std::pmr::vector allArtists_; std::optional album_; std::optional album_artist_; std::optional disc_;