mirror of
https://github.com/truewhile/MeBox.git
synced 2026-10-06 05:16:38 +08:00
优化
优化
This commit is contained in:
@@ -111,6 +111,7 @@ const (
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
embySeasonDirRE = regexp.MustCompile(`(?i)^(season[\s._-]*\d+|s\d+|specials?|sp|ova|oad|extra|extras|第\s*[0-9一二三四五六七八九十百零两]+\s*季|特别篇|特別篇|番外|特典)$`)
|
embySeasonDirRE = regexp.MustCompile(`(?i)^(season[\s._-]*\d+|s\d+|specials?|sp|ova|oad|extra|extras|第\s*[0-9一二三四五六七八九十百零两]+\s*季|特别篇|特別篇|番外|特典)$`)
|
||||||
|
embySeasonSuffixRE = regexp.MustCompile(`(?i)(?:[\s._-]+(?:season[\s._-]*\d+|s\d+|第\s*[0-9一二三四五六七八九十百零两]+\s*季|specials?|sp|ova|oad|extra|extras|特别篇|特別篇|番外|特典)|\s*第\s*[0-9一二三四五六七八九十百零两]+\s*季)\s*$`)
|
||||||
embyYearSuffixRE = regexp.MustCompile(`\s*[\((\[]\d{4}[\))\]]\s*$`)
|
embyYearSuffixRE = regexp.MustCompile(`\s*[\((\[]\d{4}[\))\]]\s*$`)
|
||||||
embyEpisodeTitleRE = regexp.MustCompile(`(?i)\s*[-_ ]*s\d{1,2}e\d{1,3}.*$`)
|
embyEpisodeTitleRE = regexp.MustCompile(`(?i)\s*[-_ ]*s\d{1,2}e\d{1,3}.*$`)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -334,3 +334,105 @@ func TestEmbyCloudAnimeUsesSeriesNameFromChineseSeasonFolder(t *testing.T) {
|
|||||||
t.Fatalf("cloud anime should be grouped as one series named 剑来, got %#v", items)
|
t.Fatalf("cloud anime should be grouped as one series named 剑来, got %#v", items)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestEmbySeriesGroupingWithPrefixedSeasonFolders(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 season := 1; season <= 5; season++ {
|
||||||
|
for ep := 1; ep <= 3; ep++ {
|
||||||
|
media := model.Media{
|
||||||
|
Base: model.Base{ID: fmt.Sprintf("shokugeki-s%02de%02d", season, ep)},
|
||||||
|
LibraryID: lib.ID,
|
||||||
|
Title: "食戟之灵",
|
||||||
|
OriginalName: "食戟のソーマ",
|
||||||
|
ScrapeStatus: "matched",
|
||||||
|
TMDbID: 62273,
|
||||||
|
BangumiID: 116461,
|
||||||
|
Path: fmt.Sprintf(`/media/动漫/食戟之灵/食戟之灵 S%02d/食戟之灵 S%02dE%02d.strm`, season, season, ep),
|
||||||
|
SeasonNum: season,
|
||||||
|
EpisodeNum: ep,
|
||||||
|
}
|
||||||
|
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 {
|
||||||
|
t.Fatalf("expected 1 series card for 食戟之灵 across 5 seasons, got %d cards: %#v", len(rootItems), rootItems)
|
||||||
|
}
|
||||||
|
if rootItems[0]["Name"] != "食戟之灵" || rootItems[0]["Type"] != "Series" {
|
||||||
|
t.Fatalf("unexpected series item: %#v", rootItems[0])
|
||||||
|
}
|
||||||
|
seriesID := rootItems[0]["Id"].(string)
|
||||||
|
|
||||||
|
seasons, err := svc.Items(t.Context(), ItemsParams{ParentID: seriesID, Limit: 50})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("series seasons: %v", err)
|
||||||
|
}
|
||||||
|
seasonItems := seasons["Items"].([]map[string]any)
|
||||||
|
if len(seasonItems) != 5 {
|
||||||
|
t.Fatalf("expected 5 seasons, got %d: %#v", len(seasonItems), seasonItems)
|
||||||
|
}
|
||||||
|
for i, s := range seasonItems {
|
||||||
|
wantSeasonNum := i + 1
|
||||||
|
if s["Type"] != "Season" || s["IndexNumber"] != wantSeasonNum {
|
||||||
|
t.Errorf("season [%d] = %#v, want IndexNumber=%d", i, s, wantSeasonNum)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
counts, err := svc.ItemCounts(t.Context(), "user-1")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("item counts: %v", err)
|
||||||
|
}
|
||||||
|
if counts["SeriesCount"] != 1 || counts["EpisodeCount"] != int64(15) {
|
||||||
|
t.Fatalf("counts = %#v, want 1 series and 15 episodes", counts)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInferSeriesNameFromPath(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
path string
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
path: `/media/动漫/食戟之灵/食戟之灵 S01/食戟之灵 S01E01.strm`,
|
||||||
|
want: "食戟之灵",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: `/media/动漫/食戟之灵/食戟之灵 S05/食戟之灵 S05E12.strm`,
|
||||||
|
want: "食戟之灵",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: `/media/动漫/食戟之灵/Season 02/01.mkv`,
|
||||||
|
want: "食戟之灵",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: `/media/动漫/进击的巨人 第2季/01.mkv`,
|
||||||
|
want: "进击的巨人",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: `cloud://openlist/国漫/剑来/第二季/04.mkv`,
|
||||||
|
want: "剑来",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: `/media/tv/间谍过家家 (2022)/Specials/S00E01.mkv`,
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -31,6 +31,14 @@ func (e *EmbyService) seriesNameForMedia(m *model.Media) string {
|
|||||||
return series.Title
|
return series.Title
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if strings.EqualFold(strings.TrimSpace(m.ScrapeStatus), "matched") && strings.TrimSpace(m.Title) != "" {
|
||||||
|
name := strings.TrimSpace(m.Title)
|
||||||
|
name = embyEpisodeTitleRE.ReplaceAllString(name, "")
|
||||||
|
name = embyYearSuffixRE.ReplaceAllString(name, "")
|
||||||
|
if name != "" {
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
}
|
||||||
if name := inferSeriesNameFromPath(m.Path); name != "" {
|
if name := inferSeriesNameFromPath(m.Path); name != "" {
|
||||||
return name
|
return name
|
||||||
}
|
}
|
||||||
@@ -53,14 +61,35 @@ func inferSeriesNameFromPath(path string) string {
|
|||||||
if embySeasonDirRE.MatchString(base) {
|
if embySeasonDirRE.MatchString(base) {
|
||||||
dir = filepath.Dir(dir)
|
dir = filepath.Dir(dir)
|
||||||
base = filepath.Base(dir)
|
base = filepath.Base(dir)
|
||||||
|
} else if stripped := strings.TrimSpace(embySeasonSuffixRE.ReplaceAllString(base, "")); stripped != "" && stripped != base {
|
||||||
|
parentDir := filepath.Dir(dir)
|
||||||
|
parentBase := filepath.Base(parentDir)
|
||||||
|
if parentBase != "." && parentBase != string(filepath.Separator) && !isEmbyGenericContainer(parentBase) {
|
||||||
|
dir = parentDir
|
||||||
|
base = parentBase
|
||||||
|
} else {
|
||||||
|
base = stripped
|
||||||
|
}
|
||||||
}
|
}
|
||||||
base = strings.TrimSpace(embyYearSuffixRE.ReplaceAllString(base, ""))
|
base = strings.TrimSpace(embyYearSuffixRE.ReplaceAllString(base, ""))
|
||||||
if base == "." || base == string(filepath.Separator) {
|
if base == "." || base == string(filepath.Separator) || isEmbyGenericContainer(base) {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
return base
|
return base
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isEmbyGenericContainer(name string) bool {
|
||||||
|
switch strings.ToLower(strings.TrimSpace(name)) {
|
||||||
|
case "movie", "movies", "film", "films", "tv", "series", "show", "shows", "anime", "animation", "variety",
|
||||||
|
"电视剧", "剧集", "连续剧", "短剧", "国产剧", "国剧", "欧美剧", "美剧", "英剧", "日韩剧", "日剧", "韩剧", "港剧", "台剧", "港台剧",
|
||||||
|
"综艺", "纪录片", "儿童", "动漫", "番剧", "国漫", "日番", "韩漫", "美漫", "欧美动漫", "欧美动画", "其他动漫", "电影", "成人", "未分类",
|
||||||
|
"media", "downloads", "download", "videos", "video", "share", "shares":
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func stableEmbyID(prefix string, parts ...string) string {
|
func stableEmbyID(prefix string, parts ...string) string {
|
||||||
h := sha256.New()
|
h := sha256.New()
|
||||||
for _, part := range parts {
|
for _, part := range parts {
|
||||||
|
|||||||
Reference in New Issue
Block a user