summaryrefslogtreecommitdiff
path: root/src/database
diff options
context:
space:
mode:
Diffstat (limited to 'src/database')
-rw-r--r--src/database/CMakeLists.txt2
-rw-r--r--src/database/database.cpp4
-rw-r--r--src/database/include/tag_parser.hpp17
-rw-r--r--src/database/index.cpp14
-rw-r--r--src/database/tag_parser.cpp90
5 files changed, 104 insertions, 23 deletions
diff --git a/src/database/CMakeLists.txt b/src/database/CMakeLists.txt
index 129920cd..008757f8 100644
--- a/src/database/CMakeLists.txt
+++ b/src/database/CMakeLists.txt
@@ -7,7 +7,7 @@ idf_component_register(
"file_gatherer.cpp" "tag_parser.cpp" "index.cpp"
INCLUDE_DIRS "include"
REQUIRES "result" "span" "esp_psram" "fatfs" "libtags" "komihash" "cbor"
- "tasks" "shared_string" "util" "tinyfsm" "events")
+ "tasks" "shared_string" "util" "tinyfsm" "events" "opusfile")
target_compile_options(${COMPONENT_LIB} PRIVATE ${EXTRA_WARNINGS})
diff --git a/src/database/database.cpp b/src/database/database.cpp
index 0ed5e389..c8dd49be 100644
--- a/src/database/database.cpp
+++ b/src/database/database.cpp
@@ -254,6 +254,10 @@ auto Database::Update() -> std::future<void> {
} else if (existing_data->filepath() != path) {
ESP_LOGW(kTag, "tag hash collision for %s and %s",
existing_data->filepath().c_str(), path.c_str());
+ ESP_LOGI(kTag, "hash components: %s, %s, %s",
+ tags.at(Tag::kTitle).value_or("no title").c_str(),
+ tags.at(Tag::kArtist).value_or("no artist").c_str(),
+ tags.at(Tag::kAlbum).value_or("no album").c_str());
}
});
events::Ui().Dispatch(event::UpdateFinished{});
diff --git a/src/database/include/tag_parser.hpp b/src/database/include/tag_parser.hpp
index dcc8aa21..85721357 100644
--- a/src/database/include/tag_parser.hpp
+++ b/src/database/include/tag_parser.hpp
@@ -20,18 +20,27 @@ class ITagParser {
-> bool = 0;
};
+class GenericTagParser : public ITagParser {
+ public:
+ auto ReadAndParseTags(const std::string& path, TrackTags* out)
+ -> bool override;
+};
+
class TagParserImpl : public ITagParser {
public:
+ TagParserImpl();
auto ReadAndParseTags(const std::string& path, TrackTags* out)
-> bool override;
private:
- std::mutex cache_mutex_;
+ std::map<std::string, std::unique_ptr<ITagParser>> extension_to_parser_;
+ GenericTagParser generic_parser_;
/*
* Cache of tags that have already been extracted from files. Ideally this
* cache should be slightly larger than any page sizes in the UI.
*/
+ std::mutex cache_mutex_;
util::LruCache<16, std::string, TrackTags> cache_;
// We could also consider keeping caches of artist name -> shared_string and
@@ -39,4 +48,10 @@ class TagParserImpl : public ITagParser {
// of our UI.
};
+class OpusTagParser : public ITagParser {
+ public:
+ auto ReadAndParseTags(const std::string& path, TrackTags* out)
+ -> bool override;
+};
+
} // namespace database
diff --git a/src/database/index.cpp b/src/database/index.cpp
index 844b33a3..6ec88622 100644
--- a/src/database/index.cpp
+++ b/src/database/index.cpp
@@ -37,7 +37,8 @@ const IndexInfo kAllAlbums{
.components = {Tag::kAlbum, Tag::kAlbumTrack},
};
-static auto missing_component_text(Tag tag) -> std::optional<std::string> {
+static auto missing_component_text(const Track& track, Tag tag)
+ -> std::optional<std::string> {
switch (tag) {
case Tag::kArtist:
return "Unknown Artist";
@@ -45,9 +46,10 @@ static auto missing_component_text(Tag tag) -> std::optional<std::string> {
return "Unknown Album";
case Tag::kGenre:
return "Unknown Genre";
+ case Tag::kTitle:
+ return track.TitleOrFilename();
case Tag::kAlbumTrack:
case Tag::kDuration:
- case Tag::kTitle:
default:
return {};
}
@@ -77,18 +79,14 @@ auto Index(const IndexInfo& info, const Track& t, leveldb::WriteBatch* batch)
value = *text;
} else {
key.item = {};
- value = missing_component_text(info.components.at(i)).value_or("");
+ value = missing_component_text(t, info.components.at(i)).value_or("");
}
// If this is the last component, then we should also fill in the track id
// and title.
if (i == info.components.size() - 1) {
key.track = t.data().id();
- if (info.components.at(i) != Tag::kTitle) {
- value = t.TitleOrFilename();
- }
- } else {
- key.track = {};
+ value = t.TitleOrFilename();
}
auto encoded = EncodeIndexKey(key);
diff --git a/src/database/tag_parser.cpp b/src/database/tag_parser.cpp
index fc6c95f2..03e92b95 100644
--- a/src/database/tag_parser.cpp
+++ b/src/database/tag_parser.cpp
@@ -13,6 +13,7 @@
#include <cstdlib>
#include <iomanip>
#include <mutex>
+#include "opusfile.h"
namespace database {
@@ -83,16 +84,20 @@ static void tag(Tagctx* ctx,
Tagread f) {
Aux* aux = reinterpret_cast<Aux*>(ctx->aux);
auto tag = convert_tag(t);
- if (tag) {
- std::string value{v};
- if (*tag == Tag::kAlbumTrack) {
- uint32_t as_int = std::atoi(v);
- std::ostringstream oss;
- oss << std::setw(4) << std::setfill('0') << as_int;
- value = oss.str();
- }
- aux->tags->set(*tag, value);
+ if (!tag) {
+ return;
+ }
+ std::string value{v};
+ if (value.empty()) {
+ return;
+ }
+ if (*tag == Tag::kAlbumTrack) {
+ uint32_t as_int = std::atoi(v);
+ std::ostringstream oss;
+ oss << std::setw(4) << std::setfill('0') << as_int;
+ value = oss.str();
}
+ aux->tags->set(*tag, value);
}
static void toc(Tagctx* ctx, int ms, int offset) {}
@@ -102,6 +107,10 @@ static void toc(Tagctx* ctx, int ms, int offset) {}
static const std::size_t kBufSize = 1024;
static const char* kTag = "TAGS";
+TagParserImpl::TagParserImpl() {
+ extension_to_parser_["opus"] = std::make_unique<OpusTagParser>();
+}
+
auto TagParserImpl::ReadAndParseTags(const std::string& path, TrackTags* out)
-> bool {
{
@@ -113,6 +122,31 @@ auto TagParserImpl::ReadAndParseTags(const std::string& path, TrackTags* out)
}
}
+ ITagParser* parser = &generic_parser_;
+ auto dot_pos = path.find_last_of(".");
+ if (dot_pos != std::string::npos && path.size() - dot_pos > 1) {
+ std::string extension = path.substr(dot_pos + 1);
+ std::transform(extension.begin(), extension.end(), extension.begin(),
+ [](unsigned char c) { return std::tolower(c); });
+ if (extension_to_parser_.contains(extension)) {
+ parser = extension_to_parser_[extension].get();
+ }
+ }
+
+ if (!parser->ReadAndParseTags(path, out)) {
+ return false;
+ }
+
+ {
+ std::lock_guard<std::mutex> lock{cache_mutex_};
+ cache_.Put(path, *out);
+ }
+
+ return true;
+}
+
+auto GenericTagParser::ReadAndParseTags(const std::string& path, TrackTags* out)
+ -> bool {
libtags::Aux aux;
aux.tags = out;
if (f_stat(path.c_str(), &aux.info) != FR_OK ||
@@ -136,7 +170,7 @@ auto TagParserImpl::ReadAndParseTags(const std::string& path, TrackTags* out)
if (res != 0) {
// Parsing failed.
- ESP_LOGE(kTag, "tag parsing failed, reason %d", res);
+ ESP_LOGE(kTag, "tag parsing for %s failed, reason %d", path.c_str(), res);
return false;
}
@@ -172,11 +206,41 @@ auto TagParserImpl::ReadAndParseTags(const std::string& path, TrackTags* out)
if (ctx.duration > 0) {
out->duration = ctx.duration;
}
+ return true;
+}
- {
- std::lock_guard<std::mutex> lock{cache_mutex_};
- cache_.Put(path, *out);
+auto OpusTagParser::ReadAndParseTags(const std::string& path, TrackTags* out)
+ -> bool {
+ std::string vfs_path = "/sdcard" + path;
+ int err;
+ OggOpusFile* f = op_test_file(vfs_path.c_str(), &err);
+ if (f == NULL) {
+ ESP_LOGE(kTag, "opusfile tag parsing failed: %d", err);
+ return false;
+ }
+ const OpusTags* tags = op_tags(f, -1);
+ if (tags == NULL) {
+ ESP_LOGE(kTag, "no tags in opusfile");
+ op_free(f);
+ return false;
+ }
+
+ out->encoding(Container::kOpus);
+ const char* tag = NULL;
+ tag = opus_tags_query(tags, "TITLE", 0);
+ if (tag != NULL) {
+ out->set(Tag::kTitle, tag);
+ }
+ tag = opus_tags_query(tags, "ARTIST", 0);
+ if (tag != NULL) {
+ out->set(Tag::kArtist, tag);
}
+ tag = opus_tags_query(tags, "ALBUM", 0);
+ if (tag != NULL) {
+ out->set(Tag::kAlbum, tag);
+ }
+
+ op_free(f);
return true;
}