mirror of
https://github.com/truewhile/MeBox.git
synced 2026-09-28 11:16:37 +08:00
优化
This commit is contained in:
@@ -600,10 +600,12 @@ func (e *EmbyService) itemPayload(ctx context.Context, m *model.Media, fav bool,
|
||||
seriesID := m.SeriesID
|
||||
seriesName := ""
|
||||
seasonID := ""
|
||||
seasonNum := m.SeasonNum
|
||||
if e.mediaShouldBeEpisode(ctx, m) {
|
||||
itemType = "Episode"
|
||||
seriesID = e.seriesIDForMedia(ctx, m)
|
||||
seriesName = e.seriesNameForMedia(ctx, m)
|
||||
seasonNum = embySeasonNumForMedia(m)
|
||||
seasonID = e.seasonIDForMedia(ctx, m)
|
||||
parentID = seasonID
|
||||
episodeTitle := strings.TrimSpace(m.EpisodeTitle)
|
||||
@@ -648,7 +650,7 @@ func (e *EmbyService) itemPayload(ctx context.Context, m *model.Media, fav bool,
|
||||
"MediaType": "Video",
|
||||
"IsFolder": false,
|
||||
"ProductionYear": m.Year,
|
||||
"ParentIndexNumber": m.SeasonNum,
|
||||
"ParentIndexNumber": seasonNum,
|
||||
"IndexNumber": m.EpisodeNum,
|
||||
"Overview": m.Overview,
|
||||
"RunTimeTicks": runTimeTicks,
|
||||
@@ -660,7 +662,7 @@ func (e *EmbyService) itemPayload(ctx context.Context, m *model.Media, fav bool,
|
||||
"Path": m.Path,
|
||||
"ParentId": parentID,
|
||||
"SeasonId": seasonID,
|
||||
"SeasonName": seasonName(m.SeasonNum),
|
||||
"SeasonName": seasonName(seasonNum),
|
||||
"SeriesId": seriesID,
|
||||
"SeriesName": seriesName,
|
||||
"ImageTags": imageTags,
|
||||
|
||||
@@ -143,8 +143,11 @@ func (e *EmbyService) findSeasonGroupBySeriesCandidates(ctx context.Context, id,
|
||||
}
|
||||
matched := make([]string, 0, 1)
|
||||
for _, cand := range candidates {
|
||||
if seasonID(cand.SeriesID, cand.SeasonNum) == id {
|
||||
matched = append(matched, cand.SeriesID)
|
||||
for _, seasonNum := range embySeasonCandidates(cand.SeasonNum) {
|
||||
if seasonID(cand.SeriesID, seasonNum) == id {
|
||||
matched = append(matched, cand.SeriesID)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, matchedSeries := range matched {
|
||||
@@ -257,10 +260,7 @@ func (e *EmbyService) seasonsForSeries(series embySeriesGroup) []embySeasonGroup
|
||||
bySeason := map[int]*embySeasonGroup{}
|
||||
order := []int{}
|
||||
for _, episode := range series.Episodes {
|
||||
seasonNum := episode.SeasonNum
|
||||
if seasonNum < 0 {
|
||||
seasonNum = 1
|
||||
}
|
||||
seasonNum := embySeasonNumForMedia(&episode)
|
||||
season, ok := bySeason[seasonNum]
|
||||
if !ok {
|
||||
season = &embySeasonGroup{
|
||||
|
||||
@@ -215,6 +215,113 @@ func TestEmbyItemsKeepSpecialsInSeasonZero(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestEmbySeparatesOVAAndOADFromSeasonOne(t *testing.T) {
|
||||
svc := newTestEmbyService(t)
|
||||
lib := model.Library{Name: "动漫", Path: `/media/影视库/动漫`, Type: "anime", Enabled: true}
|
||||
if err := svc.repo.Library.Create(t.Context(), &lib); err != nil {
|
||||
t.Fatalf("create library: %v", err)
|
||||
}
|
||||
for _, media := range []model.Media{
|
||||
{
|
||||
Base: model.Base{ID: "tolove-s01e01"},
|
||||
LibraryID: lib.ID,
|
||||
Title: "出包王女",
|
||||
Path: `/media/影视库/动漫/出包王女/S01/To LOVE-Ru S01E01.mkv.strm`,
|
||||
SeasonNum: 1,
|
||||
EpisodeNum: 1,
|
||||
},
|
||||
{
|
||||
Base: model.Base{ID: "tolove-ova01"},
|
||||
LibraryID: lib.ID,
|
||||
Title: "出包王女",
|
||||
Path: `/media/影视库/动漫/出包王女/S01/To LOVE-Ru [OVA01].mkv.strm`,
|
||||
SeasonNum: 0,
|
||||
EpisodeNum: 1,
|
||||
},
|
||||
{
|
||||
Base: model.Base{ID: "tolove-oad01"},
|
||||
LibraryID: lib.ID,
|
||||
Title: "出包王女",
|
||||
Path: `/media/影视库/动漫/出包王女/OAD/To LOVE-Ru Darkness [OAD01].mkv.strm`,
|
||||
SeasonNum: -1,
|
||||
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)
|
||||
}
|
||||
if rootItems[0]["ChildCount"] != 3 {
|
||||
t.Fatalf("series ChildCount = %#v, want 3", rootItems[0]["ChildCount"])
|
||||
}
|
||||
|
||||
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) != 3 {
|
||||
t.Fatalf("expected season 1, OVA and OAD separately, got %#v", seasonItems)
|
||||
}
|
||||
|
||||
seasonByIndex := make(map[int]map[string]any, len(seasonItems))
|
||||
for _, season := range seasonItems {
|
||||
index, ok := season["IndexNumber"].(int)
|
||||
if !ok {
|
||||
t.Fatalf("season index has unexpected type: %#v", season)
|
||||
}
|
||||
seasonByIndex[index] = season
|
||||
}
|
||||
for index, name := range map[int]string{
|
||||
1: "第 1 季",
|
||||
embySeasonOVA: "OVA",
|
||||
embySeasonOAD: "OAD",
|
||||
} {
|
||||
season := seasonByIndex[index]
|
||||
if season == nil || season["Name"] != name || season["ChildCount"] != 1 {
|
||||
t.Fatalf("season %d = %#v, want name=%q with one episode", index, season, name)
|
||||
}
|
||||
episodes, err := svc.Items(t.Context(), ItemsParams{
|
||||
ParentID: season["Id"].(string),
|
||||
IncludeItemTypes: []string{"Episode"},
|
||||
Recursive: true,
|
||||
Limit: 50,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("season %d episodes: %v", index, err)
|
||||
}
|
||||
episodeItems := episodes["Items"].([]map[string]any)
|
||||
if len(episodeItems) != 1 || episodeItems[0]["ParentIndexNumber"] != index {
|
||||
t.Fatalf("season %d episodes = %#v", index, episodeItems)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestEmbySeasonCandidatesCoverPersistedSpecialNumbers(t *testing.T) {
|
||||
for _, persisted := range []int{0, -1} {
|
||||
candidates := embySeasonCandidates(persisted)
|
||||
found := map[int]bool{}
|
||||
for _, seasonNum := range candidates {
|
||||
found[seasonNum] = true
|
||||
}
|
||||
if !found[embySeasonOVA] || !found[embySeasonOAD] {
|
||||
t.Fatalf("candidates for persisted season %d = %#v, want OVA and OAD", persisted, candidates)
|
||||
}
|
||||
}
|
||||
if candidates := embySeasonCandidates(2); len(candidates) != 1 || candidates[0] != 2 {
|
||||
t.Fatalf("regular season candidates = %#v, want [2]", candidates)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEmbyEpisodeStillIsPrimaryImageNotArt(t *testing.T) {
|
||||
svc := newTestEmbyService(t)
|
||||
lib := model.Library{Name: "剧集", Path: `/media/tv`, Type: "tv", Enabled: true}
|
||||
|
||||
@@ -22,7 +22,7 @@ func (e *EmbyService) seriesIDForMedia(ctx context.Context, m *model.Media) stri
|
||||
}
|
||||
|
||||
func (e *EmbyService) seasonIDForMedia(ctx context.Context, m *model.Media) string {
|
||||
return seasonID(e.seriesIDForMedia(ctx, m), m.SeasonNum)
|
||||
return seasonID(e.seriesIDForMedia(ctx, m), embySeasonNumForMedia(m))
|
||||
}
|
||||
|
||||
func (e *EmbyService) seriesNameForMedia(ctx context.Context, m *model.Media) string {
|
||||
@@ -100,19 +100,110 @@ func stableEmbyID(prefix string, parts ...string) string {
|
||||
return prefix + hex.EncodeToString(h.Sum(nil))[:32]
|
||||
}
|
||||
|
||||
func seasonID(seriesID string, seasonNum int) string {
|
||||
if seasonNum < 0 {
|
||||
seasonNum = 1
|
||||
const (
|
||||
embySeasonTheatrical = -1
|
||||
embySeasonOVA = -2
|
||||
embySeasonOAD = -3
|
||||
embySeasonOVD = -4
|
||||
embySeasonONA = -5
|
||||
embySeasonExtra = -6
|
||||
embySeasonBonus = -7
|
||||
embySeasonOmake = -8
|
||||
embySeasonPictureDrama = -9
|
||||
embySeasonNCOP = -10
|
||||
embySeasonNCED = -11
|
||||
embySeasonGenericSpecial = 0
|
||||
)
|
||||
|
||||
// embySeasonNumForMedia keeps MeBox's special categories distinct in the Emby
|
||||
// hierarchy. Databases may store every special as season 0 or -1, so the path
|
||||
// classification is authoritative when it identifies a special category.
|
||||
func embySeasonNumForMedia(m *model.Media) int {
|
||||
if m == nil {
|
||||
return 0
|
||||
}
|
||||
switch mediaSpecialKind(m.Path) {
|
||||
case mediaSpecialTheatrical:
|
||||
return embySeasonTheatrical
|
||||
case mediaSpecialOVA:
|
||||
return embySeasonOVA
|
||||
case mediaSpecialOAD:
|
||||
return embySeasonOAD
|
||||
case mediaSpecialOVD:
|
||||
return embySeasonOVD
|
||||
case mediaSpecialONA:
|
||||
return embySeasonONA
|
||||
case mediaSpecialExtra:
|
||||
return embySeasonExtra
|
||||
case mediaSpecialBonus:
|
||||
return embySeasonBonus
|
||||
case mediaSpecialOmake:
|
||||
return embySeasonOmake
|
||||
case mediaSpecialPicture:
|
||||
return embySeasonPictureDrama
|
||||
case mediaSpecialNCOP:
|
||||
return embySeasonNCOP
|
||||
case mediaSpecialNCED:
|
||||
return embySeasonNCED
|
||||
case mediaSpecialGeneric:
|
||||
return embySeasonGenericSpecial
|
||||
}
|
||||
if m.SeasonNum < 0 {
|
||||
return embySeasonGenericSpecial
|
||||
}
|
||||
return m.SeasonNum
|
||||
}
|
||||
|
||||
func embySeasonCandidates(seasonNum int) []int {
|
||||
if seasonNum > 0 {
|
||||
return []int{seasonNum}
|
||||
}
|
||||
return []int{
|
||||
embySeasonGenericSpecial,
|
||||
embySeasonTheatrical,
|
||||
embySeasonOVA,
|
||||
embySeasonOAD,
|
||||
embySeasonOVD,
|
||||
embySeasonONA,
|
||||
embySeasonExtra,
|
||||
embySeasonBonus,
|
||||
embySeasonOmake,
|
||||
embySeasonPictureDrama,
|
||||
embySeasonNCOP,
|
||||
embySeasonNCED,
|
||||
}
|
||||
}
|
||||
|
||||
func seasonID(seriesID string, seasonNum int) string {
|
||||
return stableEmbyID(embyVirtualSeasonPrefix, seriesID, strconv.Itoa(seasonNum))
|
||||
}
|
||||
|
||||
func seasonName(seasonNum int) string {
|
||||
if seasonNum == 0 {
|
||||
switch seasonNum {
|
||||
case embySeasonGenericSpecial:
|
||||
return "特别篇"
|
||||
}
|
||||
if seasonNum < 0 {
|
||||
seasonNum = 1
|
||||
case embySeasonTheatrical:
|
||||
return "剧场版"
|
||||
case embySeasonOVA:
|
||||
return "OVA"
|
||||
case embySeasonOAD:
|
||||
return "OAD"
|
||||
case embySeasonOVD:
|
||||
return "OVD"
|
||||
case embySeasonONA:
|
||||
return "ONA"
|
||||
case embySeasonExtra:
|
||||
return "Extra"
|
||||
case embySeasonBonus:
|
||||
return "Bonus"
|
||||
case embySeasonOmake:
|
||||
return "Omake"
|
||||
case embySeasonPictureDrama:
|
||||
return "Picture Drama"
|
||||
case embySeasonNCOP:
|
||||
return "NCOP"
|
||||
case embySeasonNCED:
|
||||
return "NCED"
|
||||
}
|
||||
return fmt.Sprintf("第 %d 季", seasonNum)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user