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"
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})

@ -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{});

@ -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

@ -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,19 +79,15 @@ 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 = {};
}
auto encoded = EncodeIndexKey(key);
batch->Put(encoded.slice, value);

@ -13,6 +13,7 @@
#include <cstdlib>
#include <iomanip>
#include <mutex>
#include "opusfile.h"
namespace database {
@ -83,8 +84,13 @@ static void tag(Tagctx* ctx,
Tagread f) {
Aux* aux = reinterpret_cast<Aux*>(ctx->aux);
auto tag = convert_tag(t);
if (tag) {
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;
@ -92,7 +98,6 @@ static void tag(Tagctx* ctx,
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;
}

Loading…
Cancel
Save