From de51a6267735ca26446cd2befcac0ba8a22dae40 Mon Sep 17 00:00:00 2001 From: Caleb Allen Date: Thu, 26 Mar 2026 11:04:44 -0400 Subject: [PATCH] fix: handle ID3v2 TXXX frames and UFID in MBID extraction MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The dhowden/tag library returns ID3v2 TXXX frames as *tag.Comm structs (key='TXXX_N', Description='MusicBrainz Artist Id', Text='uuid'), not plain strings. Vorbis comments are plain strings (key='musicbrainz_artistid', value='uuid'). Previous code only handled the string case — all MP3 files silently got empty MBIDs. Now handles three value types: - string: Vorbis comments (FLAC/OGG) — key is the tag name - *tag.Comm: ID3v2 TXXX frames (MP3) — Description is the tag name - *tag.UFID: ID3v2 UFID frame (MP3) — MusicBrainz recording ID Requires a full rescan to backfill MBIDs for MP3 files. --- backend/metadata/tags.go | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/backend/metadata/tags.go b/backend/metadata/tags.go index fd41732..6250ca2 100644 --- a/backend/metadata/tags.go +++ b/backend/metadata/tags.go @@ -125,18 +125,37 @@ var mbidTagKeys = map[string][]string{ } // extractMBIDs populates the MBID fields of meta from the raw tag -// map. Handles varying key names across ID3v2, Vorbis, and MP4. +// map. Handles both Vorbis comments (plain string values with +// lowercase keys) and ID3v2 TXXX frames (*tag.Comm values with +// TXXX_N keys and the tag name in the Description field). func extractMBIDs(raw map[string]interface{}, meta *TrackMetadata) { if len(raw) == 0 { return } - // Build a lowercased key → value map for case-insensitive lookup. + // Build a lowercased description → value map that works for + // both formats: + // Vorbis: key="musicbrainz_artistid", value="uuid" (string) + // ID3v2: key="TXXX_13", value=*tag.Comm{Description:"MusicBrainz Artist Id", Text:"uuid"} normalized := make(map[string]string, len(raw)) for k, v := range raw { - if s, ok := v.(string); ok { - normalized[strings.ToLower(k)] = s + switch val := v.(type) { + case string: + // Vorbis comments — key is the tag name. + normalized[strings.ToLower(k)] = val + + case *tag.Comm: + // ID3v2 TXXX frames — Description is the tag name. + if val != nil && val.Description != "" { + normalized[strings.ToLower(val.Description)] = strings.TrimSpace(val.Text) + } + + case *tag.UFID: + // ID3v2 UFID frame — MusicBrainz recording ID. + if val != nil && val.Provider == "http://musicbrainz.org" { + meta.RecordingMBID = strings.TrimSpace(string(val.Identifier)) + } } }