mirror of
https://github.com/navidrome/navidrome.git
synced 2026-05-03 06:51:16 +00:00
Merge 5fdee408772a3347346392c6618a45b253be0411 into 85e9982b434f27604f01817f45de006cddd18376
This commit is contained in:
commit
a7b76feac2
@ -271,4 +271,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"),
|
||||||
|
)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -27,6 +27,13 @@ char has_cover(const TagLib::FileRef f);
|
|||||||
|
|
||||||
static char TAGLIB_VERSION[16];
|
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() {
|
char* taglib_version() {
|
||||||
snprintf((char *)TAGLIB_VERSION, 16, "%d.%d.%d", TAGLIB_MAJOR_VERSION, TAGLIB_MINOR_VERSION, TAGLIB_PATCH_VERSION);
|
snprintf((char *)TAGLIB_VERSION, 16, "%d.%d.%d", TAGLIB_MAJOR_VERSION, TAGLIB_MINOR_VERSION, TAGLIB_PATCH_VERSION);
|
||||||
return (char *)TAGLIB_VERSION;
|
return (char *)TAGLIB_VERSION;
|
||||||
@ -103,13 +110,26 @@ int taglib_read(const FILENAME_CHAR_T *filename, unsigned long id) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TagLib::ID3v2::Tag *id3Tags = NULL;
|
TagLib::ID3v2::Tag *id3Tags = NULL;
|
||||||
|
bool has_tag = false;
|
||||||
|
|
||||||
// Get some extended/non-standard ID3-only tags (ex: iTunes extended frames)
|
// Get some extended/non-standard ID3-only tags (ex: iTunes extended frames)
|
||||||
TagLib::MPEG::File *mp3File(dynamic_cast<TagLib::MPEG::File *>(f.file()));
|
TagLib::MPEG::File *mp3File(dynamic_cast<TagLib::MPEG::File *>(f.file()));
|
||||||
if (mp3File != NULL) {
|
if (mp3File != NULL) {
|
||||||
|
if (mp3File->hasID3v2Tag()) {
|
||||||
id3Tags = mp3File->ID3v2Tag();
|
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) {
|
if (id3Tags == NULL) {
|
||||||
TagLib::RIFF::WAV::File *wavFile(dynamic_cast<TagLib::RIFF::WAV::File *>(f.file()));
|
TagLib::RIFF::WAV::File *wavFile(dynamic_cast<TagLib::RIFF::WAV::File *>(f.file()));
|
||||||
if (wavFile != NULL && wavFile->hasID3v2Tag()) {
|
if (wavFile != NULL && wavFile->hasID3v2Tag()) {
|
||||||
@ -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
|
// Yes, it is possible to have ID3v2 tags in FLAC. However, that can cause problems
|
||||||
// with many players, so they will not be parsed
|
// with many players, so they will not be parsed
|
||||||
|
|
||||||
if (id3Tags != NULL) {
|
if (id3Tags != NULL) {
|
||||||
const auto &frames = id3Tags->frameListMap();
|
const auto &frames = id3Tags->frameListMap();
|
||||||
|
|
||||||
|
goPutTagType(id, ID3V2_TAG);
|
||||||
|
has_tag = true;
|
||||||
|
|
||||||
for (const auto &kv: frames) {
|
for (const auto &kv: frames) {
|
||||||
if (kv.first == "USLT") {
|
if (kv.first == "USLT") {
|
||||||
for (const auto &tag: kv.second) {
|
for (const auto &tag: kv.second) {
|
||||||
@ -189,6 +218,10 @@ int taglib_read(const FILENAME_CHAR_T *filename, unsigned long id) {
|
|||||||
// M4A may have some iTunes specific tags not captured by the PropertyMap interface
|
// M4A may have some iTunes specific tags not captured by the PropertyMap interface
|
||||||
TagLib::MP4::File *m4afile(dynamic_cast<TagLib::MP4::File *>(f.file()));
|
TagLib::MP4::File *m4afile(dynamic_cast<TagLib::MP4::File *>(f.file()));
|
||||||
if (m4afile != NULL) {
|
if (m4afile != NULL) {
|
||||||
|
if (m4afile->hasMP4Tag()) {
|
||||||
|
goPutTagType(id, MP4_TAG);
|
||||||
|
has_tag = true;
|
||||||
|
|
||||||
const auto itemListMap = m4afile->tag()->itemMap();
|
const auto itemListMap = m4afile->tag()->itemMap();
|
||||||
for (const auto item: itemListMap) {
|
for (const auto item: itemListMap) {
|
||||||
char *key = const_cast<char*>(item.first.toCString(true));
|
char *key = const_cast<char*>(item.first.toCString(true));
|
||||||
@ -198,11 +231,17 @@ int taglib_read(const FILENAME_CHAR_T *filename, unsigned long id) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// WMA/ASF files may have additional tags not captured by the PropertyMap interface
|
// WMA/ASF files may have additional tags not captured by the PropertyMap interface
|
||||||
TagLib::ASF::File *asfFile(dynamic_cast<TagLib::ASF::File *>(f.file()));
|
TagLib::ASF::File *asfFile(dynamic_cast<TagLib::ASF::File *>(f.file()));
|
||||||
if (asfFile != NULL) {
|
if (asfFile != NULL) {
|
||||||
const TagLib::ASF::Tag *asfTags{asfFile->tag()};
|
const TagLib::ASF::Tag *asfTags{asfFile->tag()};
|
||||||
|
|
||||||
|
if (asfTags != NULL) {
|
||||||
|
goPutTagType(id, ASF_TAG);
|
||||||
|
has_tag = true;
|
||||||
|
|
||||||
const auto itemListMap = asfTags->attributeListMap();
|
const auto itemListMap = asfTags->attributeListMap();
|
||||||
for (const auto item : itemListMap) {
|
for (const auto item : itemListMap) {
|
||||||
char *key = const_cast<char*>(item.first.toCString(true));
|
char *key = const_cast<char*>(item.first.toCString(true));
|
||||||
@ -215,6 +254,7 @@ int taglib_read(const FILENAME_CHAR_T *filename, unsigned long id) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Send all collected tags to the Go map
|
// Send all collected tags to the Go map
|
||||||
for (TagLib::PropertyMap::ConstIterator i = tags.begin(); i != tags.end();
|
for (TagLib::PropertyMap::ConstIterator i = tags.begin(); i != tags.end();
|
||||||
@ -232,6 +272,34 @@ int taglib_read(const FILENAME_CHAR_T *filename, unsigned long id) {
|
|||||||
goPutStr(id, (char *)"has_picture", (char *)"true");
|
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;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -155,3 +155,8 @@ func goPutLyricLine(id C.ulong, lang *C.char, text *C.char, time C.int) {
|
|||||||
m[k] = []string{formattedLine}
|
m[k] = []string{formattedLine}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//export goPutTagType
|
||||||
|
func goPutTagType(id C.ulong, tag *C.char) {
|
||||||
|
doPutTag(id, "__tags", tag)
|
||||||
|
}
|
||||||
|
|||||||
@ -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 goPutInt(unsigned long id, char *key, int val);
|
||||||
extern void goPutLyrics(unsigned long id, char *lang, char *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 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);
|
int taglib_read(const FILENAME_CHAR_T *filename, unsigned long id);
|
||||||
char* taglib_version();
|
char* taglib_version();
|
||||||
|
|
||||||
|
|||||||
@ -192,6 +192,7 @@ const (
|
|||||||
TagISRC TagName = "isrc"
|
TagISRC TagName = "isrc"
|
||||||
TagBPM TagName = "bpm"
|
TagBPM TagName = "bpm"
|
||||||
TagExplicitStatus TagName = "explicitstatus"
|
TagExplicitStatus TagName = "explicitstatus"
|
||||||
|
TagMetadataTag TagName = "tags"
|
||||||
|
|
||||||
// Dates and years
|
// Dates and years
|
||||||
|
|
||||||
|
|||||||
@ -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
|
# Additional tags. You can add new tags without the need to modify the code. They will be available as fields
|
||||||
# for smart playlists
|
# for smart playlists
|
||||||
additional:
|
additional:
|
||||||
|
# Internal tag type, represents metadata tag(s) found in the file
|
||||||
|
tags:
|
||||||
|
aliases: [ __tags ]
|
||||||
asin:
|
asin:
|
||||||
aliases: [ txxx:asin, asin, ----:com.apple.itunes:asin ]
|
aliases: [ txxx:asin, asin, ----:com.apple.itunes:asin ]
|
||||||
barcode:
|
barcode:
|
||||||
|
|||||||
BIN
tests/fixtures/ape-id3v1.wv
vendored
Normal file
BIN
tests/fixtures/ape-id3v1.wv
vendored
Normal file
Binary file not shown.
BIN
tests/fixtures/ape-v1-v2.mp3
vendored
Normal file
BIN
tests/fixtures/ape-v1-v2.mp3
vendored
Normal file
Binary file not shown.
BIN
tests/fixtures/empty.mp3
vendored
Normal file
BIN
tests/fixtures/empty.mp3
vendored
Normal file
Binary file not shown.
BIN
tests/fixtures/empty.wav
vendored
Normal file
BIN
tests/fixtures/empty.wav
vendored
Normal file
Binary file not shown.
BIN
tests/fixtures/vorbis-id3v1-id3v2.flac
vendored
Normal file
BIN
tests/fixtures/vorbis-id3v1-id3v2.flac
vendored
Normal file
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user