口播视频自动剪辑:Whisper 词级时间轴 + LLM 视觉规划
- 约 8 分钟读完 - 全文 3107 字口播视频自动剪辑:Whisper 词级时间轴 + LLM 视觉规划
我开发的 GeekCast Engine 是一个面向口播类视频的 AI 自动剪辑平台。用户上传一段口播视频后,系统自动完成转写、粗剪、视觉规划和成片渲染。这篇文章会重点拆解两个核心模块:Whisper 词级时间轴处理 和 LLM 视觉元素规划。
一、口播剪辑的核心痛点
口播视频通常是一个人对着镜头讲话,形式单一,但剪辑过程却很繁琐:
- 气口和语气词太多:「嗯」「啊」「这个」「那个」需要逐个删掉
- 节奏单调:长时间只有说话人画面,观众容易疲劳
- 信息密度不均:有些段落需要配合 B-roll、标题卡、代码块来强化表达
- 长视频处理困难:10 分钟以上的口播,人工剪辑耗时往往在数小时
我们的目标不是完全替代剪辑师,而是把重复、机械、规则明确的工作自动化,让人专注于创意和审校。
二、整体技术架构
flowchart TD
A["口播视频上传"] --> B["Whisper 词级转写"]
B --> C["LLM 语义分析 + 视觉规划"]
C --> D["自动粗剪(删气口/语气词/冗余片段)"]
D --> E["Remotion 组件化渲染"]
E --> F["FFmpeg 后处理输出成片"]
整个流程里,最关键的两个环节是:
- 时间轴精度:决定了粗剪是否精准
- 视觉规划质量:决定了成片是否好看
三、Whisper 词级时间轴
3.1 为什么选择 Whisper
OpenAI Whisper 是目前开源语音识别模型中最成熟的选择之一,优势很明显:
- 支持 99 种语言,中文识别效果不错
- 提供
word-level timestamps(词级时间戳),这是自动剪辑的基础 - 模型大小可选,从小到大有 tiny/base/small/medium/large
3.2 词级时间戳的获取
Whisper 本身不直接输出词级时间戳,需要借助 whisper-timestamped 或 faster-whisper 等工具。我们用 faster-whisper,因为它:
- 推理速度更快
- 支持 VAD(语音活动检测),减少静音误判
- 对长音频更稳定
from faster_whisper import WhisperModel
model = WhisperModel("small", device="cuda", compute_type="float16")
segments, info = model.transcribe(
"input.mp3",
word_timestamps=True,
vad_filter=True,
language="zh"
)
words = []
for segment in segments:
for word in segment.words:
words.append({
"text": word.word,
"start": word.start,
"end": word.end,
"probability": word.probability
})3.3 词级数据结构
转写完成后,每个词对应一个时间片段:
{
"text": "大家好",
"start": 0.32,
"end": 0.98,
"probability": 0.95
}多个词可以聚合成句子、段落,形成完整的时间轴树:
Paragraph
├── Sentence 1
│ ├── Word 1
│ ├── Word 2
│ └── Word 3
└── Sentence 2
├── Word 4
└── Word 5
3.4 气口和语气词删除
基于词级时间轴,我们可以做很多精细化编辑:
# 语气词列表
FILLER_WORDS = {"嗯", "啊", "这个", "那个", "就是", "然后", "对吧"}
def remove_fillers(words, filler_words=FILLER_WORDS):
"""删除单个语气词,保留相邻词的时间连续性"""
result = []
for w in words:
if w["text"].strip() in filler_words:
continue
result.append(w)
return result
def remove_long_pauses(words, threshold=0.8):
"""删除过长的停顿片段"""
result = []
for i, w in enumerate(words):
if i > 0:
gap = w["start"] - words[i-1]["end"]
if gap > threshold:
continue # 也可以只压缩,不完全删除
result.append(w)
return result3.5 时间轴漂移问题
实际使用中发现,Whisper 的词级时间戳在语速快、口音重、背景音乐干扰时会出现漂移。我们做了几个处理:
- 置信度过滤:识别概率低于 0.7 的词标记为待审校
- 句子级对齐:用 VAD 检测到的语音段和转写句子做二次对齐
- 人工修正入口:前端提供波形图 + 时间轴,允许用户微调
四、LLM 视觉规划
4.1 视觉元素类型
我们定义了口播视频中常用的几类视觉元素:
| 元素类型 | 作用 | 示例 |
|---|---|---|
| 标题卡 | 章节切换、强调重点 | 大标题 + 副标题 |
| B-roll | 缓解视觉疲劳,补充画面 | 相关图片/视频片段 |
| 引用卡 | 引用名人名言、数据 | 金句 + 出处 |
| 代码块 | 技术类口播展示代码 | 高亮代码片段 |
| 列表卡 | 展示多个要点 | 1/2/3 列表 |
4.2 LLM 规划任务
输入是一段按句子切分好的口播文本,输出是每个句子应该配什么视觉元素。Prompt 设计如下:
你是一名专业的口播视频剪辑师。请根据以下口播文案,为每个句子规划合适的视觉元素。
可用视觉元素:
- TitleCard: 标题卡,用于章节开头或强调核心观点
- BRoll: B-roll 画面,用于补充说明内容
- QuoteCard: 引用卡,用于展示名言、数据
- CodeBlock: 代码块,用于展示代码
- ListCard: 列表卡,用于展示多个要点
- TalkingHead: 保持说话人画面,不添加额外元素
口播文案:
{script}
请输出 JSON 数组,每个元素包含:
- sentence_index: 句子序号
- element_type: 视觉元素类型
- reason: 选择理由
- suggested_content: 建议展示的内容(如 B-roll 的画面描述、标题文字等)
4.3 输出结构化
LLM 返回的结果类似:
[
{
"sentence_index": 0,
"element_type": "TitleCard",
"reason": "开场需要点题",
"suggested_content": "AI 自动剪辑实战"
},
{
"sentence_index": 3,
"element_type": "BRoll",
"reason": "解释'时间轴'概念时需要补充画面",
"suggested_content": "视频编辑软件中的时间轴界面"
},
{
"sentence_index": 8,
"element_type": "CodeBlock",
"reason": "展示 Whisper 调用代码",
"suggested_content": "faster_whisper 示例代码"
}
]4.4 与素材库联动
视觉规划中的 suggested_content 会作为查询条件,调用我们在第二篇文章中讲的 RAG 素材库,检索匹配的 B-roll、图片或文案片段。
def plan_and_retrieve(script: str):
# 1. LLM 做视觉规划
plan = llm.generate_visual_plan(script)
# 2. 对每个 B-roll 规划项检索素材
for item in plan:
if item["element_type"] == "BRoll":
item["materials"] = rag_retrieve(item["suggested_content"])
return plan五、长视频 Token 限制处理
口播视频动辄 10 分钟以上,对应的文案可能超过 3000 字,直接塞给 LLM 做视觉规划会触发 Token 限制,而且单次规划效果也不好。
我们的解决方案是:分段生成 + 本地组装。
5.1 分段策略
- 按段落切分:每段约 800-1200 字
- 保留上下文:每段开头会附带上一段的结尾摘要
- 独立规划:每段分别调用 LLM 做视觉规划
- 全局对齐:最后统一调整时间轴、样式和转场
def split_script(script: str, max_chars: int = 1000):
"""按段落切分文案,尽量保持段落完整性"""
paragraphs = script.split('\n')
chunks = []
current = ""
for p in paragraphs:
if len(current) + len(p) > max_chars and current:
chunks.append(current)
current = p
else:
current += '\n' + p
if current:
chunks.append(current)
return chunks5.2 本地组装
分段规划完成后,需要在本地合并:
- 统一句子索引
- 统一视觉风格(字体、配色、动画)
- 处理段与段之间的转场
- 生成最终的 Remotion 渲染数据
六、Remotion 组件化渲染
6.1 为什么用 Remotion
Remotion 让我们可以用 React 组件来描述视频画面,非常契合「视觉元素 = 组件」的思路。
每个视觉元素对应一个 React 组件:
// 标题卡组件
export const TitleCard: React.FC<{ title: string; subtitle: string }> = ({ title, subtitle }) => {
return (
<AbsoluteFill style={{ backgroundColor: '#111', justifyContent: 'center', alignItems: 'center' }}>
<h1 style={{ color: '#fff', fontSize: 80 }}>{title}</h1>
<p style={{ color: '#aaa', fontSize: 36 }}>{subtitle}</p>
</AbsoluteFill>
);
};6.2 渲染流程
flowchart TD
A["视觉规划结果"] --> B["生成 Remotion composition(每个视觉元素对应一个组件实例)"]
B --> C["Remotion 渲染为视频文件"]
C --> D["FFmpeg 后处理:合成音频、加字幕、转码、多分辨率输出"]
6.3 多分辨率输出
平台需要同时支持横屏(16:9)和竖屏(9:16),我们在 Remotion 组件内部做响应式布局:
const { width, height } = useVideoConfig();
const isVertical = height > width;七、踩过的坑
7.1 Whisper 模型选择纠结
tiny速度最快但准确率太低large准确率最高但显存和速度压力大- 最终我们选
small做默认模型,large做可选质量模式
7.2 LLM 规划风格不一致
同一个文案多次调用 LLM,返回的视觉规划可能风格差异很大。我们做了:
- Prompt 里加入平台风格示例
- 视觉元素类型和样式模板固定
- 对返回结果做规则校验和兜底
7.3 代码块渲染需要特殊处理
代码块里的长行、特殊字符、换行等都会影响 Remotion 渲染。我们专门封装了一个 CodeBlock 组件,处理:
- 自动换行和字体缩放
- 语法高亮
- 行号显示
八、效果与反思
目前 GeekCast Engine 能在一分钟左右完成 5-10 分钟口播视频的自动粗剪和视觉规划,最终成片需要人工简单审校即可发布。
量化效果(待补充真实数据):
- 10 分钟口播视频,人工剪辑平均 X 小时,自动剪辑约 X 分钟
- 气口和语气词删除覆盖率约 X%
- LLM 视觉规划采纳率约 X%
反思:
- 自动剪辑还不能做到 100% 无人工,但已经能替代 60%-70% 的重复劳动
- 视觉规划比粗剪更难,未来需要结合用户反馈做迭代优化
- 长视频场景下,分段策略和上下文保留是关键
九、总结
口播视频自动剪辑是把 ASR、LLM、视频渲染三项技术串联起来的典型 AI 工程场景。几个关键经验:
- 词级时间轴是粗剪的基础,Whisper + faster-whisper 是性价比较高的方案
- LLM 视觉规划要把输出结构固定,Prompt 设计决定可控性
- 长视频必须分段处理,本地组装时注意风格和转场一致性
- Remotion 让视频渲染工程化,组件化思路适合多变的视觉需求
如果你也在做 AI 视频生成或自动剪辑相关的项目,欢迎交流 Whisper 调优和 LLM 规划的经验。