feat(organize): 整理、季包选文件、刮削、文件管理识别统一套用集数映射;单集沿用资源判定,待确认文件不按原号入库
Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Cxwgnn2pbzQ7rXv3rq7fei
This commit is contained in:
1 parent
691671a1d2
commit
fe597187dd
12 files changed
+848
-21
No files matched your search
@@ -67,6 +67,11 @@ export interface OrganizeInput {
|
||||
export interface FileOverride {
|
||||
season?: number;
|
||||
episodes?: { start: number; end: number };
|
||||
/**
|
||||
* 不入库(调用方按作品集数映射判定:集数待确认 episode_unresolved、映射规则标为特别篇 / 花絮 / 忽略 special_extra);
|
||||
* 只按精确路径生效,目录前缀覆写不带它
|
||||
*/
|
||||
exclude?: { reason: "episode_unresolved" | "special_extra"; detail?: string };
|
||||
}
|
||||
|
||||
/** 源路径的覆写:精确命中优先,否则取最长的目录前缀 */
|
||||
@@ -112,6 +117,7 @@ export interface ExcludedFile {
|
||||
| "not_imported_kind"
|
||||
| "orphan_sidecar"
|
||||
| "no_episode"
|
||||
| "episode_unresolved"
|
||||
| "special_extra"
|
||||
| "extra_content"
|
||||
| "no_target_dir"
|
||||
@@ -226,6 +232,11 @@ 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;
|
||||
if (ex) {
|
||||
excluded.push({ src: f.path, kind, reason: ex.reason, ...(ex.detail ? { detail: ex.detail } : {}) });
|
||||
continue;
|
||||
}
|
||||
if (ov?.season !== undefined) parsed.season = ov.season;
|
||||
if (ov?.episodes) {
|
||||
parsed.episodes = ov.episodes;
|
||||
|
||||
@@ -308,6 +308,48 @@ describe("W4-E4:字幕 .default 与 latest 旧版本判定口径(对照 MP)", ()
|
||||
expect(movie.files[0]!.dst).toBe("/lib/M (2020)/M (2020).mkv");
|
||||
});
|
||||
|
||||
it("覆写 exclude:精确路径的视频不入库(记排除原因),目录前缀的 exclude 不生效;全部排除 → no_main_video", () => {
|
||||
const D = "/dl/Pack";
|
||||
const plan = planOrganize({
|
||||
operation: "copy",
|
||||
mediaType: "tv",
|
||||
sourceRoot: D,
|
||||
files: [
|
||||
{ path: `${D}/Show - 40.mkv`, size: GiB },
|
||||
{ path: `${D}/Show - 40.chs.srt`, size: 1000 },
|
||||
{ path: `${D}/Show - NCOP.mkv`, size: GiB },
|
||||
{ path: `${D}/Show.S01E02.mkv`, size: GiB },
|
||||
],
|
||||
entity: { title: "剧", year: 2020 },
|
||||
target: { seasonDirs: { 1: "/lib/剧/Season 1" } },
|
||||
overrides: {
|
||||
[`${D}/Show - 40.mkv`]: { exclude: { reason: "episode_unresolved", detail: "S01E40" } },
|
||||
[`${D}/Show - NCOP.mkv`]: { exclude: { reason: "special_extra", detail: "rule:extra" } },
|
||||
[D]: { exclude: { reason: "episode_unresolved" } },
|
||||
},
|
||||
});
|
||||
expect(plan.files.filter((f) => f.role === "main").map((f) => f.dst)).toEqual([
|
||||
"/lib/剧/Season 1/剧 - S01E02 - 第 2 集.mkv",
|
||||
]);
|
||||
expect(plan.excluded).toEqual(
|
||||
expect.arrayContaining([
|
||||
{ src: `${D}/Show - 40.mkv`, kind: "video", reason: "episode_unresolved", detail: "S01E40" },
|
||||
{ src: `${D}/Show - NCOP.mkv`, kind: "video", reason: "special_extra", detail: "rule:extra" },
|
||||
expect.objectContaining({ src: `${D}/Show - 40.chs.srt`, reason: "orphan_sidecar" }),
|
||||
]),
|
||||
);
|
||||
const none = planOrganize({
|
||||
operation: "copy",
|
||||
mediaType: "tv",
|
||||
files: [{ path: "/dl/Show - 40.mkv", size: GiB }],
|
||||
entity: { title: "剧", year: 2020 },
|
||||
target: { seasonDirs: { 1: "/lib/剧/Season 1" } },
|
||||
overrides: { "/dl/Show - 40.mkv": { exclude: { reason: "episode_unresolved" } } },
|
||||
});
|
||||
expect(none.files).toEqual([]);
|
||||
expect(none.conflicts).toEqual([expect.objectContaining({ kind: "no_main_video", blocking: true })]);
|
||||
});
|
||||
|
||||
it("findOtherVersions:同季同集同 Part、只看视频扩展名", () => {
|
||||
const sib = [
|
||||
"/lib/S01/Show - S01E01 - 1080p.mkv",
|
||||
|
||||
Reference in new issue
Block a user