diff --git a/internal/service/strm_output_dir.go b/internal/service/strm_output_dir.go index 6885173..c83925f 100644 --- a/internal/service/strm_output_dir.go +++ b/internal/service/strm_output_dir.go @@ -29,40 +29,22 @@ func strmLibrarySpecificOutputDir(base string, lib *model.Library) string { if outputDir == "" || outputDir == "." || lib == nil { return outputDir } - outputDir = stripSTRMLibraryCategoryAfterRoot(outputDir) subdir := strmLibraryOutputSubdir(*lib) if subdir == "" || strmPathHasSuffix(outputDir, subdir) || pathAlreadyEndsWith(outputDir, filepath.Base(subdir)) { return outputDir } + if tail := strmMissingCategoryTail(outputDir, subdir); tail != "" { + return filepath.Join(outputDir, tail) + } return filepath.Join(outputDir, subdir) } -func stripSTRMLibraryCategoryAfterRoot(pathValue string) string { - normalized := filepath.ToSlash(filepath.Clean(strings.TrimSpace(pathValue))) - parts := strings.Split(normalized, "/") - strmIndex := -1 - for i, part := range parts { - if strings.EqualFold(strings.TrimSpace(part), "strm") { - strmIndex = i - } +func strmMissingCategoryTail(outputDir, subdir string) string { + parts := strmSlashParts(subdir) + if len(parts) < 2 || !pathAlreadyEndsWith(outputDir, parts[0]) { + return "" } - if strmIndex < 0 || strmIndex >= len(parts)-1 { - return pathValue - } - tail := make([]string, 0, len(parts)-strmIndex-1) - for _, part := range parts[strmIndex+1:] { - if strings.TrimSpace(part) != "" { - tail = append(tail, part) - } - } - if len(strmCategoryPartsFromPath(tail)) == 0 { - return pathValue - } - root := strings.Join(parts[:strmIndex+1], "/") - if root == "" { - return pathValue - } - return filepath.FromSlash(root) + return filepath.Join(parts[1:]...) } func strmPathHasSuffix(pathValue, suffix string) bool { diff --git a/internal/service/strm_svc_test.go b/internal/service/strm_svc_test.go index 68c816d..4019944 100644 --- a/internal/service/strm_svc_test.go +++ b/internal/service/strm_svc_test.go @@ -232,8 +232,8 @@ func TestSTRMLibraryOutputSubdirUsesLibraryCategoryPath(t *testing.T) { } } -func TestSTRMLibrarySpecificOutputDirStripsPreviousLibraryCategory(t *testing.T) { - base := filepath.Join(t.TempDir(), "strm", "电影", "演唱会") +func TestSTRMLibrarySpecificOutputDirPreservesExplicitCategoryRoot(t *testing.T) { + base := filepath.Join(t.TempDir(), "strm", "电视剧") lib := model.Library{ Name: "国产剧", Path: BuildCloudLibraryPath("openlist", "/电视剧/国产剧", "/电视剧/国产剧"), @@ -241,7 +241,7 @@ func TestSTRMLibrarySpecificOutputDirStripsPreviousLibraryCategory(t *testing.T) } got := strmLibrarySpecificOutputDir(base, &lib) - want := filepath.Join(filepath.Dir(filepath.Dir(base)), "电视剧", "国产剧") + want := filepath.Join(base, "国产剧") if got != want { t.Fatalf("strmLibrarySpecificOutputDir() = %q, want %q", got, want) } diff --git a/web/src/pages/strmPageModel.ts b/web/src/pages/strmPageModel.ts index f5e80b7..b470f80 100644 --- a/web/src/pages/strmPageModel.ts +++ b/web/src/pages/strmPageModel.ts @@ -145,7 +145,7 @@ function stripLibraryCategoryAfterSTRMRoot(raw: string) { if (strmIndex < 0 || strmIndex >= parts.length - 1) return '' const tail = parts.slice(strmIndex + 1).filter((part) => part.trim()) const categoryParts = categoryPartsFromPath(tail) - if (!categoryParts?.length) return '' + if (!categoryParts || categoryParts.length < 2) return '' return trimPath(parts.slice(0, strmIndex + 1).join('/')) } diff --git a/web/src/utils/groupSeries.ts b/web/src/utils/groupSeries.ts index 6bbc289..be9adda 100644 --- a/web/src/utils/groupSeries.ts +++ b/web/src/utils/groupSeries.ts @@ -197,9 +197,12 @@ const EPISODE_ONLY_TITLE_RE = const EPISODE_TITLE_RE = /^第\s*[0-9一二三四五六七八九十百零两]+\s*[集期话話](?:\s*[上下])?\s*[::].+/ +const EPISODIC_RELEASE_TITLE_RE = + /(?:^|\s)(?:s\d{1,2}\s*e\d{1,3}|season\s*\d{1,2}\s*(?:episode|ep)\s*\d{1,3}|\d{1,2}x\d{1,3}|e(?:p(?:isode)?)?\s*\d{1,3})(?:\s|$)/i + function unsafeEpisodeTitle(title: string): boolean { const value = title.trim() - return EPISODE_ONLY_TITLE_RE.test(value) || EPISODE_TITLE_RE.test(value) + return EPISODE_ONLY_TITLE_RE.test(value) || EPISODE_TITLE_RE.test(value) || EPISODIC_RELEASE_TITLE_RE.test(normalizeTitle(value)) } export function seriesTitleFromPath(path?: string): string {