summaryrefslogtreecommitdiff
path: root/src/tangara/database/track.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/tangara/database/track.cpp')
-rw-r--r--src/tangara/database/track.cpp127
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;