summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorjacqueline <me@jacqueline.id.au>2024-09-18 13:07:42 +1000
committerjacqueline <me@jacqueline.id.au>2024-09-19 16:32:11 +1000
commit57af3e64c8d6b13ac03192d9a8429dc801558a6b (patch)
tree210a18838536a6b916d49ecbc9971b69f954b0dc /src
parent9c95c2b4222da1fb31855f3985ab96d90865086d (diff)
downloadtangara-fw-57af3e64c8d6b13ac03192d9a8429dc801558a6b.tar.gz
Introduce a MediaType for each track and index
Initially set based on filepath, or by genre if the filepath doesn't match one of our presets
Diffstat (limited to 'src')
-rw-r--r--src/tangara/database/database.cpp72
-rw-r--r--src/tangara/database/database.hpp2
-rw-r--r--src/tangara/database/index.cpp14
-rw-r--r--src/tangara/database/index.hpp3
-rw-r--r--src/tangara/database/track.cpp1
-rw-r--r--src/tangara/database/track.hpp18
6 files changed, 94 insertions, 16 deletions
diff --git a/src/tangara/database/database.cpp b/src/tangara/database/database.cpp
index 9d0de695..c45f3862 100644
--- a/src/tangara/database/database.cpp
+++ b/src/tangara/database/database.cpp
@@ -6,7 +6,9 @@
#include "database/database.hpp"
+#include <bits/ranges_algo.h>
#include <algorithm>
+#include <cctype>
#include <cstdint>
#include <functional>
#include <iomanip>
@@ -33,6 +35,7 @@
#include "leveldb/write_batch.h"
#include "collation.hpp"
+#include "database.hpp"
#include "database/db_events.hpp"
#include "database/env_esp.hpp"
#include "database/index.hpp"
@@ -44,7 +47,6 @@
#include "memory_resource.hpp"
#include "result.hpp"
#include "tasks.hpp"
-#include "database.hpp"
namespace database {
@@ -52,7 +54,6 @@ static SingletonEnv<leveldb::EspEnv> sEnv;
[[maybe_unused]] static const char* kTag = "DB";
static const char kDbPath[] = "/.tangara-db";
-static const char kMusicPath[] = "Music";
static const char kKeyDbVersion[] = "schema_version";
static const char kKeyCustom[] = "U\0";
@@ -281,7 +282,7 @@ auto Database::getTrackID(std::string path) -> std::optional<TrackId> {
auto Database::setTrackData(TrackId id, const TrackData& data) -> void {
std::string key = EncodeDataKey(id);
std::string raw_val = EncodeDataValue(data);
-
+
auto res = db_->Put(leveldb::WriteOptions(), key, raw_val);
if (!res.ok()) {
ESP_LOGI(kTag, "Updating track data failed for track ID: %lu", id);
@@ -292,10 +293,7 @@ auto Database::getIndexes() -> std::vector<IndexInfo> {
// 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,
- kTracksByGenre,
+ kAllTracks, kAllAlbums, kAlbumsByArtist, kTracksByGenre, kPodcasts,
};
}
@@ -414,8 +412,9 @@ auto Database::updateIndexes() -> void {
// At this point, we know that the track still exists in its original
// location. All that's left to do is update any metadata about it.
+ auto new_type = calculateMediaType(*tags, track->filepath);
uint64_t new_hash = tags->Hash();
- if (new_hash != track->tags_hash) {
+ if (new_hash != track->tags_hash || new_type != track->type) {
// This track's tags have changed. Since the filepath is exactly the
// same, we assume this is a legitimate correction. Update the
// database.
@@ -431,6 +430,7 @@ auto Database::updateIndexes() -> void {
track->tags_hash = new_hash;
dbIngestTagHashes(*tags, track->individual_tag_hashes, batch);
+ track->type = new_type;
dbCreateIndexesForTrack(*track, *tags, batch);
batch.Put(EncodeDataKey(track->id), EncodeDataValue(*track));
batch.Put(EncodeHashKey(new_hash), EncodeHashValue(track->id));
@@ -442,14 +442,8 @@ auto Database::updateIndexes() -> void {
update_tracker_->onVerificationFinished();
// Stage 2: search for newly added files.
- std::string root;
- FF_DIR dir;
- if (f_opendir(&dir, kMusicPath) == FR_OK) {
- f_closedir(&dir);
- root = kMusicPath;
- }
- ESP_LOGI(kTag, "scanning for new tracks in '%s'", root.c_str());
- track_finder_.launch(root);
+ ESP_LOGI(kTag, "scanning for new tracks");
+ track_finder_.launch("");
};
auto Database::processCandidateCallback(FILINFO& info, std::string_view path)
@@ -507,6 +501,7 @@ auto Database::processCandidateCallback(FILINFO& info, std::string_view path)
data->tags_hash = hash;
data->modified_at = {info.fdate, info.ftime};
data->is_tombstoned = false;
+ data->type = calculateMediaType(*tags, path);
// Apply all the actual database changes as one atomic batch. This makes
// the whole 'new track' operation atomic, and also reduces the amount of
@@ -531,6 +526,51 @@ auto Database::isUpdating() -> bool {
return is_updating_;
}
+// FIXME: Make these media paths configurable.
+static constexpr char kMusicMediaPath[] = "Music/";
+static constexpr char kPodcastMediaPath[] = "Podcasts/";
+static constexpr char kAudiobookMediaPath[] = "Audiobooks/";
+
+auto Database::calculateMediaType(TrackTags& tags, std::string_view path)
+ -> MediaType {
+ // Use the filepath first, since it's the most explicit way for the user to
+ // tell us what this track is.
+ if (path.starts_with(kMusicMediaPath)) {
+ return MediaType::kMusic;
+ }
+ if (path.starts_with(kPodcastMediaPath)) {
+ return MediaType::kPodcast;
+ }
+ if (path.starts_with(kAudiobookMediaPath)) {
+ return MediaType::kAudiobook;
+ }
+
+ // Podcasts may (rarely!) have a genre tag that tells us what they are. Look
+ // for it.
+ auto equalsIgnoreCase = [&](char lhs, char rhs) {
+ // NB: not really safe across languages, but genre tags tend to be in
+ // English anyway.
+ return std::tolower(lhs) == std::tolower(rhs);
+ };
+
+ auto genres = tags.genres();
+ for (const auto& genre : genres) {
+ if (std::ranges::equal(genre, "podcast", equalsIgnoreCase)) {
+ return MediaType::kPodcast;
+ }
+ // FIXME: Do audiobooks have a common genre as well?
+ }
+
+ // No path we recognise, no specific genre we recognise. We basically don't
+ // know what kind of media this track is at this point. If there's any genres
+ // at all, then guess that it's probably some kind of music.
+ if (!genres.empty()) {
+ return MediaType::kMusic;
+ }
+
+ return MediaType::kUnknown;
+}
+
auto Database::dbCalculateNextTrackId() -> void {
std::unique_ptr<leveldb::Iterator> it{
db_->NewIterator(leveldb::ReadOptions())};
diff --git a/src/tangara/database/database.hpp b/src/tangara/database/database.hpp
index 6dd13b0d..e46a123e 100644
--- a/src/tangara/database/database.hpp
+++ b/src/tangara/database/database.hpp
@@ -14,6 +14,7 @@
#include <optional>
#include <stack>
#include <string>
+#include <string_view>
#include <utility>
#include <vector>
@@ -134,6 +135,7 @@ class Database {
auto processCandidateCallback(FILINFO&, std::string_view) -> void;
auto indexingCompleteCallback() -> void;
+ auto calculateMediaType(TrackTags&, std::string_view) -> MediaType;
auto dbCalculateNextTrackId() -> void;
auto dbMintNewTrackId() -> TrackId;
diff --git a/src/tangara/database/index.cpp b/src/tangara/database/index.cpp
index dec458f4..f1d8f258 100644
--- a/src/tangara/database/index.cpp
+++ b/src/tangara/database/index.cpp
@@ -30,28 +30,39 @@ namespace database {
const IndexInfo kAlbumsByArtist{
.id = 1,
+ .type = MediaType::kMusic,
.name = "Albums by Artist",
.components = {Tag::kAlbumArtist, Tag::kAlbum, Tag::kAlbumOrder},
};
const IndexInfo kTracksByGenre{
.id = 2,
+ .type = MediaType::kMusic,
.name = "Tracks by Genre",
.components = {Tag::kGenres, Tag::kTitle},
};
const IndexInfo kAllTracks{
.id = 3,
+ .type = MediaType::kMusic,
.name = "All Tracks",
.components = {Tag::kTitle},
};
const IndexInfo kAllAlbums{
.id = 4,
+ .type = MediaType::kMusic,
.name = "All Albums",
.components = {Tag::kAlbum, Tag::kAlbumOrder},
};
+const IndexInfo kPodcasts{
+ .id = 5,
+ .type = MediaType::kPodcast,
+ .name = "Podcasts",
+ .components = {Tag::kAlbum, Tag::kAlbumOrder},
+};
+
static auto titleOrFilename(const TrackData& data, const TrackTags& tags)
-> std::pmr::string {
auto title = tags.title();
@@ -203,6 +214,9 @@ auto Index(locale::ICollator& collator,
const TrackData& data,
const TrackTags& tags)
-> std::vector<std::pair<IndexKey, std::string>> {
+ if (index.type != data.type) {
+ return {};
+ }
Indexer indexer{collator, index, data, tags};
return indexer.index();
}
diff --git a/src/tangara/database/index.hpp b/src/tangara/database/index.hpp
index bc01ec2f..d1c10a36 100644
--- a/src/tangara/database/index.hpp
+++ b/src/tangara/database/index.hpp
@@ -28,6 +28,8 @@ typedef uint8_t IndexId;
struct IndexInfo {
// Unique id for this index
IndexId id;
+ // What kind of media this index should be used with
+ MediaType type;
// Localised, user-friendly description of this index. e.g. "Albums by Artist"
// or "All Tracks".
std::pmr::string name;
@@ -76,5 +78,6 @@ extern const IndexInfo kAlbumsByArtist;
extern const IndexInfo kTracksByGenre;
extern const IndexInfo kAllTracks;
extern const IndexInfo kAllAlbums;
+extern const IndexInfo kPodcasts;
} // namespace database
diff --git a/src/tangara/database/track.cpp b/src/tangara/database/track.cpp
index e737dd37..51d50a38 100644
--- a/src/tangara/database/track.cpp
+++ b/src/tangara/database/track.cpp
@@ -302,6 +302,7 @@ auto database::TrackData::clone() const -> std::shared_ptr<TrackData> {
data->is_tombstoned = is_tombstoned;
data->modified_at = modified_at;
data->last_position = last_position;
+ data->type = type;
return data;
}
diff --git a/src/tangara/database/track.hpp b/src/tangara/database/track.hpp
index 03fc47b9..71f40910 100644
--- a/src/tangara/database/track.hpp
+++ b/src/tangara/database/track.hpp
@@ -47,6 +47,23 @@ enum class Container {
kOpus = 5,
};
+enum class MediaType {
+ // We don't know what this is.
+ kUnknown = 0,
+ // It's music! You know what music is. Usually short-form, but sometimes
+ // long-form, since this type includes everything from standalone tracks, to
+ // tracks within an album, to live set and event recordings.
+ kMusic = 1,
+ // Usually long usually primarily spoken content. One file per episode, and
+ // the tagging norms for each file are all over the place. Prefer looking up
+ // track tags from accompanying RSS feed files.
+ kPodcast = 2,
+ // Usually long usually primarily spoken content. One distinct piece of
+ // 'audiobook' media may be split across several files, with a playlist or
+ // cue file to tie the pieces back together. May also just be one big mp3.
+ kAudiobook = 3,
+};
+
enum class Tag {
kTitle = 0,
kArtist = 1,
@@ -169,6 +186,7 @@ struct TrackData {
bool is_tombstoned;
std::pair<uint16_t, uint16_t> modified_at;
uint32_t last_position;
+ MediaType type;
TrackData(const TrackData&& other) = delete;
TrackData& operator=(TrackData& other) = delete;