05 说话模式:剪辑与字幕
1. 回答:剪口误、去"嗯啊"、收紧停顿能不能全自动?
能,全部全自动。 三件事的区别只在误剪风险:
| 剪辑类型 | 全自动的可靠程度 | 原因 |
|---|---|---|
| 收紧停顿 | ✅ 很可靠 | 只是缩短静音,不删任何字 |
| 去"嗯、啊、呃、um、uh" | ✅ 可靠 | 这些词几乎从来不承载意思 |
| 去结巴重复("我我我觉得") | ✅ 可靠 | 规则就能判断 |
| 去"那个、就是、然后、like、you know" | ⚠️ 有风险 | 这些词有时是口头禅,有时是正常用法("那个人"、"I like it"),要靠上下文判断 |
| 剪口误和重录(说错后重说) | ⚠️ 有风险 | 要判断哪一遍是"作废的",大模型能判断大部分,但偶尔会剪掉有意义的内容 |
| 删跑题段落 | ❌ 不自动 | 这是编辑判断,不是技术问题。任何档位都只提示、不自动删 |
所以设计成三档力度,让用户自己选风险:
| 档位 | 自动剪掉 | 适合 |
|---|---|---|
| 轻 light | 停顿收紧(> 1.5 秒缩到 0.8 秒)+ 嗯/啊/um/uh | 访谈、需要保留自然感 |
| 标准 standard(默认) | 停顿收紧(> 1.0 秒缩到 0.5 秒)+ 嗯啊类 + 结巴重复 | 大多数播客 |
| 深度 deep | 停顿收紧(> 0.7 秒缩到 0.35 秒)+ 上面全部 + 上下文口头禅 + 口误和重录 | 追求紧凑;接受少量误剪 |
所有档位都生成剪辑表,用户事后仍可以在网页编辑器里把任何一刀恢复回来(非破坏性剪辑)。编辑器是给用户自己用的可选功能,交付不依赖任何人工审核。
2. 转写
| 语言 | 引擎 | 要求 |
|---|---|---|
| 英文 | faster-whisper(large-v3 或同级),word_timestamps=True |
词级时间戳 |
| 中文 | FunASR(Paraformer 一类,带时间戳和标点模型) | 字级时间戳;支持热词 |
| 说话人 | 说话人分离模型(pyannote 一类,许可证核查);多轨录音时直接用轨道当说话人 |
关键陷阱:语音识别模型会"自动美化"口语。 Whisper 一类模型经常把"嗯、呃"直接省略、把结巴合并掉,所以不能只靠转写文字找口头禅。解决办法(两者都要做):
- 引导逐字转写:Whisper 用带口头禅的
initial_prompt(例如 "Um, so, uh, I was like, you know..."),FunASR 关闭口语规整 - "有声无字"检测:用语音活动检测(VAD)找出"明明有人声、但没有任何词覆盖"的片段,时长 150~800ms、能量低于前后语句的,标为疑似口头禅(
kind=filler_gap)
自定义词表(人名、节目名、专业术语):用户在作品设置(settings)里填,转写时作为热词/提示词,转写后再做一遍替换纠正。
3. 剪辑检测
| kind | 检测方法 | 默认置信度 | light | standard | deep |
|---|---|---|---|---|---|
pause |
词间静音 > 阈值 | 1.0 | ✅ | ✅ | ✅ |
filler |
词表命中(嗯/啊/呃/额/唔/um/uh/erm/hmm) | 0.95 | ✅ | ✅ | ✅ |
filler_gap |
有声无字(见上) | 0.6~0.9 | ❌ | conf ≥ 0.85 | conf ≥ 0.7 |
stutter |
连续重复的字/词("我我我"、"the the") | 0.9 | ❌ | ✅ | ✅ |
filler_ctx |
上下文口头禅(那个/就是/然后/like/you know/I mean),大模型逐句判断 | 大模型给出 | ❌ | ❌ | conf ≥ 0.8 |
retake |
口误与重录:大模型判断"前一段是否被后一段替代",辅以文本相似度 | 大模型给出 | ❌ | ❌ | conf ≥ 0.85 |
false_start |
说半句就停、重新开始 | 大模型给出 | ❌ | ❌ | conf ≥ 0.85 |
tangent |
跑题 | 大模型给出 | ❌ | ❌ | ❌(只提示) |
3.1 大模型调用规格
- 输入:带编号和时间戳的句子列表,按 3~5 分钟切块,前后各重叠 30 秒
- 输出(JSON):
[{"kind": "retake", "remove": [12, 13], "keep": [14], "conf": 0.9, "reason": "第12-13句说错,第14句重说"}] - 大模型只能引用句子/词的编号,不能自己编时间戳;时间由编号换算,防止幻觉
- 重叠区域两个块结论冲突时,取置信度高的
keep_words(用户指定不删的词)在交给大模型之前就排除
3.2 安全护栏(全自动时强制执行)
| 规则 | 说明 |
|---|---|
| 不切在词中间 | 剪点吸附到词边界,再吸附到前后 30ms 内能量最低点 |
| 保留最小间隔 | 剪完后两句之间至少留 120ms |
| 交叉淡化 | 每个剪点 15ms 等功率交叉淡化,防止爆音 |
| 不剪音乐 | 音乐检测命中的片段(片头曲、插曲)不做任何剪辑 |
| 不剪笑声 | 非语音但能量高的片段(笑声、掌声)不当作停顿压缩 |
| 删除比例上限 | 除 pause 外,删除总时长 > 节目时长 15% → 视为异常 |
| 单处上限 | 单个 retake 删除 > 60 秒 → 不自动删,降级为提示 |
4. 剪辑表(EDL)
{
"source_media": "med_01J...",
"duration": 3605.42,
"level": "standard",
"version": 3,
"ops": [
{"id": "o1", "kind": "pause", "start": 12.48, "end": 13.91, "keep": 0.5, "conf": 1.0, "by": "rule", "status": "accepted"},
{"id": "o2", "kind": "filler", "start": 20.10, "end": 20.42, "text": "嗯", "conf": 0.95, "by": "rule", "status": "accepted"},
{"id": "o3", "kind": "retake", "start": 95.20, "end": 101.75, "text": "我们刚才…不对,我重说", "conf": 0.9, "by": "llm", "status": "pending"},
{"id": "o4", "kind": "manual", "start": 300.00, "end": 312.50, "by": "user", "status": "accepted"}
]
}
pause类用keep表示保留多少秒静音,实际删除[start + keep/2, end - keep/2]status:pending/accepted/rejected- 渲染时只处理
accepted,重叠的区间先合并
4.1 时间换算
edl.map_time(t_original) -> t_edited:原时间减去它之前所有已接受删除段的总长;落在删除段内的时间点映射到删除段起点。字幕、章节、show notes 的时间全部用这个函数换算,不对成品重新转写。
5. 渲染
| 输出 | 做法 |
|---|---|
| 纯音频 | Python 内按剪辑表拼接(numpy),剪点做 15ms 交叉淡化;之后再走 loudnorm |
| 视频 | 剪点先吸附到视频帧边界(例如 30fps 就是 1/30 秒);视频用 ffmpeg select + setpts 按保留区间取帧;音频用同一组帧对齐的区间在 Python 里拼接并交叉淡化;最后合并音视频流 |
性能目标:60 分钟音频渲染 ≤ 1 分钟;60 分钟 1080p 视频 ≤ 10 分钟(CPU 编码,-preset veryfast)。
5.1 交付前自检
渲染完自动检查以下项,任何一项异常 → 自动降一档力度重新渲染,并在交付说明里写明实际用的档位:
- 删除比例超过上限
- 每分钟剪点数 > 20(剪得太碎,听感会跳)
- 成品响度或真峰值不达标
- 成品时长 < 原时长的 60%
6. 字幕
6.1 切分规则
| 规则 | 中文 | 英文 |
|---|---|---|
| 每行最多 | 16 字 | 42 字符 |
| 每条最多行数 | 1 行(横屏)/ 1 行(竖屏每行 12 字) | 2 行 |
| 每条时长 | 1~7 秒 | 1~7 秒 |
| 阅读速度上限 | 9 字/秒 | 20 字符/秒 |
| 条间最小间隔 | 80ms | 80ms |
| 断句优先级 | 标点 > 停顿 > 说话人切换 > 语法边界 | 同左;不拆开单词 |
- 口头禅和被剪掉的内容不出现在字幕里(字幕由剪后的词序列生成)
- 中文样式默认句末不加句号、句中逗号换成空格(可在样式里关闭)
- 可选说话人前缀(例如
Amy:),用户在作品设置里填写名字
6.2 格式与样式
| 格式 | 用途 |
|---|---|
| SRT | 通用 |
| VTT | 网页播放器、YouTube |
| ASS | 带样式,用于烧录 |
| TXT | 纯文字稿(带或不带时间戳) |
样式模板(ASS):clean_white(白字黑描边)、boxed(半透明底框)、bold_center(竖屏短视频大字)。字体使用开源可商用字体(思源黑体 / Noto Sans CJK,OFL 许可),随镜像打包,不依赖系统字体。
6.3 双语字幕
- 大模型逐条翻译,输入输出条数严格 1:1(带编号的 JSON 数组),前后各带 5 条上下文
- 译文超长时让模型缩写,仍超长则该条允许 2 行
- 烧录时原文在上、译文在下(样式可调)
- 术语表(作品设置里的自定义词)一并传给模型
6.4 烧录
ffmpeg -i edited.mp4 -vf "ass=subs.ass:fontsdir=/fonts" -c:v libx264 -crf 20 -preset veryfast -c:a copy out.mp4
6.5 验收
| 指标 | 目标 |
|---|---|
| 字幕与成品的时间误差 | ≤ 100ms(开发阶段抽 20 处核对) |
| 违反切分规则的条数 | 0(自动检查) |
| 中文字错率(清晰录音) | ≤ 5%;带术语表时专有名词正确率 ≥ 95% |
7. 视频播客
| 项 | 说明 |
|---|---|
| 输入 | 单个视频文件;或视频 + 单独录的高质量音频(按音频互相关自动对齐,误差 ≤ 1 帧) |
| 处理 | 音质处理只作用于音频流;剪辑同时作用于音视频 |
| 输出 | MP4(H.264 / AAC 192kbps),保持原分辨率和帧率;可选烧录字幕 |
| P3 | 竖屏短视频切片:按大模型给出的高光片段,裁成 9:16,烧录大字幕 |
8. 用户编辑器(前端,可选)
用户可以在交付后打开编辑器微调(恢复某一刀、手动加一刀、改错别字),保存后自动重新渲染。
| 区域 | 功能 |
|---|---|
| 文字稿 | 按说话人分段;被剪部分划线并按类型着色;点击任意词从该处播放 |
| 波形 | wavesurfer.js 一类组件;剪辑区间以色块显示;可拖动边界微调 |
| 建议列表 | 按类型、置信度筛选;批量接受/拒绝 |
| 播放模式 | "原始" / "剪后":剪后模式在浏览器里直接跳过已接受的区间,不需要先渲染 |
| 快捷键 | 空格 播放/暂停;J/K 上一条/下一条建议;A 接受;R 拒绝;E 在当前选区新建剪辑 |
| 文字修改 | 可改错别字(影响字幕,不影响剪辑) |
音频在编辑器里使用代理文件(64kbps mp3),加载快;渲染时用原始无损文件。