From e9e608cfa09792a64fbda0946c92ec396622a088 Mon Sep 17 00:00:00 2001 From: jacqueline Date: Mon, 23 Dec 2024 16:08:47 +1100 Subject: [PATCH 1/6] 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_; From 4a422f4e54959303b0a6083650d2f19a25716a1a Mon Sep 17 00:00:00 2001 From: jacqueline Date: Mon, 23 Dec 2024 16:23:57 +1100 Subject: [PATCH 2/6] Use a common code path for parsing delimited track tags --- src/tangara/database/track.cpp | 116 +++++++++++++-------------------- 1 file changed, 46 insertions(+), 70 deletions(-) diff --git a/src/tangara/database/track.cpp b/src/tangara/database/track.cpp index f0959c98..9f7da2d5 100644 --- a/src/tangara/database/track.cpp +++ b/src/tangara/database/track.cpp @@ -94,6 +94,50 @@ auto tagToString(const TagValue& val) -> std::string { return ""; } +/* + * Utility for taking a string containing delimited tags, and splitting it out + * into a vector of individual tags. + */ +auto parseDelimitedTags(const std::string_view s, + const char* delimiters, + std::pmr::vector& out) -> void { + out.clear(); + std::string src = {s.data(), s.size()}; + char* token = std::strtok(src.data(), delimiters); + + auto trim_and_add = [&](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()) { + out.push_back({copy.data(), copy.size()}); + } + }; + + if (token == NULL) { + // No delimiters found in the input. Treat this as a single result. + trim_and_add(s); + } else { + while (token != NULL) { + // Add tokens until no more delimiters found. + trim_and_add(token); + token = std::strtok(NULL, delimiters); + } + } +} + auto TrackTags::create() -> std::shared_ptr { return std::allocate_shared>( @@ -203,41 +247,7 @@ auto TrackTags::allArtists() const -> std::span { } 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); - } - } + parseDelimitedTags(s, kAllArtistDelimiters, allArtists_); } auto TrackTags::album() const -> const std::optional& { @@ -281,41 +291,7 @@ auto TrackTags::genres() const -> std::span { } auto TrackTags::genres(const std::string_view s) -> void { - genres_.clear(); - std::string src = {s.data(), s.size()}; - char* token = std::strtok(src.data(), kGenreDelimiters); - - 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()) { - genres_.push_back({copy.data(), copy.size()}); - } - }; - - if (token == NULL) { - // No delimiters found in the input. Treat this as a single genre. - trim_and_add(s); - } else { - while (token != NULL) { - // Add tokens until no more delimiters found. - trim_and_add(token); - token = std::strtok(NULL, kGenreDelimiters); - } - } + parseDelimitedTags(s, kGenreDelimiters, genres_); } /* From 407d2da049457e1e90fd136e36285061579015f5 Mon Sep 17 00:00:00 2001 From: jacqueline Date: Mon, 30 Dec 2024 15:14:50 +1100 Subject: [PATCH 3/6] Add ARTISTS= vorbis comment support to libtags This is mostly just for flac handling, since we use libogg directly for ogg containers, and flac+ogg are the only containers that use vorbis comments. --- lib/libtags/tags.h | 1 + lib/libtags/vorbis.c | 1 + src/tangara/database/tag_parser.cpp | 2 ++ 3 files changed, 4 insertions(+) diff --git a/lib/libtags/tags.h b/lib/libtags/tags.h index 4b10349a..d9da6c2e 100644 --- a/lib/libtags/tags.h +++ b/lib/libtags/tags.h @@ -10,6 +10,7 @@ enum { Tunknown = -1, Tartist, + Tmultiartists, Talbumartist, Talbum, Ttitle, diff --git a/lib/libtags/vorbis.c b/lib/libtags/vorbis.c index e57f8989..bea70aaf 100644 --- a/lib/libtags/vorbis.c +++ b/lib/libtags/vorbis.c @@ -11,6 +11,7 @@ static const struct { {"album", Talbum}, {"title", Ttitle}, {"artist", Tartist}, + {"artists", Tmultiartists}, {"albumartist", Talbumartist}, {"tracknumber", Ttrack}, {"date", Tdate}, diff --git a/src/tangara/database/tag_parser.cpp b/src/tangara/database/tag_parser.cpp index cfe4b8a5..1756fa0a 100644 --- a/src/tangara/database/tag_parser.cpp +++ b/src/tangara/database/tag_parser.cpp @@ -32,6 +32,8 @@ static auto convert_tag(int tag) -> std::optional { return Tag::kTitle; case Tartist: return Tag::kArtist; + case Tmultiartists: + return Tag::kAllArtists; case Talbumartist: return Tag::kAlbumArtist; case Talbum: From b6b62cb8ea8121a3ae604b508c508a2a4994050a Mon Sep 17 00:00:00 2001 From: jacqueline Date: Tue, 31 Dec 2024 11:30:23 +1100 Subject: [PATCH 4/6] Handle synthesizing missing tag values in TrackTags instead of the indexer --- src/tangara/database/index.cpp | 5 ++--- src/tangara/database/track.cpp | 32 ++++++++++++++++++++++++-------- src/tangara/database/track.hpp | 2 ++ 3 files changed, 28 insertions(+), 11 deletions(-) diff --git a/src/tangara/database/index.cpp b/src/tangara/database/index.cpp index 58a7ead4..b3af716b 100644 --- a/src/tangara/database/index.cpp +++ b/src/tangara/database/index.cpp @@ -116,13 +116,12 @@ class Indexer { case Tag::kTitle: return titleOrFilename(track_data_, track_tags_); case Tag::kArtist: + case Tag::kAlbumArtist: return "Unknown Artist"; case Tag::kAlbum: 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"); + return std::pmr::vector{}; case Tag::kGenres: return std::pmr::vector{}; case Tag::kDisc: diff --git a/src/tangara/database/track.cpp b/src/tangara/database/track.cpp index 9f7da2d5..ad9db1ba 100644 --- a/src/tangara/database/track.cpp +++ b/src/tangara/database/track.cpp @@ -155,23 +155,23 @@ auto valueOrMonostate(std::optional t) -> TagValue { auto TrackTags::get(Tag t) const -> TagValue { switch (t) { case Tag::kTitle: - return valueOrMonostate(title_); + return valueOrMonostate(title()); case Tag::kArtist: - return valueOrMonostate(artist_); + return valueOrMonostate(artist()); case Tag::kAllArtists: - return allArtists_; + return allArtists(); case Tag::kAlbum: - return valueOrMonostate(album_); + return valueOrMonostate(album()); case Tag::kAlbumArtist: - return valueOrMonostate(album_artist_); + return valueOrMonostate(albumArtist()); case Tag::kDisc: - return valueOrMonostate(disc_); + return valueOrMonostate(disc()); case Tag::kTrack: - return valueOrMonostate(track_); + return valueOrMonostate(track()); case Tag::kAlbumOrder: return albumOrder(); case Tag::kGenres: - return genres_; + return genres(); } return std::monostate{}; } @@ -240,6 +240,7 @@ auto TrackTags::artist() const -> const std::optional& { auto TrackTags::artist(std::string_view s) -> void { artist_ = s; + maybeSynthesizeAllArtists(); } auto TrackTags::allArtists() const -> std::span { @@ -248,6 +249,7 @@ auto TrackTags::allArtists() const -> std::span { auto TrackTags::allArtists(const std::string_view s) -> void { parseDelimitedTags(s, kAllArtistDelimiters, allArtists_); + maybeSynthesizeAllArtists(); } auto TrackTags::album() const -> const std::optional& { @@ -259,6 +261,9 @@ auto TrackTags::album(std::string_view s) -> void { } auto TrackTags::albumArtist() const -> const std::optional& { + if (!album_artist_) { + return artist_; + } return album_artist_; } @@ -320,6 +325,17 @@ auto TrackTags::Hash() const -> uint64_t { return komihash_stream_final(&stream); } +/* + * Adds the current 'artist' tag to 'allArtists' if needed. Many tracks lack a + * fine-grained 'ARTISTS=' tag (or equivalent), but pushing down this nuance to + * consumers of TrackTags adds a lot of complexity. + */ +auto TrackTags::maybeSynthesizeAllArtists() -> void { + if (allArtists_.empty() && artist_) { + allArtists_.push_back(*artist_); + } +} + auto database::TrackData::clone() const -> std::shared_ptr { auto data = std::make_shared(); data->id = id; diff --git a/src/tangara/database/track.hpp b/src/tangara/database/track.hpp index 21d6349d..71eb44ce 100644 --- a/src/tangara/database/track.hpp +++ b/src/tangara/database/track.hpp @@ -144,6 +144,8 @@ class TrackTags { auto Hash() const -> uint64_t; private: + auto maybeSynthesizeAllArtists() -> void; + Container encoding_; std::optional title_; From 33e89a0672079bf30151a9353cb0fbf165afaae5 Mon Sep 17 00:00:00 2001 From: jacqueline Date: Tue, 31 Dec 2024 11:32:51 +1100 Subject: [PATCH 5/6] Pass through TXXX kv pairs from libtags, treated as vorbis comments mp3 is no good i do not like them at all --- lib/libtags/id3v2.c | 4 ++ src/tangara/database/tag_parser.cpp | 63 ++++++++++++++++++++--------- src/tangara/database/tag_parser.hpp | 2 - 3 files changed, 47 insertions(+), 22 deletions(-) diff --git a/lib/libtags/id3v2.c b/lib/libtags/id3v2.c index 78a0a5fe..69c76aaa 100644 --- a/lib/libtags/id3v2.c +++ b/lib/libtags/id3v2.c @@ -68,6 +68,10 @@ v2cb(Tagctx *ctx, char *k, char *v) return 0; }else if(strcmp(k-1, "COM") == 0 || strcmp(k-1, "COMM") == 0){ txtcb(ctx, Tcomment, k-1, v); + }else if(strcmp(k, "XXX") == 0){ + k = v; + v += strlen(v) + 1; + txtcb(ctx, Tunknown, k, v); }else{ txtcb(ctx, Tunknown, k-1, v); } diff --git a/src/tangara/database/tag_parser.cpp b/src/tangara/database/tag_parser.cpp index 1756fa0a..6cdf6175 100644 --- a/src/tangara/database/tag_parser.cpp +++ b/src/tangara/database/tag_parser.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include "database/track.hpp" #include "debug.hpp" @@ -47,6 +48,29 @@ static auto convert_tag(int tag) -> std::optional { } } +static std::unordered_map sVorbisNameToTag{ + {"TITLE", Tag::kTitle}, + {"ALBUM", Tag::kAlbum}, + {"ARTIST", Tag::kArtist}, + {"ARTISTS", Tag::kAllArtists}, + {"ALBUMARTIST", Tag::kAlbumArtist}, + {"TRACK", Tag::kTrack}, + {"TRACKNUMBER", Tag::kTrack}, + {"GENRE", Tag::kGenres}, + {"DISC", Tag::kDisc}, + {"DISCNUMBER", Tag::kDisc}, +}; + +static auto convert_vorbis_tag(const std::string_view name) + -> std::optional { + std::string name_upper{name}; + std::transform(name.begin(), name.end(), name_upper.begin(), ::toupper); + if (sVorbisNameToTag.contains(name_upper)) { + return sVorbisNameToTag[name_upper]; + } + return {}; +} + namespace libtags { struct Aux { @@ -96,7 +120,14 @@ static void tag(Tagctx* ctx, int size, Tagread f) { Aux* aux = reinterpret_cast(ctx->aux); - auto tag = convert_tag(t); + std::optional tag; + if (t == Tunknown && k && v) { + // Sometimes 'unknown' tags are vorbis comments shoved into a generic tag + // name in other containers. + tag = convert_vorbis_tag(k); + } else { + tag = convert_tag(t); + } if (!tag) { return; } @@ -168,18 +199,7 @@ auto TagParserImpl::ReadAndParseTags(std::string_view path) return tags; } -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; - nameToTag_["GENRE"] = Tag::kGenres; - nameToTag_["DISC"] = Tag::kDisc; - nameToTag_["DISCNUMBER"] = Tag::kDisc; -} +OggTagParser::OggTagParser() {} auto OggTagParser::ReadAndParseTags(std::string_view p) -> std::shared_ptr { @@ -295,8 +315,9 @@ auto OggTagParser::parseComments(TrackTags& res, std::span data) std::string key_upper{key}; std::transform(key.begin(), key.end(), key_upper.begin(), ::toupper); - if (nameToTag_.contains(key_upper) && !val.empty()) { - res.set(nameToTag_[key_upper], val); + auto tag = convert_vorbis_tag(key); + if (tag && !val.empty()) { + res.set(*tag, val); } } @@ -316,14 +337,16 @@ auto GenericTagParser::ReadAndParseTags(std::string_view p) std::string path{p}; libtags::Aux aux; - // Fail fast if trying to parse a file that doesn't appear to be a supported audio format - // For context, see: https://codeberg.org/cool-tech-zone/tangara-fw/issues/149 + // Fail fast if trying to parse a file that doesn't appear to be a supported + // audio format For context, see: + // https://codeberg.org/cool-tech-zone/tangara-fw/issues/149 bool found = false; for (const auto& ext : supported_exts) { // Case-insensitive file extension check - if (std::equal(ext.rbegin(), ext.rend(), path.rbegin(), - [](char a, char b) { return std::tolower(a) == std::tolower(b); })) { - found=true; + if (std::equal(ext.rbegin(), ext.rend(), path.rbegin(), [](char a, char b) { + return std::tolower(a) == std::tolower(b); + })) { + found = true; break; } } diff --git a/src/tangara/database/tag_parser.hpp b/src/tangara/database/tag_parser.hpp index 9130b306..69b71940 100644 --- a/src/tangara/database/tag_parser.hpp +++ b/src/tangara/database/tag_parser.hpp @@ -47,8 +47,6 @@ class OggTagParser : public ITagParser { private: auto parseComments(TrackTags&, std::span data) -> void; auto parseLength(std::span data) -> uint64_t; - - std::unordered_map nameToTag_; }; class GenericTagParser : public ITagParser { From 824fca8cd0913a4f1603cdedd046c420840a3e65 Mon Sep 17 00:00:00 2001 From: jacqueline Date: Tue, 31 Dec 2024 12:34:04 +1100 Subject: [PATCH 6/6] Reorder indexes It turns out the index *id* controls this, which is probably not good! --- src/tangara/database/index.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/tangara/database/index.cpp b/src/tangara/database/index.cpp index b3af716b..e60f56d5 100644 --- a/src/tangara/database/index.cpp +++ b/src/tangara/database/index.cpp @@ -57,21 +57,21 @@ const IndexInfo kAllAlbums{ }; const IndexInfo kAllArtists{ - .id = 7, + .id = 5, .type = MediaType::kMusic, .name = "All Artists", .components = {Tag::kAllArtists, Tag::kTitle}, }; const IndexInfo kPodcasts{ - .id = 5, + .id = 6, .type = MediaType::kPodcast, .name = "Podcasts", .components = {Tag::kAlbum, Tag::kTitle}, }; const IndexInfo kAudiobooks{ - .id = 6, + .id = 7, .type = MediaType::kAudiobook, .name = "Audiobooks", .components = {Tag::kAlbum, Tag::kAlbumOrder},