Add an opus-specific tag parser

custom
jacqueline 2 years ago
parent a9008884c9
commit dad14baa73
  1. 2
      src/database/CMakeLists.txt
  2. 4
      src/database/database.cpp
  3. 17
      src/database/include/tag_parser.hpp
  4. 12
      src/database/index.cpp
  5. 76
      src/database/tag_parser.cpp

@ -7,7 +7,7 @@ idf_component_register(
"file_gatherer.cpp" "tag_parser.cpp" "index.cpp" "file_gatherer.cpp" "tag_parser.cpp" "index.cpp"
INCLUDE_DIRS "include" INCLUDE_DIRS "include"
REQUIRES "result" "span" "esp_psram" "fatfs" "libtags" "komihash" "cbor" 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}) target_compile_options(${COMPONENT_LIB} PRIVATE ${EXTRA_WARNINGS})

@ -254,6 +254,10 @@ auto Database::Update() -> std::future<void> {
} else if (existing_data->filepath() != path) { } else if (existing_data->filepath() != path) {
ESP_LOGW(kTag, "tag hash collision for %s and %s", ESP_LOGW(kTag, "tag hash collision for %s and %s",
existing_data->filepath().c_str(), path.c_str()); 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{}); events::Ui().Dispatch(event::UpdateFinished{});

@ -20,18 +20,27 @@ class ITagParser {
-> bool = 0; -> bool = 0;
}; };
class GenericTagParser : public ITagParser {
public:
auto ReadAndParseTags(const std::string& path, TrackTags* out)
-> bool override;
};
class TagParserImpl : public ITagParser { class TagParserImpl : public ITagParser {
public: public:
TagParserImpl();
auto ReadAndParseTags(const std::string& path, TrackTags* out) auto ReadAndParseTags(const std::string& path, TrackTags* out)
-> bool override; -> bool override;
private: 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 of tags that have already been extracted from files. Ideally this
* cache should be slightly larger than any page sizes in the UI. * cache should be slightly larger than any page sizes in the UI.
*/ */
std::mutex cache_mutex_;
util::LruCache<16, std::string, TrackTags> cache_; util::LruCache<16, std::string, TrackTags> cache_;
// We could also consider keeping caches of artist name -> shared_string and // We could also consider keeping caches of artist name -> shared_string and
@ -39,4 +48,10 @@ class TagParserImpl : public ITagParser {
// of our UI. // of our UI.
}; };
class OpusTagParser : public ITagParser {
public:
auto ReadAndParseTags(const std::string& path, TrackTags* out)
-> bool override;
};
} // namespace database } // namespace database

@ -37,7 +37,8 @@ const IndexInfo kAllAlbums{
.components = {Tag::kAlbum, Tag::kAlbumTrack}, .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) { switch (tag) {
case Tag::kArtist: case Tag::kArtist:
return "Unknown Artist"; return "Unknown Artist";
@ -45,9 +46,10 @@ static auto missing_component_text(Tag tag) -> std::optional<std::string> {
return "Unknown Album"; return "Unknown Album";
case Tag::kGenre: case Tag::kGenre:
return "Unknown Genre"; return "Unknown Genre";
case Tag::kTitle:
return track.TitleOrFilename();
case Tag::kAlbumTrack: case Tag::kAlbumTrack:
case Tag::kDuration: case Tag::kDuration:
case Tag::kTitle:
default: default:
return {}; return {};
} }
@ -77,19 +79,15 @@ auto Index(const IndexInfo& info, const Track& t, leveldb::WriteBatch* batch)
value = *text; value = *text;
} else { } else {
key.item = {}; 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 // If this is the last component, then we should also fill in the track id
// and title. // and title.
if (i == info.components.size() - 1) { if (i == info.components.size() - 1) {
key.track = t.data().id(); key.track = t.data().id();
if (info.components.at(i) != Tag::kTitle) {
value = t.TitleOrFilename(); value = t.TitleOrFilename();
} }
} else {
key.track = {};
}
auto encoded = EncodeIndexKey(key); auto encoded = EncodeIndexKey(key);
batch->Put(encoded.slice, value); batch->Put(encoded.slice, value);

@ -13,6 +13,7 @@
#include <cstdlib> #include <cstdlib>
#include <iomanip> #include <iomanip>
#include <mutex> #include <mutex>
#include "opusfile.h"
namespace database { namespace database {
@ -83,8 +84,13 @@ static void tag(Tagctx* ctx,
Tagread f) { Tagread f) {
Aux* aux = reinterpret_cast<Aux*>(ctx->aux); Aux* aux = reinterpret_cast<Aux*>(ctx->aux);
auto tag = convert_tag(t); auto tag = convert_tag(t);
if (tag) { if (!tag) {
return;
}
std::string value{v}; std::string value{v};
if (value.empty()) {
return;
}
if (*tag == Tag::kAlbumTrack) { if (*tag == Tag::kAlbumTrack) {
uint32_t as_int = std::atoi(v); uint32_t as_int = std::atoi(v);
std::ostringstream oss; std::ostringstream oss;
@ -92,7 +98,6 @@ static void tag(Tagctx* ctx,
value = oss.str(); value = oss.str();
} }
aux->tags->set(*tag, value); aux->tags->set(*tag, value);
}
} }
static void toc(Tagctx* ctx, int ms, int offset) {} 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 std::size_t kBufSize = 1024;
static const char* kTag = "TAGS"; static const char* kTag = "TAGS";
TagParserImpl::TagParserImpl() {
extension_to_parser_["opus"] = std::make_unique<OpusTagParser>();
}
auto TagParserImpl::ReadAndParseTags(const std::string& path, TrackTags* out) auto TagParserImpl::ReadAndParseTags(const std::string& path, TrackTags* out)
-> bool { -> 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; libtags::Aux aux;
aux.tags = out; aux.tags = out;
if (f_stat(path.c_str(), &aux.info) != FR_OK || 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) { if (res != 0) {
// Parsing failed. // 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; return false;
} }
@ -172,11 +206,41 @@ auto TagParserImpl::ReadAndParseTags(const std::string& path, TrackTags* out)
if (ctx.duration > 0) { if (ctx.duration > 0) {
out->duration = ctx.duration; out->duration = ctx.duration;
} }
return true;
}
{ auto OpusTagParser::ReadAndParseTags(const std::string& path, TrackTags* out)
std::lock_guard<std::mutex> lock{cache_mutex_}; -> bool {
cache_.Put(path, *out); 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; return true;
} }

Loading…
Cancel
Save