@oh-story/dsh 0.1.13 → 0.1.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,31 @@
1
+ import type { CanvasPoint } from "./production-runtime.js";
2
+ /**
3
+ * Screen = world * zoom + pan. The canvas has no edges: nodes and the pan offset take any
4
+ * finite coordinate, so nothing can be pushed out of reach.
5
+ */
6
+ export interface CanvasViewport {
7
+ readonly x: number;
8
+ readonly y: number;
9
+ readonly zoom: number;
10
+ }
11
+ export interface CanvasSize {
12
+ readonly width: number;
13
+ readonly height: number;
14
+ }
15
+ export declare const CANVAS_NODE_WIDTH = 180;
16
+ export declare const CANVAS_NODE_HEIGHT = 76;
17
+ export declare const MIN_CANVAS_ZOOM = 0.1;
18
+ export declare const MAX_CANVAS_ZOOM = 3;
19
+ export declare const DEFAULT_CANVAS_VIEWPORT: CanvasViewport;
20
+ export declare function clampZoom(zoom: number): number;
21
+ /** Scales by `factor` while the world point under `anchor` (viewport pixels) stays put. */
22
+ export declare function zoomAt(viewport: CanvasViewport, factor: number, anchor: CanvasPoint): CanvasViewport;
23
+ /** Wheel deltas arrive in pixels, lines or pages depending on device; one notch is ~10%. */
24
+ export declare function wheelZoomFactor(deltaY: number, deltaMode: number): number;
25
+ export declare function screenToWorld(viewport: CanvasViewport, point: CanvasPoint): CanvasPoint;
26
+ /** Pans so the node's centre sits in the middle of the viewport, keeping the zoom. */
27
+ export declare function centerOn(viewport: CanvasViewport, node: CanvasPoint, size: CanvasSize): CanvasViewport;
28
+ export declare function nodeVisible(viewport: CanvasViewport, node: CanvasPoint, size: CanvasSize): boolean;
29
+ /** Frames every node; a single tall column fits its width rather than shrinking to dots. */
30
+ export declare function fitNodes(nodes: readonly CanvasPoint[], size: CanvasSize, fallback?: CanvasViewport): CanvasViewport;
31
+ //# sourceMappingURL=canvas-viewport.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"canvas-viewport.d.ts","sourceRoot":"","sources":["../../../src/client/canvas-viewport.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAE3D;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,eAAO,MAAM,iBAAiB,MAAM,CAAC;AACrC,eAAO,MAAM,kBAAkB,KAAK,CAAC;AACrC,eAAO,MAAM,eAAe,MAAK,CAAC;AAClC,eAAO,MAAM,eAAe,IAAI,CAAC;AACjC,eAAO,MAAM,uBAAuB,EAAE,cAA0C,CAAC;AAGjF,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE9C;AAED,2FAA2F;AAC3F,wBAAgB,MAAM,CAAC,QAAQ,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,cAAc,CAIpG;AAED,4FAA4F;AAC5F,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAGzE;AAED,wBAAgB,aAAa,CAAC,QAAQ,EAAE,cAAc,EAAE,KAAK,EAAE,WAAW,GAAG,WAAW,CAEvF;AAED,sFAAsF;AACtF,wBAAgB,QAAQ,CAAC,QAAQ,EAAE,cAAc,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,UAAU,GAAG,cAAc,CAMtG;AAED,wBAAgB,WAAW,CAAC,QAAQ,EAAE,cAAc,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,UAAU,GAAG,OAAO,CAIlG;AAED,4FAA4F;AAC5F,wBAAgB,QAAQ,CAAC,KAAK,EAAE,SAAS,WAAW,EAAE,EAAE,IAAI,EAAE,UAAU,EAAE,QAAQ,GAAE,cAAwC,GAAG,cAAc,CAe5I"}
@@ -1,4 +1,5 @@
1
1
  import { type DramaDocumentTarget, type DramaEpisodeProduction, type DramaProductionSection } from "./drama-production.js";
2
+ import type { CanvasViewport } from "./canvas-viewport.js";
2
3
  import { type CanvasPoint, type ProductionJob, type ProductionMediaVersion, type ProductionQueueEntry, type ProductionSequenceItem } from "./production-runtime.js";
