From 439edabccd6cf870036ad06d3cf5bbf75a7ddffc Mon Sep 17 00:00:00 2001 From: Deluan Date: Tue, 29 Jul 2025 21:36:01 -0400 Subject: [PATCH] feat: add basic tag extraction fallback mechanism Added basic tag extraction from TagLib's generic Tag interface as a fallback when PropertyMap doesn't contain standard metadata fields. This ensures that essential tags like title, artist, album, comment, genre, year, and track are always available even when they're not present in format-specific property maps. Changes include: - Extract basic tags (__title, __artist, etc.) in C++ wrapper - Add parseBasicTag function to process basic tags in Go extractor - Refactor parseProp function to be reusable across property parsing - Ensure basic tags are preferred over PropertyMap when available --- adapters/taglib/taglib.go | 56 +++++++++++++++++++++--------- adapters/taglib/taglib_wrapper.cpp | 26 ++++++++++++++ 2 files changed, 66 insertions(+), 16 deletions(-) diff --git a/adapters/taglib/taglib.go b/adapters/taglib/taglib.go index 62a949d85..8fa331961 100644 --- a/adapters/taglib/taglib.go +++ b/adapters/taglib/taglib.go @@ -43,23 +43,24 @@ func (e extractor) extractMetadata(filePath string) (*metadata.Info, error) { // Parse audio properties ap := metadata.AudioProperties{} - if length, ok := tags["_lengthinmilliseconds"]; ok && len(length) > 0 { - millis, _ := strconv.Atoi(length[0]) - if millis > 0 { - ap.Duration = (time.Millisecond * time.Duration(millis)).Round(time.Millisecond * 10) - } - delete(tags, "_lengthinmilliseconds") + parseProp(tags, "_bitrate", &ap.BitRate) + parseProp(tags, "_channels", &ap.Channels) + parseProp(tags, "_samplerate", &ap.SampleRate) + parseProp(tags, "_bitspersample", &ap.BitDepth) + var millis int + parseProp(tags, "_lengthinmilliseconds", &millis) + if millis > 0 { + ap.Duration = (time.Millisecond * time.Duration(millis)).Round(time.Millisecond * 10) } - parseProp := func(prop string, target *int) { - if value, ok := tags[prop]; ok && len(value) > 0 { - *target, _ = strconv.Atoi(value[0]) - delete(tags, prop) - } - } - parseProp("_bitrate", &ap.BitRate) - parseProp("_channels", &ap.Channels) - parseProp("_samplerate", &ap.SampleRate) - parseProp("_bitspersample", &ap.BitDepth) + + // Extract basic tags + parseBasicTag(tags, "__title", "title") + parseBasicTag(tags, "__artist", "artist") + parseBasicTag(tags, "__album", "album") + parseBasicTag(tags, "__comment", "comment") + parseBasicTag(tags, "__genre", "genre") + parseBasicTag(tags, "__year", "year") + parseBasicTag(tags, "__track", "tracknumber") // Parse track/disc totals parseTuple := func(prop string) { @@ -107,6 +108,29 @@ var tiplMapping = map[string]string{ "DJ-mix": "djmixer", } +// parseProp parses a property from the tags map and sets it to the target integer. +// It also deletes the property from the tags map after parsing. +func parseProp(tags map[string][]string, prop string, target *int) { + if value, ok := tags[prop]; ok && len(value) > 0 { + *target, _ = strconv.Atoi(value[0]) + delete(tags, prop) + } +} + +// parseBasicTag checks if a basic tag (like __title, __artist, etc.) exists in the tags map. +// If it does, it moves the value to a more appropriate tag name (like title, artist, etc.), +// and deletes the basic tag from the map. If the target tag already exists, it ignores the basic tag. +func parseBasicTag(tags map[string][]string, basicName string, tagName string) { + basicValue := tags[basicName] + if len(basicValue) == 0 { + return + } + delete(tags, basicName) + if len(tags[tagName]) == 0 { + tags[tagName] = basicValue + } +} + // parseTIPL parses the ID3v2.4 TIPL frame string, which is received from TagLib in the format: // // "arranger Andrew Powell engineer Chris Blair engineer Pat Stapley producer Eric Woolfson". diff --git a/adapters/taglib/taglib_wrapper.cpp b/adapters/taglib/taglib_wrapper.cpp index 224642c6d..d19672cef 100644 --- a/adapters/taglib/taglib_wrapper.cpp +++ b/adapters/taglib/taglib_wrapper.cpp @@ -70,6 +70,32 @@ int taglib_read(const FILENAME_CHAR_T *filename, unsigned long id) { // Send all properties to the Go map TagLib::PropertyMap tags = f.file()->properties(); + // Make sure at least the basic properties are extracted + TagLib::Tag *basic = f.file()->tag(); + if (!basic->isEmpty()) { + if (!basic->title().isEmpty()) { + tags.insert("__title", basic->title()); + } + if (!basic->artist().isEmpty()) { + tags.insert("__artist", basic->artist()); + } + if (!basic->album().isEmpty()) { + tags.insert("__album", basic->album()); + } + if (!basic->comment().isEmpty()) { + tags.insert("__comment", basic->comment()); + } + if (!basic->genre().isEmpty()) { + tags.insert("__genre", basic->genre()); + } + if (basic->year() > 0) { + tags.insert("__year", TagLib::String::number(basic->year())); + } + if (basic->track() > 0) { + tags.insert("__track", TagLib::String::number(basic->track())); + } + } + TagLib::ID3v2::Tag *id3Tags = NULL; // Get some extended/non-standard ID3-only tags (ex: iTunes extended frames)