summaryrefslogtreecommitdiff
path: root/src/database/track.cpp
diff options
context:
space:
mode:
authorjacqueline <me@jacqueline.id.au>2023-12-12 12:59:38 +1100
committerjacqueline <me@jacqueline.id.au>2023-12-12 12:59:38 +1100
commit01eb8683733f39a6de984111f035bb8f71dcf8b8 (patch)
treefb682164eb781a26d1798294180632e63a724f52 /src/database/track.cpp
parentbd730c82b0423af65f7148bb4abe01e1c3430691 (diff)
downloadtangara-fw-01eb8683733f39a6de984111f035bb8f71dcf8b8.tar.gz
Support more datatypes in track tags
Diffstat (limited to 'src/database/track.cpp')
-rw-r--r--src/database/track.cpp274
1 files changed, 244 insertions, 30 deletions
diff --git a/src/database/track.cpp b/src/database/track.cpp
index 871e3087..acd479f1 100644
--- a/src/database/track.cpp
+++ b/src/database/track.cpp
@@ -6,14 +6,22 @@
#include "track.hpp"
-#include <komihash.h>
-#include <sys/_stdint.h>
+#include <iomanip>
+#include <iostream>
+#include <sstream>
+#include <string>
+
+#include "esp_log.h"
+#include "komihash.h"
#include "memory_resource.hpp"
+#include "span.hpp"
namespace database {
-auto TagToString(Tag t) -> std::string {
+static constexpr char kGenreDelimiters[] = ",;";
+
+auto tagName(Tag t) -> std::string {
switch (t) {
case Tag::kTitle:
return "title";
@@ -21,42 +29,241 @@ auto TagToString(Tag t) -> std::string {
return "artist";
case Tag::kAlbum:
return "album";
- case Tag::kAlbumTrack:
- return "album_track";
- case Tag::kGenre:
+ case Tag::kAlbumArtist:
+ return "album_artist";
+ case Tag::kDisc:
+ return "disc";
+ case Tag::kTrack:
+ return "track";
+ case Tag::kAlbumOrder:
+ return "album_order";
+ case Tag::kGenres:
return "genre";
- case Tag::kDuration:
- return "duration";
- default:
- return "";
}
+ return "";
+}
+
+auto tagHash(const TagValue& t) -> uint64_t {
+ return std::visit(
+ [&](auto&& arg) {
+ using T = std::decay_t<decltype(arg)>;
+ if constexpr (std::is_same_v<T, std::monostate>) {
+ return static_cast<uint64_t>(0);
+ } else if constexpr (std::is_same_v<T, std::pmr::string>) {
+ return komihash(arg.data(), arg.size(), 0);
+ } else if constexpr (std::is_same_v<T, uint32_t>) {
+ return komihash(&arg, sizeof(arg), 0);
+ } else if constexpr (std::is_same_v<
+ T, cpp::span<const std::pmr::string>>) {
+ komihash_stream_t hash;
+ komihash_stream_init(&hash, 0);
+ for (const auto& i : arg) {
+ komihash_stream_update(&hash, i.data(), i.size());
+ }
+ return komihash_stream_final(&hash);
+ }
+ },
+ t);
+ return 0;
}
-auto TrackTags::set(const Tag& key, const std::pmr::string& val) -> void {
- tags_[key] = val;
+auto tagToString(const TagValue& val) -> std::string {
+ return std::visit(
+ [&](auto&& arg) -> std::string {
+ using T = std::decay_t<decltype(arg)>;
+ if constexpr (std::is_same_v<T, std::monostate>) {
+ return "";
+ } else if constexpr (std::is_same_v<T, std::pmr::string>) {
+ return {arg.data(), arg.size()};
+ } else if constexpr (std::is_same_v<T, uint32_t>) {
+ return std::to_string(arg);
+ } else if constexpr (std::is_same_v<
+ T, cpp::span<const std::pmr::string>>) {
+ std::ostringstream builder{};
+ for (const auto& str : arg) {
+ builder << std::string{str.data(), str.size()} << ",";
+ }
+ return builder.str();
+ }
+ },
+ val);
+ return "";
+}
+
+template <typename T>
+auto valueOrMonostate(std::optional<T> t) -> TagValue {
+ if (t) {
+ return *t;
+ }
+ return std::monostate{};
}
-auto TrackTags::at(const Tag& key) const -> std::optional<std::pmr::string> {
- if (tags_.contains(key)) {
- return tags_.at(key);
+auto TrackTags::get(Tag t) const -> TagValue {
+ switch (t) {
+ case Tag::kTitle:
+ return valueOrMonostate(title_);
+ case Tag::kArtist:
+ return valueOrMonostate(artist_);
+ case Tag::kAlbum:
+ return valueOrMonostate(album_);
+ case Tag::kAlbumArtist:
+ return valueOrMonostate(album_artist_);
+ case Tag::kDisc:
+ return valueOrMonostate(disc_);
+ case Tag::kTrack:
+ return valueOrMonostate(track_);
+ case Tag::kAlbumOrder:
+ return albumOrder();
+ case Tag::kGenres:
+ return genres_;
}
- return {};
+ return std::monostate{};
+}
+
+auto TrackTags::set(Tag t, std::string_view v) -> void {
+ switch (t) {
+ case Tag::kTitle:
+ title(v);
+ break;
+ case Tag::kArtist:
+ artist(v);
+ break;
+ case Tag::kAlbum:
+ album(v);
+ break;
+ case Tag::kAlbumArtist:
+ albumArtist(v);
+ break;
+ case Tag::kDisc:
+ disc(v);
+ break;
+ case Tag::kTrack:
+ track(v);
+ break;
+ case Tag::kAlbumOrder:
+ // This tag is derices from disc and track, and so it can't be set.
+ break;
+ case Tag::kGenres:
+ genres(v);
+ break;
+ }
+}
+
+auto TrackTags::allPresent() const -> std::vector<Tag> {
+ std::vector<Tag> out;
+ auto add_if_present = [&](Tag t, auto opt) {
+ if (opt) {
+ out.push_back(t);
+ }
+ };
+ add_if_present(Tag::kTitle, title_);
+ add_if_present(Tag::kArtist, artist_);
+ add_if_present(Tag::kAlbum, album_);
+ add_if_present(Tag::kAlbumArtist, album_artist_);
+ add_if_present(Tag::kDisc, disc_);
+ add_if_present(Tag::kTrack, track_);
+ add_if_present(Tag::kGenres, !genres_.empty());
+ return out;
+}
+
+auto TrackTags::title() const -> const std::optional<std::pmr::string>& {
+ return title_;
+}
+
+auto TrackTags::title(std::string_view s) -> void {
+ title_ = s;
+}
+
+auto TrackTags::artist() const -> const std::optional<std::pmr::string>& {
+ return artist_;
+}
+
+auto TrackTags::artist(std::string_view s) -> void {
+ artist_ = s;
+}
+
+auto TrackTags::album() const -> const std::optional<std::pmr::string>& {
+ return album_;
+}
+
+auto TrackTags::album(std::string_view s) -> void {
+ album_ = s;
+}
+
+auto TrackTags::albumArtist() const -> const std::optional<std::pmr::string>& {
+ return album_artist_;
}
-auto TrackTags::operator[](const Tag& key) const
- -> std::optional<std::pmr::string> {
- return at(key);
+auto TrackTags::albumArtist(std::string_view s) -> void {
+ album_artist_ = s;
}
-/* Helper function to update a komihash stream with a std::pmr::string. */
-auto HashString(komihash_stream_t* stream, const std::pmr::string& str)
- -> void {
- komihash_stream_update(stream, str.c_str(), str.length());
+auto TrackTags::disc() const -> const std::optional<uint8_t>& {
+ return disc_;
+}
+
+auto TrackTags::disc(const std::string_view s) -> void {
+ disc_ = std::stoi({s.data(), s.size()});
+}
+
+auto TrackTags::track() const -> const std::optional<uint16_t>& {
+ return track_;
+}
+
+auto TrackTags::track(const std::string_view s) -> void {
+ track_ = std::stoi({s.data(), s.size()});
+}
+
+auto TrackTags::albumOrder() const -> uint32_t {
+ return (disc_.value_or(0) << 16) | track_.value_or(0);
+}
+
+auto TrackTags::genres() const -> cpp::span<const std::pmr::string> {
+ return genres_;
+}
+
+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 = [=](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);
+ }
+ }
}
/*
- * Uses a komihash stream to incrementally hash tags. This lowers the function's
- * memory footprint a little so that it's safe to call from any stack.
+ * Uses a komihash stream to incrementally hash tags. This lowers the
+ * function's memory footprint a little so that it's safe to call from any
+ * stack.
*/
auto TrackTags::Hash() const -> uint64_t {
// TODO(jacqueline): this function doesn't work very well for tracks with no
@@ -64,16 +271,23 @@ auto TrackTags::Hash() const -> uint64_t {
komihash_stream_t stream;
komihash_stream_init(&stream, 0);
- HashString(&stream, at(Tag::kTitle).value_or(""));
- HashString(&stream, at(Tag::kArtist).value_or(""));
- HashString(&stream, at(Tag::kAlbum).value_or(""));
- HashString(&stream, at(Tag::kAlbumTrack).value_or(""));
+ auto add = [&](const uint64_t& h) {
+ komihash_stream_update(&stream, &h, sizeof(h));
+ };
+
+ add(tagHash(get(Tag::kTitle)));
+ add(tagHash(get(Tag::kArtist)));
+ add(tagHash(get(Tag::kAlbum)));
+ add(tagHash(get(Tag::kAlbumArtist)));
+
+ // TODO: Should we be including this?
+ add(tagHash(get(Tag::kAlbumOrder)));
return komihash_stream_final(&stream);
}
auto Track::TitleOrFilename() const -> std::pmr::string {
- auto title = tags().at(Tag::kTitle);
+ auto title = tags().title();
if (title) {
return *title;
}