mirror of
https://github.com/navidrome/navidrome.git
synced 2026-10-29 22:47:48 +01:00
fix(jellyfin): display playlist cover art
Uploaded playlist covers never showed in clients for two reasons: the playlist BaseItemDto advertised no Primary ImageTag (so clients didn't know to fetch a cover), and the public image endpoint resolved artwork under the request's anonymous context, so a private playlist failed its visibility filter and fell back to the placeholder. Advertise the Primary image tag/blurhash on playlists, and resolve artwork under an elevated context (as core/artwork's cache warmer does).
This commit is contained in:
parent
ead911a9e1
commit
d24ba9b5ea
4 changed files with 34 additions and 3 deletions
|
|
@ -191,6 +191,8 @@ func PlaylistToBaseItem(p model.Playlist) BaseItemDto {
|
|||
MediaType: "Audio",
|
||||
ChildCount: new(p.SongCount),
|
||||
RunTimeTicks: TicksFromSeconds(p.Duration),
|
||||
ImageTags: map[string]string{"Primary": p.ID},
|
||||
ImageBlurHashes: map[string]map[string]string{"Primary": {p.ID: blurHash(p.ID)}},
|
||||
BackdropImageTags: []string{},
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -166,5 +166,8 @@ var _ = Describe("mappers", func() {
|
|||
Expect(item.MediaType).To(Equal("Audio"))
|
||||
Expect(*item.ChildCount).To(Equal(7))
|
||||
Expect(item.RunTimeTicks).To(Equal(int64(1_200_000_000)))
|
||||
Expect(item.ImageTags["Primary"]).To(Equal("pl-1"))
|
||||
Expect(item.ImageBlurHashes["Primary"]).To(HaveKey("pl-1"))
|
||||
Expect(item.ImageBlurHashes["Primary"]["pl-1"]).To(HaveLen(6))
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -13,11 +13,16 @@ import (
|
|||
"github.com/go-chi/chi/v5"
|
||||
"github.com/navidrome/navidrome/log"
|
||||
"github.com/navidrome/navidrome/model"
|
||||
"github.com/navidrome/navidrome/model/request"
|
||||
"github.com/navidrome/navidrome/server/jellyfin/dto"
|
||||
)
|
||||
|
||||
func (api *Router) getItemImage(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
// This endpoint is intentionally public (see routes), so the request carries no user. Artwork
|
||||
// resolution for user-scoped items (private playlists) would otherwise fail its visibility
|
||||
// filter and fall back to the placeholder, so resolve under an elevated context — the same
|
||||
// approach core/artwork's cache warmer uses to pre-generate every item's cover.
|
||||
ctx := request.WithUser(r.Context(), model.User{IsAdmin: true})
|
||||
itemId := dto.DecodeID(chi.URLParam(r, "itemId"))
|
||||
size, _ := strconv.Atoi(r.URL.Query().Get("maxWidth"))
|
||||
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import (
|
|||
"github.com/go-chi/chi/v5"
|
||||
"github.com/navidrome/navidrome/core/artwork"
|
||||
"github.com/navidrome/navidrome/model"
|
||||
"github.com/navidrome/navidrome/model/request"
|
||||
"github.com/navidrome/navidrome/server/jellyfin/dto"
|
||||
"github.com/navidrome/navidrome/tests"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
|
|
@ -21,12 +22,14 @@ import (
|
|||
|
||||
type fakeArtwork struct {
|
||||
artwork.Artwork
|
||||
recvId string
|
||||
data []byte
|
||||
recvId string
|
||||
recvCtx context.Context
|
||||
data []byte
|
||||
}
|
||||
|
||||
func (f *fakeArtwork) GetOrPlaceholder(ctx context.Context, id string, size int, square bool) (io.ReadCloser, time.Time, error) {
|
||||
f.recvId = id
|
||||
f.recvCtx = ctx
|
||||
data := f.data
|
||||
if data == nil {
|
||||
data = []byte("IMG")
|
||||
|
|
@ -86,6 +89,24 @@ var _ = Describe("Images", func() {
|
|||
Expect(w.Code).To(Equal(http.StatusOK))
|
||||
Expect(fa.recvId).To(ContainSubstring("pl1"))
|
||||
})
|
||||
|
||||
// This endpoint is public (no user in the request), so artwork must be resolved under an
|
||||
// elevated context; otherwise a private playlist's cover fails its visibility filter and
|
||||
// silently falls back to the placeholder.
|
||||
It("resolves artwork under an elevated admin context", func() {
|
||||
ds := &tests.MockDataStore{}
|
||||
ds.Album(context.Background()).(*tests.MockAlbumRepo).SetData(model.Albums{{ID: "a1", Name: "One"}})
|
||||
fa := &fakeArtwork{}
|
||||
api := &Router{ds: ds, artwork: fa}
|
||||
|
||||
w, r := newImageRequest(dto.EncodeID("a1"))
|
||||
api.getItemImage(w, r)
|
||||
|
||||
Expect(w.Code).To(Equal(http.StatusOK))
|
||||
u, ok := request.UserFrom(fa.recvCtx)
|
||||
Expect(ok).To(BeTrue())
|
||||
Expect(u.IsAdmin).To(BeTrue())
|
||||
})
|
||||
})
|
||||
|
||||
var _ = Describe("postItemImage", func() {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue