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
custom
jacqueline 4 months ago
parent e09ab5f6fb
commit e9e608cfa0
  1. 2
      src/tangara/database/database.cpp
  2. 2
      src/tangara/database/database.hpp
  3. 9
      src/tangara/database/index.cpp
  4. 1
      src/tangara/database/index.hpp
  5. 1
      src/tangara/database/tag_parser.cpp
  6. 51
      src/tangara/database/track.cpp
  7. 5
      src/tangara/database/track.hpp

@ -292,7 +292,7 @@ auto Database::setTrackData(TrackId id, const TrackData& data) -> void {
auto Database::getIndexes() -> std::vector<IndexInfo> { auto Database::getIndexes() -> std::vector<IndexInfo> {
// TODO(jacqueline): This probably needs to be async? When we have runtime // TODO(jacqueline): This probably needs to be async? When we have runtime
// configurable indexes, they will need to come from somewhere. // configurable indexes, they will need to come from somewhere.
return {kAllTracks, kAllAlbums, kAlbumsByArtist, return {kAllTracks, kAllAlbums, kAllArtists, kAlbumsByArtist,
kTracksByGenre, kPodcasts, kAudiobooks}; kTracksByGenre, kPodcasts, kAudiobooks};
} }

@ -38,7 +38,7 @@
namespace database { namespace database {
const uint8_t kCurrentDbVersion = 8; const uint8_t kCurrentDbVersion = 9;
struct SearchKey; struct SearchKey;
class Record; class Record;

@ -56,6 +56,13 @@ const IndexInfo kAllAlbums{
.components = {Tag::kAlbum, Tag::kAlbumOrder}, .components = {Tag::kAlbum, Tag::kAlbumOrder},
}; };
const IndexInfo kAllArtists{
.id = 7,
.type = MediaType::kMusic,
.name = "All Artists",
.components = {Tag::kAllArtists, Tag::kTitle},
};
const IndexInfo kPodcasts{ const IndexInfo kPodcasts{
.id = 5, .id = 5,
.type = MediaType::kPodcast, .type = MediaType::kPodcast,
@ -114,6 +121,8 @@ class Indexer {
return "Unknown Album"; return "Unknown Album";
case Tag::kAlbumArtist: case Tag::kAlbumArtist:
return track_tags_.artist().value_or("Unknown Artist"); return track_tags_.artist().value_or("Unknown Artist");
case Tag::kAllArtists:
return track_tags_.artist().value_or("Unknown Artist");
case Tag::kGenres: case Tag::kGenres:
return std::pmr::vector<std::pmr::string>{}; return std::pmr::vector<std::pmr::string>{};
case Tag::kDisc: case Tag::kDisc:

@ -78,6 +78,7 @@ extern const IndexInfo kAlbumsByArtist;
extern const IndexInfo kTracksByGenre; extern const IndexInfo kTracksByGenre;
extern const IndexInfo kAllTracks; extern const IndexInfo kAllTracks;
extern const IndexInfo kAllAlbums; extern const IndexInfo kAllAlbums;
extern const IndexInfo kAllArtists;
extern const IndexInfo kPodcasts; extern const IndexInfo kPodcasts;
extern const IndexInfo kAudiobooks; extern const IndexInfo kAudiobooks;

@ -170,6 +170,7 @@ OggTagParser::OggTagParser() {
nameToTag_["TITLE"] = Tag::kTitle; nameToTag_["TITLE"] = Tag::kTitle;
nameToTag_["ALBUM"] = Tag::kAlbum; nameToTag_["ALBUM"] = Tag::kAlbum;
nameToTag_["ARTIST"] = Tag::kArtist; nameToTag_["ARTIST"] = Tag::kArtist;
nameToTag_["ARTISTS"] = Tag::kAllArtists;
nameToTag_["ALBUMARTIST"] = Tag::kAlbumArtist; nameToTag_["ALBUMARTIST"] = Tag::kAlbumArtist;
nameToTag_["TRACK"] = Tag::kTrack; nameToTag_["TRACK"] = Tag::kTrack;
nameToTag_["TRACKNUMBER"] = Tag::kTrack; nameToTag_["TRACKNUMBER"] = Tag::kTrack;

@ -20,6 +20,7 @@
namespace database { namespace database {
static constexpr char kAllArtistDelimiters[] = ";";
static constexpr char kGenreDelimiters[] = ",;"; static constexpr char kGenreDelimiters[] = ",;";
auto tagName(Tag t) -> std::string { auto tagName(Tag t) -> std::string {
@ -28,6 +29,8 @@ auto tagName(Tag t) -> std::string {
return "title"; return "title";
case Tag::kArtist: case Tag::kArtist:
return "artist"; return "artist";
case Tag::kAllArtists:
return "all_artists";
case Tag::kAlbum: case Tag::kAlbum:
return "album"; return "album";
case Tag::kAlbumArtist: case Tag::kAlbumArtist:
@ -111,6 +114,8 @@ auto TrackTags::get(Tag t) const -> TagValue {
return valueOrMonostate(title_); return valueOrMonostate(title_);
case Tag::kArtist: case Tag::kArtist:
return valueOrMonostate(artist_); return valueOrMonostate(artist_);
case Tag::kAllArtists:
return allArtists_;
case Tag::kAlbum: case Tag::kAlbum:
return valueOrMonostate(album_); return valueOrMonostate(album_);
case Tag::kAlbumArtist: case Tag::kAlbumArtist:
@ -135,6 +140,9 @@ auto TrackTags::set(Tag t, std::string_view v) -> void {
case Tag::kArtist: case Tag::kArtist:
artist(v); artist(v);
break; break;
case Tag::kAllArtists:
allArtists(v);
break;
case Tag::kAlbum: case Tag::kAlbum:
album(v); album(v);
break; break;
@ -165,6 +173,7 @@ auto TrackTags::allPresent() const -> std::vector<Tag> {
}; };
add_if_present(Tag::kTitle, title_); add_if_present(Tag::kTitle, title_);
add_if_present(Tag::kArtist, artist_); add_if_present(Tag::kArtist, artist_);
add_if_present(Tag::kAllArtists, !allArtists_.empty());
add_if_present(Tag::kAlbum, album_); add_if_present(Tag::kAlbum, album_);
add_if_present(Tag::kAlbumArtist, album_artist_); add_if_present(Tag::kAlbumArtist, album_artist_);
add_if_present(Tag::kDisc, disc_); add_if_present(Tag::kDisc, disc_);
@ -189,6 +198,48 @@ auto TrackTags::artist(std::string_view s) -> void {
artist_ = s; artist_ = s;
} }
auto TrackTags::allArtists() const -> std::span<const std::pmr::string> {
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<std::pmr::string>& { auto TrackTags::album() const -> const std::optional<std::pmr::string>& {
return album_; return album_;
} }

@ -73,6 +73,7 @@ enum class Tag {
kTrack = 5, kTrack = 5,
kAlbumOrder = 6, kAlbumOrder = 6,
kGenres = 7, kGenres = 7,
kAllArtists = 8,
}; };
using TagValue = std::variant<std::monostate, using TagValue = std::variant<std::monostate,
@ -114,6 +115,9 @@ class TrackTags {
auto artist() const -> const std::optional<std::pmr::string>&; auto artist() const -> const std::optional<std::pmr::string>&;
auto artist(std::string_view) -> void; auto artist(std::string_view) -> void;
auto allArtists() const -> std::span<const std::pmr::string>;
auto allArtists(const std::string_view) -> void;
auto album() const -> const std::optional<std::pmr::string>&; auto album() const -> const std::optional<std::pmr::string>&;
auto album(std::string_view) -> void; auto album(std::string_view) -> void;
@ -144,6 +148,7 @@ class TrackTags {
std::optional<std::pmr::string> title_; std::optional<std::pmr::string> title_;
std::optional<std::pmr::string> artist_; std::optional<std::pmr::string> artist_;
std::pmr::vector<std::pmr::string> allArtists_;
std::optional<std::pmr::string> album_; std::optional<std::pmr::string> album_;
std::optional<std::pmr::string> album_artist_; std::optional<std::pmr::string> album_artist_;
std::optional<uint8_t> disc_; std::optional<uint8_t> disc_;

Loading…
Cancel
Save