diff --git a/conf/configuration.go b/conf/configuration.go index 08008105d..c6a52b778 100644 --- a/conf/configuration.go +++ b/conf/configuration.go @@ -63,6 +63,7 @@ type configOptions struct { FFmpegPath string MPVPath string MPVCmdTemplate string + ArtworkFolder string CoverArtPriority string CoverJpegQuality int ArtistArtPriority string @@ -243,6 +244,10 @@ func Load(noConfigDump bool) { os.Exit(1) } + if Server.ArtworkFolder == "" { + Server.ArtworkFolder = filepath.Join(Server.DataFolder, "artwork") + } + Server.ConfigFile = viper.GetViper().ConfigFileUsed() if Server.DbPath == "" { Server.DbPath = filepath.Join(Server.DataFolder, consts.DefaultDbPath) @@ -456,6 +461,7 @@ func init() { viper.SetDefault("ffmpegpath", "") viper.SetDefault("mpvcmdtemplate", "mpv --audio-device=%d --no-audio-display --pause %f --input-ipc-server=%s") + viper.SetDefault("ArtworkFolder", "") viper.SetDefault("coverartpriority", "cover.*, folder.*, front.*, embedded, external") viper.SetDefault("coverjpegquality", 75) viper.SetDefault("artistartpriority", "artist.*, album/artist.*, external") diff --git a/conf/mime/mime_types.go b/conf/mime/mime_types.go index 33542cb8e..2c65cd41f 100644 --- a/conf/mime/mime_types.go +++ b/conf/mime/mime_types.go @@ -15,7 +15,10 @@ type mimeConf struct { Lossless []string `yaml:"lossless"` } -var LosslessFormats []string +var ( + LosslessFormats []string + ValidImageExtensions []string +) func initMimeTypes() { // In some circumstances, Windows sets JS mime-type to `text/plain`! @@ -36,6 +39,9 @@ func initMimeTypes() { } for ext, typ := range mimeConf.Types { _ = mime.AddExtensionType(ext, typ) + if strings.HasPrefix(typ, "image/") { + ValidImageExtensions = append(ValidImageExtensions, ext) + } } for _, ext := range mimeConf.Lossless { diff --git a/core/artwork/reader_playlist.go b/core/artwork/reader_playlist.go index a9f289ad8..a73959d3a 100644 --- a/core/artwork/reader_playlist.go +++ b/core/artwork/reader_playlist.go @@ -44,6 +44,7 @@ func (a *playlistArtworkReader) LastUpdated() time.Time { func (a *playlistArtworkReader) Reader(ctx context.Context) (io.ReadCloser, string, error) { ff := []sourceFunc{ + fromNamedArtwork(ctx, "playlist", a.pl.ID, a.pl.Name), a.fromGeneratedTiledCover(ctx), fromAlbumPlaceholder(), } diff --git a/core/artwork/sources.go b/core/artwork/sources.go index 121e6c38b..8fea7a610 100644 --- a/core/artwork/sources.go +++ b/core/artwork/sources.go @@ -3,6 +3,7 @@ package artwork import ( "bytes" "context" + "errors" "fmt" "io" "net/http" @@ -16,6 +17,8 @@ import ( "time" "github.com/dhowden/tag" + "github.com/navidrome/navidrome/conf" + "github.com/navidrome/navidrome/conf/mime" "github.com/navidrome/navidrome/consts" "github.com/navidrome/navidrome/core/external" "github.com/navidrome/navidrome/core/ffmpeg" @@ -192,3 +195,32 @@ func fromURL(ctx context.Context, imageUrl *url.URL) (io.ReadCloser, string, err } return resp.Body, imageUrl.String(), nil } + +func fromNamedArtwork(ctx context.Context, resource string, names ...string) sourceFunc { + return func() (io.ReadCloser, string, error) { + for _, name := range names { + playlistName := name + imagePath, err := findMatchingImage(ctx, resource, playlistName) + if err != nil { + continue + } + file, err := os.Open(imagePath) + if err != nil { + return nil, "", err + } + return file, imagePath, nil + } + return nil, "", errors.New("no matching image found") + } +} + +func findMatchingImage(_ context.Context, resource string, name string) (string, error) { + path := filepath.Join(conf.Server.ArtworkFolder, resource, name) + for _, ext := range mime.ValidImageExtensions { + filename := path + ext + if _, err := os.Stat(filename); err == nil { + return filename, nil + } + } + return "", errors.New("no matching image found") +}