diff --git a/adapters/taglib/end_to_end_test.go b/adapters/taglib/end_to_end_test.go index e4d94bb24..525adedbf 100644 --- a/adapters/taglib/end_to_end_test.go +++ b/adapters/taglib/end_to_end_test.go @@ -275,4 +275,30 @@ 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) + Expect(mf.Tags[model.TagMetadataTag]).To(ConsistOf(tags)) + }, + // 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 2985e8f18..668424e3c 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; @@ -103,11 +110,24 @@ int taglib_read(const FILENAME_CHAR_T *filename, unsigned long id) { } 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) { @@ -124,12 +144,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) { @@ -189,12 +218,17 @@ 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) { - 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); + 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); + } } } } @@ -203,15 +237,21 @@ int taglib_read(const FILENAME_CHAR_T *filename, unsigned long id) { TagLib::ASF::File *asfFile(dynamic_cast(f.file())); if (asfFile != NULL) { 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); + } } } } @@ -232,6 +272,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; } @@ -270,7 +338,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/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..3fbdfb8be 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, represents metadata 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 000000000..5ce2fe15e Binary files /dev/null and b/tests/fixtures/ape-id3v1.wv differ diff --git a/tests/fixtures/ape-v1-v2.mp3 b/tests/fixtures/ape-v1-v2.mp3 new file mode 100644 index 000000000..50cbc0e2c Binary files /dev/null and b/tests/fixtures/ape-v1-v2.mp3 differ diff --git a/tests/fixtures/empty.mp3 b/tests/fixtures/empty.mp3 new file mode 100644 index 000000000..5fe9a963e Binary files /dev/null and b/tests/fixtures/empty.mp3 differ diff --git a/tests/fixtures/empty.wav b/tests/fixtures/empty.wav new file mode 100644 index 000000000..ae4bc21eb Binary files /dev/null and b/tests/fixtures/empty.wav differ diff --git a/tests/fixtures/vorbis-id3v1-id3v2.flac b/tests/fixtures/vorbis-id3v1-id3v2.flac new file mode 100644 index 000000000..41530c089 Binary files /dev/null and b/tests/fixtures/vorbis-id3v1-id3v2.flac differ