Add an opus-specific tag parser
This commit is contained in:
@@ -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,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);
|
||||
|
||||
+77
-13
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user