From 6b89ea00e511a75b7e982d9fa1df0cddaab8f9d4 Mon Sep 17 00:00:00 2001 From: Kendall Garner <17521368+kgarner7@users.noreply.github.com> Date: Fri, 1 Aug 2025 21:36:29 -0700 Subject: [PATCH 1/2] add tags tag for showing tags embedded in a file --- adapters/taglib/end_to_end_test.go | 30 +++++++++++ adapters/taglib/taglib_wrapper.cpp | 67 ++++++++++++++++++++++++- adapters/taglib/taglib_wrapper.go | 5 ++ adapters/taglib/taglib_wrapper.h | 1 + model/tag.go | 1 + resources/mappings.yaml | 3 ++ tests/fixtures/ape-id3v1.wv | Bin 0 -> 43010 bytes tests/fixtures/ape-v1-v2.mp3 | Bin 0 -> 3575 bytes tests/fixtures/empty.mp3 | Bin 0 -> 2297 bytes tests/fixtures/empty.wav | Bin 0 -> 88244 bytes tests/fixtures/vorbis-id3v1-id3v2.flac | Bin 0 -> 31191 bytes 11 files changed, 106 insertions(+), 1 deletion(-) create mode 100644 tests/fixtures/ape-id3v1.wv create mode 100644 tests/fixtures/ape-v1-v2.mp3 create mode 100644 tests/fixtures/empty.mp3 create mode 100644 tests/fixtures/empty.wav create mode 100644 tests/fixtures/vorbis-id3v1-id3v2.flac diff --git a/adapters/taglib/end_to_end_test.go b/adapters/taglib/end_to_end_test.go index e4d94bb24..fb4fd023c 100644 --- a/adapters/taglib/end_to_end_test.go +++ b/adapters/taglib/end_to_end_test.go @@ -275,4 +275,34 @@ var _ = Describe("Extractor", func() { } }) }) + + Describe("tags", func() { + DescribeTable("test metadata tags across files, and special cases", func(file string, tags ...string) { + mf := parseTestFile("tests/fixtures/" + file) + metadataTags := mf.Tags[model.TagMetadataTag] + Expect(metadataTags).To(HaveLen(len(tags))) + for _, tag := range tags { + Expect(mf.Tags[model.TagMetadataTag]).To(ContainElement(tag)) + } + }, + // weirder cases + Entry("file with multiple tags", "ape-v1-v2.mp3", "ape", "id3v1", "id3v2"), + Entry("wavpak with both ape and id3v1", "ape-id3v1.wv", "ape", "id3v1"), + Entry("flac with vorbis, id3v1 and id3v2", "vorbis-id3v1-id3v2.flac", "vorbis", "id3v1", "id3v2"), + + // No Metadata at all + Entry("mp3 with no tags", "empty.mp3"), + Entry("wav with no tags", "empty.wav"), + + // More standard cases + Entry("normal flac", "test.flac", "vorbis"), + Entry("normal m4a", "test.m4a", "mp4"), + Entry("mp3 with id3v2", "no_replaygain.mp3", "id3v2"), + Entry("normal wma", "test.wma", "asf"), + Entry("normal opus", "test.ogg", "vorbis"), + Entry("wavpak with ape", "test.wv", "ape"), + Entry("nonempty wav", "test.wav", "id3v2"), + Entry("nonempty aiff", "test.aiff", "id3v2"), + ) + }) }) diff --git a/adapters/taglib/taglib_wrapper.cpp b/adapters/taglib/taglib_wrapper.cpp index 224642c6d..1d918483a 100644 --- a/adapters/taglib/taglib_wrapper.cpp +++ b/adapters/taglib/taglib_wrapper.cpp @@ -27,6 +27,13 @@ char has_cover(const TagLib::FileRef f); static char TAGLIB_VERSION[16]; +static char APE_TAG[] = "ape"; +static char ASF_TAG[] = "asf"; +static char ID3V1_TAG[] = "id3v1"; +static char ID3V2_TAG[] = "id3v2"; +static char MP4_TAG[] = "mp4"; +static char VORBIS_TAG[] = "vorbis"; + char* taglib_version() { snprintf((char *)TAGLIB_VERSION, 16, "%d.%d.%d", TAGLIB_MAJOR_VERSION, TAGLIB_MINOR_VERSION, TAGLIB_PATCH_VERSION); return (char *)TAGLIB_VERSION; @@ -71,11 +78,24 @@ int taglib_read(const FILENAME_CHAR_T *filename, unsigned long id) { TagLib::PropertyMap tags = f.file()->properties(); TagLib::ID3v2::Tag *id3Tags = NULL; + bool has_tag = false; // Get some extended/non-standard ID3-only tags (ex: iTunes extended frames) TagLib::MPEG::File *mp3File(dynamic_cast(f.file())); if (mp3File != NULL) { - id3Tags = mp3File->ID3v2Tag(); + if (mp3File->hasID3v2Tag()) { + id3Tags = mp3File->ID3v2Tag(); + } + + if (mp3File->hasID3v1Tag()) { + goPutTagType(id, ID3V1_TAG); + has_tag = true; + } + + if (mp3File->hasAPETag()) { + goPutTagType(id, APE_TAG); + has_tag = true; + } } if (id3Tags == NULL) { @@ -92,12 +112,21 @@ int taglib_read(const FILENAME_CHAR_T *filename, unsigned long id) { } } + if (id3Tags == NULL) { + if (TagLib::DSF::File * dsffile{ dynamic_cast(f.file())}) { + id3Tags = dsffile->tag(); + } + } + // Yes, it is possible to have ID3v2 tags in FLAC. However, that can cause problems // with many players, so they will not be parsed if (id3Tags != NULL) { const auto &frames = id3Tags->frameListMap(); + goPutTagType(id, ID3V2_TAG); + has_tag = true; + for (const auto &kv: frames) { if (kv.first == "USLT") { for (const auto &tag: kv.second) { @@ -157,6 +186,11 @@ int taglib_read(const FILENAME_CHAR_T *filename, unsigned long id) { // M4A may have some iTunes specific tags not captured by the PropertyMap interface TagLib::MP4::File *m4afile(dynamic_cast(f.file())); if (m4afile != NULL) { + if (m4afile->hasMP4Tag()) { + goPutTagType(id, MP4_TAG); + has_tag = true; + } + const auto itemListMap = m4afile->tag()->itemMap(); for (const auto item: itemListMap) { char *key = const_cast(item.first.toCString(true)); @@ -170,6 +204,9 @@ int taglib_read(const FILENAME_CHAR_T *filename, unsigned long id) { // WMA/ASF files may have additional tags not captured by the PropertyMap interface TagLib::ASF::File *asfFile(dynamic_cast(f.file())); if (asfFile != NULL) { + goPutTagType(id, ASF_TAG); + has_tag = true; + const TagLib::ASF::Tag *asfTags{asfFile->tag()}; const auto itemListMap = asfTags->attributeListMap(); for (const auto item : itemListMap) { @@ -200,6 +237,34 @@ int taglib_read(const FILENAME_CHAR_T *filename, unsigned long id) { goPutStr(id, (char *)"has_picture", (char *)"true"); } + if (!has_tag) { + if (TagLib::FLAC::File * flacFile{dynamic_cast(f.file())}) { + if (flacFile->hasXiphComment()) { + goPutTagType(id, VORBIS_TAG); + } + + if (flacFile->hasID3v2Tag()) { + goPutTagType(id, ID3V2_TAG); + } + + if (flacFile->hasID3v1Tag()) { + goPutTagType(id, ID3V1_TAG); + } + } else if (TagLib::Ogg::Vorbis::File * vorbisFile{dynamic_cast(f.file())}) { + goPutTagType(id, VORBIS_TAG); + } else if (TagLib::Ogg::Opus::File * opusFile{dynamic_cast(f.file())}) { + goPutTagType(id, VORBIS_TAG); + } else if (TagLib::WavPack::File * wvFile{dynamic_cast(f.file())}) { + if (wvFile->hasAPETag()) { + goPutTagType(id, APE_TAG); + } + + if (wvFile->hasID3v1Tag()) { + goPutTagType(id, ID3V1_TAG); + } + } + } + return 0; } diff --git a/adapters/taglib/taglib_wrapper.go b/adapters/taglib/taglib_wrapper.go index 4a979920a..64d452948 100644 --- a/adapters/taglib/taglib_wrapper.go +++ b/adapters/taglib/taglib_wrapper.go @@ -155,3 +155,8 @@ func goPutLyricLine(id C.ulong, lang *C.char, text *C.char, time C.int) { m[k] = []string{formattedLine} } } + +//export goPutTagType +func goPutTagType(id C.ulong, tag *C.char) { + doPutTag(id, "__tags", tag) +} diff --git a/adapters/taglib/taglib_wrapper.h b/adapters/taglib/taglib_wrapper.h index c93f4c14a..6f4447c24 100644 --- a/adapters/taglib/taglib_wrapper.h +++ b/adapters/taglib/taglib_wrapper.h @@ -16,6 +16,7 @@ extern void goPutStr(unsigned long id, char *key, char *val); extern void goPutInt(unsigned long id, char *key, int val); extern void goPutLyrics(unsigned long id, char *lang, char *val); extern void goPutLyricLine(unsigned long id, char *lang, char *text, int time); +extern void goPutTagType(unsigned long id, char *tag); int taglib_read(const FILENAME_CHAR_T *filename, unsigned long id); char* taglib_version(); diff --git a/model/tag.go b/model/tag.go index 674f688ca..c1b788577 100644 --- a/model/tag.go +++ b/model/tag.go @@ -194,6 +194,7 @@ const ( TagISRC TagName = "isrc" TagBPM TagName = "bpm" TagExplicitStatus TagName = "explicitstatus" + TagMetadataTag TagName = "tags" // Dates and years diff --git a/resources/mappings.yaml b/resources/mappings.yaml index d1da5c620..7d32a5397 100644 --- a/resources/mappings.yaml +++ b/resources/mappings.yaml @@ -202,6 +202,9 @@ main: # Additional tags. You can add new tags without the need to modify the code. They will be available as fields # for smart playlists additional: + # Internal tag type, reprentes medatada tag(s) found in the file + tags: + aliases: [ __tags ] asin: aliases: [ txxx:asin, asin, ----:com.apple.itunes:asin ] barcode: diff --git a/tests/fixtures/ape-id3v1.wv b/tests/fixtures/ape-id3v1.wv new file mode 100644 index 0000000000000000000000000000000000000000..5ce2fe15e5aad915fefc37b059aad78e2c409448 GIT binary patch literal 43010 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 FKLE&i`?UZ7 literal 0 HcmV?d00001 diff --git a/tests/fixtures/empty.mp3 b/tests/fixtures/empty.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..5fe9a963e496c0a959e7cde1ececaa2485d39939 GIT binary patch literal 2297 zcmezWJAi=@@XSlg2dd!*VpawQj-L#!n4km;ScC;!!~*_f0U$T|B$lNq%f;bEe zO0ghPg_#XRf!s0e!U_j46N&i$dqN5W{|5$!Opp==23`dQ21X!S0^|ajCJhV>EIy9D zuEu(X1_q%c4-B~9|Nowp;?R#jf<|7nAo+MShL8edP%=kjXi!EAlA}gr2q`c|nb<-A E09uCO)&Kwi literal 0 HcmV?d00001 diff --git a/tests/fixtures/empty.wav b/tests/fixtures/empty.wav new file mode 100644 index 0000000000000000000000000000000000000000..ae4bc21eba3b6ae18de251d7b8d7ce6d38888cd3 GIT binary patch literal 88244 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 literal 0 HcmV?d00001 From d6114df91ff8bdaccbf32b02f11b6464b67f0085 Mon Sep 17 00:00:00 2001 From: Kendall Garner <17521368+kgarner7@users.noreply.github.com> Date: Fri, 1 Aug 2025 21:52:21 -0700 Subject: [PATCH 2/2] address feedback round 1 --- adapters/taglib/end_to_end_test.go | 6 +---- adapters/taglib/taglib_wrapper.cpp | 39 ++++++++++++++++-------------- resources/mappings.yaml | 2 +- 3 files changed, 23 insertions(+), 24 deletions(-) diff --git a/adapters/taglib/end_to_end_test.go b/adapters/taglib/end_to_end_test.go index fb4fd023c..525adedbf 100644 --- a/adapters/taglib/end_to_end_test.go +++ b/adapters/taglib/end_to_end_test.go @@ -279,11 +279,7 @@ var _ = Describe("Extractor", func() { Describe("tags", func() { DescribeTable("test metadata tags across files, and special cases", func(file string, tags ...string) { mf := parseTestFile("tests/fixtures/" + file) - metadataTags := mf.Tags[model.TagMetadataTag] - Expect(metadataTags).To(HaveLen(len(tags))) - for _, tag := range tags { - Expect(mf.Tags[model.TagMetadataTag]).To(ContainElement(tag)) - } + Expect(mf.Tags[model.TagMetadataTag]).To(ConsistOf(tags)) }, // weirder cases Entry("file with multiple tags", "ape-v1-v2.mp3", "ape", "id3v1", "id3v2"), diff --git a/adapters/taglib/taglib_wrapper.cpp b/adapters/taglib/taglib_wrapper.cpp index 1d918483a..762d7085c 100644 --- a/adapters/taglib/taglib_wrapper.cpp +++ b/adapters/taglib/taglib_wrapper.cpp @@ -189,14 +189,14 @@ int taglib_read(const FILENAME_CHAR_T *filename, unsigned long id) { if (m4afile->hasMP4Tag()) { goPutTagType(id, MP4_TAG); has_tag = true; - } - const auto itemListMap = m4afile->tag()->itemMap(); - for (const auto item: itemListMap) { - char *key = const_cast(item.first.toCString(true)); - for (const auto value: item.second.toStringList()) { - char *val = const_cast(value.toCString(true)); - goPutM4AStr(id, key, val); + const auto itemListMap = m4afile->tag()->itemMap(); + for (const auto item: itemListMap) { + char *key = const_cast(item.first.toCString(true)); + for (const auto value: item.second.toStringList()) { + char *val = const_cast(value.toCString(true)); + goPutM4AStr(id, key, val); + } } } } @@ -204,19 +204,22 @@ int taglib_read(const FILENAME_CHAR_T *filename, unsigned long id) { // WMA/ASF files may have additional tags not captured by the PropertyMap interface TagLib::ASF::File *asfFile(dynamic_cast(f.file())); if (asfFile != NULL) { - goPutTagType(id, ASF_TAG); - has_tag = true; - const TagLib::ASF::Tag *asfTags{asfFile->tag()}; - const auto itemListMap = asfTags->attributeListMap(); - for (const auto item : itemListMap) { - char *key = const_cast(item.first.toCString(true)); - for (auto j = item.second.begin(); - j != item.second.end(); ++j) { + if (asfTags != NULL) { + goPutTagType(id, ASF_TAG); + has_tag = true; - char *val = const_cast(j->toString().toCString(true)); - goPutStr(id, key, val); + const auto itemListMap = asfTags->attributeListMap(); + for (const auto item : itemListMap) { + char *key = const_cast(item.first.toCString(true)); + + for (auto j = item.second.begin(); + j != item.second.end(); ++j) { + + char *val = const_cast(j->toString().toCString(true)); + goPutStr(id, key, val); + } } } } @@ -303,7 +306,7 @@ char has_cover(const TagLib::FileRef f) { hasCover = !frameListMap["APIC"].isEmpty(); } } - // ----- AIFF + // ----- AIFF else if (TagLib::RIFF::AIFF::File * aiffFile{ dynamic_cast(f.file())}) { if (aiffFile->hasID3v2Tag()) { const auto& frameListMap{ aiffFile->tag()->frameListMap() }; diff --git a/resources/mappings.yaml b/resources/mappings.yaml index 7d32a5397..3fbdfb8be 100644 --- a/resources/mappings.yaml +++ b/resources/mappings.yaml @@ -202,7 +202,7 @@ main: # Additional tags. You can add new tags without the need to modify the code. They will be available as fields # for smart playlists additional: - # Internal tag type, reprentes medatada tag(s) found in the file + # Internal tag type, represents metadata tag(s) found in the file tags: aliases: [ __tags ] asin: