mirror of
https://github.com/navidrome/navidrome.git
synced 2026-08-31 07:30:32 +00:00
* test(artwork): add failing e2e tests for artist image leaking as album art Reproduces a v0.62.0 regression (#5451/#5457): the album cover-art parent-folder fallback can include the artist folder, serving the artist thumbnail (e.g. Artist/folder.jpg) as album art for any album without image files in its own folder(s). Covers three scenarios: a plain Artist/Album layout with no album images, a single-disc album spread across sibling folders under the artist folder, and a spread album whose own front.jpg is shadowed by the artist's cover.jpg via CoverArtPriority order. Also adds an albumByName test helper for multi-album layouts. The tests are expected to fail until the parent-folder inclusion is gated by a structural check (skip the common parent when audio from other albums lives under it). * fix(artwork): never serve artist folder images as album art The album cover-art parent-folder fallback (introduced in #5451/#5457) could include the artist folder as a source of album images, serving the artist thumbnail (e.g. Artist/folder.jpg) as cover art for any album without image files in its own folder(s). This affected both plain Artist/Album layouts and single-disc albums spread across sibling folders under the artist folder. Gate the common-parent inclusion with a structural check: the parent only qualifies as an album root when no audio belonging to other albums lives in it or anywhere beneath it. An artist folder contains other albums' tracks, while an album root above disc subfolders contains only this album's, so the check works for any disc folder naming scheme and never affects the multi-disc fixes from #5376/#5456. A single-album artist with no images anywhere remains structurally indistinguishable from an album root and is a known residual case. * refactor(artwork): move album-root audio check into folder repository Replace the raw subtree SQL (LIKE/ESCAPE expression and wildcard escaping) that lived in core/artwork with an explicit FolderRepository.HasAudioOutsideFolders method, implemented in the persistence layer next to the existing folder-subtree query pattern. This also removes the test mock's brittle dispatch that sniffed the generated SQL to recognize the query; the fake now overrides the new method directly. Extract the whole parent-folder resolution from loadAlbumFoldersPaths into an albumRootParent helper, flattening four levels of nesting back into a linear flow. Behavior is unchanged; the unit test for a parent containing audio moved to the persistence suite, with added coverage for subtree boundaries, missing folders, and LIKE-wildcard escaping in folder paths. * refactor(persistence): use exists helper in HasAudioOutsideFolders Replace the hand-rolled count(*) query with the repository's canonical exists helper, as suggested in PR review.
470 lines
18 KiB
Go
470 lines
18 KiB
Go
package artworke2e_test
|
|
|
|
import (
|
|
"testing/fstest"
|
|
|
|
"github.com/navidrome/navidrome/conf"
|
|
"github.com/navidrome/navidrome/model"
|
|
. "github.com/onsi/ginkgo/v2"
|
|
. "github.com/onsi/gomega"
|
|
)
|
|
|
|
const (
|
|
defaultCoverPriority = "cover.*, folder.*, front.*, embedded, external"
|
|
defaultDiscPriority = "disc*.*, cd*.*, cover.*, folder.*, front.*, discsubtitle, embedded"
|
|
)
|
|
|
|
var _ = Describe("Album artwork resolution", func() {
|
|
BeforeEach(func() {
|
|
setupHarness()
|
|
})
|
|
|
|
When("an album has a single folder with cover.jpg at the album root", func() {
|
|
// Artist/
|
|
// └── Album/
|
|
// ├── 01 - Track.mp3
|
|
// └── cover.jpg ← matched by cover.*
|
|
It("returns the album-root cover", func() {
|
|
conf.Server.CoverArtPriority = defaultCoverPriority
|
|
setLayout(fstest.MapFS{
|
|
"Artist/Album/01 - Track.mp3": trackFile(1, "Track"),
|
|
"Artist/Album/cover.jpg": imageFile("album-root"),
|
|
})
|
|
scan()
|
|
|
|
al := firstAlbum()
|
|
Expect(readArtwork(al.CoverArtID())).To(Equal(imageBytes("album-root")))
|
|
})
|
|
})
|
|
|
|
// https://github.com/navidrome/navidrome/issues/5376
|
|
// cover.* basenames tie across album-root and per-disc folders;
|
|
// compareImageFiles must prefer shallower paths.
|
|
When("a multi-disc album has a cover.jpg at the album root and per-disc covers", func() {
|
|
// Artist/
|
|
// └── Album/
|
|
// ├── CD1/
|
|
// │ ├── 01 - Track.mp3
|
|
// │ └── cover.jpg ← should not win
|
|
// ├── CD2/
|
|
// │ ├── 01 - Track.mp3
|
|
// │ └── cover.jpg
|
|
// └── cover.jpg ← should win (album-root fallback)
|
|
It("prefers the album-root cover over per-disc covers", func() {
|
|
conf.Server.CoverArtPriority = defaultCoverPriority
|
|
setLayout(fstest.MapFS{
|
|
"Artist/Album/CD1/01 - Track.mp3": trackFile(1, "Track CD1"),
|
|
"Artist/Album/CD2/01 - Track.mp3": trackFile(1, "Track CD2"),
|
|
"Artist/Album/cover.jpg": imageFile("album-root"),
|
|
"Artist/Album/CD1/cover.jpg": imageFile("disc1"),
|
|
"Artist/Album/CD2/cover.jpg": imageFile("disc2"),
|
|
})
|
|
scan()
|
|
|
|
al := firstAlbum()
|
|
Expect(al.FolderIDs).To(HaveLen(2),
|
|
"sanity check: scanner should treat the two disc subfolders as one multi-disc album")
|
|
Expect(readArtwork(al.CoverArtID())).To(Equal(imageBytes("album-root")))
|
|
})
|
|
})
|
|
|
|
// https://github.com/navidrome/navidrome/issues/5376
|
|
// folder.jpg basenames tie across album-root and per-disc folders;
|
|
// compareImageFiles must prefer shallower paths.
|
|
When("a multi-disc album has folder.jpg at the album root AND in each disc subfolder", func() {
|
|
// Artist/
|
|
// └── Album/
|
|
// ├── CD1/
|
|
// │ ├── 01 - Track.mp3
|
|
// │ └── folder.jpg ← should not win
|
|
// ├── CD2/
|
|
// │ ├── 01 - Track.mp3
|
|
// │ └── folder.jpg
|
|
// └── folder.jpg ← should win (album-root fallback)
|
|
It("prefers the album-root folder.jpg over per-disc folder.jpg", func() {
|
|
conf.Server.CoverArtPriority = defaultCoverPriority
|
|
setLayout(fstest.MapFS{
|
|
"Artist/Album/CD1/01 - Track.mp3": trackFile(1, "Track CD1"),
|
|
"Artist/Album/CD2/01 - Track.mp3": trackFile(1, "Track CD2"),
|
|
"Artist/Album/folder.jpg": imageFile("album-root"),
|
|
"Artist/Album/CD1/folder.jpg": imageFile("disc1"),
|
|
"Artist/Album/CD2/folder.jpg": imageFile("disc2"),
|
|
})
|
|
scan()
|
|
|
|
al := firstAlbum()
|
|
Expect(readArtwork(al.CoverArtID())).To(Equal(imageBytes("album-root")))
|
|
})
|
|
})
|
|
|
|
// https://github.com/navidrome/navidrome/issues/5376
|
|
// Single-subfolder albums must still consider the parent folder's images.
|
|
When("an album lives entirely under a single disc subfolder with cover.jpg at the parent", func() {
|
|
// Artist/
|
|
// └── Album/
|
|
// ├── disc1/
|
|
// │ └── 01 - Track.mp3
|
|
// └── cover.jpg ← should win (parent-folder fallback)
|
|
It("uses the parent-folder cover for single-disc-subfolder albums", func() {
|
|
conf.Server.CoverArtPriority = defaultCoverPriority
|
|
setLayout(fstest.MapFS{
|
|
"Artist/Album/disc1/01 - Track.mp3": trackFile(1, "Track"),
|
|
"Artist/Album/cover.jpg": imageFile("album-root"),
|
|
})
|
|
scan()
|
|
|
|
al := firstAlbum()
|
|
Expect(readArtwork(al.CoverArtID())).To(Equal(imageBytes("album-root")))
|
|
})
|
|
})
|
|
|
|
// https://github.com/navidrome/navidrome/issues/5456
|
|
When("a top-level multi-disc album has cover.jpg at the album root and per-disc folder.jpg", func() {
|
|
// Album/ (top-level folder, Path=".")
|
|
// ├── CD1/
|
|
// │ ├── 01 - Track.mp3
|
|
// │ └── folder.jpg
|
|
// ├── CD2/
|
|
// │ ├── 01 - Track.mp3
|
|
// │ └── folder.jpg
|
|
// └── cover.jpg ← should win (album-root)
|
|
It("prefers the album-root cover.jpg", func() {
|
|
conf.Server.CoverArtPriority = defaultCoverPriority
|
|
setLayout(fstest.MapFS{
|
|
"Album/CD1/01 - Track.mp3": trackFile(1, "Track CD1"),
|
|
"Album/CD2/01 - Track.mp3": trackFile(1, "Track CD2"),
|
|
"Album/cover.jpg": imageFile("album-root"),
|
|
"Album/CD1/folder.jpg": imageFile("disc1"),
|
|
"Album/CD2/folder.jpg": imageFile("disc2"),
|
|
})
|
|
scan()
|
|
|
|
al := firstAlbum()
|
|
Expect(readArtwork(al.CoverArtID())).To(Equal(imageBytes("album-root")))
|
|
})
|
|
})
|
|
|
|
When("CoverArtPriority puts embedded first and the album has both embedded and external art", func() {
|
|
// Artist/
|
|
// └── Album/
|
|
// ├── 01 - Track.mp3 ← has embedded picture (wins via "embedded")
|
|
// └── cover.jpg
|
|
It("returns the embedded image", func() {
|
|
conf.Server.CoverArtPriority = "embedded, cover.*, folder.*, front.*, external"
|
|
setLayout(fstest.MapFS{
|
|
"Artist/Album/01 - Track.mp3": trackFile(1, "Track", map[string]any{"has_picture": "true"}),
|
|
"Artist/Album/cover.jpg": imageFile("external"),
|
|
})
|
|
scan()
|
|
// Swap in real MP3 bytes so libFS.Open returns a taglib-readable stream.
|
|
replaceWithRealMP3("Artist/Album/01 - Track.mp3")
|
|
|
|
al := firstAlbum()
|
|
Expect(readArtwork(al.CoverArtID())).To(Equal(embeddedArtBytes))
|
|
})
|
|
})
|
|
|
|
When("CoverArtPriority lists external first but no external file is present", func() {
|
|
// Artist/
|
|
// └── Album/
|
|
// └── 01 - Track.mp3 ← has embedded picture (falls through to "embedded")
|
|
It("falls through to embedded artwork", func() {
|
|
conf.Server.CoverArtPriority = "external, embedded"
|
|
setLayout(fstest.MapFS{
|
|
"Artist/Album/01 - Track.mp3": trackFile(1, "Track", map[string]any{"has_picture": "true"}),
|
|
})
|
|
scan()
|
|
replaceWithRealMP3("Artist/Album/01 - Track.mp3")
|
|
|
|
al := firstAlbum()
|
|
Expect(readArtwork(al.CoverArtID())).To(Equal(embeddedArtBytes))
|
|
})
|
|
})
|
|
|
|
When("the only cover file uses uppercase extension and a different case in its name", func() {
|
|
// Artist/
|
|
// └── Album/
|
|
// ├── 01 - Track.mp3
|
|
// └── Cover.JPG ← matched case-insensitively by cover.*
|
|
It("matches case-insensitively against cover.*", func() {
|
|
conf.Server.CoverArtPriority = "cover.*, folder.*"
|
|
setLayout(fstest.MapFS{
|
|
"Artist/Album/01 - Track.mp3": trackFile(1, "Track"),
|
|
"Artist/Album/Cover.JPG": imageFile("case-insensitive"),
|
|
})
|
|
scan()
|
|
|
|
al := firstAlbum()
|
|
Expect(readArtwork(al.CoverArtID())).To(Equal(imageBytes("case-insensitive")))
|
|
})
|
|
})
|
|
|
|
When("two cover files have basenames that tie under the natural-sort tiebreaker", func() {
|
|
// Artist/
|
|
// └── Album/
|
|
// ├── 01 - Track.mp3
|
|
// ├── cover.jpg ← wins (no numeric suffix)
|
|
// └── cover.1.jpg
|
|
It("prefers the file without a numeric suffix", func() {
|
|
conf.Server.CoverArtPriority = "cover.*"
|
|
setLayout(fstest.MapFS{
|
|
"Artist/Album/01 - Track.mp3": trackFile(1, "Track"),
|
|
"Artist/Album/cover.jpg": imageFile("primary"),
|
|
"Artist/Album/cover.1.jpg": imageFile("secondary"),
|
|
})
|
|
scan()
|
|
|
|
al := firstAlbum()
|
|
Expect(readArtwork(al.CoverArtID())).To(Equal(imageBytes("primary")))
|
|
})
|
|
})
|
|
|
|
When("the album has no cover and CoverArtPriority lists only file patterns", func() {
|
|
// Artist/
|
|
// └── Album/
|
|
// └── 01 - Track.mp3 (no image files — returns ErrUnavailable)
|
|
It("returns ErrUnavailable", func() {
|
|
conf.Server.CoverArtPriority = "cover.*, folder.*"
|
|
setLayout(fstest.MapFS{
|
|
"Artist/Album/01 - Track.mp3": trackFile(1, "Track"),
|
|
})
|
|
scan()
|
|
|
|
al := firstAlbum()
|
|
_, err := readArtworkOrErr(model.NewArtworkID(model.KindAlbumArtwork, al.ID, &al.UpdatedAt))
|
|
Expect(err).To(HaveOccurred())
|
|
})
|
|
})
|
|
|
|
// Doc scenarios from:
|
|
// https://www.navidrome.org/docs/usage/library/artwork/#albums
|
|
// Default CoverArtPriority is "cover.*, folder.*, front.*, embedded, external".
|
|
When("only folder.jpg is present (cover.* and front.* missing)", func() {
|
|
// Artist/
|
|
// └── Album/
|
|
// ├── 01 - Track.mp3
|
|
// └── folder.jpg ← matched by folder.*
|
|
It("falls through to folder.jpg", func() {
|
|
conf.Server.CoverArtPriority = defaultCoverPriority
|
|
setLayout(fstest.MapFS{
|
|
"Artist/Album/01 - Track.mp3": trackFile(1, "Track"),
|
|
"Artist/Album/folder.jpg": imageFile("folder"),
|
|
})
|
|
scan()
|
|
|
|
al := firstAlbum()
|
|
Expect(readArtwork(al.CoverArtID())).To(Equal(imageBytes("folder")))
|
|
})
|
|
})
|
|
|
|
When("only front.jpg is present (cover.* and folder.* missing)", func() {
|
|
// Artist/
|
|
// └── Album/
|
|
// ├── 01 - Track.mp3
|
|
// └── front.jpg ← matched by front.*
|
|
It("falls through to front.jpg", func() {
|
|
conf.Server.CoverArtPriority = defaultCoverPriority
|
|
setLayout(fstest.MapFS{
|
|
"Artist/Album/01 - Track.mp3": trackFile(1, "Track"),
|
|
"Artist/Album/front.jpg": imageFile("front"),
|
|
})
|
|
scan()
|
|
|
|
al := firstAlbum()
|
|
Expect(readArtwork(al.CoverArtID())).To(Equal(imageBytes("front")))
|
|
})
|
|
})
|
|
|
|
When("cover.*, folder.*, and front.* all exist in the same folder", func() {
|
|
// Artist/
|
|
// └── Album/
|
|
// ├── 01 - Track.mp3
|
|
// ├── cover.jpg ← wins (cover.* is first in priority)
|
|
// ├── folder.jpg
|
|
// └── front.jpg
|
|
It("prefers cover.* (first in CoverArtPriority)", func() {
|
|
conf.Server.CoverArtPriority = defaultCoverPriority
|
|
setLayout(fstest.MapFS{
|
|
"Artist/Album/01 - Track.mp3": trackFile(1, "Track"),
|
|
"Artist/Album/cover.jpg": imageFile("cover"),
|
|
"Artist/Album/folder.jpg": imageFile("folder"),
|
|
"Artist/Album/front.jpg": imageFile("front"),
|
|
})
|
|
scan()
|
|
|
|
al := firstAlbum()
|
|
Expect(readArtwork(al.CoverArtID())).To(Equal(imageBytes("cover")))
|
|
})
|
|
})
|
|
|
|
When("only folder.* and front.* exist (priority order check)", func() {
|
|
// Artist/
|
|
// └── Album/
|
|
// ├── 01 - Track.mp3
|
|
// ├── folder.jpg ← wins (folder.* comes before front.*)
|
|
// └── front.jpg
|
|
It("prefers folder.* over front.*", func() {
|
|
conf.Server.CoverArtPriority = defaultCoverPriority
|
|
setLayout(fstest.MapFS{
|
|
"Artist/Album/01 - Track.mp3": trackFile(1, "Track"),
|
|
"Artist/Album/folder.jpg": imageFile("folder"),
|
|
"Artist/Album/front.jpg": imageFile("front"),
|
|
})
|
|
scan()
|
|
|
|
al := firstAlbum()
|
|
Expect(readArtwork(al.CoverArtID())).To(Equal(imageBytes("folder")))
|
|
})
|
|
})
|
|
|
|
When("three cover files tie by basename and differ only by numeric suffix", func() {
|
|
// Artist/
|
|
// └── Album/
|
|
// ├── 01 - Track.mp3
|
|
// ├── cover.jpg ← wins (no numeric suffix)
|
|
// ├── cover.1.jpg
|
|
// └── cover.2.jpg
|
|
It("selects the unsuffixed file first regardless of numeric-suffix order", func() {
|
|
conf.Server.CoverArtPriority = "cover.*"
|
|
setLayout(fstest.MapFS{
|
|
"Artist/Album/01 - Track.mp3": trackFile(1, "Track"),
|
|
"Artist/Album/cover.2.jpg": imageFile("second"),
|
|
"Artist/Album/cover.jpg": imageFile("primary"),
|
|
"Artist/Album/cover.1.jpg": imageFile("first"),
|
|
})
|
|
scan()
|
|
|
|
al := firstAlbum()
|
|
Expect(readArtwork(al.CoverArtID())).To(Equal(imageBytes("primary")))
|
|
})
|
|
})
|
|
|
|
When("CoverArtPriority contains an unknown pattern before a matching one", func() {
|
|
// Artist/
|
|
// └── Album/
|
|
// ├── 01 - Track.mp3
|
|
// └── cover.jpg ← wins (unknown "bogus.*" is skipped)
|
|
It("skips the unknown pattern and falls through to the matching one", func() {
|
|
conf.Server.CoverArtPriority = "bogus.*, cover.*"
|
|
setLayout(fstest.MapFS{
|
|
"Artist/Album/01 - Track.mp3": trackFile(1, "Track"),
|
|
"Artist/Album/cover.jpg": imageFile("cover"),
|
|
})
|
|
scan()
|
|
|
|
al := firstAlbum()
|
|
Expect(readArtwork(al.CoverArtID())).To(Equal(imageBytes("cover")))
|
|
})
|
|
})
|
|
|
|
// Regression introduced in v0.62.0 (#5451 + #5457): the parent-folder
|
|
// fallback can pick up images from the ARTIST folder, serving the artist
|
|
// thumbnail as album art for any album without its own image files.
|
|
When("an album has no images and the artist folder has folder.jpg", func() {
|
|
// Artist/
|
|
// ├── folder.jpg ← artist thumbnail, must NOT become album art
|
|
// ├── Album A/
|
|
// │ └── 01 - Track.mp3 (no images)
|
|
// └── Album B/
|
|
// ├── 01 - Track.mp3
|
|
// └── cover.jpg
|
|
It("does not use the artist image as album art", func() {
|
|
conf.Server.CoverArtPriority = defaultCoverPriority
|
|
setLayout(fstest.MapFS{
|
|
"Artist/folder.jpg": imageFile("artist-thumbnail"),
|
|
"Artist/Album A/01 - Track.mp3": trackFile(1, "Track A", map[string]any{"album": "Album A", "albumartist": "Artist"}),
|
|
"Artist/Album B/01 - Track.mp3": trackFile(1, "Track B", map[string]any{"album": "Album B", "albumartist": "Artist"}),
|
|
"Artist/Album B/cover.jpg": imageFile("album-b"),
|
|
})
|
|
scan()
|
|
|
|
alA := albumByName("Album A")
|
|
_, err := readArtworkOrErr(alA.CoverArtID())
|
|
Expect(err).To(HaveOccurred(),
|
|
"Album A has no images of its own, so it must fall through to the placeholder "+
|
|
"instead of inheriting the artist folder's folder.jpg")
|
|
|
|
alB := albumByName("Album B")
|
|
Expect(readArtwork(alB.CoverArtID())).To(Equal(imageBytes("album-b")))
|
|
})
|
|
})
|
|
|
|
When("a single-disc album is spread across sibling folders under the artist folder", func() {
|
|
// Artist/
|
|
// ├── folder.jpg ← artist thumbnail, must NOT become album art
|
|
// ├── Album A/
|
|
// │ └── 01 - Track.mp3 (album: "Album A")
|
|
// ├── Album A bonus/
|
|
// │ └── 02 - Track.mp3 (album: "Album A" — same album, second folder)
|
|
// └── Album B/
|
|
// ├── 01 - Track.mp3
|
|
// └── cover.jpg
|
|
It("does not use the artist image as album art for the spread album", func() {
|
|
conf.Server.CoverArtPriority = defaultCoverPriority
|
|
setLayout(fstest.MapFS{
|
|
"Artist/folder.jpg": imageFile("artist-thumbnail"),
|
|
"Artist/Album A/01 - Track.mp3": trackFile(1, "Track A1", map[string]any{"album": "Album A", "albumartist": "Artist"}),
|
|
"Artist/Album A bonus/02 - Track.mp3": trackFile(2, "Track A2", map[string]any{"album": "Album A", "albumartist": "Artist"}),
|
|
"Artist/Album B/01 - Track.mp3": trackFile(1, "Track B", map[string]any{"album": "Album B", "albumartist": "Artist"}),
|
|
"Artist/Album B/cover.jpg": imageFile("album-b"),
|
|
})
|
|
scan()
|
|
|
|
alA := albumByName("Album A")
|
|
Expect(alA.FolderIDs).To(HaveLen(2),
|
|
"sanity check: scanner should treat the two sibling folders as one spread album")
|
|
_, err := readArtworkOrErr(alA.CoverArtID())
|
|
Expect(err).To(HaveOccurred(),
|
|
"the spread album has no images of its own, so it must fall through to the "+
|
|
"placeholder instead of inheriting the artist folder's folder.jpg")
|
|
})
|
|
})
|
|
|
|
When("a spread album has its own front.jpg but the artist folder has cover.jpg", func() {
|
|
// Artist/
|
|
// ├── cover.jpg ← artist image; matches cover.* (first pattern),
|
|
// │ must NOT shadow the album's own front.jpg
|
|
// ├── Album A/
|
|
// │ ├── 01 - Track.mp3 (album: "Album A")
|
|
// │ └── front.jpg ← should win
|
|
// ├── Album A bonus/
|
|
// │ └── 02 - Track.mp3 (album: "Album A")
|
|
// └── Album B/
|
|
// └── 01 - Track.mp3
|
|
It("prefers the album's own art over the artist image", func() {
|
|
conf.Server.CoverArtPriority = defaultCoverPriority
|
|
setLayout(fstest.MapFS{
|
|
"Artist/cover.jpg": imageFile("artist-image"),
|
|
"Artist/Album A/01 - Track.mp3": trackFile(1, "Track A1", map[string]any{"album": "Album A", "albumartist": "Artist"}),
|
|
"Artist/Album A/front.jpg": imageFile("album-a-front"),
|
|
"Artist/Album A bonus/02 - Track.mp3": trackFile(2, "Track A2", map[string]any{"album": "Album A", "albumartist": "Artist"}),
|
|
"Artist/Album B/01 - Track.mp3": trackFile(1, "Track B", map[string]any{"album": "Album B", "albumartist": "Artist"}),
|
|
})
|
|
scan()
|
|
|
|
alA := albumByName("Album A")
|
|
Expect(alA.FolderIDs).To(HaveLen(2),
|
|
"sanity check: scanner should treat the two sibling folders as one spread album")
|
|
Expect(readArtwork(alA.CoverArtID())).To(Equal(imageBytes("album-a-front")))
|
|
})
|
|
})
|
|
|
|
When("embedded is first in CoverArtPriority but the track has no embedded art", func() {
|
|
// Artist/
|
|
// └── Album/
|
|
// ├── 01 - Track.mp3 (no embedded picture)
|
|
// └── cover.jpg ← wins (embedded skipped, falls through)
|
|
It("falls through to the next priority entry", func() {
|
|
conf.Server.CoverArtPriority = "embedded, cover.*"
|
|
setLayout(fstest.MapFS{
|
|
"Artist/Album/01 - Track.mp3": trackFile(1, "Track"),
|
|
"Artist/Album/cover.jpg": imageFile("cover"),
|
|
})
|
|
scan()
|
|
|
|
al := firstAlbum()
|
|
Expect(readArtwork(al.CoverArtID())).To(Equal(imageBytes("cover")))
|
|
})
|
|
})
|
|
})
|