feat(organize): 文件级标题粗核,混合包里明显不是本作品的视频降为待确认

- 同一批视频里文件名标题与作品标题 / 原名 / 别名明显不符(没有足够共同词)的排除为 title_mismatch,不按本作品命名入库;
- 只在同批至少一个文件对得上作品时才判断,剧集只看带 SxxEyy 的文件,用户逐文件指定过的不核;
- title_mismatch 算「本该入库却被排除」:记录部分完成、开待处理、Move 不移除下载任务。

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Cxwgnn2pbzQ7rXv3rq7fei
This commit is contained in:
rootandClaude Opus 5.5 committed 2026-10-03 23:33:41 +08:00
1 parent b40716d74f
commit c532060c83
10 files changed
+283 -10

No files matched your search

+6 -4
View File
@@ -69,9 +69,9 @@ export interface FileOverride {
episodes?: { start: number; end: number };
/**
* 不入库(调用方按作品集数映射判定:集数待确认 episode_unresolved、映射规则标为特别篇 / 花絮 / 忽略 special_extra);
* 只按精确路径生效,目录前缀覆写不带它
* 只按精确路径生效,目录前缀覆写不带它。title_mismatch = 文件标题明显不是本作品(混合包里别的作品),电影也生效
*/
exclude?: { reason: "episode_unresolved" | "special_extra"; detail?: string };
exclude?: { reason: "episode_unresolved" | "special_extra" | "title_mismatch"; detail?: string };
}
/** 源路径的覆写:精确命中优先,否则取最长的目录前缀 */
@@ -121,7 +121,8 @@ export interface ExcludedFile {
| "special_extra"
| "extra_content"
| "no_target_dir"
| "naming_failed";
| "naming_failed"
| "title_mismatch";
detail?: string;
}
@@ -232,7 +233,8 @@ export function planOrganize(input: OrganizeInput): OrganizePlan {
}
const parsed = parseReleasePath(segmentsFor(f.path, input.sourceRoot), input.parse ?? {});
const ov = input.mediaType === "tv" ? overrideFor(input.overrides, f.path) : undefined;
const ex = input.mediaType === "tv" ? input.overrides?.[f.path]?.exclude : undefined;
const exact = input.overrides?.[f.path]?.exclude;
const ex = input.mediaType === "tv" || exact?.reason === "title_mismatch" ? exact : undefined;
if (ex) {
excluded.push({ src: f.path, kind, reason: ex.reason, ...(ex.detail ? { detail: ex.detail } : {}) });
continue;