mirror of
https://github.com/gabehf/Koito.git
synced 2026-04-22 20:11:50 -07:00
Add MusicBrainz search-by-name enrichment for scrobbles without IDs
When a listen arrives with no MBZ IDs and no album title (the common multi-scrobbler/Last.fm case), search MusicBrainz by artist+track name to resolve recording, release, and release group IDs. This unlocks CoverArtArchive album art, proper album association, and duration data. New file: internal/mbz/search.go - SearchRecording() method with Lucene query escaping - Confidence filter: case-insensitive exact match on title + artist credit - Release selection: prefer Official status, then first available - Uses existing rate-limited queue (1 req/sec) Integration in catalog.go: - Only triggers when RecordingMbzID, ReleaseMbzID, AND ReleaseTitle are all missing — no impact on scrobbles that already have MBZ data - Soft failure — search errors don't block the listen - KOITO_DISABLE_MUSICBRAINZ handled automatically (MbzErrorCaller returns error) Interface + mocks updated: - SearchRecording added to MusicBrainzCaller interface - MbzMockCaller: SearchResults map for test data - MbzErrorCaller: returns error (existing pattern) New tests: - TestSubmitListen_SearchByName — mock search, verify album+duration resolved - TestSubmitListen_SearchByNameNoMatch — verify graceful fallback Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
0ec7b458cc
commit
2c29499403
5 changed files with 304 additions and 0 deletions
|
|
@ -15,6 +15,7 @@ type MbzMockCaller struct {
|
|||
ReleaseGroups map[uuid.UUID]*MusicBrainzReleaseGroup
|
||||
Releases map[uuid.UUID]*MusicBrainzRelease
|
||||
Tracks map[uuid.UUID]*MusicBrainzTrack
|
||||
SearchResults map[string]*MusicBrainzSearchResult
|
||||
}
|
||||
|
||||
func (m *MbzMockCaller) GetReleaseGroup(ctx context.Context, id uuid.UUID) (*MusicBrainzReleaseGroup, error) {
|
||||
|
|
@ -70,6 +71,14 @@ func (m *MbzMockCaller) GetArtistPrimaryAliases(ctx context.Context, id uuid.UUI
|
|||
return ss, nil
|
||||
}
|
||||
|
||||
func (m *MbzMockCaller) SearchRecording(ctx context.Context, artist string, track string) (*MusicBrainzSearchResult, error) {
|
||||
key := artist + "\x00" + track
|
||||
if result, exists := m.SearchResults[key]; exists {
|
||||
return result, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (m *MbzMockCaller) Shutdown() {}
|
||||
|
||||
type MbzErrorCaller struct{}
|
||||
|
|
@ -94,4 +103,8 @@ func (m *MbzErrorCaller) GetArtistPrimaryAliases(ctx context.Context, id uuid.UU
|
|||
return nil, fmt.Errorf("error: GetArtistPrimaryAliases not implemented")
|
||||
}
|
||||
|
||||
func (m *MbzErrorCaller) SearchRecording(ctx context.Context, artist string, track string) (*MusicBrainzSearchResult, error) {
|
||||
return nil, fmt.Errorf("error: SearchRecording not implemented")
|
||||
}
|
||||
|
||||
func (m *MbzErrorCaller) Shutdown() {}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue