summaryrefslogtreecommitdiff
path: root/src/database/song.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/database/song.cpp')
-rw-r--r--src/database/song.cpp131
1 files changed, 131 insertions, 0 deletions
diff --git a/src/database/song.cpp b/src/database/song.cpp
new file mode 100644
index 00000000..32507fa2
--- /dev/null
+++ b/src/database/song.cpp
@@ -0,0 +1,131 @@
+#include "song.hpp"
+
+#include <esp_log.h>
+#include <ff.h>
+#include <komihash.h>
+#include <tags.h>
+
+namespace database {
+
+namespace libtags {
+
+struct Aux {
+ FIL file;
+ FILINFO info;
+ SongTags* tags;
+};
+
+static int read(Tagctx* ctx, void* buf, int cnt) {
+ Aux* aux = reinterpret_cast<Aux*>(ctx->aux);
+ UINT bytes_read;
+ if (f_read(&aux->file, buf, cnt, &bytes_read) != FR_OK) {
+ return -1;
+ }
+ return bytes_read;
+}
+
+static int seek(Tagctx* ctx, int offset, int whence) {
+ Aux* aux = reinterpret_cast<Aux*>(ctx->aux);
+ FRESULT res;
+ if (whence == 0) {
+ // Seek from the start of the file. This is f_lseek's behaviour.
+ res = f_lseek(&aux->file, offset);
+ } else if (whence == 1) {
+ // Seek from current offset.
+ res = f_lseek(&aux->file, aux->file.fptr + offset);
+ } else if (whence == 2) {
+ // Seek from the end of the file
+ res = f_lseek(&aux->file, aux->info.fsize + offset);
+ } else {
+ return -1;
+ }
+ return res;
+}
+
+static void tag(Tagctx* ctx,
+ int t,
+ const char* k,
+ const char* v,
+ int offset,
+ int size,
+ Tagread f) {
+ Aux* aux = reinterpret_cast<Aux*>(ctx->aux);
+ if (t == Ttitle) {
+ aux->tags->title = v;
+ } else if (t == Tartist) {
+ aux->tags->artist = v;
+ } else if (t == Talbum) {
+ aux->tags->album = v;
+ }
+}
+
+static void toc(Tagctx* ctx, int ms, int offset) {}
+
+} // namespace libtags
+
+static const std::size_t kBufSize = 1024;
+static const char* kTag = "TAGS";
+
+auto ReadAndParseTags(const std::string& path, SongTags* out) -> bool {
+ libtags::Aux aux;
+ aux.tags = out;
+ if (f_stat(path.c_str(), &aux.info) != FR_OK ||
+ f_open(&aux.file, path.c_str(), FA_READ) != FR_OK) {
+ ESP_LOGW(kTag, "failed to open file %s", path.c_str());
+ return false;
+ }
+ // Fine to have this on the stack; this is only called on the leveldb task.
+ char buf[kBufSize];
+ Tagctx ctx;
+ ctx.read = libtags::read;
+ ctx.seek = libtags::seek;
+ ctx.tag = libtags::tag;
+ ctx.toc = libtags::toc;
+ ctx.aux = &aux;
+ ctx.buf = buf;
+ ctx.bufsz = kBufSize;
+ int res = tagsget(&ctx);
+ f_close(&aux.file);
+
+ if (res != 0) {
+ // Parsing failed.
+ return false;
+ }
+
+ switch (ctx.format) {
+ case Fmp3:
+ out->encoding = ENC_MP3;
+ break;
+ default:
+ out->encoding = ENC_UNSUPPORTED;
+ }
+
+ return true;
+}
+
+auto HashString(komihash_stream_t* stream, std::string str) -> void {
+ komihash_stream_update(stream, str.c_str(), str.length());
+}
+
+auto SongTags::Hash() const -> uint64_t {
+ komihash_stream_t stream;
+ komihash_stream_init(&stream, 0);
+ HashString(&stream, title.value_or(""));
+ HashString(&stream, artist.value_or(""));
+ HashString(&stream, album.value_or(""));
+ return komihash_stream_final(&stream);
+}
+
+auto SongData::UpdateHash(uint64_t new_hash) const -> SongData {
+ return SongData(id_, filepath_, new_hash, play_count_, is_tombstoned_);
+}
+
+auto SongData::Entomb() const -> SongData {
+ return SongData(id_, filepath_, tags_hash_, play_count_, true);
+}
+
+auto SongData::Exhume(const std::string& new_path) const -> SongData {
+ return SongData(id_, new_path, tags_hash_, play_count_, false);
+}
+
+} // namespace database