navidrome/core/artwork/artwork.go

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

128 lines
4.0 KiB
Go
Raw Normal View History

package artwork
import (
"context"
2022-12-19 21:34:21 +01:00
"errors"
_ "image/gif"
"io"
2022-12-25 07:25:10 +01:00
"time"
"github.com/navidrome/navidrome/consts"
2023-01-13 20:30:26 +01:00
"github.com/navidrome/navidrome/core"
2022-12-20 18:25:47 +01:00
"github.com/navidrome/navidrome/core/ffmpeg"
2022-12-19 21:34:21 +01:00
"github.com/navidrome/navidrome/log"
2020-01-24 01:44:08 +01:00
"github.com/navidrome/navidrome/model"
"github.com/navidrome/navidrome/resources"
2022-12-20 17:27:40 +01:00
"github.com/navidrome/navidrome/utils/cache"
2020-08-21 17:33:23 +02:00
_ "golang.org/x/image/webp"
)
var ErrUnavailable = errors.New("artwork unavailable")
type Artwork interface {
Get(ctx context.Context, artID model.ArtworkID, size int) (io.ReadCloser, time.Time, error)
GetOrPlaceholder(ctx context.Context, id string, size int) (io.ReadCloser, time.Time, error)
}
2023-01-13 20:30:26 +01:00
func NewArtwork(ds model.DataStore, cache cache.FileCache, ffmpeg ffmpeg.FFmpeg, em core.ExternalMetadata) Artwork {
return &artwork{ds: ds, cache: cache, ffmpeg: ffmpeg, em: em}
}
type artwork struct {
2022-12-20 18:25:47 +01:00
ds model.DataStore
cache cache.FileCache
ffmpeg ffmpeg.FFmpeg
2023-01-13 20:30:26 +01:00
em core.ExternalMetadata
2020-07-24 19:30:27 +02:00
}
2022-12-27 17:36:23 +01:00
type artworkReader interface {
cache.Item
LastUpdated() time.Time
Reader(ctx context.Context) (io.ReadCloser, string, error)
}
func (a *artwork) GetOrPlaceholder(ctx context.Context, id string, size int) (reader io.ReadCloser, lastUpdate time.Time, err error) {
artID, err := a.getArtworkId(ctx, id)
if err == nil {
reader, lastUpdate, err = a.Get(ctx, artID, size)
2022-12-20 17:27:40 +01:00
}
if errors.Is(err, ErrUnavailable) {
if artID.Kind == model.KindArtistArtwork {
reader, _ = resources.FS().Open(consts.PlaceholderArtistArt)
} else {
reader, _ = resources.FS().Open(consts.PlaceholderAlbumArt)
}
return reader, consts.ServerStart, nil
}
return reader, lastUpdate, err
}
2022-12-20 17:27:40 +01:00
func (a *artwork) Get(ctx context.Context, artID model.ArtworkID, size int) (reader io.ReadCloser, lastUpdate time.Time, err error) {
2022-12-27 18:54:51 +01:00
artReader, err := a.getArtworkReader(ctx, artID, size)
2022-12-19 23:07:29 +01:00
if err != nil {
2022-12-27 18:54:51 +01:00
return nil, time.Time{}, err
2022-12-19 21:34:21 +01:00
}
2022-12-27 17:36:23 +01:00
r, err := a.cache.Get(ctx, artReader)
2022-12-28 16:19:52 +01:00
if err != nil {
2023-02-02 16:36:27 +01:00
if !errors.Is(err, context.Canceled) && !errors.Is(err, ErrUnavailable) {
log.Error(ctx, "Error accessing image cache", "id", artID, "size", size, err)
2022-12-28 16:19:52 +01:00
}
return nil, time.Time{}, err
2022-12-22 19:53:49 +01:00
}
2022-12-28 16:19:52 +01:00
return r, artReader.LastUpdated(), nil
2022-12-27 18:54:51 +01:00
}
func (a *artwork) getArtworkId(ctx context.Context, id string) (model.ArtworkID, error) {
if id == "" {
return model.ArtworkID{}, ErrUnavailable
}
artID, err := model.ParseArtworkID(id)
if err == nil {
return artID, nil
}
log.Trace(ctx, "ArtworkID invalid. Trying to figure out kind based on the ID", "id", id)
entity, err := model.GetEntityByID(ctx, a.ds, id)
if err != nil {
return model.ArtworkID{}, err
}
switch e := entity.(type) {
2022-12-31 05:52:53 +01:00
case *model.Artist:
artID = model.NewArtworkID(model.KindArtistArtwork, e.ID)
log.Trace(ctx, "ID is for an Artist", "id", id, "name", e.Name, "artist", e.Name)
case *model.Album:
artID = model.NewArtworkID(model.KindAlbumArtwork, e.ID)
log.Trace(ctx, "ID is for an Album", "id", id, "name", e.Name, "artist", e.AlbumArtist)
case *model.MediaFile:
artID = model.NewArtworkID(model.KindMediaFileArtwork, e.ID)
log.Trace(ctx, "ID is for a MediaFile", "id", id, "title", e.Title, "album", e.Album)
case *model.Playlist:
artID = model.NewArtworkID(model.KindPlaylistArtwork, e.ID)
log.Trace(ctx, "ID is for a Playlist", "id", id, "name", e.Name)
}
return artID, nil
}
2022-12-27 18:54:51 +01:00
func (a *artwork) getArtworkReader(ctx context.Context, artID model.ArtworkID, size int) (artworkReader, error) {
var artReader artworkReader
var err error
if size > 0 {
artReader, err = resizedFromOriginal(ctx, a, artID, size)
} else {
switch artID.Kind {
2022-12-31 05:52:53 +01:00
case model.KindArtistArtwork:
2023-01-13 20:30:26 +01:00
artReader, err = newArtistReader(ctx, a, artID, a.em)
2022-12-27 18:54:51 +01:00
case model.KindAlbumArtwork:
artReader, err = newAlbumArtworkReader(ctx, a, artID, a.em)
2022-12-27 18:54:51 +01:00
case model.KindMediaFileArtwork:
artReader, err = newMediafileArtworkReader(ctx, a, artID)
case model.KindPlaylistArtwork:
artReader, err = newPlaylistArtworkReader(ctx, a, artID)
2022-12-27 18:54:51 +01:00
default:
return nil, ErrUnavailable
2022-12-27 18:54:51 +01:00
}
}
return artReader, err
2022-12-22 19:53:49 +01:00
}