粗剪:去芜存菁(Rough cut: trim the fat)
剪辑师称之为初剪和粗剪。每一句可用的台词按顺序放入,所有不值得保留的部分被剔除。它不是最终成片。它是每一秒钟说话者都很好地表达了自己台词的版本,以便后续的加工(节奏、调色、图形、音效)能在良好的素材上工作。
其核心理念:编辑文字,而非编辑波形。 获取一份每个词都有开始和结束时间的转录稿,通过阅读来决定保留什么,剪切点自然就由这些时间决定。Descript 以及 Premiere 和 Resolve 中的基于文本的编辑就是这样工作的。本技能增加了它们做不到的两件事:它将每个剪切点精确对齐到真实的声音,并再次聆听渲染结果来验证它。
设置(仅一次)
sh ~/simcraft/skills/rough-cut/setup.sh
在此文件旁边创建 .venv,其中包含 Whisper(Apple Silicon 上使用 mlx-whisper,其它平台使用 faster-whisper)。需要 ffmpeg:可在 PATH 中找到的 ffmpeg,或 $FFMPEG,或 ~/neatlogs-motion/ffmpeg。使用其下的 ~/simcraft/skills/rough-cut/.venv/bin/python 运行以下所有脚本。
工作区
每个视频一个文件夹。如果用户尚未创建,则创建 ~/Movies/edits/YYYY-MM-DD-<slug>/ 并将原始文件复制(切勿移动)到 raw/ 中。脚本会在此文件夹旁写入 audio/、cuts/ 和 renders/ 目录。raw/ 中的内容永远不会被修改。
处理流程(按顺序)
1. 转录
python scripts/transcribe.py <workspace> [--lang en]
写入 audio/words.json(每个词:开始、结束、文本、置信度)和 audio/transcript.txt(每一条录制一行)。在 M 系列 Mac 上,每 2 分钟素材大约耗时 1 分钟。
如果是英语,传递 --lang en(更准确)。如果是印地语/英语混合语音,则不传递此参数。
其准确性保证机制(每个机制都解决了我们在真实素材上遇到的一个失败案例):
- 每条录制都独立转录。 音频会在每次 0.5 秒或更长的停顿时被分割。一次性转录整个文件,Whisper 会将说了五次的句子只写一次,因此多余的段落没有单词,无法被剪掉。
- 语气词保留在转录稿中。 Whisper 默认会丢弃“um”和“uh”。一个充满犹豫语气的提示会让它们被识别出来,以便被剪掉。
- 转录循环被丢弃。 Whisper 有时会在噪音上重复一个短语几十次(“there you go there you go……”)。每秒超过 7 个词是不可能的,因此这些行会被丢弃。
2. 查找剪切点
python scripts/find_cuts.py <workspace> [--gap 0.35] [--keep-fillers]
写入 cuts/edl.json(要保留的片段,按顺序)和 cuts/cut_sheet.md(每个剪切点及其原因,以及需要检查的低置信度单词)。
| 剪切类型 | 规则 |
|---|---|
| 死寂 | 任何长于 --gap(0.35 秒)的停顿。也包括头部和尾部的静音。 |
| 语气词 | um, uh, erm, hmm, mm, ah。 |
| 重复 | 一个词或最多 4 个词的片段连续说了两遍(如“所以这个所以这个”):保留第二个。 |
| 重拍 | 在 20 秒内再次说出相同的台词,通过字母匹配,因此即使误听也能匹配(“Vanta”/“Wanda”,“1”/“one”):保留最后一次录制。 |
| 错误开头 | 一条只说了一半就被放弃,然后又被完整说出的台词(如“一个 3000 亿。”然后“一个 3 亿美元的业务。”)。 |
| 相同结尾 | 一条开头措辞不同但结尾相同的台词(如“那是,那是 Vanta 自己的创始人”然后“这不是我说的,这是 Vanta 自己的创始人说的”)。 |
| 幽灵词 | 一个词单独出现,其下方没有声音。Whisper 在静音中会虚构出这些(如“谢谢。”,“哈利路亚。”)。 |
每个剪切点的定位方式:
- 每个边界都精确对齐到语音实际开始和结束的位置,通过 10 毫秒步长的响度测量,并采用自适应于环境噪音的阈值来测量。Whisper 的单词时间可能偏差 0.1–0.3 秒。
- 边界最多只能向后移动 0.12 秒,因为剪断一个词比保留一次呼吸更糟糕。
- 在语音之前保留 0.06 秒的余量,在语音之后保留 0.10 秒的余量。
- 当没有真正间隙时,例如一个“嗯”紧贴在下一个词上,剪切点会被放在它们之间最安静的那个 10 毫秒点上。
- 隐藏在一个长的 Whisper“词”内部的停顿仍然会被发现并剪掉。
3. 人工判断(真正的剪辑,切勿跳过)
在 audio/transcript.txt 旁边阅读 cuts/cut_sheet.md,然后手动修改 cuts/edl.json。脚本无法判断这些:
- 哪个镜头最好。 默认保留最后一个镜头,但最好的镜头是那个有能量、结尾干净并且没有笑声打断它的镜头。有时,一个镜头的前半部分加上另一个镜头的结尾是最好的组合。
- 作为笑话一部分的语气词。 在笑点之前有意的“呃”要保留。
- “like”,“you know”,“I mean”,“basically”。 这些永远不会被自动剪切。只剪掉那些纯属拖延时间的。全部剪掉会让说话者听起来不像自己。
- 作为节奏停顿的暂停。 在笑点、反应或看向镜头之前的暂停要保留。并非所有沉默都是死寂。
- 用不同词语表达同一观点两遍。 剪掉整句话。
- 非语音时刻,如歌曲、视觉笑话或反应镜头。只保留语音,因此需要手动添加这些片段。
- 顺序。 片段遵循文件顺序。如果用户想要,将最强的台词移到开头作为冷开场。
- “检查这些词”列表。 低置信度通常意味着含糊不清。如果一句台词不清楚,寻找一个更清晰的镜头。
每个片段的结构是 {"clip", "in", "out", "text"},"speed" 是可选的。剪切点保持在词与词之间。
4. 节奏调整(先询问用户)
剪切表会报告每秒字数(words per second)。紧凑的说话内容大致在 3.3–3.8 之间。如果说话者速度较慢,建议将速度提高到 1.05–1.2,并在 edl.json 中为整个视频或每个片段设置 "speed"。音高保持不变。切勿加快音乐或反应片段的速度。
5. 预览渲染
python scripts/render.py <workspace> --w 854 --v 1 [--aspect 9:16]
一个 480p 的预览会输出到 renders/v1_854.mp4。每次一轮修改意见后,增加 --v 的值,以便旧版本得以保留。
- 每个片段会使用精确的帧数和未压缩的音频进行渲染,然后所有片段通过解码进行合并。使用流复制合并会在剪切点掉帧,并且音频在几分钟内会有约 2 秒的漂移。
- 每个片段都有 12 毫秒的淡入和 20 毫秒的淡出,这样剪切就不会产生“咔嗒”声。
- 不同形状的片段会放置在一个经过模糊处理的自身副本上,而不是黑边。
6. 验证(在向用户展示前进行)
python scripts/verify.py <workspace> --v 1 [--lang en]
它会再次逐段聆听渲染结果,将其与剪切列表进行比较,并写入 cuts/verify_v1.md。报告包含四个部分:
- 剪切点落在语音仍在进行中的地方: 一个不依赖于 Whisper 的声音测试。这是真正的裁剪词检测器。通过在 edl.json 中移动该边界来修复每个此类问题。
- 缺失的词语: 应出现但未听到的词语。
- 意外的词语: 泄露进来的语气词或重拍的一部分。
- 误听: 两版转录稿在措辞上不一致。这通常是含糊不清,而不是剪切问题。
在清晰的音频上,目标是 100% 的词出现,0 次剪切落在声音内部,0 个意外词。嘈杂或含糊不清的音频会产生一些缺失/意外的配对,这些实际上是误听。相信声音测试胜过这些。
修复它发现的问题,再次渲染,再次验证。
7. 展示给用户,然后交付终版
- 告知用户:剪辑前后的时长、剪切次数、验证结果以及你做出的任何人工判断(保留哪个镜头,刻意保留了哪个停顿)。
- 在制作新版本之前记录他们的修改意见。
- 只有在他们批准预览后:执行
python scripts/render.py <workspace> --w <源宽度> --v N。
在真实素材上的效果
在一个 3 分钟的原始说话人头素材上,有大量重拍(一句台词拍了 9 次,另一句拍了 4 次),2 分 55 秒的说话内容变成了 36 秒。它保留了每一句台词的最后一个清晰镜头,并且验证发现 100% 的词出现,0 次剪切落在声音内部,0 次泄露。
用户的品味
- 紧凑。词语间隙处的跳切是好的;持续的交替推拉放大镜头则不好。
- 保留脏话、打趣和有趣的片段。永远不要清除个性。
- 在此阶段不要添加嗖声音效,也不要进行调色。
- 当用户要求“只要精华部分,最多 X 分钟”时,要剪切掉整个时刻,而不仅仅是停顿。