diff --git a/core/lyrics/lyrics.go b/core/lyrics/lyrics.go index 73a0479f3..82e60a64d 100644 --- a/core/lyrics/lyrics.go +++ b/core/lyrics/lyrics.go @@ -9,20 +9,23 @@ import ( "github.com/navidrome/navidrome/model" ) -// Lyrics can fetch lyrics for a media file. -type Lyrics interface { +// Provider fetches lyrics for a single media file. It is the contract +// implemented by individual lyrics sources, such as plugins. +type Provider interface { GetLyrics(ctx context.Context, mf *model.MediaFile) (model.LyricList, error) } -// BatchLyrics can resolve lyrics across multiple candidate media files while -// still honoring the configured source priority globally. -type BatchLyrics interface { +// Lyrics resolves lyrics for media files, honoring the configured source +// priority. GetLyricsForMediaFiles preserves that priority across a set of +// duplicate candidates instead of only consulting the first match. +type Lyrics interface { + Provider GetLyricsForMediaFiles(ctx context.Context, mediaFiles []model.MediaFile) (model.LyricList, error) } // PluginLoader discovers and loads lyrics provider plugins. type PluginLoader interface { - LoadLyricsProvider(name string) (Lyrics, bool) + LoadLyricsProvider(name string) (Provider, bool) } type lyricsService struct { diff --git a/core/lyrics/lyrics_test.go b/core/lyrics/lyrics_test.go index ec38c9202..78a1a310f 100644 --- a/core/lyrics/lyrics_test.go +++ b/core/lyrics/lyrics_test.go @@ -176,10 +176,8 @@ var _ = Describe("sources", func() { Expect(err).To(BeNil()) svc := lyrics.NewLyrics(nil) - batchSvc, ok := svc.(lyrics.BatchLyrics) - Expect(ok).To(BeTrue()) - list, err := batchSvc.GetLyricsForMediaFiles(ctx, []model.MediaFile{ + list, err := svc.GetLyricsForMediaFiles(ctx, []model.MediaFile{ { Lyrics: string(embeddedJSON), Path: "tests/fixtures/01 Invisible (RED) Edit Version.mp3", @@ -372,7 +370,7 @@ func (m *mockPluginLoader) PluginNames(_ string) []string { return []string{"test-lyrics-plugin"} } -func (m *mockPluginLoader) LoadLyricsProvider(name string) (lyrics.Lyrics, bool) { +func (m *mockPluginLoader) LoadLyricsProvider(name string) (lyrics.Provider, bool) { if m.notFound { return nil, false } diff --git a/plugins/manager.go b/plugins/manager.go index 67e0ee987..a7649d47e 100644 --- a/plugins/manager.go +++ b/plugins/manager.go @@ -241,7 +241,7 @@ func (m *Manager) LoadScrobbler(name string) (scrobbler.Scrobbler, bool) { return loadPlugin(m, name, CapabilityScrobbler, newScrobblerPlugin) } -func (m *Manager) LoadLyricsProvider(name string) (lyrics.Lyrics, bool) { +func (m *Manager) LoadLyricsProvider(name string) (lyrics.Provider, bool) { return loadPlugin(m, name, CapabilityLyrics, newLyricsPlugin) } diff --git a/server/subsonic/media_retrieval.go b/server/subsonic/media_retrieval.go index 8461723ed..b807d17ca 100644 --- a/server/subsonic/media_retrieval.go +++ b/server/subsonic/media_retrieval.go @@ -10,7 +10,6 @@ import ( "github.com/navidrome/navidrome/conf" "github.com/navidrome/navidrome/consts" - lyricssvc "github.com/navidrome/navidrome/core/lyrics" "github.com/navidrome/navidrome/log" "github.com/navidrome/navidrome/model" "github.com/navidrome/navidrome/resources" @@ -102,7 +101,7 @@ func (api *Router) GetLyrics(r *http.Request) (*responses.Subsonic, error) { lyricsResponse := responses.Lyrics{} response.Lyrics = &lyricsResponse opts := filter.SongsByArtistTitleWithLyricsFirst(artist, title) - // Search a bounded duplicate window so source-priority fallback can still + // Search a bounded duplicate window so source-priority resolution can still // reach older matches without turning legacy getLyrics into an unbounded scan. opts.Max = maxLegacyLyricsCandidates mediaFiles, err := api.ds.MediaFile(r.Context()).GetAll(opts) @@ -115,22 +114,9 @@ func (api *Router) GetLyrics(r *http.Request) (*responses.Subsonic, error) { return response, nil } - var structuredLyrics model.LyricList - if batchLyrics, ok := api.lyrics.(lyricssvc.BatchLyrics); ok { - structuredLyrics, err = batchLyrics.GetLyricsForMediaFiles(r.Context(), mediaFiles) - if err != nil { - return nil, err - } - } else { - for i := range mediaFiles { - structuredLyrics, err = api.lyrics.GetLyrics(r.Context(), &mediaFiles[i]) - if err != nil { - return nil, err - } - if len(structuredLyrics) > 0 { - break - } - } + structuredLyrics, err := api.lyrics.GetLyricsForMediaFiles(r.Context(), mediaFiles) + if err != nil { + return nil, err } if len(structuredLyrics) == 0 {