--- name: video-cut user-invocable: false description: > 把长视频按 Agent 选择的原片区间剪成短片。作为两阶段创作流程中的剪辑环节,读取 clip_plan.json 与源视频, 输出 edited_source.mp4;随后 Agent 按输出时间线写 narration.json。支持单视频与多视频(sources manifest)拼剪, 本工具不读取、不映射旁白。 触发词:视频剪辑、剪辑式解说、video cut、clip plan、拼剪。 --- ## 1. 定位 本技能只执行 Agent 已经做出的剪辑决定: 1. 校验并补全 `clip_plan.json`,写出带 `clip_id`、原片/输出时间与时长的 `clip_plan_validated.json`。 2. 先避开原片硬切附近的闪帧风险,再把边界吸附到可靠句末/自然停顿;声音完整性拥有最终优先级。 3. 把每个入点对齐到源视频帧网格、每段时长对齐到整数个输出帧(不足一帧的移动,优先选句界门禁仍判为安全、仍在停顿内的一侧;两侧同样安全时选不跨过原片硬切的一侧,避免闪一帧),句界门禁检查的是对齐后的边界。 4. 拼接选定区间,输出恒定帧率的 `edited_source.mp4`,帧数与 `clip_plan_validated.json` 记录的一致。 5. 到此停止,由 Agent 按真实输出时间线写 `narration.json`;本工具不读取旁白,也不做原片→输出映射。 相同输入会得到相同输出。`edited_source.mp4.meta.json` 记录标准化 clips、渲染设置和每个源文件的 `size`/`mtime_ns`;三者与当前一致且 `edited_source.mp4` 存在非空才复用,任一不同即重渲染。只有 sidecar 而没有媒体文件不复用。 ## 2. 输入契约 `work_dir/clip_plan.json` 可以是数组,也可以是 `{"clips": [...]}`: ```json {"start": 12.0, "end": 28.5, "reason": "b02 | turn | power: A→B | POV=女主 | 保留反应 | 入点=问题落下 | 出点=沉默结束"} ``` - `start` / `end` 是原片秒数;也接受 `source_start` / `source_end` 或 `in` / `out`。 - 顶层可选 `target_duration`,例如 `"10m"`。 - 多视频项目的每个片段还必须填写 `source_id`(不接受 `id` 代替),并用 `--sources-manifest` 传入来源清单(形状见下)。 - `speech_boundary_anchors.json` 与 ASR 时间段由理解阶段提供;Agent 先写大致区间,工具会尝试吸附并把仍在讲话区间内的入/出点作为 blocker 返回。只含语气词或 ASR 杂音的窗口("啊!"、"Hi.")不算讲话区间,只在紧挨真实对白的一侧保留 1 秒保护。只有标点的窗口("……")仍算讲话。 多视频来源清单只接受一种形状,其他形状直接报错并写明期望形状: ```json {"sources": [{"source_id": "ep1", "source_path": "/media/ep1.mp4", "duration": 1520.0, "source_work_dir": "sources/ep1"}]} ``` `duration` 可省略(省略时用 ffprobe 读取);`source_work_dir` 可省略,填写时相对 `--work-dir`,用于读取该来源的静音、句末锚点与 ASR。其他键忽略。 ## 3. 剪辑意图契约 工具不会替 Agent 做创作选择。写片段前先完成本节的剪辑意图检查,并让每个区间映射到 `recap_story_plan.json` 的一个 beat。 使用现有自由文本 `reason` 保存简洁决定: ```text beat_id | function | change | POV | preferred moment | 入点 reason | 出点 reason ``` 不要因为“事件重要”就保留整段;要保留最能让 change 成立的具体表演、反应、动作或揭示。理解与情绪允许时晚进早出,同时保证台词、动作和技术边界完整。 对不能删去的问答、反应或动作兑现,先核源证据,再在同一 `clip_plan.json` 登记精确区间: ```json { "clips": [{"start": 12, "end": 18}], "required_evidence": { "nodes": [ {"id": "refusal", "source": "/media/episode.mp4", "start": 12.25, "end": 14.5, "track": "audio", "content": "对方拒绝请求"}, {"id": "response", "source": "/media/episode.mp4", "start": 15, "end": 17.5, "track": "video", "content": "听到拒绝后的反应与决定"} ], "before": [["refusal", "response"]] } } ``` `source` 使用实际源文件绝对路径,`start/end` 是原片秒;多源可另填 `source_id` 消歧。只登记确实需要保留的具体时刻,不将整个 beat 默认锁死。`before` 只登记本片必需的先后关系;无需约束顺序时写 `before: []`。 工具在全部画面/句界吸附后检查每个必保时刻至少有一处完整连续保留、来源和先后;音频节点还检查源音轨是否存在。每次结果出现(包括局部片段)都需满足其声明的前提,不能用后面的完整段替开头缺前提的片段过关。结果写入 `clip_plan_validated.json.qc.required_evidence`;缺段、错序或无效声明会在预检、缓存复用和渲染前阻断,时长放宽选项不会跳过。该结果验证选段保留,实际语义与最终混音仍按审片步骤核对。 下面的 `scripts/...` 均相对于本技能目录。若执行器从仓库根目录启动,请给脚本路径加上本技能的绝对目录。 ## 4. 运行命令 ```bash python3 scripts/cut.py