This commit is contained in:
truewhile
2026-09-27 13:04:28 +08:00
parent 594166f696
commit cb539afd2a
5 changed files with 217 additions and 50 deletions
+4
View File
@@ -155,6 +155,10 @@ func (e *EmbyService) episodeItems(ctx context.Context, rows []model.Media, p It
}
return rows[i].CreatedAt.Before(rows[j].CreatedAt)
})
// 先折叠同集的多个版本再统计总数与分页,与 payloadsForMedia 内部保持同一步骤。
// 若按未折叠的行数报 TotalRecordCount(例如 S01E11 与 S01E11.5 两行折叠成一条),
// 客户端会认为还有一条没取到,反复请求下一页 —— 分集列表就会一直加载不出来。
rows = e.collapseMediaVersionRows(ctx, rows)
total := len(rows)
items, err := e.payloadsForMedia(ctx, pageSlice(rows, p.StartIndex, p.Limit), p.UserID)
if err != nil {
+5 -1
View File
@@ -68,7 +68,11 @@ func applyLocalEpisodeMetadata(m *model.Media, local *LocalMetadata) {
if local.EpisodeTitle != "" {
m.EpisodeTitle = local.EpisodeTitle
}
if local.SeasonNum > 0 || local.EpisodeNum > 0 {
// tvshow.nfo 里常带 scraper 写的 <season>-1</season> / <episode>-1</episode>
// 哨兵(表示「整剧级、不适用」)。负季号不是有效季号:一旦让它覆盖,该集会被
// 写成 -1,网页端按 seasonLabel(-1) 显示成「剧场版」。因此负值一律忽略,
// 保留文件名解析出来的季号(S00Exx 会被解析成第 0 季 = 特别篇)。
if local.SeasonNum >= 0 && (local.SeasonNum > 0 || local.EpisodeNum > 0) {
m.SeasonNum = local.SeasonNum
}
if local.EpisodeNum > 0 {
+47
View File
@@ -4,6 +4,8 @@ import (
"os"
"path/filepath"
"testing"
"github.com/truewhile/MeBox/internal/model"
)
func TestReadLocalMetadataDropsResolutionArtifactEpisode(t *testing.T) {
@@ -436,3 +438,48 @@ func TestReadLocalMetadataFindsLegacyImgPoster(t *testing.T) {
t.Fatalf("PosterURL = %q, want legacy .img poster %q", got.PosterURL, poster)
}
}
// tvshow.nfo 里 scraper 写的 <season>-1</season>(整剧级"不适用"哨兵)不能覆盖
// 文件名解析出的季号:一旦覆盖,该集会被写成 -1,网页端按 seasonLabel(-1) 显示成
// 「剧场版」,Emby 端也只能落进特别篇。S00Exx 这类特别篇命名应保留第 0 季。
func TestApplyLocalEpisodeMetadataIgnoresNegativeSeasonSentinel(t *testing.T) {
cases := []struct {
name string
parsed model.Media
local LocalMetadata
wantSeason int
wantEpisode int
}{
{
name: "show level sentinel keeps parsed season",
parsed: model.Media{SeasonNum: 2, EpisodeNum: 5},
local: LocalMetadata{SeasonNum: -1, EpisodeNum: 5, HasNFO: true},
wantSeason: 2,
wantEpisode: 5,
},
{
name: "special naming keeps season zero",
parsed: model.Media{SeasonNum: 0, EpisodeNum: 11},
local: LocalMetadata{SeasonNum: -1, EpisodeNum: 11, HasNFO: true},
wantSeason: 0,
wantEpisode: 11,
},
{
name: "positive episode season still applies",
parsed: model.Media{SeasonNum: 0, EpisodeNum: 11},
local: LocalMetadata{SeasonNum: 1, EpisodeNum: 11, HasNFO: true},
wantSeason: 1,
wantEpisode: 11,
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
media := tc.parsed
local := tc.local
applyLocalMetadata(&media, &local)
if media.SeasonNum != tc.wantSeason || media.EpisodeNum != tc.wantEpisode {
t.Fatalf("season/episode = %d/%d, want %d/%d", media.SeasonNum, media.EpisodeNum, tc.wantSeason, tc.wantEpisode)
}
})
}
}