mirror of
https://github.com/truewhile/MeBox.git
synced 2026-09-28 03:06:38 +08:00
修复 Emby 大海报在剧集图缓存被淘汰后显示占位图
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -32,17 +32,14 @@ func (e *EmbyService) ImageURL(ctx context.Context, id, imageType string) (strin
|
||||
}
|
||||
return backdrop
|
||||
}
|
||||
if strings.HasPrefix(id, embyVirtualSeasonPrefix) {
|
||||
if strings.HasPrefix(id, embyVirtualSeasonPrefix) || strings.HasPrefix(id, embyVirtualSeriesPrefix) {
|
||||
if raw, ok := e.cachedArtworkURL(id, imageType); ok {
|
||||
return raw, nil
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
if strings.HasPrefix(id, embyVirtualSeriesPrefix) {
|
||||
if raw, ok := e.cachedArtworkURL(id, imageType); ok {
|
||||
return raw, nil
|
||||
}
|
||||
return "", nil
|
||||
// Latest JSON can outlive (or be served after) the in-memory artwork
|
||||
// map. Rebuild from the library instead of handing the client a 1x1
|
||||
// placeholder that it then caches as a successful image.
|
||||
return e.resolveVirtualArtwork(ctx, id, imageType, pick)
|
||||
}
|
||||
m, err := e.repo.Media.FindByID(ctx, id)
|
||||
if err == nil && m != nil {
|
||||
@@ -71,6 +68,21 @@ func (e *EmbyService) ImageURL(ctx context.Context, id, imageType string) (strin
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (e *EmbyService) resolveVirtualArtwork(ctx context.Context, id, imageType string, pick func(primary, backdrop string) string) (string, error) {
|
||||
if strings.HasPrefix(id, embyVirtualSeasonPrefix) {
|
||||
season, ok, err := e.findSeasonGroup(ctx, id, "")
|
||||
if err != nil || !ok {
|
||||
return "", err
|
||||
}
|
||||
return pick(season.Series.PosterURL, season.Series.BackdropURL), nil
|
||||
}
|
||||
series, ok, err := e.findSeriesGroup(ctx, id, "")
|
||||
if err != nil || !ok {
|
||||
return "", err
|
||||
}
|
||||
return pick(series.PosterURL, series.BackdropURL), nil
|
||||
}
|
||||
|
||||
// imageInfoTypes 是 GET /Items/{Id}/Images 会报告的图片类型。只列 MeBox
|
||||
// 真正存储的两类:ImageURL 对 Thumb / Logo / Banner 等其余类型会回退到
|
||||
// 主图,若一并列出会让客户端以为存在这些图并去请求,实际拿到的却是主图。
|
||||
|
||||
@@ -117,6 +117,18 @@ const (
|
||||
embyVirtualCacheTTL = 10 * time.Minute
|
||||
embyVisibilityCacheTTL = 30 * time.Second
|
||||
embySeriesGroupingLimit = maxMediaSearchLimit
|
||||
// Virtual artwork used to be wiped entirely once the in-memory map crossed
|
||||
// a few thousand entries. A homepage refresh asks Latest for every library
|
||||
// at once, so that wipe dropped the series the client was about to paint.
|
||||
// Caps are sized for that fan-out; overflow evicts the oldest entries only.
|
||||
embyVirtualSeriesCap = 8000
|
||||
embyVirtualSeasonCap = 16000
|
||||
embyVirtualArtworkCap = 24000
|
||||
// Clients that already cached the 1x1 placeholder treat a stable tag as
|
||||
// immutable. Virtual ids are the ones that served that placeholder, so
|
||||
// only those tags get a suffix that forces a refetch.
|
||||
embyVirtualPrimaryTagSuffix = "-p2"
|
||||
embyVirtualBackdropTagSuffix = "-bd2"
|
||||
)
|
||||
|
||||
var (
|
||||
|
||||
@@ -15,7 +15,8 @@ type embyItemsCacheValue struct {
|
||||
}
|
||||
|
||||
type embyLatestCacheValue struct {
|
||||
Items []map[string]any `json:"items"`
|
||||
Items []map[string]any `json:"items"`
|
||||
Artwork map[string]embyArtworkRef `json:"artwork,omitempty"`
|
||||
}
|
||||
|
||||
func (e *EmbyService) embyItemsCacheKey(kind string, p ItemsParams) string {
|
||||
@@ -43,7 +44,8 @@ func (e *EmbyService) embyItemsCacheKey(kind string, p ItemsParams) string {
|
||||
}
|
||||
|
||||
func (e *EmbyService) embyLatestCacheKey(userID, parentID string, limit int) string {
|
||||
sum := sha256.Sum256([]byte(strings.Join([]string{"latest", userID, parentID, strconv.Itoa(limit)}, "|")))
|
||||
// v2: payload tags for virtual artwork changed so clients drop cached placeholders.
|
||||
sum := sha256.Sum256([]byte(strings.Join([]string{"latest-v2", userID, parentID, strconv.Itoa(limit)}, "|")))
|
||||
return "media:emby:" + hex.EncodeToString(sum[:])
|
||||
}
|
||||
|
||||
|
||||
@@ -132,15 +132,16 @@ func (e *EmbyService) LatestItems(ctx context.Context, userID, parentID string,
|
||||
cacheKey := e.embyLatestCacheKey(userID, parentID, limit)
|
||||
var cached embyLatestCacheValue
|
||||
if e.cache != nil && e.cache.GetJSON(ctx, cacheKey, &cached) {
|
||||
e.rememberArtworkRefs(cached.Artwork)
|
||||
return cached.Items, nil
|
||||
}
|
||||
q := e.repo.DB.WithContext(ctx).Model(&model.Media{}).Where("deleted_at IS NULL")
|
||||
q = e.applyUserMediaVisibility(ctx, q, userID)
|
||||
if parentID != "" {
|
||||
if episodic, err := e.libraryIsEpisodic(ctx, parentID); err == nil && episodic {
|
||||
out, err := e.latestSeriesItemsForLibrary(ctx, userID, parentID, limit)
|
||||
out, artwork, err := e.latestSeriesItemsForLibrary(ctx, userID, parentID, limit)
|
||||
if err == nil && e.cache != nil {
|
||||
e.cache.SetJSON(ctx, cacheKey, embyLatestCacheValue{Items: out}, time.Duration(e.embyLatestCacheTTLSeconds())*time.Second)
|
||||
e.cache.SetJSON(ctx, cacheKey, embyLatestCacheValue{Items: out, Artwork: artwork}, time.Duration(e.embyLatestCacheTTLSeconds())*time.Second)
|
||||
}
|
||||
return out, err
|
||||
}
|
||||
@@ -171,7 +172,7 @@ func (e *EmbyService) LatestItems(ctx context.Context, userID, parentID string,
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (e *EmbyService) latestSeriesItemsForLibrary(ctx context.Context, userID, libraryID string, limit int) ([]map[string]any, error) {
|
||||
func (e *EmbyService) latestSeriesItemsForLibrary(ctx context.Context, userID, libraryID string, limit int) ([]map[string]any, map[string]embyArtworkRef, error) {
|
||||
if limit <= 0 || limit > 100 {
|
||||
limit = 20
|
||||
}
|
||||
@@ -180,7 +181,7 @@ func (e *EmbyService) latestSeriesItemsForLibrary(ctx context.Context, userID, l
|
||||
q = e.applyUserMediaVisibility(ctx, q, userID)
|
||||
var rows []model.Media
|
||||
if err := q.Order(mediaReleaseOrderSQL(true)).Limit(embySeriesGroupingLimit).Find(&rows).Error; err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
groups := e.seriesGroupsFromMedia(ctx, rows)
|
||||
sortSeriesGroups(groups, ItemsParams{SortBy: "premieredate", SortOrder: "Descending"})
|
||||
@@ -191,7 +192,7 @@ func (e *EmbyService) latestSeriesItemsForLibrary(ctx context.Context, userID, l
|
||||
for _, group := range groups {
|
||||
items = append(items, e.seriesPayload(group))
|
||||
}
|
||||
return items, nil
|
||||
return items, e.artworkRefsForSeriesGroups(groups), nil
|
||||
}
|
||||
|
||||
// ResumeItems 列出有未完成播放进度的媒体。
|
||||
@@ -519,11 +520,11 @@ func (e *EmbyService) itemPayload(ctx context.Context, m *model.Media, fav bool,
|
||||
if seriesID != "" {
|
||||
if sEntry, ok, _ := e.payloadSeriesEntry(ctx, seriesID); ok {
|
||||
if sEntry.posterURL != "" {
|
||||
item["SeriesPrimaryImageTag"] = seriesID
|
||||
item["SeriesPrimaryImageTag"] = embyVirtualImageTag(seriesID, embyVirtualPrimaryTagSuffix)
|
||||
}
|
||||
if len(backdropTags) == 0 && sEntry.backdropURL != "" {
|
||||
if len(backdropTags) == 0 && (sEntry.backdropURL != "" || sEntry.posterURL != "") {
|
||||
item["ParentBackdropItemId"] = seriesID
|
||||
item["ParentBackdropImageTags"] = []string{seriesID + "-bd"}
|
||||
item["ParentBackdropImageTags"] = []string{embyVirtualImageTag(seriesID, embyVirtualBackdropTagSuffix)}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
@@ -37,11 +38,7 @@ func (e *EmbyService) rememberSeriesGroup(group embySeriesGroup) {
|
||||
if e.virtualArtwork == nil {
|
||||
e.virtualArtwork = make(map[string]embyArtworkCacheEntry)
|
||||
}
|
||||
if len(e.virtualSeries) > 2000 || len(e.virtualSeasons) > 5000 || len(e.virtualArtwork) > 7000 {
|
||||
e.virtualSeries = make(map[string]embySeriesCacheEntry)
|
||||
e.virtualSeasons = make(map[string]embySeasonCacheEntry)
|
||||
e.virtualArtwork = make(map[string]embyArtworkCacheEntry)
|
||||
}
|
||||
e.trimVirtualCachesLocked(time.Now())
|
||||
e.virtualSeries[group.ID] = embySeriesCacheEntry{group: group, expiresAt: expiresAt}
|
||||
e.virtualArtwork[group.ID] = embyArtworkCacheEntry{primary: group.PosterURL, backdrop: group.BackdropURL, expiresAt: expiresAt}
|
||||
e.virtualArtwork[group.ID+"-bd"] = embyArtworkCacheEntry{primary: group.PosterURL, backdrop: group.BackdropURL, expiresAt: expiresAt}
|
||||
@@ -65,6 +62,7 @@ func (e *EmbyService) rememberSeasonGroup(season embySeasonGroup) {
|
||||
if e.virtualArtwork == nil {
|
||||
e.virtualArtwork = make(map[string]embyArtworkCacheEntry)
|
||||
}
|
||||
e.trimVirtualCachesLocked(time.Now())
|
||||
e.virtualSeasons[season.ID] = embySeasonCacheEntry{season: season, expiresAt: expiresAt}
|
||||
e.virtualArtwork[season.ID] = embyArtworkCacheEntry{primary: season.Series.PosterURL, backdrop: season.Series.BackdropURL, expiresAt: expiresAt}
|
||||
e.virtualArtwork[season.ID+"-bd"] = embyArtworkCacheEntry{primary: season.Series.PosterURL, backdrop: season.Series.BackdropURL, expiresAt: expiresAt}
|
||||
@@ -135,3 +133,123 @@ func (e *EmbyService) cachedArtworkURL(id, imageType string) (string, bool) {
|
||||
}
|
||||
return entry.backdrop, entry.backdrop != ""
|
||||
}
|
||||
|
||||
// embyArtworkRef is the poster/backdrop pair stored next to a Latest payload
|
||||
// so a JSON-cache hit can refill the in-memory artwork map before the client
|
||||
// asks for the image.
|
||||
type embyArtworkRef struct {
|
||||
Primary string `json:"primary,omitempty"`
|
||||
Backdrop string `json:"backdrop,omitempty"`
|
||||
}
|
||||
|
||||
func (e *EmbyService) artworkRefsForSeriesGroups(groups []embySeriesGroup) map[string]embyArtworkRef {
|
||||
if e == nil || len(groups) == 0 {
|
||||
return nil
|
||||
}
|
||||
refs := make(map[string]embyArtworkRef, len(groups)*2)
|
||||
for _, group := range groups {
|
||||
if group.PosterURL == "" && group.BackdropURL == "" {
|
||||
continue
|
||||
}
|
||||
ref := embyArtworkRef{Primary: group.PosterURL, Backdrop: group.BackdropURL}
|
||||
refs[group.ID] = ref
|
||||
for _, season := range e.seasonsForSeries(group) {
|
||||
refs[season.ID] = ref
|
||||
}
|
||||
}
|
||||
if len(refs) == 0 {
|
||||
return nil
|
||||
}
|
||||
return refs
|
||||
}
|
||||
|
||||
func (e *EmbyService) rememberArtworkRefs(refs map[string]embyArtworkRef) {
|
||||
if e == nil || len(refs) == 0 {
|
||||
return
|
||||
}
|
||||
expiresAt := time.Now().Add(embyVirtualCacheTTL)
|
||||
e.virtualMu.Lock()
|
||||
defer e.virtualMu.Unlock()
|
||||
if e.virtualArtwork == nil {
|
||||
e.virtualArtwork = make(map[string]embyArtworkCacheEntry, len(refs))
|
||||
}
|
||||
e.trimVirtualArtworkLocked(time.Now())
|
||||
for id, ref := range refs {
|
||||
if strings.TrimSpace(id) == "" || (ref.Primary == "" && ref.Backdrop == "") {
|
||||
continue
|
||||
}
|
||||
e.virtualArtwork[id] = embyArtworkCacheEntry{primary: ref.Primary, backdrop: ref.Backdrop, expiresAt: expiresAt}
|
||||
}
|
||||
}
|
||||
|
||||
func (e *EmbyService) trimVirtualCachesLocked(now time.Time) {
|
||||
e.trimVirtualSeriesLocked(now)
|
||||
e.trimVirtualSeasonsLocked(now)
|
||||
e.trimVirtualArtworkLocked(now)
|
||||
}
|
||||
|
||||
func (e *EmbyService) trimVirtualSeriesLocked(now time.Time) {
|
||||
for id, entry := range e.virtualSeries {
|
||||
if now.After(entry.expiresAt) {
|
||||
delete(e.virtualSeries, id)
|
||||
}
|
||||
}
|
||||
evictOldest(e.virtualSeries, embyVirtualSeriesCap, func(entry embySeriesCacheEntry) time.Time {
|
||||
return entry.expiresAt
|
||||
})
|
||||
}
|
||||
|
||||
func (e *EmbyService) trimVirtualSeasonsLocked(now time.Time) {
|
||||
for id, entry := range e.virtualSeasons {
|
||||
if now.After(entry.expiresAt) {
|
||||
delete(e.virtualSeasons, id)
|
||||
}
|
||||
}
|
||||
evictOldest(e.virtualSeasons, embyVirtualSeasonCap, func(entry embySeasonCacheEntry) time.Time {
|
||||
return entry.expiresAt
|
||||
})
|
||||
}
|
||||
|
||||
func (e *EmbyService) trimVirtualArtworkLocked(now time.Time) {
|
||||
for id, entry := range e.virtualArtwork {
|
||||
if now.After(entry.expiresAt) {
|
||||
delete(e.virtualArtwork, id)
|
||||
}
|
||||
}
|
||||
evictOldest(e.virtualArtwork, embyVirtualArtworkCap, func(entry embyArtworkCacheEntry) time.Time {
|
||||
return entry.expiresAt
|
||||
})
|
||||
}
|
||||
|
||||
// evictOldest drops the soonest-expiring entries until the map is under cap.
|
||||
// It must not replace the map: a homepage refresh remembers many series at
|
||||
// once, and wiping the whole cache made the just-advertised backdrops 404
|
||||
// into a 1x1 placeholder.
|
||||
func evictOldest[T any](items map[string]T, cap int, expiresAt func(T) time.Time) {
|
||||
excess := len(items) - cap
|
||||
if cap <= 0 || excess <= 0 {
|
||||
return
|
||||
}
|
||||
type pair struct {
|
||||
id string
|
||||
at time.Time
|
||||
}
|
||||
ordered := make([]pair, 0, len(items))
|
||||
for id, entry := range items {
|
||||
ordered = append(ordered, pair{id: id, at: expiresAt(entry)})
|
||||
}
|
||||
sort.Slice(ordered, func(i, j int) bool { return ordered[i].at.Before(ordered[j].at) })
|
||||
if excess > len(ordered) {
|
||||
excess = len(ordered)
|
||||
}
|
||||
for i := 0; i < excess; i++ {
|
||||
delete(items, ordered[i].id)
|
||||
}
|
||||
}
|
||||
|
||||
func embyVirtualImageTag(id, suffix string) string {
|
||||
if strings.HasPrefix(id, embyVirtualSeriesPrefix) || strings.HasPrefix(id, embyVirtualSeasonPrefix) {
|
||||
return id + suffix
|
||||
}
|
||||
return id
|
||||
}
|
||||
|
||||
@@ -296,6 +296,71 @@ func TestEmbyVirtualSeriesArtworkUsesListCache(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestEmbyVirtualSeriesArtworkRebuildsAfterMemoryDrop(t *testing.T) {
|
||||
svc := newTestEmbyService(t)
|
||||
svc.cache = NewRuntimeCacheService(nil, nil)
|
||||
lib := model.Library{Name: "番剧", Path: `/media/anime`, 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: "ep-hero"},
|
||||
LibraryID: lib.ID,
|
||||
Title: "树海之魔",
|
||||
Path: `/media/anime/树海之魔/Season 01/树海之魔 - S01E01.mkv`,
|
||||
PosterURL: `/poster.jpg`,
|
||||
BackdropURL: `/backdrop.jpg`,
|
||||
SeasonNum: 1,
|
||||
EpisodeNum: 1,
|
||||
}
|
||||
if err := svc.repo.DB.Create(&media).Error; err != nil {
|
||||
t.Fatalf("create media: %v", err)
|
||||
}
|
||||
|
||||
items, err := svc.LatestItems(t.Context(), "", lib.ID, 5)
|
||||
if err != nil {
|
||||
t.Fatalf("latest items: %v", err)
|
||||
}
|
||||
if len(items) != 1 {
|
||||
t.Fatalf("latest len = %d, want 1", len(items))
|
||||
}
|
||||
seriesID, _ := items[0]["Id"].(string)
|
||||
tags, _ := items[0]["BackdropImageTags"].([]string)
|
||||
if seriesID == "" || len(tags) != 1 || tags[0] != seriesID+embyVirtualBackdropTagSuffix {
|
||||
t.Fatalf("hero item should advertise a cache-busted backdrop tag, got id=%q tags=%#v", seriesID, items[0]["BackdropImageTags"])
|
||||
}
|
||||
|
||||
svc.virtualMu.Lock()
|
||||
svc.virtualArtwork = nil
|
||||
svc.virtualSeries = nil
|
||||
svc.virtualSeasons = nil
|
||||
svc.virtualMu.Unlock()
|
||||
|
||||
backdrop, err := svc.ImageURL(t.Context(), seriesID, "Backdrop")
|
||||
if err != nil {
|
||||
t.Fatalf("backdrop after memory drop: %v", err)
|
||||
}
|
||||
if backdrop != "/backdrop.jpg" {
|
||||
t.Fatalf("backdrop = %q, want rebuilt backdrop", backdrop)
|
||||
}
|
||||
|
||||
svc.virtualMu.Lock()
|
||||
svc.virtualArtwork = nil
|
||||
svc.virtualMu.Unlock()
|
||||
if _, err := svc.LatestItems(t.Context(), "", lib.ID, 5); err != nil {
|
||||
t.Fatalf("cached latest: %v", err)
|
||||
}
|
||||
cancelled, cancel := context.WithCancel(t.Context())
|
||||
cancel()
|
||||
backdrop, err = svc.ImageURL(cancelled, seriesID, "Backdrop")
|
||||
if err != nil {
|
||||
t.Fatalf("backdrop from rewarmed cache: %v", err)
|
||||
}
|
||||
if backdrop != "/backdrop.jpg" {
|
||||
t.Fatalf("rewarmed backdrop = %q, want cached backdrop", backdrop)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEmbyCloudAnimeUsesSeriesNameFromChineseSeasonFolder(t *testing.T) {
|
||||
svc := newTestEmbyService(t)
|
||||
lib := model.Library{Name: "OpenList · 国漫", Path: `cloud://openlist/国漫`, Type: "anime", Enabled: true}
|
||||
@@ -429,13 +494,13 @@ func TestInferSeriesNameFromPath(t *testing.T) {
|
||||
want: "间谍过家家",
|
||||
},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
got := inferSeriesNameFromPath(tc.path)
|
||||
if got != tc.want {
|
||||
t.Errorf("inferSeriesNameFromPath(%q) = %q, want %q", tc.path, got, tc.want)
|
||||
}
|
||||
}
|
||||
for _, tc := range tests {
|
||||
got := inferSeriesNameFromPath(tc.path)
|
||||
if got != tc.want {
|
||||
t.Errorf("inferSeriesNameFromPath(%q) = %q, want %q", tc.path, got, tc.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestEmbySeriesSortByDateLastMediaAdded(t *testing.T) {
|
||||
svc := newTestEmbyService(t)
|
||||
|
||||
@@ -5,28 +5,28 @@ func (e *EmbyService) seriesPayload(group embySeriesGroup) map[string]any {
|
||||
imageTags := map[string]string{}
|
||||
backdropTags := []string{}
|
||||
if group.PosterURL != "" {
|
||||
imageTags["Primary"] = group.ID
|
||||
imageTags["Primary"] = embyVirtualImageTag(group.ID, embyVirtualPrimaryTagSuffix)
|
||||
}
|
||||
if group.BackdropURL != "" {
|
||||
backdropTags = append(backdropTags, group.ID+"-bd")
|
||||
if group.BackdropURL != "" || group.PosterURL != "" {
|
||||
backdropTags = append(backdropTags, embyVirtualImageTag(group.ID, embyVirtualBackdropTagSuffix))
|
||||
}
|
||||
lastMediaAdded := group.DateLastMediaAdded
|
||||
if lastMediaAdded.IsZero() {
|
||||
lastMediaAdded = group.CreatedAt
|
||||
}
|
||||
item := map[string]any{
|
||||
"Id": group.ID,
|
||||
"Name": group.Name,
|
||||
"ServerId": embyServerID,
|
||||
"Type": "Series",
|
||||
"MediaType": "Video",
|
||||
"IsFolder": true,
|
||||
"ParentId": group.LibraryID,
|
||||
"ProductionYear": group.Year,
|
||||
"Overview": group.Overview,
|
||||
"CommunityRating": group.Rating,
|
||||
"RecursiveItemCount": len(group.Episodes),
|
||||
"ChildCount": len(e.seasonsForSeries(group)),
|
||||
lastMediaAdded := group.DateLastMediaAdded
|
||||
if lastMediaAdded.IsZero() {
|
||||
lastMediaAdded = group.CreatedAt
|
||||
}
|
||||
item := map[string]any{
|
||||
"Id": group.ID,
|
||||
"Name": group.Name,
|
||||
"ServerId": embyServerID,
|
||||
"Type": "Series",
|
||||
"MediaType": "Video",
|
||||
"IsFolder": true,
|
||||
"ParentId": group.LibraryID,
|
||||
"ProductionYear": group.Year,
|
||||
"Overview": group.Overview,
|
||||
"CommunityRating": group.Rating,
|
||||
"RecursiveItemCount": len(group.Episodes),
|
||||
"ChildCount": len(e.seasonsForSeries(group)),
|
||||
"DateCreated": group.CreatedAt,
|
||||
"DateLastMediaAdded": lastMediaAdded,
|
||||
"ImageTags": imageTags,
|
||||
@@ -39,7 +39,7 @@ func (e *EmbyService) seriesPayload(group embySeriesGroup) map[string]any {
|
||||
"UserData": emptyUserData(),
|
||||
}
|
||||
if group.PosterURL != "" {
|
||||
item["PrimaryImageTag"] = group.ID
|
||||
item["PrimaryImageTag"] = embyVirtualImageTag(group.ID, embyVirtualPrimaryTagSuffix)
|
||||
}
|
||||
if premiered, ok := embyPremiereDate(group.ReleaseDate); ok {
|
||||
item["PremiereDate"] = premiered
|
||||
@@ -52,10 +52,10 @@ func (e *EmbyService) seasonPayload(season embySeasonGroup) map[string]any {
|
||||
imageTags := map[string]string{}
|
||||
backdropTags := []string{}
|
||||
if season.Series.PosterURL != "" {
|
||||
imageTags["Primary"] = season.ID
|
||||
imageTags["Primary"] = embyVirtualImageTag(season.ID, embyVirtualPrimaryTagSuffix)
|
||||
}
|
||||
if season.Series.BackdropURL != "" {
|
||||
backdropTags = append(backdropTags, season.ID+"-bd")
|
||||
if season.Series.BackdropURL != "" || season.Series.PosterURL != "" {
|
||||
backdropTags = append(backdropTags, embyVirtualImageTag(season.ID, embyVirtualBackdropTagSuffix))
|
||||
}
|
||||
item := map[string]any{
|
||||
"Id": season.ID,
|
||||
@@ -75,12 +75,12 @@ func (e *EmbyService) seasonPayload(season embySeasonGroup) map[string]any {
|
||||
"UserData": emptyUserData(),
|
||||
}
|
||||
if season.Series.PosterURL != "" {
|
||||
item["PrimaryImageTag"] = season.ID
|
||||
item["SeriesPrimaryImageTag"] = season.Series.ID
|
||||
item["PrimaryImageTag"] = embyVirtualImageTag(season.ID, embyVirtualPrimaryTagSuffix)
|
||||
item["SeriesPrimaryImageTag"] = embyVirtualImageTag(season.Series.ID, embyVirtualPrimaryTagSuffix)
|
||||
}
|
||||
if season.Series.BackdropURL != "" {
|
||||
if season.Series.BackdropURL != "" || season.Series.PosterURL != "" {
|
||||
item["ParentBackdropItemId"] = season.Series.ID
|
||||
item["ParentBackdropImageTags"] = []string{season.Series.ID + "-bd"}
|
||||
item["ParentBackdropImageTags"] = []string{embyVirtualImageTag(season.Series.ID, embyVirtualBackdropTagSuffix)}
|
||||
}
|
||||
return item
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user