fix media playback and library workflows

This commit is contained in:
ShukeBta
2026-06-21 12:53:52 +08:00
parent ada41e7078
commit efca3cbe69
26 changed files with 921 additions and 81 deletions
+96
View File
@@ -103,6 +103,102 @@ func TestEmbyItemsExposeSeriesSeasonEpisodeHierarchy(t *testing.T) {
}
}
func TestEmbyItemsKeepSpecialsInSeasonZero(t *testing.T) {
svc := newTestEmbyService(t)
lib := model.Library{Name: "番剧", Path: `F:\downloads\日番`, Type: "anime", Enabled: true}
if err := svc.repo.Library.Create(t.Context(), &lib); err != nil {
t.Fatalf("create library: %v", err)
}
media := model.Media{
Base: model.Base{ID: "sp-1"},
LibraryID: lib.ID,
Title: "间谍过家家",
Path: `F:\downloads\日番\间谍过家家\Specials\间谍过家家 - S00E01.mkv`,
PosterURL: `F:\episode-still.jpg`,
SeasonNum: 0,
EpisodeNum: 1,
}
if err := svc.repo.DB.Create(&media).Error; err != nil {
t.Fatalf("create media: %v", err)
}
root, err := svc.Items(t.Context(), ItemsParams{ParentID: lib.ID, Limit: 50})
if err != nil {
t.Fatalf("library items: %v", err)
}
rootItems := root["Items"].([]map[string]any)
if len(rootItems) != 1 || rootItems[0]["Type"] != "Series" {
t.Fatalf("expected one series card, got %#v", rootItems)
}
seasons, err := svc.Items(t.Context(), ItemsParams{ParentID: rootItems[0]["Id"].(string), Limit: 50})
if err != nil {
t.Fatalf("series seasons: %v", err)
}
seasonItems := seasons["Items"].([]map[string]any)
if len(seasonItems) != 1 || seasonItems[0]["Type"] != "Season" || seasonItems[0]["IndexNumber"] != 0 || seasonItems[0]["Name"] != "特别篇" {
t.Fatalf("specials should be exposed as season zero: %#v", seasonItems)
}
episodes, err := svc.Items(t.Context(), ItemsParams{ParentID: seasonItems[0]["Id"].(string), IncludeItemTypes: []string{"Episode"}, Recursive: true, Limit: 50})
if err != nil {
t.Fatalf("special episodes: %v", err)
}
episodeItems := episodes["Items"].([]map[string]any)
if len(episodeItems) != 1 {
t.Fatalf("expected one special episode, got %#v", episodeItems)
}
if episodeItems[0]["ParentIndexNumber"] != 0 || episodeItems[0]["SeasonId"] != seasonItems[0]["Id"] || episodeItems[0]["ParentId"] != seasonItems[0]["Id"] {
t.Fatalf("special episode linked to wrong season: %#v season=%#v", episodeItems[0], seasonItems[0])
}
if tags, ok := episodeItems[0]["ImageTags"].(map[string]string); !ok || tags["Primary"] != "sp-1" {
t.Fatalf("episode still should be exposed as Primary image: %#v", episodeItems[0]["ImageTags"])
}
}
func TestEmbyEpisodeStillIsPrimaryImageNotArt(t *testing.T) {
svc := newTestEmbyService(t)
lib := model.Library{Name: "剧集", Path: `/media/tv`, Type: "tv", Enabled: true}
if err := svc.repo.Library.Create(t.Context(), &lib); err != nil {
t.Fatalf("create library: %v", err)
}
media := model.Media{
Base: model.Base{ID: "ep-still"},
LibraryID: lib.ID,
Title: "间谍过家家",
Path: `/media/tv/间谍过家家/Season 02/间谍过家家 - S02E01.mkv`,
PosterURL: `https://image.example/show-poster.jpg`,
BackdropURL: `https://image.example/episode-still.jpg`,
SeasonNum: 2,
EpisodeNum: 1,
}
if err := svc.repo.DB.Create(&media).Error; err != nil {
t.Fatalf("create media: %v", err)
}
item := svc.itemPayload(t.Context(), &media, false, 0)
if tags, ok := item["ImageTags"].(map[string]string); !ok || tags["Primary"] != "ep-still" {
t.Fatalf("episode should expose a primary image tag: %#v", item["ImageTags"])
}
if tags, ok := item["BackdropImageTags"].([]string); !ok || len(tags) != 0 {
t.Fatalf("episode still must not be exposed as art/backdrop: %#v", item["BackdropImageTags"])
}
primary, err := svc.ImageURL(t.Context(), "ep-still", "Primary")
if err != nil {
t.Fatalf("primary image url: %v", err)
}
if primary != media.BackdropURL {
t.Fatalf("episode Primary image = %q, want still %q", primary, media.BackdropURL)
}
art, err := svc.ImageURL(t.Context(), "ep-still", "Art")
if err != nil {
t.Fatalf("art image url: %v", err)
}
if art == media.BackdropURL {
t.Fatalf("episode still must not be returned as Art image")
}
}
func TestEmbyLatestItemsIncludesMergedCloudMovieLibrary(t *testing.T) {
svc := newTestEmbyService(t)
local := model.Library{Name: "国产电影", Path: `/media/国产电影`, Type: "movie", Enabled: true}