{"id":"video-assemble","name":"video-assemble","summary":"コンポジットビデオの説明の最終結果:ナレーション音声をソースビデオの上に重ね、ナレーションウィンドウを押して元の音声を下げ、焼き付け可能なSRT/ASS字幕を生成し、最後に音量を正規化します。","body":"## 1. 定位\n\n本技能负责最终合成：\n\n1. 把各段旁白音频放到视频时间线上。\n2. 在旁白窗口内压低原声，支持 fixed / sidechain / zone 模式。\n3. 根据旁白位置生成 `subtitles.srt`；默认同时生成并烧录 `subtitles.ass`，`--no-burn-subtitles` 可关闭。\n4. 可选把最终响度标准化到目标 LUFS。\n\n## 2. 声音收尾契约\n\n合成阶段只实现创作决定，不凭空制造决定。Agent 在写旁白位置前，已在 `visual_audio_board.json` 为每个 beat 指定 `audio_owner`：\n\n- `original_dialogue`\n- `action_sound`\n- `ambience` / `music`\n- `silence`\n- `narration`\n\n因此，旁白间隙是主动选择，不是必须填满的空白。不要为了“更满”而加入通用 BGM、压住必须听见的台词或消除有意义的沉默。\n\n当前渲染器不解析 `visual_audio_board.json`；Agent 通过旁白时间、`overlaps_speech`、原声留白与现有混音参数落实这些决定。\n\n## 3. 输入契约\n\n- `<video>`：源视频；cut 模式下为 `edited_source.mp4`。\n- `work_dir/tts_meta.json`：配音阶段写出的 `{segments: [...]}`。每段包含 `audio_path`、时间、`pause_after_ms`、`overlaps_speech` 和用于混音/字幕的位置。\n\n下面的 `scripts/...` 均相对于本技能目录。若执行器从仓库根目录启动，请给脚本路径加上本技能的绝对目录。脚本不从其他技能目录读取文件；外部输入仅限命令显式传入的视频、参数与 `work_dir` 产物。\n\n## 4. 运行命令\n\n```bash\npython3 scripts/assemble.py <video> --work-dir <work_dir> \\\n  [--recap-stem <name>] [--output-dir <dir>] [--no-burn-subtitles] \\\n  [--subtitle-y-top <inclusive-y> --subtitle-y-bot <exclusive-y>] \\\n  [--source-video <orig.mp4>] [--export-jianying [--jianying-out <dir>]]\n```\n\n## 5. 输出契约\n\n- `recap_<stem>.mp4`：稳定的最终输出别名；每次运行覆盖更新。\n- `work_dir/output.mp4`：工作目录内成片。\n- `subtitles.srt`：旁白字幕；烧录时另有 `subtitles.ass`。\n- `timeline.json`：后端无关的多轨模型，包含视频、原声、旁白、BGM、字幕和 ducking 自动化。\n- `_placed_*.wav`：实际写入主混音的完整逐段旁白 PCM；时间线与剪映只引用这些文件。\n- `assembly_manifest.json`：输入来源、cut 来源指纹、渲染设置与最终输出路径。\n- `assembly_qc.json`：旁白完整性、原声句末交接、时间线素材时长与交付质量的发布门禁。\n- 剪映草稿目录：仅 `--export-jianying` 时生成，包含 `draft_content.json`、`draft_info.json` 与 `draft_meta_info.json`。\n\n## 6. 合成规则\n\n- 音频按轨道混合：原声、可选 BGM 与旁白各自独立。\n- 旁白不做任何容差裁尾；温和加速后仍放不下即 `no_safe_fit`。每段 `_placed_*.wav`\n  必须与序列化后的时间线区间等长或更短，否则 `timeline_audio_mismatch` 阻断。\n- 原声在旁白结束后保持压低到下一可靠句末的 `pause_start`，只在实测停顿内渐强，\n  于 `source_restore_at` 回满；无后续锚点时保持压低到时间线末端，而不是放出半句。\n- `--export-jianying` / `EXPORT_JIANYING=1` 可把 `timeline.json` 导出为可编辑草稿。cut 模式应传 `--source-video <orig>`，让草稿引用真实原片区间。\n- 剪映导出默认把视频、音频与图片复制到 `Resources/local/{video,audio,image}`，保持草稿可搬迁；`--jianying-no-bundle-media` 只适合原路径始终可访问的情况。\n- 重叠覆盖物会拆到编号轨道；非空目标目录不会覆盖，而会创建编号兄弟目录。\n- 常速、倒放、变换、富文本、转场、蒙版、LUT、绿幕复合草稿及显式特效轨道通过 timeline v2 扩展表达。需要素材包的功能只接受调用方合法提供的离线资源。\n- 剪映草稿引用未烧录的源视频，因此原片硬字幕仍会保留，必要时在剪映内另行遮罩。\n- 字幕外观可用 `SUBTITLE_FONT_SIZE`、`SUBTITLE_MARGIN_V`、`SUBTITLE_MAX_CHARS` 等控制。\n- `SUBTITLE_Y_TOP/BOT` 把 ASS 基线放到测得的原片字幕区域，坐标为半开 `[top, bot)`；显式遮罩策略下默认 `SUBTITLE_MASK_OPACITY=0.6`，`SOURCE_SUBTITLE_MASK_TIMING=narration`。\n- 原声在旁白间隙回到 `IDLE_ORIG_VOLUME`，旁白下压到 `SPEECH_DUCKING_VOLUME`；`DUCK_FADE_SECONDS` 控制过渡。还可配置 `DUCKING_MODE`、`ZONE_DUCKING_VOLUME`、`FINAL_LOUDNORM` 与 `TARGET_LUFS`。\n- 可通过 `BGM_PATH` 指定 BGM；它会循环到成片长度，并按 `BGM_VOLUME` / `BGM_DUCKING_VOLUME` 混音。不要在没有创作依据时设置通用 BGM。\n- 烧录字幕需要带 `subtitles` / libass 的 ffmpeg；合成阶段会预检并在缺失时明确失败。\n- 原声留白中的对白字幕优先读取 Agent 校对的 `original_subtitles.json`；否则保守映射 ASR。只有遮罩覆盖留白或用户字幕明确要求替换时才烧录原声对白，并用 `「」` 与旁白区分。\n\n## 7. 能力边界\n\n- 不生成旁白文字，也不合成 TTS。\n- 不重新转写视频，不擅自改变 Agent 的时间决定。\n- 字幕烧录默认开启；关闭时不会重编码绘制字幕区域。","author":"@worldwonderer","ownerProfile":null,"authorContacts":null,"sourceUrl":"https://github.com/worldwonderer/video-recap-skills/tree/main/skills/video-assemble","license":"MIT","category":null,"lang":"multi","tokens":1534,"stars":0,"calls30d":1,"claimed":false,"visibility":"public","origin":"crawler","version":"0.1.0","createdAt":"2026-08-22","updatedAt":"2026-08-22","files":[{"path":"references/jianying/empty_draft_meta_info.json","size":1768,"sha256":"ea2d503b2c6b76a39e8d2ae9ba344608204d18675f88a01cc628898ca15bfbde"},{"path":"references/jianying/empty_jy_combination_segment.json","size":1681,"sha256":"ed5b7847ea168cd91126243307377ac576605b8523a958a4e11dd09053c9527f"},{"path":"references/jianying/empty_jy_combination_video_material.json","size":2202,"sha256":"bf005f8ea51d2f0359b8f164147ed9745d5bf93357f33acb958e82a40b1e662c"},{"path":"references/jianying/empty_jy_draft.json","size":5500,"sha256":"4061fd9e751ac4800cfe8d84c844077d34e487f14f2c300a31de15be45233f21"},{"path":"references/jianying/empty_jy_material_video.json","size":1811,"sha256":"d99fd9767b80cb5403f9801f431e7e2f048440a731c1994677737f2470dcbe99"},{"path":"references/jianying/empty_jy_meta_material_value.json","size":358,"sha256":"e790f40b4779dac860cb8d28e3b54327683e17e96a03ccc4487f7fcec5d3a9f3"},{"path":"references/jianying/empty_jy_project_info.json","size":3718,"sha256":"6a60397528bf33c1adfe13d17fc441837232a3c15df9d4a4785e3e03d6d86985"},{"path":"references/jianying/empty_jy_segment.json","size":1370,"sha256":"c5caef48205ff09b3199ad2871e6b6ede71c018031a8fe2c9285f03a7cc83140"},{"path":"references/jianying/empty_jy_text_styles.json","size":440,"sha256":"e23db2336b109bc471d94d5c1af41ecee3528fe322966f4ecc52b9738c9abfd3"},{"path":"references/jianying/empty_yj_material_audio.json","size":865,"sha256":"14fe738bbf03128537c2be0a44cc011487539d8ce6d0374123f86540e35762f8"},{"path":"references/jianying/empty_yj_material_text.json","size":2447,"sha256":"d70bbce903e07b025a7235b0bfc7c2acc633257105050fc21ce7b5ada296a45b"},{"path":"references/jianying/LICENSE.duo-video","size":1062,"sha256":"b2fbd6012637739aaf80574c32ae82653bf81d35861d7e06be2a5541ea6d6a21"},{"path":"references/jianying/SOURCE.md","size":728,"sha256":"8db68179c2f66c1e8b74653f69d26a4389c8b9505892e6eca1b16da60c83f4cc"},{"path":"scripts/artifacts.py","size":2920,"sha256":"e25ab034dcdf4df7d9d441e4b49a2aeae9303cab73fcd6c4f63d4c43fcb974f6"},{"path":"scripts/assemble_constants.py","size":1044,"sha256":"6c2d0d58a7d2d5f91a7fdf9cac4c1455458f837e3cadbd03a378bd6dad0bee62"},{"path":"scripts/assemble.py","size":17395,"sha256":"c6dafc9e6d68b60b99bf0cce39fcb475059a494faf1e2c550c060506634645dd"},{"path":"scripts/assembly_contract.py","size":13548,"sha256":"63b3af7491dace0937c2eabec421bc32ba4c47478094c8b18bd6fc8c90d6c1ef"},{"path":"scripts/assembly_settings.py","size":4888,"sha256":"8a6fe52e4c75f5073b77068f803011eca3253a574f6da493778fb482da39f9b3"},{"path":"scripts/audio_automation.py","size":7355,"sha256":"9dc057d2c74eb575f6bffc7023685f1ecc211624ffb9f4cb7127075b442519e7"},{"path":"scripts/audio_mix.py","size":22591,"sha256":"f1c29d72849b6afc70ab3b1d15550bd09347e119bd74fb9cd2d2b7f810b3d986"},{"path":"scripts/export_jianying.py","size":8874,"sha256":"bf5d57dbb58b2f5b4b640035a17f318ae714fdf424d5b55945b356ffd0de7588"},{"path":"scripts/jianying_builders.py","size":33039,"sha256":"f264b8c7ac7ba9e108331021102890f970407bbf672c3d36f083000ad807cc3a"},{"path":"scripts/jianying_model.py","size":3686,"sha256":"80d60848b5a889cdce5c3a245bb58c955e67c51f5b01fb6dc59adea286240fb1"},{"path":"scripts/jianying_optional.py","size":1107,"sha256":"266291e22e09ffb049b2d839587a8133e4edc71b2426e1f90eebf1b3747f1202"}],"requires":{"mcp":[],"tools":[]},"safety":{"flags":[],"scannedAt":"2026-08-22","hasScripts":true,"networkEndpoints":[]}}