package engine import ( "bytes" "context" "errors" "image" _ "image/gif" "image/jpeg" _ "image/png" "io" "net/http" "os" "strings" "github.com/deluan/navidrome/model" "github.com/deluan/navidrome/static" "github.com/dhowden/tag" "github.com/nfnt/resize" ) type Cover interface { Get(ctx context.Context, id string, size int, out io.Writer) error } type cover struct { ds model.DataStore } func NewCover(ds model.DataStore) Cover { return &cover{ds} } func (c *cover) getCoverPath(ctx context.Context, id string) (string, error) { switch { case strings.HasPrefix(id, "al-"): id = id[3:] al, err := c.ds.Album(ctx).Get(id) if err != nil { return "", err } return al.CoverArtPath, nil default: mf, err := c.ds.MediaFile(ctx).Get(id) if err != nil { return "", err } if mf.HasCoverArt { return mf.Path, nil } } return "", model.ErrNotFound } func (c *cover) Get(ctx context.Context, id string, size int, out io.Writer) error { path, err := c.getCoverPath(ctx, id) if err != nil && err != model.ErrNotFound { return err } var reader io.Reader if err != model.ErrNotFound { reader, err = readFromTag(path) } else { var f http.File f, err = static.AssetFile().Open("default_cover.jpg") if err == nil { defer f.Close() reader = f } } if err != nil { return model.ErrNotFound } if size > 0 { return resizeImage(reader, size, out) } _, err = io.Copy(out, reader) return err } func resizeImage(reader io.Reader, size int, out io.Writer) error { img, _, err := image.Decode(reader) if err != nil { return err } m := resize.Resize(uint(size), 0, img, resize.NearestNeighbor) return jpeg.Encode(out, m, &jpeg.Options{Quality: 75}) } func readFromTag(path string) (io.Reader, error) { f, err := os.Open(path) if err != nil { return nil, err } defer f.Close() m, err := tag.ReadFrom(f) if err != nil { return nil, err } picture := m.Picture() if picture == nil { return nil, errors.New("error extracting art from file " + path) } return bytes.NewReader(picture.Data), nil }