feat: extract MusicBrainz IDs from audio tags and store in library DB

Migration 13 adds nullable mbid TEXT columns to artists,
release_groups, and recordings with partial indexes.

Metadata extraction (tags.go) now reads MusicBrainz IDs from Raw()
tags — handles both Vorbis (musicbrainz_artistid) and ID3v2
(MusicBrainz Artist Id) key formats.

Scan pipeline (library.go) updates MBIDs after entity upsert via
raw SQL UPDATE. Only sets mbid if currently NULL (preserves existing
values on rescan).

LibraryMBIDIndex (librarymbid.go) provides:
- CheckMBIDs: batch lookup for 'In Library' badges
- GetArtistMBID: single artist name→MBID lookup
- AllArtistMBIDs: full dump for search index Tier 3

MBIDs will be populated on next library rescan. Existing files
need a rescan to backfill.
This commit is contained in:
2026-03-26 09:24:39 -04:00
parent 806de8fd45
commit b941057a46
3 changed files with 229 additions and 0 deletions
+50
View File
@@ -1234,9 +1234,59 @@ func (l *Library) processMetadata(
}
}
// 7. Update MusicBrainz IDs (if present in tags).
if releaseGroupID.Valid {
l.updateMBIDs(cache, tags, artistName, releaseGroupID.Int64, recording.ID)
} else {
l.updateMBIDs(cache, tags, artistName, 0, recording.ID)
}
return recording.ID, nil
}
// updateMBIDs writes MusicBrainz IDs from audio file tags to the
// corresponding database entities. Uses raw SQL since the sqlc
// queries predate the mbid columns. Skips silently if tags have
// no MBIDs.
func (l *Library) updateMBIDs(
cache *entityCache,
tags *metadata.TrackMetadata,
artistName string,
releaseGroupID int64,
recordingID int64,
) {
// Artist MBID — prefer album artist, fall back to track artist.
artistMBID := tags.AlbumArtistMBID
if artistMBID == "" {
artistMBID = tags.ArtistMBID
}
if artistMBID != "" {
if artist, ok := cache.artists[artistName]; ok {
_, _ = l.db.ExecContext(
"UPDATE artists SET mbid = ? WHERE id = ? AND (mbid IS NULL OR mbid = '')",
artistMBID, artist.ID,
)
}
}
// Release group MBID.
if tags.ReleaseGroupMBID != "" && releaseGroupID > 0 {
_, _ = l.db.ExecContext(
"UPDATE release_groups SET mbid = ? WHERE id = ? AND (mbid IS NULL OR mbid = '')",
tags.ReleaseGroupMBID, releaseGroupID,
)
}
// Recording MBID.
if tags.RecordingMBID != "" && recordingID > 0 {
_, _ = l.db.ExecContext(
"UPDATE recordings SET mbid = ? WHERE id = ? AND (mbid IS NULL OR mbid = '')",
tags.RecordingMBID, recordingID,
)
}
}
// processCoverArt saves cover art to disk and upserts the DB record,
// using the cache to skip work for previously seen images. When
// thumbChan is non-nil, thumbnail generation is dispatched to the