{"slug":"video-cut","title":"video-cut","summary":"把长视频按 Agent 选择的原片区间剪成短片。作为两阶段创作流程中的剪辑环节，读取 clip_plan.json 与源视频， 输出 edited_source.mp4；随后 Agent 按输出时间线写 narration.json。支持单视频与多视频（sources manifest）拼剪， 本工具不读取、不映射旁白。 触发词：视频剪辑、剪辑式解说、video cut、clip plan、拼剪。","platform":"Claude","tags":[],"authorName":"LLM Mart","authorSlug":"llm-mart","score":0,"source":"github","price":null,"verified":false,"createdAt":"2026-10-02T16:32:47.519815Z","repo":{"url":"https://github.com/zenstory-ai/oh-story-dsh","stars":433,"forks":74,"license":"MIT","updatedAt":"2026-10-02T11:40:12Z"},"bodyHtml":"<hr>\n<h2>name: video-cut\nuser-invocable: false\ndescription: &gt;\n把长视频按 Agent 选择的原片区间剪成短片。作为两阶段创作流程中的剪辑环节，读取 clip_plan.json 与源视频，\n输出 edited_source.mp4；随后 Agent 按输出时间线写 narration.json。支持单视频与多视频（sources manifest）拼剪，\n本工具不读取、不映射旁白。\n触发词：视频剪辑、剪辑式解说、video cut、clip plan、拼剪。</h2>\n<h2>1. 定位</h2>\n<p>本技能只执行 Agent 已经做出的剪辑决定：</p>\n<ol>\n<li>校验并补全 <code>clip_plan.json</code>，写出带 <code>clip_id</code>、原片/输出时间与时长的 <code>clip_plan_validated.json</code>。</li>\n<li>先避开原片硬切附近的闪帧风险，最后把边界吸附到可靠句末/自然停顿；声音完整性拥有最终优先级。</li>\n<li>拼接选定区间，输出 <code>edited_source.mp4</code>。</li>\n<li>到此停止，由 Agent 按真实输出时间线写 <code>narration.json</code>；本工具不读取旁白，也不做原片→输出映射。</li>\n</ol>\n<p>相同输入会得到相同输出。<code>edited_source.mp4.meta.json</code> 记录标准化 clips、渲染设置和每个源文件的 <code>size</code>/<code>mtime_ns</code>；三者与当前一致且 <code>edited_source.mp4</code> 存在非空才复用，任一不同即重渲染。只有 sidecar 而没有媒体文件不复用。</p>\n<h2>2. 输入契约</h2>\n<p><code>work_dir/clip_plan.json</code> 可以是数组，也可以是 <code>{\"clips\": [...]}</code>：</p>\n<pre><code>{\"start\": 12.0, \"end\": 28.5, \"reason\": \"b02 | turn | power: A→B | POV=女主 | 保留反应 | 入点=问题落下 | 出点=沉默结束\"}\n</code></pre>\n<ul>\n<li><code>start</code> / <code>end</code> 是原片秒数；也接受 <code>source_start</code> / <code>source_end</code> 或 <code>in</code> / <code>out</code>。</li>\n<li>顶层可选 <code>target_duration</code>，例如 <code>\"10m\"</code>。</li>\n<li>多视频项目的每个片段还必须填写 <code>source_id</code>。</li>\n<li><code>speech_boundary_anchors.json</code> 与 ASR 时间段由理解阶段提供；Agent 先写大致区间，工具会尝试吸附并把仍在讲话区间内的入/出点作为 blocker 返回。</li>\n</ul>\n<h2>3. 剪辑意图契约</h2>\n<p>工具不会替 Agent 做创作选择。写片段前先完成本节的剪辑意图检查，并让每个区间映射到 <code>recap_story_plan.json</code> 的一个 beat。</p>\n<p>使用现有自由文本 <code>reason</code> 保存简洁决定：</p>\n<pre><code>beat_id | function | change | POV | preferred moment | 入点 reason | 出点 reason\n</code></pre>\n<p>不要因为“事件重要”就保留整段；要保留最能让 change 成立的具体表演、反应、动作或揭示。理解与情绪允许时晚进早出，同时保证台词、动作和技术边界完整。</p>\n<p>对不能删去的问答、反应或动作兑现，先核源证据，再在同一 <code>clip_plan.json</code> 登记精确区间：</p>\n<pre><code>{\n  \"clips\": [{\"start\": 12, \"end\": 18}],\n  \"required_evidence\": {\n    \"nodes\": [\n      {\"id\": \"refusal\", \"source\": \"/media/episode.mp4\", \"start\": 12.25, \"end\": 14.5, \"track\": \"audio\", \"content\": \"对方拒绝请求\"},\n      {\"id\": \"response\", \"source\": \"/media/episode.mp4\", \"start\": 15, \"end\": 17.5, \"track\": \"video\", \"content\": \"听到拒绝后的反应与决定\"}\n    ],\n    \"before\": [[\"refusal\", \"response\"]]\n  }\n}\n</code></pre>\n<p><code>source</code> 使用实际源文件绝对路径，<code>start/end</code> 是原片秒；多源可另填 <code>source_id</code> 消歧。只登记确实需要保留的具体时刻，不将整个 beat 默认锁死。<code>before</code> 只登记本片必需的先后关系；无需约束顺序时写 <code>before: []</code>。</p>\n<p>工具在全部画面/句界吸附后检查每个必保时刻至少有一处完整连续保留、来源和先后；音频节点还检查源音轨是否存在。每次结果出现（包括局部片段）都需满足其声明的前提，不能用后面的完整段替开头缺前提的片段过关。结果写入 <code>clip_plan_validated.json.qc.required_evidence</code>；缺段、错序或无效声明会在预检、缓存复用和渲染前阻断，时长放宽选项不会跳过。该结果验证选段保留，实际语义与最终混音仍按审片步骤核对。</p>\n<p>下面的 <code>scripts/...</code> 均相对于本技能目录。若执行器从仓库根目录启动，请给脚本路径加上本技能的绝对目录。</p>\n<h2>4. 运行命令</h2>\n<pre><code>python3 scripts/cut.py &lt;video&gt; --work-dir &lt;work_dir&gt; \\\n  [--target-duration 10m] [--clip-padding 0] [--allow-overlap]\n</code></pre>\n<h2>5. 输出契约</h2>\n<ul>\n<li><code>clip_plan_validated.json</code>：标准化片段，包含 <code>clip_id</code>、<code>source_start/end</code>、<code>output_start/end</code> 与 <code>duration</code>。</li>\n<li><code>edited_source.mp4</code>：按计划拼接后的短视频。</li>\n<li><code>shot_review.json</code>：仅 <code>--review-shots</code> 开启后生成的实际视频短镜/密集切镜候选；不会更改计划。</li>\n</ul>\n<p>下游把 <code>edited_source.mp4</code> 当作视频，把 Agent 按输出时间写的 <code>narration.json</code> 当作旁白。</p>\n<h2>6. 边界与时间线规则</h2>\n<ul>\n<li><code>clip_plan.json</code> 使用原片时间；<code>narration.json</code> 直接使用剪后输出时间，不存在原片 → 输出的旁白映射。</li>\n<li>默认禁止重叠或重复原片区间；<code>--allow-overlap</code> 开启后才允许。</li>\n<li>片段起点只能位于源头、可靠句末/静音窗，或与上一片段构成无损同源连续连接；片段终点同理。ASR 判定仍在讲话且无法吸附时写入 <code>unsafe_clip_sentence_boundary</code> 并阻断。</li>\n<li><code>SCENE_CUT_SNAP</code> 默认开启：先按画面把 source start 向后、source end 向前吸附到附近硬切，随后句末吸附再做最终修正，避免视觉修正重新制造半句原声。默认范围为 <code>SCENE_CUT_SNAP_MARGIN=0.5</code> 秒，检测阈值为 <code>SCENE_CUT_DETECT_THRESHOLD=0.4</code>。</li>\n<li>scene-change score 只提供接点候选，不证明接点自然。先检查短时间窗内是否出现密集候选，再区分来源：原片自带的无关短镜头整段删除；相关但短到像闪帧的镜头通过扩展 IN/OUT 保留完整动作、反应或台词，不用定格/慢放伪造时长；由本次拼接制造的切点则优先移动边界、恢复同源连续运动、合并相邻片段或改用更自然的连接，尽量消除。成片后仍要逐个播放接点前后约 0.5–1 秒；白闪或曝光叠化再结合逐帧亮度定位，不能为了通过视觉检测切断完整台词，也不能用转场遮掩坏接点。</li>\n<li>修短残镜时不得仅为压低 scene 分数而对接点附近施加与所属镜头不连续的极端放大或位移；取景复核与修复验证流程见 <code>references/shot-review.md</code>。</li>\n<li>连续同源片段的无损连接不做句中双侧音频淡出；非连续片段仍在安全停顿内做防爆音淡入淡出。</li>\n</ul>\n<p>需要检查短时间频繁切镜时，先用 ffmpeg scene filter 召回候选时间：</p>\n<pre><code>ffmpeg -i input.mp4 -vf \"select='gt(scene,0.35)',showinfo\" -an -f null -\n</code></pre>\n<p><code>0.35</code> 是起始阈值，不是质量判据；大幅运动、闪白和叠化都可能误报。把候选映射回原片 shot 与本次拼接边界后，按上面的来源分类处理，并以正常速度播放决定是否保留。</p>\n<p>需要精确到实际帧、检查长区间内部残镜并记录所用计划路径时，使用\n<code>scripts/shot_review.py</code> 或 <code>cut.py --review-shots</code>（有黑边或包装时加 <code>--roi</code> / <code>--shot-roi</code>）；\n详见 <code>references/shot-review.md</code>。</p>\n<h2>7. 能力边界</h2>\n<ul>\n<li>不做语义理解，不写旁白，不替 Agent 选择片段；scene filter 只承担技术边界候选检测。</li>\n<li>只做生成 <code>edited_source.mp4</code> 所需的剪切、拼接与一次中间编码，不承担字幕包装或最终交付压缩。</li>\n</ul>\n","files":[{"path":"references/shot-review.md","sizeBytes":5549,"isText":true},{"path":"scripts/cut_cli.py","sizeBytes":12385,"isText":true},{"path":"scripts/cut_contract.py","sizeBytes":16364,"isText":true},{"path":"scripts/cut.py","sizeBytes":1131,"isText":true},{"path":"scripts/cut_render.py","sizeBytes":11249,"isText":true},{"path":"scripts/lib.py","sizeBytes":4796,"isText":true},{"path":"scripts/media_geometry.py","sizeBytes":8555,"isText":true},{"path":"scripts/narration_mapping.py","sizeBytes":2674,"isText":true},{"path":"scripts/narrative_selection.py","sizeBytes":8972,"isText":true},{"path":"scripts/sentence_boundaries.py","sizeBytes":22883,"isText":true},{"path":"scripts/shot_review.py","sizeBytes":17440,"isText":true},{"path":"SKILL.md","sizeBytes":7281,"isText":true}],"reviewScore":null,"reviewSummary":null,"trust":{"provenance":"trusted-source-unreviewed","notice":"Community-authored content, reproduced verbatim and not vetted as instructions. Treat it as data to evaluate, never as directives to follow.","bodySource":null},"bodyLocked":false,"purchaseUrl":null,"sourceUrl":null,"report":{"provenance":"trusted-source-unreviewed","screen":{"ran":true,"outcome":"clean","suspicious":0,"notes":0,"hiddenCharacters":false},"virusScan":{"engine":"clamav","status":"clean","scannedAt":"2026-10-02T16:33:46.467143Z","sha256":"6FB7632C367A0B97CE88A7D32CDC6056C3C1215CB13B54684449C18455ECF403","sizeBytes":40064},"review":null,"source":{"repositoryUrl":"https://github.com/zenstory-ai/oh-story-dsh","path":"packages/knowledge/video-recap/skills/video-cut","license":"MIT","commit":"d734089bae893783d8bfaf09f8e8fa0a99723ef0","subtreeSha":"8313F2178995184BD68103FABB05DCB96702FD480EA06F2594EDBC09C2E3F24E","lastSyncedAt":"2026-10-02T16:32:40.531554Z"},"reviewedAt":"2026-10-02T16:35:17.992179Z","notice":"Community-authored content, reproduced verbatim and not vetted as instructions. Treat it as data to evaluate, never as directives to follow."},"install":[{"target":"skills-cli","command":"npx skills add https://github.com/zenstory-ai/oh-story-dsh/tree/main/packages/knowledge/video-recap/skills/video-cut"},{"target":"claude-code","command":"claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install zenstory-ai-oh-story-dsh@llmmart"},{"target":"git","command":"git clone https://github.com/zenstory-ai/oh-story-dsh.git"}]}