mirror of
https://github.com/navidrome/navidrome.git
synced 2026-10-27 21:57:17 +01:00
feat(jellyfin): item image endpoint via artwork service
This commit is contained in:
parent
30d3efebb9
commit
6e4a180feb
3 changed files with 95 additions and 0 deletions
|
|
@ -50,6 +50,11 @@ func (api *Router) routes() http.Handler {
|
|||
r.Post("/Users/AuthenticateByName", api.authenticateByName)
|
||||
r.Get("/Users/Public", api.getPublicUsers)
|
||||
|
||||
// Images are intentionally public and not library-scoped: artwork isn't sensitive media
|
||||
// content, and clients (e.g. Finamp) load it via <img> tags with only ?api_key= in the URL.
|
||||
r.Get("/Items/{itemId}/Images/{type}", api.getItemImage)
|
||||
r.Get("/Items/{itemId}/Images/{type}/{index}", api.getItemImage)
|
||||
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Use(api.authenticate)
|
||||
r.Get("/UserViews", api.getUserViews)
|
||||
|
|
|
|||
42
server/jellyfin/images.go
Normal file
42
server/jellyfin/images.go
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
package jellyfin
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/navidrome/navidrome/model"
|
||||
)
|
||||
|
||||
func (api *Router) getItemImage(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
itemId := chi.URLParam(r, "itemId")
|
||||
size, _ := strconv.Atoi(r.URL.Query().Get("maxWidth"))
|
||||
|
||||
artID := api.resolveArtworkID(ctx, itemId)
|
||||
reader, _, err := api.artwork.GetOrPlaceholder(ctx, artID, size, false)
|
||||
if err != nil {
|
||||
http.Error(w, "Not Found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
defer reader.Close()
|
||||
w.Header().Set("Content-Type", "image/jpeg")
|
||||
_, _ = io.Copy(w, reader)
|
||||
}
|
||||
|
||||
// resolveArtworkID maps a bare Jellyfin item id to a Navidrome ArtworkID string
|
||||
// by probing album -> artist -> media file (music items only).
|
||||
func (api *Router) resolveArtworkID(ctx context.Context, itemId string) string {
|
||||
if al, err := api.ds.Album(ctx).Get(itemId); err == nil {
|
||||
return al.CoverArtID().String()
|
||||
}
|
||||
if ar, err := api.ds.Artist(ctx).Get(itemId); err == nil {
|
||||
return ar.CoverArtID().String()
|
||||
}
|
||||
if mf, err := api.ds.MediaFile(ctx).Get(itemId); err == nil {
|
||||
return mf.CoverArtID().String()
|
||||
}
|
||||
return (model.ArtworkID{}).String()
|
||||
}
|
||||
48
server/jellyfin/images_test.go
Normal file
48
server/jellyfin/images_test.go
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
package jellyfin
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/navidrome/navidrome/core/artwork"
|
||||
"github.com/navidrome/navidrome/model"
|
||||
"github.com/navidrome/navidrome/tests"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
type fakeArtwork struct {
|
||||
artwork.Artwork
|
||||
recvId string
|
||||
}
|
||||
|
||||
func (f *fakeArtwork) GetOrPlaceholder(ctx context.Context, id string, size int, square bool) (io.ReadCloser, time.Time, error) {
|
||||
f.recvId = id
|
||||
return io.NopCloser(strings.NewReader("IMG")), time.Now(), nil
|
||||
}
|
||||
|
||||
var _ = Describe("Images", func() {
|
||||
It("streams album artwork", 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 := httptest.NewRecorder()
|
||||
r := httptest.NewRequest("GET", "/Items/a1/Images/Primary", nil)
|
||||
rctx := chi.NewRouteContext()
|
||||
rctx.URLParams.Add("itemId", "a1")
|
||||
rctx.URLParams.Add("type", "Primary")
|
||||
r = r.WithContext(context.WithValue(r.Context(), chi.RouteCtxKey, rctx))
|
||||
api.getItemImage(w, r)
|
||||
|
||||
Expect(w.Code).To(Equal(http.StatusOK))
|
||||
Expect(w.Body.String()).To(Equal("IMG"))
|
||||
Expect(fa.recvId).To(ContainSubstring("a1"))
|
||||
})
|
||||
})
|
||||
Loading…
Add table
Add a link
Reference in a new issue