fix(jellyfin): hydrate artwork on the song cursor

Jellyfin's listSongs streamed media files via GetCursor, which never
hydrates artwork, so songs emitted entity-id image tags and no blurhash.

media_file now uses the same id pre-pass as the other three cursors
(album/artist/playlist), for consistency, but on a separate method,
GetCursorWithArtwork: GetCursor itself must stay untouched, since it's
also the scanner's hot path and the scanner never reads artwork.

Measured on 1,000,000 tracks, the pre-pass over all ids costs +41.8 MB
heap and +298 ms versus GetCursor's bounded +0.0 MB. The Jellyfin path
is paginated, though, so in practice it only ever pre-passes a page's
worth of ids, not the full library, and doesn't pay that cost.
This commit is contained in:
Deluan 2026-07-23 23:37:52 -04:00
commit 8545cc4762
5 changed files with 125 additions and 1 deletions

View file

@ -588,7 +588,7 @@ func (api *Router) listSongs(ctx context.Context, opts model.QueryOptions, q ite
// A full-library request (Finamp's sync, with MediaSources) is tens of thousands of fat rows.
total, _ := repo.CountAll(model.QueryOptions{Filters: opts.Filters})
open := streamCursor(func() (func(func(model.MediaFile, error) bool), error) {
return repo.GetCursor(opts)
return repo.GetCursorWithArtwork(opts)
}, toItem)
return streamed(open, int(total), opts.Offset), nil
}