我在做 CutVoke,一个开源(MIT)的本地视频剪辑器,Claude Code / Codex 可以通过 MCP 和你编辑同一条时间线。这篇只讲其中一小块:多机位音频对齐,以及它为什么宁可拒绝也不瞎猜。
多机位剪辑里最无聊的那部分
做播客、访谈的剪辑者常描述同一种一周:剪本身还好,时间都花在对齐机位、切机位、拉平音量上。对齐机位是机械活,很适合交给软件,或者交给驱动剪辑器的 Agent。但这也是"自信地给错答案比不给答案更糟"的活:一个机位差 0.3 秒,看起来就像配音片,而你可能导出后才发现。
所以 CutVoke 里 multicam.sync 的规则是:音频明显一致时才对齐,不一致就不碰时间线。
它具体做了什么
multicam.sync {referenceTrackId, targetTrackId, maxOffsetSeconds?}(源码:src/cutvoke/rendering/multicam_sync.py):
- 用 ffmpeg 读取每个机位的真实源音频:单声道、8 kHz、最多前 60 秒。
- 按 10 ms 窗口算 RMS 能量包络,所以时间分辨率是 10 ms。
- 在默认 ±10 秒(最大可调到 ±30 秒)的偏移范围内做归一化互相关。
- 最高分作为
confidence;与最佳偏移相距至少 150 ms 的最高分作为次优,ambiguityMargin= 最佳 − 次优。 - 如果
confidence < 0.65或ambiguityMargin < 0.05,直接报错 "Ambiguous waveform sync ... align manually",时间线不做任何改动。 - 否则移动目标片段;如果移动后会跑到时间线零点之前,就裁掉这段前缀并相应调整源入点。
结果会返回给调用方并存进项目:method、lagSeconds、confidence、ambiguityMargin、appliedDeltaSeconds、clippedPrefixSeconds。和其他编辑一样,可以撤销。
为什么要看"差距",而不只看阈值
峰值高不代表就对。重复性强的音频(稳定的节拍、拍手声、房间底噪)会在相隔几分之一秒的位置出现好几个差不多高的峰。挑最高那个等于抛硬币,却给你一个看起来很精确的数字。差距检查把"两个答案几乎一样好"变成明确的拒绝,Agent 可以如实汇报("B 机位没对上,请手动对齐"),而不是悄悄交付一个偏移。
护栏(明确的限制)
- 每个机位只能是一段连续、普通 1x 速度的片段;变速、定格、嵌套片段会被拒绝。
- 至少需要两秒非静音音频。
- 要先对齐目标机位,再加效果或关键帧,否则命令会拒绝。
- 不处理长录制的时钟漂移:它只用第一分钟求一个偏移。对齐后请检查开头和结尾。
对齐之后:切机位和音频
multicam.switch {trackId, time}在时间线时间点(有理数)记录显式切点;同一时间再切一次会替换该切点。机位源片段不会被切碎,预览和导出使用同一套选择。- 节目音频保持在初始机位上,不随画面切换跳动。
audio.normalize用 ffmpegloudnorm两遍处理(先测量,再按测量值应用),输出到文件,默认目标 −16 LUFS。
让 Agent 来做
因为这些都是共享时间线上的普通命令,Claude Code 或 Codex 可以通过 MCP 调用,你可以在界面里看着并随时接管。一个合理的 Agent 流程是:建多机位组、逐个机位对齐、汇报每个机位的 confidence / ambiguityMargin,遇到第一个拒绝就停下来交给人。至于什么时候切哪个机位,仍然由你决定,CutVoke 不会替你选镜头。
如果你用别的方式对齐多机位(时间码、打板、PluralEyes 一类工具),很想听听纯音频对齐在你那里会在哪儿失效。