Move artwork handling to its own package

This commit is contained in:
Deluan 2022-12-25 16:07:28 -05:00 committed by Deluan Quintão
parent 8cf78efb9c
commit c1c4645501
10 changed files with 163 additions and 143 deletions

View file

@ -12,6 +12,7 @@ import (
"github.com/navidrome/navidrome/core/agents" "github.com/navidrome/navidrome/core/agents"
"github.com/navidrome/navidrome/core/agents/lastfm" "github.com/navidrome/navidrome/core/agents/lastfm"
"github.com/navidrome/navidrome/core/agents/listenbrainz" "github.com/navidrome/navidrome/core/agents/listenbrainz"
"github.com/navidrome/navidrome/core/artwork"
"github.com/navidrome/navidrome/core/ffmpeg" "github.com/navidrome/navidrome/core/ffmpeg"
"github.com/navidrome/navidrome/core/scrobbler" "github.com/navidrome/navidrome/core/scrobbler"
"github.com/navidrome/navidrome/db" "github.com/navidrome/navidrome/db"
@ -45,9 +46,9 @@ func CreateNativeAPIRouter() *nativeapi.Router {
func CreateSubsonicAPIRouter() *subsonic.Router { func CreateSubsonicAPIRouter() *subsonic.Router {
sqlDB := db.Db() sqlDB := db.Db()
dataStore := persistence.New(sqlDB) dataStore := persistence.New(sqlDB)
fileCache := core.GetImageCache() fileCache := artwork.GetImageCache()
fFmpeg := ffmpeg.New() fFmpeg := ffmpeg.New()
artwork := core.NewArtwork(dataStore, fileCache, fFmpeg) artworkArtwork := artwork.NewArtwork(dataStore, fileCache, fFmpeg)
transcodingCache := core.GetTranscodingCache() transcodingCache := core.GetTranscodingCache()
mediaStreamer := core.NewMediaStreamer(dataStore, fFmpeg, transcodingCache) mediaStreamer := core.NewMediaStreamer(dataStore, fFmpeg, transcodingCache)
archiver := core.NewArchiver(mediaStreamer, dataStore) archiver := core.NewArchiver(mediaStreamer, dataStore)
@ -58,7 +59,7 @@ func CreateSubsonicAPIRouter() *subsonic.Router {
broker := events.GetBroker() broker := events.GetBroker()
playlists := core.NewPlaylists(dataStore) playlists := core.NewPlaylists(dataStore)
playTracker := scrobbler.GetPlayTracker(dataStore, broker) playTracker := scrobbler.GetPlayTracker(dataStore, broker)
router := subsonic.New(dataStore, artwork, mediaStreamer, archiver, players, externalMetadata, scanner, broker, playlists, playTracker) router := subsonic.New(dataStore, artworkArtwork, mediaStreamer, archiver, players, externalMetadata, scanner, broker, playlists, playTracker)
return router return router
} }
@ -80,10 +81,10 @@ func createScanner() scanner.Scanner {
sqlDB := db.Db() sqlDB := db.Db()
dataStore := persistence.New(sqlDB) dataStore := persistence.New(sqlDB)
playlists := core.NewPlaylists(dataStore) playlists := core.NewPlaylists(dataStore)
fileCache := core.GetImageCache() fileCache := artwork.GetImageCache()
fFmpeg := ffmpeg.New() fFmpeg := ffmpeg.New()
artwork := core.NewArtwork(dataStore, fileCache, fFmpeg) artworkArtwork := artwork.NewArtwork(dataStore, fileCache, fFmpeg)
cacheWarmer := core.NewArtworkCacheWarmer(artwork) cacheWarmer := artwork.NewCacheWarmer(artworkArtwork)
broker := events.GetBroker() broker := events.GetBroker()
scannerScanner := scanner.New(dataStore, playlists, cacheWarmer, broker) scannerScanner := scanner.New(dataStore, playlists, cacheWarmer, broker)
return scannerScanner return scannerScanner

View file

@ -1,4 +1,4 @@
package core package artwork
import ( import (
"bufio" "bufio"
@ -12,21 +12,15 @@ import (
"image/png" "image/png"
"io" "io"
"net/http" "net/http"
"os"
"path/filepath"
"reflect"
"runtime"
"strings" "strings"
"time" "time"
"github.com/dhowden/tag"
"github.com/disintegration/imaging" "github.com/disintegration/imaging"
"github.com/navidrome/navidrome/conf" "github.com/navidrome/navidrome/conf"
"github.com/navidrome/navidrome/consts" "github.com/navidrome/navidrome/consts"
"github.com/navidrome/navidrome/core/ffmpeg" "github.com/navidrome/navidrome/core/ffmpeg"
"github.com/navidrome/navidrome/log" "github.com/navidrome/navidrome/log"
"github.com/navidrome/navidrome/model" "github.com/navidrome/navidrome/model"
"github.com/navidrome/navidrome/resources"
"github.com/navidrome/navidrome/utils/cache" "github.com/navidrome/navidrome/utils/cache"
"github.com/navidrome/navidrome/utils/singleton" "github.com/navidrome/navidrome/utils/singleton"
_ "golang.org/x/image/webp" _ "golang.org/x/image/webp"
@ -59,24 +53,15 @@ func (a *artwork) Get(ctx context.Context, id string, size int) (io.ReadCloser,
} }
} }
key := &artworkKey{a: a, artID: artID, size: size} item := &artItem{a: a, artID: artID, size: size}
r, err := a.cache.Get(ctx, key) r, err := a.cache.Get(ctx, item)
if err != nil && !errors.Is(err, context.Canceled) { if err != nil && !errors.Is(err, context.Canceled) {
log.Error(ctx, "Error accessing image cache", "id", id, "size", size, err) log.Error(ctx, "Error accessing image cache", "id", id, "size", size, err)
} }
return r, err return r, err
} }
type fromFunc func() (io.ReadCloser, string, error)
func (f fromFunc) String() string {
name := runtime.FuncForPC(reflect.ValueOf(f).Pointer()).Name()
name = strings.TrimPrefix(name, "github.com/navidrome/navidrome/core.")
name = strings.TrimSuffix(name, ".func1")
return name
}
func (a *artwork) get(ctx context.Context, artID model.ArtworkID, size int) (reader io.ReadCloser, path string, err error) { func (a *artwork) get(ctx context.Context, artID model.ArtworkID, size int) (reader io.ReadCloser, path string, err error) {
// If requested a resized image, get the original (possibly from cache) // If requested a resized image, get the original (possibly from cache)
if size > 0 { if size > 0 {
@ -110,7 +95,7 @@ func (a *artwork) extractAlbumImage(ctx context.Context, artID model.ArtworkID)
log.Error(ctx, "Could not retrieve album", "id", artID.ID, err) log.Error(ctx, "Could not retrieve album", "id", artID.ID, err)
return nil, "" return nil, ""
} }
var ff = a.fromCoverArtPriority(ctx, conf.Server.CoverArtPriority, *al) var ff = fromCoverArtPriority(ctx, a.ffmpeg, conf.Server.CoverArtPriority, *al)
ff = append(ff, fromPlaceholder()) ff = append(ff, fromPlaceholder())
return extractImage(ctx, artID, ff...) return extractImage(ctx, artID, ff...)
} }
@ -126,9 +111,9 @@ func (a *artwork) extractMediaFileImage(ctx context.Context, artID model.Artwork
return nil, "" return nil, ""
} }
var ff []fromFunc var ff []sourceFunc
if mf.CoverArtID().Kind == model.KindMediaFileArtwork { if mf.CoverArtID().Kind == model.KindMediaFileArtwork {
ff = []fromFunc{ ff = []sourceFunc{
fromTag(mf.Path), fromTag(mf.Path),
fromFFmpegTag(ctx, a.ffmpeg, mf.Path), fromFFmpegTag(ctx, a.ffmpeg, mf.Path),
} }
@ -152,38 +137,28 @@ func (a *artwork) resizedFromOriginal(ctx context.Context, artID model.ArtworkID
return resized, nil return resized, nil
} }
func extractImage(ctx context.Context, artID model.ArtworkID, extractFuncs ...fromFunc) (io.ReadCloser, string) { func extractImage(ctx context.Context, artID model.ArtworkID, extractFuncs ...sourceFunc) (io.ReadCloser, string) {
for _, f := range extractFuncs { for _, f := range extractFuncs {
if ctx.Err() != nil { if ctx.Err() != nil {
return nil, "" return nil, ""
} }
r, path, err := f() r, path, err := f()
if r != nil { if r != nil {
log.Trace(ctx, "Found artwork", "artID", artID, "path", path, "origin", f) log.Trace(ctx, "Found artwork", "artID", artID, "path", path, "source", f)
return r, path return r, path
} }
log.Trace(ctx, "Tried to extract artwork", "artID", artID, "origin", f, err) log.Trace(ctx, "Tried to extract artwork", "artID", artID, "source", f, err)
} }
log.Error(ctx, "extractImage should never reach this point!", "artID", artID, "path") log.Error(ctx, "extractImage should never reach this point!", "artID", artID, "path")
return nil, "" return nil, ""
} }
func (a *artwork) fromAlbum(ctx context.Context, id model.ArtworkID) fromFunc { func fromCoverArtPriority(ctx context.Context, ffmpeg ffmpeg.FFmpeg, priority string, al model.Album) []sourceFunc {
return func() (io.ReadCloser, string, error) { var ff []sourceFunc
r, path, err := a.get(ctx, id, 0)
if err != nil {
return nil, "", err
}
return r, path, nil
}
}
func (a *artwork) fromCoverArtPriority(ctx context.Context, priority string, al model.Album) []fromFunc {
var ff []fromFunc
for _, pattern := range strings.Split(strings.ToLower(priority), ",") { for _, pattern := range strings.Split(strings.ToLower(priority), ",") {
pattern = strings.TrimSpace(pattern) pattern = strings.TrimSpace(pattern)
if pattern == "embedded" { if pattern == "embedded" {
ff = append(ff, fromTag(al.EmbedArtPath), fromFFmpegTag(ctx, a.ffmpeg, al.EmbedArtPath)) ff = append(ff, fromTag(al.EmbedArtPath), fromFFmpegTag(ctx, ffmpeg, al.EmbedArtPath))
continue continue
} }
if al.ImageFiles != "" { if al.ImageFiles != "" {
@ -193,79 +168,6 @@ func (a *artwork) fromCoverArtPriority(ctx context.Context, priority string, al
return ff return ff
} }
func fromExternalFile(ctx context.Context, files string, pattern string) fromFunc {
return func() (io.ReadCloser, string, error) {
for _, file := range filepath.SplitList(files) {
_, name := filepath.Split(file)
match, err := filepath.Match(pattern, strings.ToLower(name))
if err != nil {
log.Warn(ctx, "Error matching cover art file to pattern", "pattern", pattern, "file", file)
continue
}
if !match {
continue
}
f, err := os.Open(file)
if err != nil {
log.Warn(ctx, "Could not open cover art file", "file", file, err)
continue
}
return f, file, err
}
return nil, "", fmt.Errorf("pattern '%s' not matched by files %v", pattern, files)
}
}
func fromTag(path string) fromFunc {
return func() (io.ReadCloser, string, error) {
if path == "" {
return nil, "", nil
}
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, "", fmt.Errorf("no embedded image found in %s", path)
}
return io.NopCloser(bytes.NewReader(picture.Data)), path, nil
}
}
func fromFFmpegTag(ctx context.Context, ffmpeg ffmpeg.FFmpeg, path string) fromFunc {
return func() (io.ReadCloser, string, error) {
if path == "" {
return nil, "", nil
}
r, err := ffmpeg.ExtractImage(ctx, path)
if err != nil {
return nil, "", err
}
defer r.Close()
buf := new(bytes.Buffer)
_, err = io.Copy(buf, r)
if err != nil {
return nil, "", err
}
return io.NopCloser(buf), path, nil
}
}
func fromPlaceholder() fromFunc {
return func() (io.ReadCloser, string, error) {
r, _ := resources.FS().Open(consts.PlaceholderAlbumArt)
return r, consts.PlaceholderAlbumArt, nil
}
}
func asImageReader(r io.Reader) (io.Reader, string, error) { func asImageReader(r io.Reader) (io.Reader, string, error) {
br := bufio.NewReader(r) br := bufio.NewReader(r)
buf, err := br.Peek(512) buf, err := br.Peek(512)
@ -305,26 +207,26 @@ func resizeImage(reader io.Reader, size int) (io.Reader, error) {
return buf, err return buf, err
} }
type ArtworkCache struct { type imageCache struct {
cache.FileCache cache.FileCache
} }
type artworkKey struct { type artItem struct {
a *artwork a *artwork
artID model.ArtworkID artID model.ArtworkID
size int size int
} }
func (k *artworkKey) Key() string { func (k *artItem) Key() string {
return fmt.Sprintf("%s.%d.%d", k.artID, k.size, conf.Server.CoverJpegQuality) return fmt.Sprintf("%s.%d.%d", k.artID, k.size, conf.Server.CoverJpegQuality)
} }
func GetImageCache() cache.FileCache { func GetImageCache() cache.FileCache {
return singleton.GetInstance(func() *ArtworkCache { return singleton.GetInstance(func() *imageCache {
return &ArtworkCache{ return &imageCache{
FileCache: cache.NewFileCache("Image", conf.Server.ImageCacheSize, consts.ImageCacheDir, consts.DefaultImageCacheMaxItems, FileCache: cache.NewFileCache("Image", conf.Server.ImageCacheSize, consts.ImageCacheDir, consts.DefaultImageCacheMaxItems,
func(ctx context.Context, arg cache.Item) (io.Reader, error) { func(ctx context.Context, arg cache.Item) (io.Reader, error) {
info := arg.(*artworkKey) info := arg.(*artItem)
r, _, err := info.a.get(ctx, info.artID, info.size) r, _, err := info.a.get(ctx, info.artID, info.size)
return r, err return r, err
}), }),

View file

@ -1,4 +1,4 @@
package core package artwork
import ( import (
"context" "context"
@ -11,17 +11,17 @@ import (
"github.com/navidrome/navidrome/utils/pl" "github.com/navidrome/navidrome/utils/pl"
) )
type ArtworkCacheWarmer interface { type CacheWarmer interface {
PreCache(artID model.ArtworkID) PreCache(artID model.ArtworkID)
} }
func NewArtworkCacheWarmer(artwork Artwork) ArtworkCacheWarmer { func NewCacheWarmer(artwork Artwork) CacheWarmer {
// If image cache is disabled, return a NOOP implementation // If image cache is disabled, return a NOOP implementation
if conf.Server.ImageCacheSize == "0" { if conf.Server.ImageCacheSize == "0" {
return &noopCacheWarmer{} return &noopCacheWarmer{}
} }
a := &artworkCacheWarmer{ a := &cacheWarmer{
artwork: artwork, artwork: artwork,
input: make(chan string), input: make(chan string),
} }
@ -29,23 +29,23 @@ func NewArtworkCacheWarmer(artwork Artwork) ArtworkCacheWarmer {
return a return a
} }
type artworkCacheWarmer struct { type cacheWarmer struct {
artwork Artwork artwork Artwork
input chan string input chan string
} }
func (a *artworkCacheWarmer) PreCache(artID model.ArtworkID) { func (a *cacheWarmer) PreCache(artID model.ArtworkID) {
a.input <- artID.String() a.input <- artID.String()
} }
func (a *artworkCacheWarmer) run(ctx context.Context) { func (a *cacheWarmer) run(ctx context.Context) {
errs := pl.Sink(ctx, 2, a.input, a.doCacheImage) errs := pl.Sink(ctx, 2, a.input, a.doCacheImage)
for err := range errs { for err := range errs {
log.Warn(ctx, "Error warming cache", err) log.Warn(ctx, "Error warming cache", err)
} }
} }
func (a *artworkCacheWarmer) doCacheImage(ctx context.Context, id string) error { func (a *cacheWarmer) doCacheImage(ctx context.Context, id string) error {
r, err := a.artwork.Get(ctx, id, 0) r, err := a.artwork.Get(ctx, id, 0)
if err != nil { if err != nil {
return fmt.Errorf("error cacheing id='%s': %w", id, err) return fmt.Errorf("error cacheing id='%s': %w", id, err)

View file

@ -1,4 +1,4 @@
package core package artwork
import ( import (
"context" "context"

113
core/artwork/sources.go Normal file
View file

@ -0,0 +1,113 @@
package artwork
import (
"bytes"
"context"
"fmt"
"io"
"os"
"path/filepath"
"reflect"
"runtime"
"strings"
"github.com/dhowden/tag"
"github.com/navidrome/navidrome/consts"
"github.com/navidrome/navidrome/core/ffmpeg"
"github.com/navidrome/navidrome/log"
"github.com/navidrome/navidrome/model"
"github.com/navidrome/navidrome/resources"
)
type sourceFunc func() (io.ReadCloser, string, error)
func (f sourceFunc) String() string {
name := runtime.FuncForPC(reflect.ValueOf(f).Pointer()).Name()
name = strings.TrimPrefix(name, "github.com/navidrome/navidrome/core.")
name = strings.TrimPrefix(name, "(*artwork).")
name = strings.TrimSuffix(name, ".func1")
return name
}
func (a *artwork) fromAlbum(ctx context.Context, id model.ArtworkID) sourceFunc {
return func() (io.ReadCloser, string, error) {
r, path, err := a.get(ctx, id, 0)
if err != nil {
return nil, "", err
}
return r, path, nil
}
}
func fromExternalFile(ctx context.Context, files string, pattern string) sourceFunc {
return func() (io.ReadCloser, string, error) {
for _, file := range filepath.SplitList(files) {
_, name := filepath.Split(file)
match, err := filepath.Match(pattern, strings.ToLower(name))
if err != nil {
log.Warn(ctx, "Error matching cover art file to pattern", "pattern", pattern, "file", file)
continue
}
if !match {
continue
}
f, err := os.Open(file)
if err != nil {
log.Warn(ctx, "Could not open cover art file", "file", file, err)
continue
}
return f, file, err
}
return nil, "", fmt.Errorf("pattern '%s' not matched by files %v", pattern, files)
}
}
func fromTag(path string) sourceFunc {
return func() (io.ReadCloser, string, error) {
if path == "" {
return nil, "", nil
}
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, "", fmt.Errorf("no embedded image found in %s", path)
}
return io.NopCloser(bytes.NewReader(picture.Data)), path, nil
}
}
func fromFFmpegTag(ctx context.Context, ffmpeg ffmpeg.FFmpeg, path string) sourceFunc {
return func() (io.ReadCloser, string, error) {
if path == "" {
return nil, "", nil
}
r, err := ffmpeg.ExtractImage(ctx, path)
if err != nil {
return nil, "", err
}
defer r.Close()
buf := new(bytes.Buffer)
_, err = io.Copy(buf, r)
if err != nil {
return nil, "", err
}
return io.NopCloser(buf), path, nil
}
}
func fromPlaceholder() sourceFunc {
return func() (io.ReadCloser, string, error) {
r, _ := resources.FS().Open(consts.PlaceholderAlbumArt)
return r, consts.PlaceholderAlbumArt, nil
}
}

View file

@ -3,15 +3,16 @@ package core
import ( import (
"github.com/google/wire" "github.com/google/wire"
"github.com/navidrome/navidrome/core/agents" "github.com/navidrome/navidrome/core/agents"
"github.com/navidrome/navidrome/core/artwork"
"github.com/navidrome/navidrome/core/ffmpeg" "github.com/navidrome/navidrome/core/ffmpeg"
"github.com/navidrome/navidrome/core/scrobbler" "github.com/navidrome/navidrome/core/scrobbler"
) )
var Set = wire.NewSet( var Set = wire.NewSet(
NewArtwork, artwork.NewArtwork,
NewMediaStreamer, NewMediaStreamer,
GetTranscodingCache, GetTranscodingCache,
GetImageCache, artwork.GetImageCache,
NewArchiver, NewArchiver,
NewExternalMetadata, NewExternalMetadata,
NewPlayers, NewPlayers,
@ -20,5 +21,5 @@ var Set = wire.NewSet(
scrobbler.GetPlayTracker, scrobbler.GetPlayTracker,
NewShare, NewShare,
NewPlaylists, NewPlaylists,
NewArtworkCacheWarmer, artwork.NewCacheWarmer,
) )

View file

@ -8,7 +8,7 @@ import (
"time" "time"
"github.com/Masterminds/squirrel" "github.com/Masterminds/squirrel"
"github.com/navidrome/navidrome/core" "github.com/navidrome/navidrome/core/artwork"
"github.com/navidrome/navidrome/log" "github.com/navidrome/navidrome/log"
"github.com/navidrome/navidrome/model" "github.com/navidrome/navidrome/model"
"github.com/navidrome/navidrome/utils" "github.com/navidrome/navidrome/utils"
@ -25,10 +25,10 @@ type refresher struct {
album map[string]struct{} album map[string]struct{}
artist map[string]struct{} artist map[string]struct{}
dirMap dirMap dirMap dirMap
cacheWarmer core.ArtworkCacheWarmer cacheWarmer artwork.CacheWarmer
} }
func newRefresher(ds model.DataStore, cw core.ArtworkCacheWarmer, dirMap dirMap) *refresher { func newRefresher(ds model.DataStore, cw artwork.CacheWarmer, dirMap dirMap) *refresher {
return &refresher{ return &refresher{
ds: ds, ds: ds,
album: map[string]struct{}{}, album: map[string]struct{}{},

View file

@ -9,6 +9,7 @@ import (
"time" "time"
"github.com/navidrome/navidrome/core" "github.com/navidrome/navidrome/core"
"github.com/navidrome/navidrome/core/artwork"
"github.com/navidrome/navidrome/log" "github.com/navidrome/navidrome/log"
"github.com/navidrome/navidrome/model" "github.com/navidrome/navidrome/model"
"github.com/navidrome/navidrome/server/events" "github.com/navidrome/navidrome/server/events"
@ -46,7 +47,7 @@ type scanner struct {
ds model.DataStore ds model.DataStore
pls core.Playlists pls core.Playlists
broker events.Broker broker events.Broker
cacheWarmer core.ArtworkCacheWarmer cacheWarmer artwork.CacheWarmer
} }
type scanStatus struct { type scanStatus struct {
@ -56,7 +57,7 @@ type scanStatus struct {
lastUpdate time.Time lastUpdate time.Time
} }
func New(ds model.DataStore, playlists core.Playlists, cacheWarmer core.ArtworkCacheWarmer, broker events.Broker) Scanner { func New(ds model.DataStore, playlists core.Playlists, cacheWarmer artwork.CacheWarmer, broker events.Broker) Scanner {
s := &scanner{ s := &scanner{
ds: ds, ds: ds,
pls: playlists, pls: playlists,

View file

@ -11,6 +11,7 @@ import (
"github.com/navidrome/navidrome/conf" "github.com/navidrome/navidrome/conf"
"github.com/navidrome/navidrome/core" "github.com/navidrome/navidrome/core"
"github.com/navidrome/navidrome/core/artwork"
"github.com/navidrome/navidrome/core/auth" "github.com/navidrome/navidrome/core/auth"
"github.com/navidrome/navidrome/log" "github.com/navidrome/navidrome/log"
"github.com/navidrome/navidrome/model" "github.com/navidrome/navidrome/model"
@ -27,10 +28,10 @@ type TagScanner struct {
plsSync *playlistImporter plsSync *playlistImporter
cnt *counters cnt *counters
mapper *mediaFileMapper mapper *mediaFileMapper
cacheWarmer core.ArtworkCacheWarmer cacheWarmer artwork.CacheWarmer
} }
func NewTagScanner(rootFolder string, ds model.DataStore, playlists core.Playlists, cacheWarmer core.ArtworkCacheWarmer) FolderScanner { func NewTagScanner(rootFolder string, ds model.DataStore, playlists core.Playlists, cacheWarmer artwork.CacheWarmer) FolderScanner {
s := &TagScanner{ s := &TagScanner{
rootFolder: rootFolder, rootFolder: rootFolder,
plsSync: newPlaylistImporter(ds, playlists, rootFolder), plsSync: newPlaylistImporter(ds, playlists, rootFolder),

View file

@ -12,6 +12,7 @@ import (
"github.com/go-chi/chi/v5/middleware" "github.com/go-chi/chi/v5/middleware"
"github.com/navidrome/navidrome/consts" "github.com/navidrome/navidrome/consts"
"github.com/navidrome/navidrome/core" "github.com/navidrome/navidrome/core"
"github.com/navidrome/navidrome/core/artwork"
"github.com/navidrome/navidrome/core/scrobbler" "github.com/navidrome/navidrome/core/scrobbler"
"github.com/navidrome/navidrome/log" "github.com/navidrome/navidrome/log"
"github.com/navidrome/navidrome/model" "github.com/navidrome/navidrome/model"
@ -30,7 +31,7 @@ type handlerRaw = func(http.ResponseWriter, *http.Request) (*responses.Subsonic,
type Router struct { type Router struct {
http.Handler http.Handler
ds model.DataStore ds model.DataStore
artwork core.Artwork artwork artwork.Artwork
streamer core.MediaStreamer streamer core.MediaStreamer
archiver core.Archiver archiver core.Archiver
players core.Players players core.Players
@ -41,7 +42,7 @@ type Router struct {
scrobbler scrobbler.PlayTracker scrobbler scrobbler.PlayTracker
} }
func New(ds model.DataStore, artwork core.Artwork, streamer core.MediaStreamer, archiver core.Archiver, func New(ds model.DataStore, artwork artwork.Artwork, streamer core.MediaStreamer, archiver core.Archiver,
players core.Players, externalMetadata core.ExternalMetadata, scanner scanner.Scanner, broker events.Broker, players core.Players, externalMetadata core.ExternalMetadata, scanner scanner.Scanner, broker events.Broker,
playlists core.Playlists, scrobbler scrobbler.PlayTracker) *Router { playlists core.Playlists, scrobbler scrobbler.PlayTracker) *Router {
r := &Router{ r := &Router{