Merge bd77ca1c96f3b66b93f85a7ede0d5f9aca036762 into 9bb933c0d67e90c22a58f96f067ca37f70c27bca

This commit is contained in:
Kendall Garner 2025-11-07 19:43:47 -05:00 committed by GitHub
commit eed9dd3cd5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 119 additions and 15 deletions

View File

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

View File

@ -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<TagLib::MPEG::File *>(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<TagLib::DSF::File *>(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<TagLib::MP4::File *>(f.file()));
if (m4afile != NULL) {
const auto itemListMap = m4afile->tag()->itemMap();
for (const auto item: itemListMap) {
char *key = const_cast<char*>(item.first.toCString(true));
for (const auto value: item.second.toStringList()) {
char *val = const_cast<char*>(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<char*>(item.first.toCString(true));
for (const auto value: item.second.toStringList()) {
char *val = const_cast<char*>(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<TagLib::ASF::File *>(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<char*>(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<char*>(j->toString().toCString(true));
goPutStr(id, key, val);
const auto itemListMap = asfTags->attributeListMap();
for (const auto item : itemListMap) {
char *key = const_cast<char*>(item.first.toCString(true));
for (auto j = item.second.begin();
j != item.second.end(); ++j) {
char *val = const_cast<char*>(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<TagLib::FLAC::File *>(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<TagLib::Ogg::Vorbis::File *>(f.file())}) {
goPutTagType(id, VORBIS_TAG);
} else if (TagLib::Ogg::Opus::File * opusFile{dynamic_cast<TagLib::Ogg::Opus::File *>(f.file())}) {
goPutTagType(id, VORBIS_TAG);
} else if (TagLib::WavPack::File * wvFile{dynamic_cast<TagLib::WavPack::File *>(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<TagLib::RIFF::AIFF::File *>(f.file())}) {
if (aiffFile->hasID3v2Tag()) {
const auto& frameListMap{ aiffFile->tag()->frameListMap() };

View File

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

View File

@ -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();

View File

@ -194,6 +194,7 @@ const (
TagISRC TagName = "isrc"
TagBPM TagName = "bpm"
TagExplicitStatus TagName = "explicitstatus"
TagMetadataTag TagName = "tags"
// Dates and years

View File

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

BIN
tests/fixtures/ape-id3v1.wv vendored Normal file

Binary file not shown.

BIN
tests/fixtures/ape-v1-v2.mp3 vendored Normal file

Binary file not shown.

BIN
tests/fixtures/empty.mp3 vendored Normal file

Binary file not shown.

BIN
tests/fixtures/empty.wav vendored Normal file

Binary file not shown.

BIN
tests/fixtures/vorbis-id3v1-id3v2.flac vendored Normal file

Binary file not shown.