mirror of
https://github.com/navidrome/navidrome.git
synced 2026-02-02 06:24:14 +00:00
Merge bd77ca1c96f3b66b93f85a7ede0d5f9aca036762 into 9bb933c0d67e90c22a58f96f067ca37f70c27bca
This commit is contained in:
commit
eed9dd3cd5
@ -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"),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@ -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() };
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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();
|
||||
|
||||
|
||||
@ -194,6 +194,7 @@ const (
|
||||
TagISRC TagName = "isrc"
|
||||
TagBPM TagName = "bpm"
|
||||
TagExplicitStatus TagName = "explicitstatus"
|
||||
TagMetadataTag TagName = "tags"
|
||||
|
||||
// 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
|
||||
# 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
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