3
4
  interface Props {
4
5
  readonly sessionId: string;
@@ -14,7 +15,7 @@ interface Props {
14
15
  readonly manualReferences: Readonly<Record<string, readonly string[]>>;
15
16
  readonly sequence: readonly ProductionSequenceItem[];
16
17
  readonly canvas: Readonly<Record<string, CanvasPoint>>;
17
- readonly zoom: number;
18
+ readonly viewport: CanvasViewport | undefined;
18
19
  readonly onSectionChange: (section: DramaProductionSection) => void;
19
20
  readonly onSelect: (id: string | undefined) => void;
20
21
  readonly onNavigate: (target: DramaDocumentTarget) => void;
@@ -24,7 +25,7 @@ interface Props {
24
25
  readonly onOpenMedia: (path: string) => void;
25
26
  readonly onSequenceChange: (sequence: ProductionSequenceItem[]) => void;
26
27
  readonly onCanvasChange: (canvas: Record<string, CanvasPoint>) => void;
27
- readonly onZoomChange: (zoom: number) => void;
28
+ readonly onViewportChange: (viewport: CanvasViewport) => void;
28
29
  readonly onDispatchPrompt: (prompt: string) => Promise<void>;
29
30
  readonly onCancelTurn: () => Promise<void>;
30
31
  readonly onRemoveQueued: (itemId: string) => Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"drama-production-view.d.ts","sourceRoot":"","sources":["../../../src/client/drama-production-view.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"drama-production-view.d.ts","sourceRoot":"","sources":["../../../src/client/drama-production-view.tsx"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/client/index.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAgEpE,eAAO,MAAM,IAAI,aAAa,CAAC;AAC/B,eAAO,MAAM,MAAM,UAAwC,CAAC;AAE5D,OAAO,QAAQ,kCAAkC,CAAC;IAChD,UAAU,OAAO;QACf,oBAAoB,EAAE;YAAE,IAAI,EAAE,QAAQ,CAAC;YAAC,KAAK,EAAE,SAAS,CAAA;SAAE,CAAC;KAC5D;CACF;AAuzDD,iFAAiF;AACjF,wBAAgB,KAAK,CAAC,OAAO,EAAE,aAAa,GAAG,IAAI,CAsClD;;;;;;AAED,wBAAuC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/client/index.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAiEpE,eAAO,MAAM,IAAI,aAAa,CAAC;AAC/B,eAAO,MAAM,MAAM,UAAwC,CAAC;AAE5D,OAAO,QAAQ,kCAAkC,CAAC;IAChD,UAAU,OAAO;QACf,oBAAoB,EAAE;YAAE,IAAI,EAAE,QAAQ,CAAC;YAAC,KAAK,EAAE,SAAS,CAAA;SAAE,CAAC;KAC5D;CACF;AAuzDD,iFAAiF;AACjF,wBAAgB,KAAK,CAAC,OAAO,EAAE,aAAa,GAAG,IAAI,CAsClD;;;;;;AAED,wBAAuC"}
@@ -0,0 +1,24 @@
1
+ import type { DramaDocumentTarget, DramaEpisodeProduction } from "./drama-production.js";
2
+ import type { CanvasPoint } from "./production-runtime.js";
3
+ import { type CanvasViewport } from "./canvas-viewport.js";
4
+ interface Props {
5
+ readonly production: DramaEpisodeProduction;
6
+ readonly selectedId: string | undefined;
7
+ readonly canvas: Readonly<Record<string, CanvasPoint>>;
8
+ /** Undefined until the creator pans or zooms this episode; the first view then frames the nodes. */
9
+ readonly viewport: CanvasViewport | undefined;
10
+ readonly onSelect: (id: string | undefined) => void;
11
+ readonly onNavigate: (target: DramaDocumentTarget) => void;
12
+ readonly onCanvasChange: (canvas: Record<string, CanvasPoint>) => void;
13
+ readonly onViewportChange: (viewport: CanvasViewport) => void;
14
+ }
15
+ interface CanvasNode {
16
+ readonly id: string;
17
+ readonly label: string;
18
+ readonly type: "asset" | "shot";
19
+ readonly initial: CanvasPoint;
20
+ }
21
+ export declare function canvasNodes(production: DramaEpisodeProduction): CanvasNode[];
22
+ export declare function ProductionCanvas(props: Props): import("react/jsx-runtime").JSX.Element;
23
+ export {};
24
+ //# sourceMappingURL=production-canvas.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"production-canvas.d.ts","sourceRoot":"","sources":["../../../src/client/production-canvas.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,mBAAmB,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AACzF,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,EAUL,KAAK,cAAc,EACpB,MAAM,sBAAsB,CAAC;AAE9B,UAAU,KAAK;IACb,QAAQ,CAAC,UAAU,EAAE,sBAAsB,CAAC;IAC5C,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS,CAAC;IACxC,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC,CAAC;IACvD,oGAAoG;IACpG,QAAQ,CAAC,QAAQ,EAAE,cAAc,GAAG,SAAS,CAAC;IAC9C,QAAQ,CAAC,QAAQ,EAAE,CAAC,EAAE,EAAE,MAAM,GAAG,SAAS,KAAK,IAAI,CAAC;IACpD,QAAQ,CAAC,UAAU,EAAE,CAAC,MAAM,EAAE,mBAAmB,KAAK,IAAI,CAAC;IAC3D,QAAQ,CAAC,cAAc,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,KAAK,IAAI,CAAC;IACvE,QAAQ,CAAC,gBAAgB,EAAE,CAAC,QAAQ,EAAE,cAAc,KAAK,IAAI,CAAC;CAC/D;AAED,UAAU,UAAU;IAClB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,CAAC;IAChC,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAC;CAC/B;AAQD,wBAAgB,WAAW,CAAC,UAAU,EAAE,sBAAsB,GAAG,UAAU,EAAE,CAI5E;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,KAAK,2CA8Q5C"}
@@ -1 +1 @@
1
- {"version":3,"file":"skill-provider.d.ts","sourceRoot":"","sources":["../../src/skill-provider.ts"],"names":[],"mappings":"AAIA,OAAO,EAIL,KAAK,aAAa,EACnB,MAAM,wBAAwB,CAAC;AAwNhC,UAAU,WAAW;IACnB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC;CACjC;AAuBD,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,WAAW,CAa7D;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAIrE;AAED,wBAAgB,uBAAuB,IAAI,MAAM,CAKhD;AAED,wBAAgB,qBAAqB,IAAI,MAAM,CAK9C;AAED,wBAAgB,2BAA2B,IAAI,MAAM,CAKpD;AAuDD,wBAAgB,0BAA0B,CAAC,SAAS,SAA4B,GAAG,aAAa,CAE/F;AAED,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,SAAS,SAA0B,GAAG,MAAM,CAM/G;AAED,wBAAgB,wBAAwB,CAAC,SAAS,SAA0B,GAAG,aAAa,CAE3F;AAED,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAEjF;AAED,wBAAgB,8BAA8B,CAAC,SAAS,SAAgC,GAAG,aAAa,CAEvG;AAED,wBAAgB,0BAA0B,IAAI,MAAM,CAKnD;AAED,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAEhF;AAED,wBAAgB,6BAA6B,CAAC,SAAS,SAA+B,GAAG,aAAa,CAErG"}
1
+ {"version":3,"file":"skill-provider.d.ts","sourceRoot":"","sources":["../../src/skill-provider.ts"],"names":[],"mappings":"AAIA,OAAO,EAIL,KAAK,aAAa,EACnB,MAAM,wBAAwB,CAAC;AAyNhC,UAAU,WAAW;IACnB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC;CACjC;AAuBD,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,WAAW,CAa7D;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAIrE;AAED,wBAAgB,uBAAuB,IAAI,MAAM,CAKhD;AAED,wBAAgB,qBAAqB,IAAI,MAAM,CAK9C;AAED,wBAAgB,2BAA2B,IAAI,MAAM,CAKpD;AAuDD,wBAAgB,0BAA0B,CAAC,SAAS,SAA4B,GAAG,aAAa,CAE/F;AAED,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,SAAS,SAA0B,GAAG,MAAM,CAM/G;AAED,wBAAgB,wBAAwB,CAAC,SAAS,SAA0B,GAAG,aAAa,CAE3F;AAED,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAEjF;AAED,wBAAgB,8BAA8B,CAAC,SAAS,SAAgC,GAAG,aAAa,CAEvG;AAED,wBAAgB,0BAA0B,IAAI,MAAM,CAKnD;AAED,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAEhF;AAED,wBAAgB,6BAA6B,CAAC,SAAS,SAA+B,GAAG,aAAa,CAErG"}
@@ -10,6 +10,20 @@ All notable changes to this project are documented here.
10
10
 
11
11
  ## [Unreleased]
12
12
 
13
+ ## [0.6.2] - 2026-10-03
14
+
15
+ 用 dub 模式的命令(包括旧 `work_dir` 打印的续跑命令)都要补上 `--confirm-voice-rights`,否则在外发任何音频之前报错退出;只在有权使用这段视频、且说话人同意被克隆声音时确认。
16
+ 补丁号不代表可以直接升级:本版收紧了 dub 的运行条件;video-script 与 video-voiceover 写明每条远程调用发什么、发给谁、怎样关闭;没有 key 时评审不再把稿件发出去。
17
+
18
+ ### Changed
19
+
20
+ - **dub 模式必须显式确认声音权利:`--edit-mode dub` 需要 `--confirm-voice-rights`。** dub 会把源视频音轨分窗发给 MiMo ASR,并把原说话人约 10 秒的声音当参考发给 MiMo voiceclone 克隆音色,以前只要选了 dub 就会运行,`dub.py` 被直接执行时也没有任何确认。现在 recap 在 dub 模式下缺少该参数时于建 `work_dir` 和探测视频之前报错,其他模式传入同样报错;`dub.py` 的 `--stage prepare|render` 也都必须带它,缺少时在抽取音频与任何请求之前退出并说明会外发什么。只在用户有权使用这段视频与音频、且说话人同意被克隆声音时确认。升级后在所有 dub 命令(包括旧 `work_dir` 打印的续跑命令)上补这个参数;没有环境变量形式。
21
+ - **video-script 与 video-voiceover 的 SKILL.md 写明每条远程调用发什么、发给谁、怎样关闭。** video-script 新增 §2:`review.py` 是唯一联网的脚本,只把旁白全文、选定时间段内的画面描述与对白转写文字、背景资料和策划文件摘录发到 MiMo chat 接口(不发视频、帧图片或音频),单独使用时只在显式执行时运行,编排时用 `--no-review-narration` / `REVIEW_NARRATION=0` 关闭;`validate.py` 与 lint 只在本地运行。video-voiceover 新增 §2 逐路径列出 MiMo TTS、`--voice-ref` 克隆、Fish Audio、自托管 IndexTTS 与 dub 各自收到的内容,新增 §8 说明 dub 是实验功能、如何触发与权利要求。两份文档其后的章节号各顺延一号。
22
+
23
+ ### Fixed
24
+
25
+ - **没有 `MIMO_API_KEY` 时 `review.py` 不再把稿件发出去。** 以前缺 key 也会把旁白与证据 POST 到 MiMo,再因 401 失败;现在在构造请求前退出,不写评审产物。编排入口的建议型评审照旧失败不阻断。
26
+
13
27
  ## [0.6.1] - 2026-10-03
14
28
 
15
29
  升级前先从脚本与环境里删掉本版移除的参数和环境变量(见 Removed,如 `--mimo-qc`、`validate.py --preserve-approved-text`、`cut.py --clip-padding`、`dashboard_server.py --host` 和 19 个调参变量),读 `golden_eval.json`、`mimo_qc.json`、`preflight_qc.json` 的脚本按 Removed 改读 `final_qc.json` 或对应产物。
@@ -463,7 +477,8 @@ recap feels like a recap, not captions over a clip.
463
477
  MiMo API key. Five independent skills (understanding, script, cut, voiceover, assemble)
464
478
  plus a thin orchestrator; optional 剪映 draft export.
465
479
 
466
- [Unreleased]: https://github.com/zenstory-ai/video-recap-skills/compare/v0.6.1...HEAD
480
+ [Unreleased]: https://github.com/zenstory-ai/video-recap-skills/compare/v0.6.2...HEAD
481
+ [0.6.2]: https://github.com/zenstory-ai/video-recap-skills/compare/v0.6.1...v0.6.2
467
482
  [0.6.1]: https://github.com/zenstory-ai/video-recap-skills/compare/v0.6.0...v0.6.1
468
483
  [0.6.0]: https://github.com/zenstory-ai/video-recap-skills/compare/v0.5.0...v0.6.0
469
484
  [0.5.0]: https://github.com/zenstory-ai/video-recap-skills/compare/v0.4.0...v0.5.0
@@ -396,10 +396,10 @@ The reference audio is sent to MiMo for synthesis; use it only with the voice ow
396
396
  **Dub an English video into Chinese while keeping the speaker's voice:**
397
397
 
398
398
  ```text
399
- Dub /path/to/english.mp4 into Chinese, keeping the original speaker's voice.
399
+ Dub /path/to/english.mp4 into Chinese, keeping the original speaker's voice. I have the rights to this video and the speaker consents to having their voice cloned.
400
400
  ```
401
401
 
402
- This replaces the original speech rather than overlaying commentary; the current version supports one speaker and full-track replacement without background-music separation.
402
+ This is experimental and replaces the original speech rather than overlaying commentary; the current version supports one speaker and full-track replacement without background-music separation. The source audio is sent to MiMo ASR for transcription and about 10 seconds of the original speaker's voice are sent to MiMo as the voice-clone reference, so the run requires `--confirm-voice-rights`: confirm only when you have the rights to the video and the speaker's consent to clone their voice.
403
403
 
404
404
  **Reuse subtitle style, voice, BGM and frame across a series:**
405
405
 
@@ -385,10 +385,10 @@ flowchart LR
385
385
  **英语视频译成中文并保留原说话人的声音:**
386
386
 
387
387
  ```text
388
- 把 /path/to/english.mp4 翻译成中文配音,保留原说话人的声音。
388
+ 把 /path/to/english.mp4 翻译成中文配音,保留原说话人的声音。我有权使用这段视频,说话人同意克隆其声音。
389
389
  ```
390
390
 
391
- 这是替换原始台词而不是叠加解说;当前支持单说话人整轨替换,不分离背景音乐。
391
+ 这是实验功能,替换原始台词而不是叠加解说;当前支持单说话人整轨替换,不分离背景音乐。源视频音频会发给 MiMo ASR 转写,原说话人约 10 秒的声音会作为参考发给 MiMo 克隆音色,所以运行需要 `--confirm-voice-rights`:只在你有权使用该视频、且说话人同意被克隆声音时确认。
392
392
 
393
393
  **同一个系列复用字幕样式、音色、BGM 与包框:**
394
394
 
@@ -2,10 +2,10 @@
2
2
  "schemaVersion": 1,
3
3
  "upstream": {
4
4
  "repository": "https://github.com/zenstory-ai/video-recap-skills.git",
5
- "commit": "88af4d4f37b09510a1169e945d43d0532770a8e4",
6
- "releaseVersion": "0.6.1"
5
+ "commit": "539168622918e058bd250c793b82f043eced31e0",
6
+ "releaseVersion": "0.6.2"
7
7
  },
8
- "generatedAt": "2026-10-03T14:32:42.171Z",
8
+ "generatedAt": "2026-10-08T11:36:39.521Z",
9
9
  "skills": [
10
10
  "video-assemble",
11
11
  "video-cut",
@@ -18,8 +18,8 @@
18
18
  "files": [
19
19
  {
20
20
  "path": "CHANGELOG.md",
21
- "sha256": "8b6c971f123fe170a97e7c517131fab120a2fcb5cb9bd06ebd2a795a7d26dfc3",
22
- "bytes": 104618
21
+ "sha256": "043143b851ca8357b371bb782c838a5e6593b6018b911edacab58fa862213786",
22
+ "bytes": 107052
23
23
  },
24
24
  {
25
25
  "path": "LICENSE",
@@ -28,13 +28,13 @@
28
28
  },
29
29
  {
30
30
  "path": "README.en.md",
31
- "sha256": "7557cd1dee6734697a26517d40a6337c2de5bcc470c17fd739c2a496fb018a24",
32
- "bytes": 30570
31
+ "sha256": "61ca8e1d4d2ff312fb15444a6dfe1044de77ce83f47063b16db3a245a2a41d02",
32
+ "bytes": 30977
33
33
  },
34
34
  {
35
35
  "path": "README.md",
36
- "sha256": "d70a2957ee9f1dcaa685fba9420ccd12506dc7509b2d96a647937ce535c465a4",
37
- "bytes": 26510
36
+ "sha256": "e431bb4c4fb45c21e8d9a9e4523bcaceb14cb5be545c55fbc7af3f379252f253",
37
+ "bytes": 26828
38
38
  },
39
39
  {
40
40
  "path": "skills/video-assemble/SKILL.md",
@@ -398,8 +398,8 @@
398
398
  },
399
399
  {
400
400
  "path": "skills/video-recap/SKILL.md",
401
- "sha256": "940ba0b6621d59f1fe30f7e598a80f96acfb700628bbdd86d852f98b32c5c38b",
402
- "bytes": 16498
401
+ "sha256": "dcee225f8a0e2f797ef03640bf5462c91b28efb34f4e9378ed7fcad373d5df2c",
402
+ "bytes": 17027
403
403
  },
404
404
  {
405
405
  "path": "skills/video-recap/references/audio-routing.md",
@@ -408,8 +408,8 @@
408
408
  },
409
409
  {
410
410
  "path": "skills/video-recap/references/config-playbook.md",
411
- "sha256": "45a1cbdff2c66fb0a083341355f3e051f8bb2a50a9943b1a29ba329421b1a68e",
412
- "bytes": 12839
411
+ "sha256": "0cfc3cf66d39a9faa56d1b61e1b1fba3fefb81a825a3352174a2d0f286cbd821",
412
+ "bytes": 13265
413
413
  },
414
414
  {
415
415
  "path": "skills/video-recap/references/data-schema.md",
@@ -453,8 +453,8 @@
453
453
  },
454
454
  {
455
455
  "path": "skills/video-recap/scripts/recap_cli.py",
456
- "sha256": "45058a6ea3cb147b79e3765d2314e184c0732acf9e08760d0bbac1aaa36c21d9",
457
- "bytes": 8819
456
+ "sha256": "02af53cc5a70229b8b4cafac470a650c996e6c6dc8403c96bb3c77f8a57bab4a",
457
+ "bytes": 9088
458
458
  },
459
459
  {
460
460
  "path": "skills/video-recap/scripts/recap_inspect.py",
@@ -468,8 +468,8 @@
468
468
  },
469
469
  {
470
470
  "path": "skills/video-recap/scripts/recap_runner.py",
471
- "sha256": "4643ddc62689940cf5ffff76f8f862bab10cb79a52ad392364d33172685f2ebc",
472
- "bytes": 28261
471
+ "sha256": "63eb03a6f383785cdbd1e1af736d97bc8f763045b63903a3cf1c5513485e07cf",
472
+ "bytes": 28906
473
473
  },
474
474
  {
475
475
  "path": "skills/video-recap/scripts/recap_runtime.py",
@@ -548,8 +548,8 @@
548
548
  },
549
549
  {
550
550
  "path": "skills/video-script/SKILL.md",
551
- "sha256": "387b8e7f57c11938c934d736ed5df564871b8f654a7d421dc2aeef73537dc3c9",
552
- "bytes": 15352
551
+ "sha256": "7ae12d12f77e300829650789452caf867f1cab6bd4f8e002fcb3fc4394b54369",
552
+ "bytes": 18296
553
553
  },
554
554
  {
555
555
  "path": "skills/video-script/references/creative-editing-playbook.md",
@@ -618,8 +618,8 @@
618
618
  },
619
619
  {
620
620
  "path": "skills/video-script/scripts/review_runner.py",
621
- "sha256": "da3c0b402bd11df72fad80a8a4f266e329a5b2c807f97ff53d92b67a7df4712b",
622
- "bytes": 6667
621
+ "sha256": "653c4b17ae6c6fc283934a82cd71801b00897bc9e0b7b862c64fdfe13985cecb",
622
+ "bytes": 7307
623
623
  },
624
624
  {
625
625
  "path": "skills/video-script/scripts/speech_ownership.py",
@@ -758,8 +758,8 @@
758
758
  },
759
759
  {
760
760
  "path": "skills/video-voiceover/SKILL.md",
761
- "sha256": "c6b407aec1310c2db4d7704bc522700461fae681cdfcafd99a533676db4192a1",
762
- "bytes": 7583
761
+ "sha256": "2ba0c0c0ded5d561f89ed80d42b7075edc469fb820224a547e97d122ff50fdf9",
762
+ "bytes": 11407
763
763
  },
764
764
  {
765
765
  "path": "skills/video-voiceover/references/index-tts.md",
@@ -773,8 +773,8 @@
773
773
  },
774
774
  {
775
775
  "path": "skills/video-voiceover/scripts/dub.py",
776
- "sha256": "78d494206648c6adb1637ec81a288dccc71a02beda53e646269b51a01a3ac7d3",
777
- "bytes": 25292
776
+ "sha256": "fd6eb27baf2c16f542e177d5ab99c55761c54da16efba71f1a4760f338851dd1",
777
+ "bytes": 26601
778
778
  },
779
779
  {
780
780
  "path": "skills/video-voiceover/scripts/lib.py",
@@ -210,10 +210,12 @@ python3 scripts/recap.py adopted.mp4 --work-dir packaging_work --audio-mode adop
210
210
 
211
211
  ## 5. 英译中原声复刻模式
212
212
 
213
- `--edit-mode dub` 把英文视频翻译为中文,并用原说话者的克隆音色替换人声;它不是在压低原声上叠加解说。
213
+ `--edit-mode dub` 是实验功能:把英文视频翻译为中文,并用原说话者的克隆音色替换人声;它不是在压低原声上叠加解说。只在用户明确要求英译中配音时使用。
214
+
215
+ dub 会把源视频音轨分窗发给 MiMo ASR 转写,并把截取的约 10 秒原说话人声音作为参考,连同每句译文发给 MiMo voiceclone。因此必须带 `--confirm-voice-rights`:先向用户确认他有权使用这段视频与音频、且说话人同意被克隆声音,确认后才加这个参数;缺少时启动即报错,不抽音频、不发请求。该参数只用于 dub,其他模式传入会报错。
214
216
 
215
217
  ```bash
216
- python3 scripts/recap.py <video> --edit-mode dub --work-dir <work_dir>
218
+ python3 scripts/recap.py <video> --edit-mode dub --confirm-voice-rights --work-dir <work_dir>
217
219
  ```
218
220
 
219
221
  准备阶段会转写英文、提取一段参考音频,并写出 `dub_brief.md` 与 `dub_transcript.json`。Agent 按 `dub_brief.md` 里的翻译要求(逐句忠实、时间窗、语速)写 `dub_script.json`:
@@ -41,7 +41,7 @@ Defaults below are bundle-level defaults unless a note scopes them to a specific
41
41
  | Final loudness | `FINAL_LOUDNORM` / `TARGET_LUFS` / `LOUDNESS_LIMITER_MAX_DB` | `true` / `-14` / `6` | end-of-pipeline two-pass normalize with one constant gain, never dynamically compressed; when the mix's true peak has no room for that gain an oversampled true-peak limiter takes up to `LOUDNESS_LIMITER_MAX_DB` of peak first, and only the gain beyond that lowers the target (`0` disables the limiter). The -1 dBTP ceiling holds for the delivered AAC file: the first render aims 0.5 dB under it, the encoded file is decoded and measured, and an overshoot re-encodes only the audio lower (at most twice; still over blocks with `delivered_true_peak_over_target`). `assembly_qc.json` records the mode ffmpeg actually ran (`loudness_mode`, `loudnorm_final_pass` incl. `peak_limiter` and the measured `delivered` file) |
42
42
  | Output compression | `OUTPUT_CRF` / `OUTPUT_PRESET` / `OUTPUT_MAX_HEIGHT` | `18` / `veryfast` / `0` | x264 re-encode controls, applied whenever the final mux re-encodes (burning subtitles / masking / scaling / `FORCE_VIDEO_REENCODE`, or a source picture that is not H.264 8-bit 4:2:0 with even dimensions; the delivery is always H.264 8-bit 4:2:0 with `+faststart`: `yuv420p`, or a full-range `yuvj420p` H.264 source copied unchanged; tagged BT.709 when the source is untagged or BT.709, keeping any other declared colour tags, and an RGB source (colour space `gbr` or an RGB/paletted pixel format) is converted to BT.709 limited range; multi-source cuts whose sources disagree convert every clip's matrix and range to BT.709 limited. Dub mode (`--edit-mode dub`) copies the source picture as is). Higher `OUTPUT_CRF` = smaller file/lower quality (18≈visually lossless, 23–26 much smaller); `slow`/`slower` preset shrinks more at the same CRF; `OUTPUT_MAX_HEIGHT>0` downscales the final height (keeps aspect, even width), e.g. `720` to halve 1080p pixels. Subtitles/mask render at native res then downscale, so they stay crisp |
43
43
  | Style | `--style` | `纪录片` | freeform verbatim guidance passed into the agent brief. The agent synthesizes voice/pacing from this exact text plus evidence; it is not a fixed option list, preset, switch, or finite style taxonomy |
44
- | Edit mode | `EDIT_MODE` / `--edit-mode` | `full` | `full`, `cut`, or `dub`; multi-source input supports `cut` only |
44
+ | Edit mode | `EDIT_MODE` / `--edit-mode` | `full` | `full`, `cut`, or `dub`; multi-source input supports `cut` only. Experimental `dub` also requires `--confirm-voice-rights` (the user may use the source audio and the speaker consents to voice cloning) and is rejected without it |
45
45
  | Cut target | `TARGET_DURATION` / `--target-duration` | — | e.g. `10m` (cut mode) |
46
46
  | Scene threshold | `--scene-threshold` | `0.1` | scene-cut sensitivity |
47
47
  | Shot-change-aware cut | `SCENE_CUT_SNAP` / `SCENE_CUT_SNAP_MARGIN` / `SCENE_CUT_DETECT_THRESHOLD` | on / `0.5` / `0.4` | cut mode: nudge each clip boundary off the original footage's hard cuts so the edit point doesn't flash a sliver of the adjacent shot (闪烁). source_start moves forward onto / source_end back onto any shot-change within the margin; boundaries already on a cut, or that would shrink a clip below ~0.5s, are left as-is. Set `SCENE_CUT_SNAP=0` to disable |
@@ -49,7 +49,7 @@ Defaults below are bundle-level defaults unless a note scopes them to a specific
49
49
  | Subtitle size | `SUBTITLE_FONT_SIZE` / `SUBTITLE_MARGIN_V` | `42` / `48` | look & placement |
50
50
  | Subtitle font | `SUBTITLE_FONT_NAME` / `SUBTITLE_FONT_FILE` | `Arial` / — | with a font file, burned ASS subtitles load fonts from its directory (`fontsdir`) and on-screen text overlays use it (`fontfile`); `SUBTITLE_FONT_NAME` must be that file's family name. A bound `subtitle_style` template sets both |
51
51
  | 整理 / index | `--no-consolidate` / `--consolidate-asr` | on | build the understanding index (and optionally clean ASR); use `--no-consolidate` to skip |
52
- | Advisory / strict narration review | `REVIEW_NARRATION` / `--review-narration` / `--no-review-narration`; strict: `REQUIRE_NARRATION_REVIEW` / `--require-narration-review` | advisory on, strict off | runs the narration review stage after validation and before TTS. Default advisory mode is fail-open; strict mode blocks TTS on review failure, parse error, or error-severity findings. In cut mode the reviewer uses `clip_plan_validated.json` to remap VLM/ASR grounding onto the output timeline |
52
+ | Advisory / strict narration review | `REVIEW_NARRATION` / `--review-narration` / `--no-review-narration`; strict: `REQUIRE_NARRATION_REVIEW` / `--require-narration-review` | advisory on, strict off | runs the narration review stage after validation and before TTS; it sends the narration text plus text excerpts of the understanding evidence and planning files (no video, frames or audio) to the MiMo chat endpoint, so `--no-review-narration` / `REVIEW_NARRATION=0` keeps the script on this machine unless strict review is on. Default advisory mode is fail-open; strict mode blocks TTS on review failure, parse error, or error-severity findings. In cut mode the reviewer uses `clip_plan_validated.json` to remap VLM/ASR grounding onto the output timeline |
53
53
  | 剪映 export (optional) | `--export-jianying` / `EXPORT_JIANYING` | off | after rendering, also write a 剪映/JianYing draft from `timeline.json`. Decoupled — the core render never needs it |
54
54
  | 剪映 draft dir | `JIANYING_DRAFT_DIR` | work_dir | parent folder for the exported draft (point it at 剪映's drafts root to open in-app) |
55
55
  | 剪映 bundle media | `JIANYING_BUNDLE_MEDIA` / `--jianying-no-bundle-media` | **on** | copies video/audio/photo into `Resources/local/*`, uses draft-placeholder paths, and writes the type-0 material index in `draft_meta_info.json`. This makes a cloned/moved draft self-contained and is required on sandboxed macOS. Use `--jianying-no-bundle-media` only if 剪映 can reach the original paths |
@@ -92,6 +92,12 @@ def parse_args(argv=None):
92
92
  default=os.environ.get("EDIT_MODE", "full"),
93
93
  choices=["full", "cut", "dub"],
94
94
  )
95
+ core.add_argument(
96
+ "--confirm-voice-rights",
97
+ action="store_true",
98
+ help="dub mode only, required there: the user confirms they may use this video's audio "
99
+ "and clone its speaker's voice (sent to MiMo ASR and MiMo voiceclone)",
100
+ )
95
101
  core.add_argument(
96
102
  "--target-duration", default=os.environ.get("TARGET_DURATION") or None
97
103
  )
@@ -434,6 +434,8 @@ def main():
434
434
  if (args.doctor or needs_voiceover(args)) and args.tts_provider not in TTS_PROVIDERS:
435
435
  ap.error("TTS_PROVIDER/--tts-provider must be one of: " + ", ".join(TTS_PROVIDERS))
436
436
 
437
+ if args.edit_mode != "dub" and args.confirm_voice_rights:
438
+ ap.error("--confirm-voice-rights only applies to --edit-mode dub")
437
439
  if args.doctor:
438
440
  if any(
439
441
  getattr(args, field) is not None
@@ -501,6 +503,12 @@ def main():
501
503
  )
502
504
  if args.edit_mode == "dub" and args.burn_subtitles:
503
505
  ap.error("--burn-subtitles is only supported in full/cut modes; dub never burns subtitles")
506
+ if args.edit_mode == "dub" and not args.confirm_voice_rights:
507
+ ap.error(
508
+ "--edit-mode dub requires --confirm-voice-rights: dub sends the source audio to "
509
+ "MiMo ASR and clones the original speaker's voice with MiMo voiceclone; confirm only "
510
+ "when the user has the rights to this video and the speaker's consent to clone the voice"
511
+ )
504
512
  if (args.subtitle_y_top is None) != (args.subtitle_y_bot is None):
505
513
  ap.error("--subtitle-y-top and --subtitle-y-bot must be provided together")
506
514
  if args.subtitle_y_top is not None:
@@ -572,7 +580,8 @@ def _run_dub(video, work_dir, args):
572
580
  One pause: prepare (ASR + sentence-seg + reference) -> agent writes the Chinese
573
581
  translation (dub_script.json) -> render (clone TTS + full-replace mux)."""
574
582
  dub_script = work_dir / "dub_script.json"
575
- dub_args = ["--video", str(video), "--work-dir", str(work_dir)]
583
+ # main() has already required --confirm-voice-rights for dub; forward it to both stages.
584
+ dub_args = ["--video", str(video), "--work-dir", str(work_dir), "--confirm-voice-rights"]
576
585
  if not dub_script.exists():
577
586
  _run("video-voiceover", "dub.py", "--stage", "prepare", *dub_args)
578
587
  _write_run_manifest(work_dir, video, args)
@@ -5,6 +5,9 @@ description: >
5
5
  宣发标题、花字修订和外部文案回填。普通策划输入 work_dir 的 agent_narration_brief.md 与
6
6
  vlm_analysis.json;文案返修输入当前成片的工程与内容证据。策划输出 recap_story_plan.json、visual_audio_board.json、
7
7
  可选 style_card.json、cut 模式需要的 clip_plan.json,以及通过校验的 narration.json;仅宣发文案任务交付提案或回填既有包装计划。
8
+ 外发说明:只有建议型评审 review.py 联网,它把旁白稿全文与理解证据、策划文件的文字摘录发到 MiMo chat 接口
9
+ (MIMO_API_KEY / MIMO_API_URL,不发视频、图片或音频);单独使用时只在显式执行时运行,端到端编排默认在 TTS 前运行一次,
10
+ 可用 --no-review-narration / REVIEW_NARRATION=0 关闭(严格评审开启时除外);validate.py 与 lint 仅在本地运行。
8
11
  触发词:解说词、写解说、视频旁白、宣发标题、花字修订、文案回填、
9
12
  narration script、写稿、解说文案、剪辑思路、导演思路。
10
13
  ---
@@ -37,7 +40,30 @@ Agent 先记录简洁决定,再写时间线产物。`validate.py` 负责对理
37
40
 
38
41
  REVISION 先明确本轮修改项与冻结项,再编辑对应层:表达、口语节奏、字幕反馈更新 `style_card.json`;镜头、入出点、表演和声音分工更新 `visual_audio_board.json`;只有观众承诺、POV、主线或 beat 改变时才更新 `recap_story_plan.json`。被删除的镜头、原声或文案也要从相关计划中删除,不能保留过期锚点。不要把看片修改重新做成一次 CREATE。
39
42
 
40
- ## 2. 读取素材并确认状态
43
+ ## 2. 远程调用与数据外发
44
+
45
+ 本技能只有一个脚本会联网:`review.py`(§7.1 的建议型语义评审)。其余全部在本地运行:`validate.py` 及其 lint、去 AI 味检查只读写 `work_dir` 里的文件,不发任何网络请求;策划与写稿由 Agent 直接读写 `work_dir` 完成。
46
+
47
+ `review.py` 发给谁:
48
+
49
+ - MiMo 的 OpenAI 兼容 chat 接口 `<MIMO_API_URL>/chat/completions`,用 `MIMO_API_KEY` 认证,模型为 `MIMO_MODEL`(默认 `mimo-v2.5`)。未设 `MIMO_API_URL` 时,按量计费 key 发往 `https://api.xiaomimimo.com/v1`,`tp-` 开头的 Token Plan key 发往 `MIMO_TOKEN_PLAN_CLUSTER` 选定的 token-plan 集群(默认 `https://token-plan-cn.xiaomimimo.com/v1`)。
50
+ - 未设 `MIMO_API_KEY` 时直接退出,不构造也不发送任何请求。
51
+
52
+ `review.py` 发送什么(只有文字,不发视频、帧图片或音频文件):
53
+
54
+ - `narration.json` 全部旁白段的时间与文字。
55
+ - 理解索引的文字证据:开头、中段、结尾、长空档与各旁白时间窗附近选出的时间段内,`vlm_analysis.json` 的场景描述与 `frame_facts` 文字,以及对白转写(有 `asr_clean.json` 时用它,否则 `asr_result.json`)。
56
+ - `background_research.json` 的梗概、角色、关系与剧情线摘录。
57
+ - 存在时附上 `packaging_plan.json`、`recap_story_plan.json`、`visual_audio_board.json`、`style_card.json`(各截前 3000 字)和 `original_subtitles.json` 的原声字幕文字。
58
+ - 内容较长时分块,每块一次请求;返回的意见只写回本地 `narration_review.json` / `narration_review.md`。
59
+
60
+ 何时运行、怎样关闭:
61
+
62
+ - 单独使用本技能时,`review.py` 只在用户或 Agent 显式执行它时运行;不执行它就没有任何外发。用户不希望稿件离开本机时,不要运行它,改为人工复核后直接跑 `validate.py`。
63
+ - 端到端编排默认在 TTS 前自动调用一次这项评审,失败不阻断;关闭方式是编排入口的 `--no-review-narration` 或环境变量 `REVIEW_NARRATION=0`。若同时开启了严格评审(`--require-narration-review` 或 `REQUIRE_NARRATION_REVIEW=1`),评审必须运行,关闭开关不生效。
64
+ - 评审是建议型的:只写报告,从不改写 `narration.json`;是否修改由 Agent 或用户决定。
65
+
66
+ ## 3. 读取素材并确认状态
41
67
 
42
68
  首先阅读:
43
69
 
@@ -46,7 +72,7 @@ REVISION 先明确本轮修改项与冻结项,再编辑对应层:表达、
46
72
  - `timeline_fusion.json`:判断某段是否有对白或静音槽。
47
73
  - `vlm_analysis.json` / `asr_result.json`:核对具体画面与原声证据;有 `asr_clean.json` 时以它的文本为准(lint、评审、剪辑和合成都读它)。
48
74
  - brief 顶部列出的 contact sheet:不要只依赖场景摘要;反应、走位、静止和台词前后的具体时刻常常更重要。
49
- - `production_reference.json`(仅当 `work_dir` 里有):另一部成片拆出的可迁移方法与节奏数值,用法见 §3。
75
+ - `production_reference.json`(仅当 `work_dir` 里有):另一部成片拆出的可迁移方法与节奏数值,用法见 §4。
50
76
 
51
77
  full 模式使用原片时间。cut 模式第一阶段只写 `clip_plan.json`;`edited_source.mp4` 产生后,第二阶段才按输出时间写 `narration.json`。
52
78
 
@@ -59,7 +85,7 @@ full 模式使用原片时间。cut 模式第一阶段只写 `clip_plan.json`;
59
85
 
60
86
  必须确认旁白没有跨越错误剪辑边界,也没有落进已删除区间。整个判断只依赖 `work_dir` 产物。
61
87
 
62
- ## 3. 制定创作方案
88
+ ## 4. 制定创作方案
63
89
 
64
90
  先阅读 `references/creative-editing-playbook.md`,再按创作控制模式写或更新工作产物:
65
91
 
@@ -71,7 +97,7 @@ full 模式使用原片时间。cut 模式第一阶段只写 `clip_plan.json`;
71
97
 
72
98
  若 `work_dir` 有 `production_reference.json`,制定方案前先读它。它来自另一部成片,只含可迁移的方法和测得的节奏,不含本片事实,不能作为本片画面、剧情或台词的证据。优先级:用户指令 > 本片证据 > 参考。CREATE 可把它的 `structure` 当作一个候选假设,与素材自生的假设比较;DIRECTED / REVISION 默认不套用。`targets` 是参考值,不是配额:与本片的 `audio_owner`、完整台词或表演冲突时以素材为准。可在 `recap_story_plan.json` 写可选字段 `reference_methods: [{"id": "m1", "decision": "adopt|adapt|skip", "note": "…"}]`。没有这个文件就跳过本段。
73
99
 
74
- ### 3.1 导演判断
100
+ ### 4.1 导演判断
75
101
 
76
102
  锁定:
77
103
 
@@ -82,11 +108,11 @@ full 模式使用原片时间。cut 模式第一阶段只写 `clip_plan.json`;
82
108
  - 隐瞒与揭示
83
109
  - 结尾余味
84
110
 
85
- ### 3.2 故事编辑
111
+ ### 4.2 故事编辑
86
112
 
87
113
  CREATE 比较两个真正可行的结构后选择一个;DIRECTED / REVISION 沿用用户指定或已确认的结构,除非最新反馈明确改变故事方向。每个 beat 至少改变一项:知识、权力、目标、关系、情绪或风险。若删除后因果、人物和情绪都没有损失,该 beat 通常不应保留。
88
114
 
89
- ### 3.3 画面剪辑
115
+ ### 4.3 画面剪辑
90
116
 
91
117
  选择具体时刻,而不是只选择事件。比较:
92
118
 
@@ -97,7 +123,7 @@ CREATE 比较两个真正可行的结构后选择一个;DIRECTED / REVISION
97
123
 
98
124
  在不破坏理解的前提下晚进早出,同时保留不可替代的表演、停顿、失误、动作声和完整台词。
99
125
 
100
- ### 3.4 声音与旁白分工
126
+ ### 4.4 声音与旁白分工
101
127
 
102
128
  先指定 `audio_owner`,再写字。旁白只允许承担以下 `narration_job`:
103
129
 
@@ -110,7 +136,7 @@ CREATE 比较两个真正可行的结构后选择一个;DIRECTED / REVISION
110
136
 
111
137
  画面、原声或沉默已经足够时使用 `none`,不要默认铺旁白。
112
138
 
113
- ### 3.5 cut 模式第一阶段
139
+ ### 4.5 cut 模式第一阶段
114
140
 
115
141
  cut 模式先根据 `recap_story_plan.json` 与 `visual_audio_board.json` 写原片时间的 `clip_plan.json`,此时不要写 `narration.json`:
116
142
 
@@ -135,7 +161,7 @@ beat_id | function | change | POV | preferred moment | 入点 | 出点
135
161
 
136
162
  片段顺序必须构成一条完整故事线,而不是无序高光。可使用 0–1 个 cold open,随后回到因果清楚的 setup → turn → escalation → payoff。片段长度服从具体时刻,不使用统一秒数模板;片尾必须保留完整台词或动作。密集 scene-change 候选的来源判断与处理规则由剪辑阶段定义,写计划时遵循同一规则,不制造人工闪切。
137
163
 
138
- ## 4. 撰写旁白
164
+ ## 5. 撰写旁白
139
165
 
140
166
  full 模式直接按原片时间写;cut 第二阶段先查看 `edited_source.mp4` 与剪后故事板,补充 `visual_audio_board.json` 的输出时间并重新确认 `audio_owner` / `narration_job`,再按输出时间写:
141
167
 
@@ -162,7 +188,7 @@ full 模式直接按原片时间写;cut 第二阶段先查看 `edited_source.m
162
188
  | `overlaps_speech` | 是否与原对白重叠;连续铺底窗口通常为 `true`,真正静音槽才为 `false` |
163
189
  | `emotion` | 整个解说块的 MiMo TTS 情绪/语气标签 |
164
190
 
165
- ### 4.1 写作规则
191
+ ### 5.1 写作规则
166
192
 
167
193
  1. **先有 `narration_job`,后有句子**:没有明确任务就不写;旁白不是默认音轨。
168
194
  2. **按连续思路写**:旁白拥有一个 beat 时,用一个或少量完整句子完成“前提 → 触发动作 → 变化/意义”,并在一次 TTS 中合成。句号服从口语思路和呼吸,不服从字幕换行;不要固定句数,也不要“一句一停”。
@@ -174,7 +200,7 @@ full 模式直接按原片时间写;cut 第二阶段先查看 `edited_source.m
174
200
  8. **写给耳朵听**:使用具体名词和动词,句子完整、口语可听;避免字幕腔、半句、空泛拔高和破折号。TTS 文本先保证听感连续,字幕再按阅读宽度拆分,不能反过来把朗读稿切碎。
175
201
  9. **避免模板化纠偏**:“不是 A,而是 B”只在确实存在一个观众可能相信、而素材又要纠正的判断时使用。它不是禁句,但不能靠先否定再肯定制造假洞察;优先直接写人物的动作、因果和后果。
176
202
 
177
- ### 4.2 解说结构
203
+ ### 5.2 解说结构
178
204
 
179
205
  - **钩子**:提出正文会真实兑现的问题或利害,不用无关留存话术。
180
206
  - **主线**:围绕选定 POV 与主线推进,不在每个场景重新开篇。
@@ -183,7 +209,7 @@ full 模式直接按原片时间写;cut 第二阶段先查看 `edited_source.m
183
209
  - **收尾**:回答或有意转化开头问题,留下明确余味。
184
210
  - **衔接**:旁白块与相邻原声属于同一个 beat,前者铺垫、后者呈现、下一块承接。
185
211
 
186
- ### 4.3 原声留白字幕
212
+ ### 5.3 原声留白字幕
187
213
 
188
214
  可选写 `original_subtitles.json`,使用成片输出时间:
189
215
 
@@ -193,7 +219,7 @@ full 模式直接按原片时间写;cut 第二阶段先查看 `edited_source.m
193
219
 
194
220
  只写留白中实际听得到的台词,订正 ASR 错字与人名,每条尽量控制在一行;被旁白盖住或已经剪掉的句子不要写。省略时,合成阶段会使用保守的 ASR 映射兜底,并在成片中用 `「」` 区分原声对白与旁白。
195
221
 
196
- ## 5. 创作自审
222
+ ## 6. 创作自审
197
223
 
198
224
  在调用 LLM 评审前做以下**反事实检查**:
199
225
 
@@ -209,12 +235,12 @@ full 模式直接按原片时间写;cut 第二阶段先查看 `edited_source.m
209
235
 
210
236
  REVISION 还要逐项确认:用户点名的问题已经改变,未点名的冻结项没有意外变化,相关 `style_card.json` / `visual_audio_board.json` 中不存在旧镜头或旧表达。除非用户要求备选版本,不额外扩展新方向。
211
237
 
212
- ## 6. 评审与校验
238
+ ## 7. 评审与校验
213
239
 
214
- ### 6.1 建议型语义评审
240
+ ### 7.1 建议型语义评审
215
241
 
216
242
  ```bash
217
- python3 scripts/review.py --work-dir <work_dir>
243
+ python3 scripts/review.py --work-dir <work_dir> # 会联网:外发内容与关闭方式见 §2
218
244
  ```
219
245
 
220
246
  评审会自动识别 cut 模式,并在存在已校验剪辑计划时按输出时间线核对;`--timeline source` 可强制使用原片时间。打开 `narration_review.md`,逐项处理 `error`,尤其是 `category=hallucination`。
@@ -236,7 +262,7 @@ python3 scripts/review.py --work-dir <work_dir>
236
262
 
237
263
  `review.py` 本身只写报告,默认调用策略为建议型、失败开放;若调用方显式开启严格评审,事实矛盾、残句、解析失败或评审不可用可在 TTS 前阻断。覆盖记录只用于审计,`review.py` / `validate.py` 不读取它。
238
264
 
239
- ### 6.2 确定性硬校验
265
+ ### 7.2 确定性硬校验
240
266
 
241
267
  ```bash
242
268
  python3 scripts/validate.py --work-dir <work_dir> --mode full
@@ -247,7 +273,8 @@ python3 scripts/validate.py --work-dir <work_dir> --mode full
247
273
 
248
274
  片名或题材明确但缺少剧情上下文时,先按本技能的 `references/research-guide.md` 写 `background_research.json`。若理解素材偏薄,brief 中的数量只能当上限:宁可少写、写实,也不要为凑数复述画面。
249
275
 
250
- ## 7. 能力边界
276
+ ## 8. 能力边界
251
277
 
252
278
  - 不运行 ASR / VLM,不合成 TTS,不渲染视频;只消费视频理解索引。
279
+ - 唯一的远程调用是 `review.py` 的 MiMo 文字评审(见 §2);`validate.py` 与 lint 只在本地运行。
253
280
  - 平台研究仅用于明确的宣发任务;不替代当前片内事实,也不默认改变解说和剪辑。
@@ -1,4 +1,8 @@
1
- """Run narration review and write narration_review.json / .md."""
1
+ """Run narration review and write narration_review.json / .md.
2
+
3
+ Network: sends the narration text plus text excerpts of VLM/ASR evidence, research and planning
4
+ files to the MiMo chat endpoint (MIMO_API_KEY / MIMO_API_URL); no video, frames or audio.
5
+ """
2
6
 
3
7
  import argparse
4
8
 
@@ -142,7 +146,8 @@ def _auto_timeline(work_dir):
142
146
 
143
147
  def main():
144
148
  ap = argparse.ArgumentParser(
145
- description="Review an agent-written narration.json for quality (LLM-as-judge)."
149
+ description=("Review an agent-written narration.json for quality (LLM-as-judge). Sends the narration "
150
+ "text and text excerpts of the evidence to the MiMo chat endpoint (MIMO_API_KEY); no media.")
146
151
  )
147
152
  ap.add_argument("--work-dir", required=True)
148
153
  ap.add_argument(
@@ -159,6 +164,10 @@ def main():
159
164
  help="block instead of advisory fail-open when required cut-output evidence mapping is missing/stale",
160
165
  )
161
166
  args = ap.parse_args()
167
+ if not CONFIG["api_key"]:
168
+ # The review sends narration and evidence text to MiMo; without a key it would only
169
+ # post that text to be rejected, so stop before building any request.
170
+ raise SystemExit("review.py 需要 MIMO_API_KEY:未设置,评审未运行,也未发送任何内容")
162
171
  timeline = args.timeline or _auto_timeline(args.work_dir)
163
172
  if args.timeline is None and timeline != "source":
164
173
  log(f"评审 grounding 时间轴自动判定为 {timeline}(检测到已校验的剪辑产物)")