mirror of
https://github.com/truewhile/MeBox.git
synced 2026-10-04 04:26:38 +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
|
seriesID := m.SeriesID
|
||||||
seriesName := ""
|
seriesName := ""
|
||||||
seasonID := ""
|
seasonID := ""
|
||||||
|
seasonNum := m.SeasonNum
|
||||||
if e.mediaShouldBeEpisode(ctx, m) {
|
if e.mediaShouldBeEpisode(ctx, m) {
|
||||||
itemType = "Episode"
|
itemType = "Episode"
|
||||||
seriesID = e.seriesIDForMedia(ctx, m)
|
seriesID = e.seriesIDForMedia(ctx, m)
|
||||||
seriesName = e.seriesNameForMedia(ctx, m)
|
seriesName = e.seriesNameForMedia(ctx, m)
|
||||||
|
seasonNum = embySeasonNumForMedia(m)
|
||||||
seasonID = e.seasonIDForMedia(ctx, m)
|
seasonID = e.seasonIDForMedia(ctx, m)
|
||||||
parentID = seasonID
|
parentID = seasonID
|
||||||
episodeTitle := strings.TrimSpace(m.EpisodeTitle)
|
episodeTitle := strings.TrimSpace(m.EpisodeTitle)
|
||||||
@@ -648,7 +650,7 @@ func (e *EmbyService) itemPayload(ctx context.Context, m *model.Media, fav bool,
|
|||||||
"MediaType": "Video",
|
"MediaType": "Video",
|
||||||
"IsFolder": false,
|
"IsFolder": false,
|
||||||
"ProductionYear": m.Year,
|
"ProductionYear": m.Year,
|
||||||
"ParentIndexNumber": m.SeasonNum,
|
"ParentIndexNumber": seasonNum,
|
||||||
"IndexNumber": m.EpisodeNum,
|
"IndexNumber": m.EpisodeNum,
|
||||||
"Overview": m.Overview,
|
"Overview": m.Overview,
|
||||||
"RunTimeTicks": runTimeTicks,
|
"RunTimeTicks": runTimeTicks,
|
||||||
@@ -660,7 +662,7 @@ func (e *EmbyService) itemPayload(ctx context.Context, m *model.Media, fav bool,
|
|||||||
"Path": m.Path,
|
"Path": m.Path,
|
||||||
"ParentId": parentID,
|
"ParentId": parentID,
|
||||||
"SeasonId": seasonID,
|
"SeasonId": seasonID,
|
||||||
"SeasonName": seasonName(m.SeasonNum),
|
"SeasonName": seasonName(seasonNum),
|
||||||
"SeriesId": seriesID,
|
"SeriesId": seriesID,
|
||||||
"SeriesName": seriesName,
|
"SeriesName": seriesName,
|
||||||
"ImageTags": imageTags,
|
"ImageTags": imageTags,
|
||||||
|
|||||||
@@ -143,8 +143,11 @@ func (e *EmbyService) findSeasonGroupBySeriesCandidates(ctx context.Context, id,
|
|||||||
}
|
}
|
||||||
matched := make([]string, 0, 1)
|
matched := make([]string, 0, 1)
|
||||||
for _, cand := range candidates {
|
for _, cand := range candidates {
|
||||||
if seasonID(cand.SeriesID, cand.SeasonNum) == id {
|
for _, seasonNum := range embySeasonCandidates(cand.SeasonNum) {
|
||||||
matched = append(matched, cand.SeriesID)
|
if seasonID(cand.SeriesID, seasonNum) == id {
|
||||||
|
matched = append(matched, cand.SeriesID)
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, matchedSeries := range matched {
|
for _, matchedSeries := range matched {
|
||||||
@@ -257,10 +260,7 @@ func (e *EmbyService) seasonsForSeries(series embySeriesGroup) []embySeasonGroup
|
|||||||
bySeason := map[int]*embySeasonGroup{}
|
bySeason := map[int]*embySeasonGroup{}
|
||||||
order := []int{}
|
order := []int{}
|
||||||
for _, episode := range series.Episodes {
|
for _, episode := range series.Episodes {
|
||||||
seasonNum := episode.SeasonNum
|
seasonNum := embySeasonNumForMedia(&episode)
|
||||||
if seasonNum < 0 {
|
|
||||||
seasonNum = 1
|
|
||||||
}
|
|
||||||
season, ok := bySeason[seasonNum]
|
season, ok := bySeason[seasonNum]
|
||||||
if !ok {
|
if !ok {
|
||||||
season = &embySeasonGroup{
|
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) {
|
func TestEmbyEpisodeStillIsPrimaryImageNotArt(t *testing.T) {
|
||||||
svc := newTestEmbyService(t)
|
svc := newTestEmbyService(t)
|
||||||
lib := model.Library{Name: "剧集", Path: `/media/tv`, Type: "tv", Enabled: true}
|
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 {
|
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 {
|
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]
|
return prefix + hex.EncodeToString(h.Sum(nil))[:32]
|
||||||
}
|
}
|
||||||
|
|
||||||
func seasonID(seriesID string, seasonNum int) string {
|
const (
|
||||||
if seasonNum < 0 {
|
embySeasonTheatrical = -1
|
||||||
seasonNum = 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))
|
return stableEmbyID(embyVirtualSeasonPrefix, seriesID, strconv.Itoa(seasonNum))
|
||||||
}
|
}
|
||||||
|
|
||||||
func seasonName(seasonNum int) string {
|
func seasonName(seasonNum int) string {
|
||||||
if seasonNum == 0 {
|
switch seasonNum {
|
||||||
|
case embySeasonGenericSpecial:
|
||||||
return "特别篇"
|
return "特别篇"
|
||||||
}
|
case embySeasonTheatrical:
|
||||||
if seasonNum < 0 {
|
return "剧场版"
|
||||||
seasonNum = 1
|
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)
|
return fmt.Sprintf("第 %d 季", seasonNum)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user