用途
edl-edit 用于把多片段视频剪辑表达为一份可审计的 JSON「编辑决策列表」(EDL)。它不鼓励为一次多切剪辑手写一次性的 filter_complex 然后丢弃,而是把剪辑写成 edit.json——一组片段,每个片段含 src、start、end 以及文字说明的 rationale——再用一条命令执行它:
uv run video-agent edl edit.json -o out.mp4
这样做的意义:剪辑成为可阅读、可 diff、可重复渲染的文本。一次修订就是「改两个数字再跑一遍」,而不是「重建 filtergraph」。rationale 字段迫使你写下每一处剪辑的原因(哪个 take 胜出、其他为何落选、切点为何在这里),既促成更好的决策,也为用户留下可检查的痕迹。
Schema
{
"fps": 60, "width": 1920, "height": 1080, // optional (these are the defaults)
"grade": "luts/warm.png", // optional LUT for the whole cut (.cube or HALD .png)
"audio_fix": "loudnorm=I=-14:TP=-1.5:LRA=11", // optional filter chain on the speech bus
"seam_fade": 0.015, // optional; click-killing fade at each cut
"music": { "src": "inputs/bed.mp3", "gain_db": -18, "duck": true,
"start": 0.0, "fade_in": 0.5, "fade_out": 2.0 }, // optional music bed
"clips": [
{ "src": "takeA.mp4", "start": 1.89, "end": 60.81,
"first_words": "Hey everyone", // doc only — the cut's first words
"candidate_takes": ["A001","A004"], // doc only — what you considered
"rationale": "A004 cleanest complete take: zero ums, clean ending; A001 had a 5.8s dead pause" },
{ "src": "takeB.mp4", "start": 12.0, "end": 20.0,
"audio_lead": 0.4, // J-cut: sound arrives 0.4s before picture
"punch": [1.0, 1.06], // slow push over the clip (static shot)
"rationale": "answer starts under the tail of the question so the cut disappears" },
{ "src": "talk.mp4", "start": 70.0, "end": 78.0,
"vsrc": "roomcam.mp4", "vstart": 161.3, "vend": 169.3,
"rationale": "audio stays on the mic'd talk; cut the PICTURE to the wide cam while the slide is static" }
]
}
start/end 是源时间线上的秒数(浮点)。剪辑是帧精确的(trim filter,而非 -ss 定位);渲染器把每个边界量化到帧网格上,因此 snap/tighten 写入的时间不会让画面与声音漂移。
first_words、candidate_takes、rationale 仅作文档——渲染器忽略它们,人读取它们。务必填写 rationale。
每个片段都会被归一化到 width×height(scale-to-fit + pad)并统一 fps,因此不同分辨率/fps 的片段可以干净地拼接(例如 4K take 挨着 720p)。
audio_lead 使该剪辑成为分离剪辑(+ = J-cut,− = L-cut)。这是防止剪辑显得跳切的主要手段——具体用多少见 cutting-rhythm 技能。视频与音频作为两条独立链拼接,因此 lead 永远不会失步。
punch 是缓慢的缩放推镜头(1.06 或 [1.0, 1.06])——为原本静态的镜头增添生气。
music 在同一次渲染中混入音乐床(循环覆盖、默认在语音下闪避),因此蒙太奇不需要二次重编码。audio_fix 在混音前作用于语音,所以闪避是在归一化后的对白上触发的。
seam_fade(默认 15 ms)作用于每个内部音频边界;作为淡变不可闻,但能消除对接拼接产生的咔哒声。设为 0 可禁用。
多机位切入(vsrc)
一个片段可以音频取自 src,而画面取自另一台摄像机,通过 vsrc/vstart/vend 指定。音频时间线保持连续(单一麦克风);只有视频切换——干净的机位切换,没有音频接缝。这就是表达「屏幕录制切入房间机位」的方式(寻找每个切入点的同步偏移见 editor 技能的多机位章节,通过音频互相关)。
从转录文本获取切点,而非拖时间线
从文本构建 EDL:transcribe src.mp4 --words(词级时间戳)得到说了什么,speech-segments src.mp4 得到帧精确的静音边缘。在转录中 grep 你要的那句,把其 start/end 写入 EDL。绝不用肉眼瞄时间线。
然后让 snap 精确放置切点,而不是手动微调数字:
uv run video-agent snap edit.json -o edit.snapped.json --to silence # onto silence edges
uv run video-agent snap edit.json -o edit.snapped.json \
--to beats --ref inputs/music.mp3 --tolerance 0.4 # onto the music grid
它会打印每一处移动,并放过任何在 --tolerance 内没有候选的切点。
渲染前检查节奏
uv run video-agent edl edit.json -o /dev/null --report --dry-run
以条形图打印每个镜头的长度,并对单调的镜头长度、缺失的分离剪辑、过短的结尾镜头给出警告——这三件事会让一份正确的剪辑清单看起来很差。它不花费成本,能发现 JSON 中看不见的问题。根据 cutting-rhythm 技能采取行动。--draft 渲染快速的 480p 版本供自己验证。
通过重新转录输出进行验证
检查剪辑是否正确的最强手段,是转录你实际渲染出的内容并与意图比较:
uv run video-agent edl edit.json -o out.mp4
uv run video-agent transcribe out.mp4 --clean -o check.txt # read it: right words, no fillers, nothing dropped
如果某个 take 本应无填充词,就在重新转录中 grep um/uh。如果某处剪辑落错,输出转录会显示出一个被截断或重复的词,这是帧检查发现不了的。在 EDL 中修正出问题片段的数字并重新运行。(仅内部验证——按 no-partial-previews 规则,向用户展示成品视频,而不是检查项。)
注意事项
grade / audio_fix 是原始 ffmpeg 应用于已拼接的剪辑——audio_fix 作用于拼接后的音频(适合放 loudnorm、acompressor;见 audio-edit),grade 是 LUT 路径,.cube 或来自 grade gen-lut 的 HALD .png(见 color-grade)。
一次重编码。整个 EDL 在单个 filter_complex concat 流程中渲染(h264_videotoolbox,aac 48k)——之后不要再做 stream-copy concat 后处理。现在这已包含音乐床和调色,因此没有理由加第二次处理。
此 LGPL ffmpeg 缺少 eq/drawtext——audio_fix 和 grade 必须使用可用的滤镜(loudnorm/acompressor/curves/colorbalance/lut3d/haldclut),而非 eq。
vsrc 切入必须与其覆盖的音频长度匹配。切入只替换画面;如果 vend-vstart ≠ end-start,画面会在剪辑剩余部分与声音脱节。渲染器会拒绝不匹配而非渲染它。
audio_lead 需要源素材能够向前延伸——J-cut 从片段入点之前借用音频,因此源中接近 0 处开始的片段无法获取较大的 lead。渲染器会报错而非静默缩短。它在片段 0 上被忽略(前面没有内容)。