diff options
Diffstat (limited to 'src/tangara/database/track.cpp')
| -rw-r--r-- | src/tangara/database/track.cpp | 127 |
1 files changed, 85 insertions, 42 deletions
diff --git a/src/tangara/database/track.cpp b/src/tangara/database/track.cpp index 49babb6a..ad9db1ba 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: @@ -91,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<std::pmr::string>& 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<TrackTags> { return std::allocate_shared<TrackTags, std::pmr::polymorphic_allocator<TrackTags>>( @@ -108,21 +155,23 @@ auto valueOrMonostate(std::optional<T> 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(); 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{}; } @@ -135,6 +184,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 +217,7 @@ auto TrackTags::allPresent() const -> std::vector<Tag> { }; 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_); @@ -187,6 +240,16 @@ auto TrackTags::artist() const -> const std::optional<std::pmr::string>& { auto TrackTags::artist(std::string_view s) -> void { artist_ = s; + maybeSynthesizeAllArtists(); +} + +auto TrackTags::allArtists() const -> std::span<const std::pmr::string> { + return allArtists_; +} + +auto TrackTags::allArtists(const std::string_view s) -> void { + parseDelimitedTags(s, kAllArtistDelimiters, allArtists_); + maybeSynthesizeAllArtists(); } auto TrackTags::album() const -> const std::optional<std::pmr::string>& { @@ -198,6 +261,9 @@ auto TrackTags::album(std::string_view s) -> void { } auto TrackTags::albumArtist() const -> const std::optional<std::pmr::string>& { + if (!album_artist_) { + return artist_; + } return album_artist_; } @@ -230,41 +296,7 @@ auto TrackTags::genres() const -> std::span<const std::pmr::string> { } 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_); } /* @@ -293,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<TrackData> { auto data = std::make_shared<TrackData>(); data->id = id; |
