Compare commits

...

1 Commits

Author SHA1 Message Date
truewhile fd54d42db7 优化 2026-09-14 00:02:46 +08:00
3 changed files with 84 additions and 2 deletions
@@ -101,7 +101,21 @@ func localBackdropCandidates(mediaPath string) []string {
seen[key] = struct{}{}
names = append(names, name)
}
_, episode := ParseEpisode(mediaPath)
for _, base := range mediaSidecarBaseVariants(mediaPath) {
// Jellyfin/Emby commonly stores an episode still beside the video as
// "<episode>-thumb.jpg" or "<episode>.jpg". Episode-specific artwork
// must win over a series-level fanart.jpg in the parent folder,
// otherwise a rescan flattens every episode to the same backdrop.
add(base + "-thumb")
add(base + ".thumb")
if episode > 0 {
add(base + "-still")
add(base + ".still")
add(base + "-scene")
add(base + ".scene")
add(base)
}
add(base + "-fanart")
add(base + ".fanart")
add(base + "-backdrop")
+2 -2
View File
@@ -236,8 +236,8 @@ func TestReadLocalVarietyMetadataUsesLocalArtwork(t *testing.T) {
if got.PosterURL != showPoster {
t.Fatalf("PosterURL = %q, want show poster %q, not episode thumb %q", got.PosterURL, showPoster, episodeThumb)
}
if got.BackdropURL != backdrop {
t.Fatalf("BackdropURL = %q, want %q", got.BackdropURL, backdrop)
if got.BackdropURL != episodeThumb {
t.Fatalf("BackdropURL = %q, want episode thumb %q, not series backdrop %q", got.BackdropURL, episodeThumb, backdrop)
}
}
@@ -268,6 +268,74 @@ func TestScanLibraryRefreshesStaleNoMatchDerivedMetadata(t *testing.T) {
}
}
func TestScanLibraryPrefersEpisodeArtworkOverSeriesFanart(t *testing.T) {
for _, tc := range []struct {
name string
episodeArt string
}{
{name: "thumb suffix", episodeArt: "Show - S01E01-thumb.jpg"},
{name: "video stem", episodeArt: "Show - S01E01.jpg"},
} {
t.Run(tc.name, func(t *testing.T) {
root := t.TempDir()
seasonDir := filepath.Join(root, "Show", "Season 01")
if err := os.MkdirAll(seasonDir, 0o755); err != nil {
t.Fatal(err)
}
mediaPath := filepath.Join(seasonDir, "Show - S01E01.mkv")
if err := os.WriteFile(mediaPath, []byte("x"), 0o644); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(nfoPath(mediaPath), []byte(
`<episodedetails><title>第一集</title><season>1</season><episode>1</episode></episodedetails>`,
), 0o644); err != nil {
t.Fatal(err)
}
episodeArt := filepath.Join(seasonDir, tc.episodeArt)
if err := os.WriteFile(episodeArt, []byte("episode still"), 0o644); err != nil {
t.Fatal(err)
}
seriesFanart := filepath.Join(root, "Show", "fanart.jpg")
if err := os.WriteFile(seriesFanart, []byte("series backdrop"), 0o644); err != nil {
t.Fatal(err)
}
db := newServiceTestDB(t, &model.Library{}, &model.Media{}, &model.Setting{})
repos := repository.New(db)
lib := model.Library{Name: "TV", Path: root, Type: "tv", Enabled: true}
if err := repos.Library.Create(t.Context(), &lib); err != nil {
t.Fatal(err)
}
existing := model.Media{
LibraryID: lib.ID,
Title: "Show",
Path: mediaPath,
SizeBytes: 1,
SeasonNum: 1,
EpisodeNum: 1,
BackdropURL: "https://image.example/episode-still.jpg",
ScrapeStatus: "matched",
}
if err := repos.DB.Create(&existing).Error; err != nil {
t.Fatal(err)
}
scanner := NewScannerService(&config.Config{}, zap.NewNop(), repos, NewHub(zap.NewNop()), nil, nil)
if _, err := scanner.ScanLibrary(t.Context(), lib.ID); err != nil {
t.Fatal(err)
}
var media model.Media
if err := db.First(&media, "id = ?", existing.ID).Error; err != nil {
t.Fatal(err)
}
if media.BackdropURL != episodeArt {
t.Fatalf("backdrop_url = %q, want episode artwork %q (series fanart %q must not win)", media.BackdropURL, episodeArt, seriesFanart)
}
})
}
}
func TestScanLibraryPrunesMissingMedia(t *testing.T) {
root := t.TempDir()
mediaPath := filepath.Join(root, "Show.S02E03.mkv")