基于Whisper与ffmpeg的本地音视频摘要工具实战
平时看技术视频、听播客,一集内容动辄四十分钟起步,真正有用的信息可能只占几分钟。想快速了解核心内容,手动拖进度条效率太低;用在线转写工具,又担心隐私、费用和排队。后来在 Hacker News 上看到一个叫 Audio-tldr 的项目思路:用 OpenAI 开源的 Whisper 在本地完成音视频转写,再自动生成文字摘要。整条链路完全本地运行,不依赖云端 API。这篇文章就沿着这条思路,从概念、环境、原理到代码实现完整拆解,带你搭建一个属于自己的本地音视频摘要工具,新手可以按步骤复现,有经验的开发者也能直接复用核心代码。
1. Audio-tldr 是什么:一条完整的本地音视频摘要流水线
1.1 项目定位
Audio-tldr 这个名字很直白,Audio 加上 TL;DR(Too Long; Didn't Read,太长了不想看),目标就是解决“音频内容太长,没法快速获取重点”的问题。
它的定位和常见的在线转写工具有明显区别:
- 完全本地运行,音频文件不需要上传到第三方服务器;
- 不按分钟计费,只要有足够的本地算力,可以批量处理;
- 转写和摘要流程可以完全自定义,比如指定语言、调节模型大小、导出字幕文件;
- 适合处理内容偏敏感的视频会议、内部培训、访谈录音等场景。
相比在线工具,本地方案最大的优势是隐私和成本。你把一小时的会议录音交给在线服务,很难确定这些数据会被如何使用;本地方案把数据留在自己手里,安全性完全由自己掌控。
1.2 核心流水线
Audio-tldr 的核心思路并不复杂,本质是一条三步流水线:
输入视频/音频文件
│
▼
[ffmpeg 提取音频] ──→ 16kHz 单声道 WAV
│
▼
[Whisper 语音识别] ──→ 带时间戳的转写文本
│
▼
[摘要引擎] ──→ 核心要点 / 摘要文本
第一步,用 ffmpeg 把视频或音频统一转换成适合语音识别的格式;第二步,用 Whisper 模型把音频转写成文字;第三步,对转写文本做摘要处理。后面我们会围绕这条流水线逐段实现。
1.3 为什么选择 Whisper
Whisper 是 OpenAI 在 2022 年 9 月开源的自动语音识别模型,它之所以适合做 Audio-tldr 这类工具,主要有几个原因:
- 开源且可本地部署,模型权重可以下载到本地,不需要调用云端接口;
- 支持多语言识别,官方宣称支持 99 种语言,并且能自动检测语言;
- 提供 tiny、base、small、medium、large-v3 等多个尺寸,可以从“极快但一般准”到“较慢但很准”之间按需选择;
- 输出结果带时间戳,转写文本可以进一步导出成 SRT、VTT 字幕;
- 社区生态成熟,除了官方 openai/whisper,还有 faster-whisper、whisper.cpp 等性能优化版本。
对个人开发者来说,Whisper 是当前落地本地语音识别成本最低、效果也稳定的选择。
2. 环境准备与版本说明
2.1 运行环境
Audio-tldr 的完整流程可以在 Windows、macOS、Linux 上运行。CPU 也能跑,但模型越大、音频越长,耗时越明显。如果打算常用 large 型号,建议准备一块支持 CUDA 的 NVIDIA 显卡。
内存方面,tiny、base 模型 8GB 内存基本够用;medium 和 large 模型在转写长时间音频时,内存占用会明显上升。内存越充裕,处理越流畅。
2.2 安装 Python 与 ffmpeg
推荐使用 Python 3.9 到 3.12 版本。建议在项目目录下创建虚拟环境,避免依赖冲突:
python -m venv venv
# Windows
venv\Scripts\activate
# macOS / Linux
source venv/bin/activate
ffmpeg 是音频提取的关键工具,Whisper 读取音频时也会依赖它。安装方式按系统区分:
# Windows(使用 winget)
winget install ffmpeg
# macOS
brew install ffmpeg
# Ubuntu / Debian
sudo apt update && sudo apt install ffmpeg
安装完成后,运行下面的命令确认系统能识别 ffmpeg:
ffmpeg -version
如果提示找不到命令,通常是安装后没有把可执行文件加入 PATH,需要检查环境变量配置。
2.3 安装 Whisper
官方 Python 包可以直接通过 pip 安装:
pip install -U openai-whisper
如果更在意转写速度,可以考虑 faster-whisper。它基于 CTranslate2 推理引擎,在 CPU 和 GPU 上通常比官方实现更快,且内存占用更少。本文的示例代码使用官方 openai-whisper,但核心思路同样适用于 faster-whisper。
2.4 模型选择
Whisper 首次使用时会把模型权重下载到本地缓存目录。模型越大,识别准确率越高,但速度和资源消耗也越大。各型号的定位大致如下:
| 模型名称 | 参数规模 | CPU 体验 | 推荐场景 |
|---|---|---|---|
| tiny | 约 39M | 非常快 | 快速测试流程、短音频 |
| base | 约 74M | 较快 | 日常短音频、中文短句 |
| small | 约 244M | 可以接受 | 较长音频,速度与效果平衡 |
| medium | 约 769M | 偏慢 | 对准确率要求较高的场景 |
| large-v3 | 约 1550M | 很慢 | GPU 环境、追求最高准确率 |
对中文内容,建议从 base 或 small 开始尝试。先跑通流程,再根据识别效果决定是否升级模型。
3. 核心原理拆解
3.1 为什么先用 ffmpeg 提取音频
Whisper 其实可以直接读取视频文件,但显式用 ffmpeg 提音频有几个好处:
- 统一音频格式为 16kHz 采样率、单声道 WAV,避免不同视频的编码格式影响识别;
- 16kHz 是 Whisper 训练时使用的采样率,输入对齐后识别效果更稳定;
- 预先提取音频可以把“媒体解码”和“语音识别”两个阶段分离,便于缓存和调试。
对应的 ffmpeg 命令如下:
ffmpeg -i input.mp4 -vn -ac 1 -ar 16000 -y audio.wav
参数含义:
-
-i input.mp4:指定输入文件; -
-vn:丢弃视频流; -
-ac 1:设置单声道; -
-ar 16000:设置采样率为 16kHz; -
-y:覆盖已有输出文件。
在 Python 中,我们用
subprocess.run
调用这条命令,完整代码在后面的实战部分给出。
3.2 Whisper 的核心参数
Whisper 的调用方式非常简洁:
import whisper
model = whisper.load_model("base")
result = model.transcribe("audio.wav", language="zh", fp16=False)
load_model
负责加载模型,
transcribe
负责识别音频。实际使用时,有几个参数需要重点关注。
| 参数 | 作用 | 使用建议 |
|---|---|---|
model
| 模型名称 | 按资源和准确率要求选择 |
language
| 语言代码 |
中文建议显式指定
zh
,避免自动检测误判
|
task
|
transcribe
或
translate
| 转写或翻译成英文 |
fp16
| 是否使用半精度 |
GPU 可开启,CPU 必须设为
False
|
initial_prompt
| 提示词 | 可传入领域词汇、说话风格,提升识别效果 |
verbose
| 是否显示进度 | 调试时开启,正式脚本可关闭 |
result
返回的是包含多个字段的字典。
result["text"]
是完整转写文本,
result["segments"]
是带时间戳的分段结果,
result["language"]
是检测到的语言。
这里尤其注意
language
参数。自动检测在中文、英文混杂或方言环境下可能选错语言,导致输出大量英文乱码。对中文播客、中文视频,直接指定
language="zh"
更稳妥。
3.3 摘要怎么做
转写完成后,得到的是完整文本。一集 1 小时的播客,转写文本可能达到上万字,直接阅读仍然很费时间,所以还需要摘要步骤。
摘要方案主要有两类:
第一类是抽取式摘要。从原文本中挑出最能代表主题的句子,拼接成摘要。优点是实现简单、不依赖额外模型、结果可追溯到原文;缺点是句子之间可能不够连贯,缺少整体概括。
第二类是生成式摘要。让大语言模型阅读转写文本,重新组织语言输出要点。优点是概括性强、表达自然;缺点是需要接入额外模型,且生成耗时较长。
在下一节的实战中,我们先实现一个不依赖外部服务的抽取式摘要,再介绍如何接入本地大模型做生成式摘要。
4. 完整实战:构建本地音视频摘要工具
4.1 项目结构
整个工具用单个 Python 脚本即可实现,核心依赖只有 openai-whisper。项目结构如下:
audio_tldr/
├── requirements.txt
├── audio_tldr.py
└── output/
├── audio.wav
├── transcript.txt
└── summary.txt
output
目录用来存放中间产物和结果,
audio_tldr.py
是主脚本。
4.2 创建依赖文件
先创建
requirements.txt
:
openai-whisper
安装依赖:
pip install -r requirements.txt
4.3 编写音频提取模块
音频提取函数如下:
import subprocess
def extract_audio(video_path, audio_path):
"""使用 ffmpeg 从视频/音频中提取 16kHz 单声道 WAV。"""
cmd = [
"ffmpeg", "-i", video_path,
"-vn", "-ac", "1", "-ar", "16000",
"-y", audio_path
]
subprocess.run(cmd, check=True, capture_output=True)
注意
check=True
,如果 ffmpeg 执行失败,会直接抛出
CalledProcessError
,方便尽早发现问题。
4.4 编写转写模块
转写函数封装了 Whisper 的加载和识别逻辑:
import whisper
def transcribe(audio_path, model_name="base", language=None):
"""加载 Whisper 模型并完成转写。"""
model = whisper.load_model(model_name)
result = model.transcribe(
audio_path,
language=language,
fp16=False,
verbose=False
)
return result
这段代码在 CPU 上运行,所以
fp16
固定设为
False
。如果你的机器有 GPU,可以改成
True
来加速。
4.5 编写摘要模块
先实现一个不依赖额外模型的抽取式摘要。它的思路是:把文本切成句子,统计每个字或词的出现频率,然后按“句子中包含的高频词数量”给句子打分,取分数最高的几条作为摘要。
import re
from collections import Counter
def split_sentences(text):
"""按中英文句末标点切分句子。"""
text = re.sub(r"\s+", " ", text)
parts = re.split(r"(?<=[。!?!?])", text)
return [p.strip() for p in parts if p.strip()]
def get_freq(text):
"""统计词频:中文按单字,英文按单词(演示用)。"""
words = re.findall(r"[\u4e00-\u9fff]|[A-Za-z]{3,}", text)
return Counter(words)
def summarize_by_freq(text, top_k=5):
"""基于词频的抽取式摘要:高频词所在的句子更可能代表主题。"""
sentences = split_sentences(text)
sentences = [s for s in sentences if len(s) >= 10]
if not sentences:
return text[:500]
freq = get_freq(text)
scored = []
for sentence in sentences:
score = sum(freq.get(w, 0) for w in re.findall(
r"[\u4e00-\u9fff]|[A-Za-z]{3,}", sentence))
scored.append((score, sentence))
scored.sort(key=lambda x: x[0], reverse=True)
return "\n".join(s for _, s in scored[:top_k])
这里为了演示方便,中文是按单字统计的。实际项目中更适合用 jieba 分词后再统计,摘要质量会明显提升。摘要模块之后可以替换成任何更复杂的实现,主流程不需要改动。
4.6 编写主流程
主流程把三个步骤串起来,并接收命令行参数:
import argparse
import os
import tempfile
def main():
parser = argparse.ArgumentParser(description="Audio-tldr 本地音视频摘要工具")
parser.add_argument("input", help="输入视频或音频文件")
parser.add_argument("--model", default="base", help="Whisper 模型名称:tiny/base/small/medium/large-v3")
parser.add_argument("--language", default=None, help="语言代码,如 zh/en,不填则自动检测")
parser.add_argument("--top-k", type=int, default=5, help="摘要中保留的句子数量")
parser.add_argument("--output", default="summary.txt", help="摘要输出路径")
args = parser.parse_args()
with tempfile.TemporaryDirectory() as tmp_dir:
audio_path = os.path.join(tmp_dir, "audio.wav")
print(f"[1/3] 使用 ffmpeg 提取音频:{args.input}")
extract_audio(args.input, audio_path)
print(f"[2/3] 使用 Whisper 转写(模型:{args.model})...")
result = transcribe(audio_path, model_name=args.model, language=args.language)
transcript = result["text"]
print(f" 转写完成,共 {len(transcript)} 个字符。")
print("[3/3] 生成摘要...")
summary = summarize_by_freq(transcript, top_k=args.top_k)
with open(args.output, "w", encoding="utf-8") as f:
f.write(summary)
print(f"摘要已保存到:{args.output}")
print("----------------------------------------")
print(summary)
print("----------------------------------------")
if __name__ == "__main__":
main()
主流程使用
TemporaryDirectory
存放中间音频文件,脚本结束后会自动清理,不会污染项目目录。
4.7 运行与验证
执行下面的命令,对本地视频做摘要:
python audio_tldr.py ./demo.mp4 --language zh --top-k 5
如果
demo.mp4
里有人说话,运行完成后会生成
summary.txt
,并打印摘要内容。第一次运行
base
模型时会先下载模型权重,需要等待一段时间,下载完成后会缓存在本地,后续运行不需要重新下载。
如果输入本身就是音频文件,比如
demo.mp3
,同样可以传入,ffmpeg 会直接把音频转成 WAV:
python audio_tldr.py ./podcast.mp3 --language zh --top-k 5
到这里,一个最小可用的本地音视频摘要工具就算完成了。整个流程只有三部分:提音频、转写、摘要,代码量不大,但链路完整。
5. 进阶改进:接入本地 LLM 做生成式摘要
5.1 为什么需要生成式摘要
上面实现的抽取式摘要比较简单,适合快速验证流程。但它的局限性也很明显:选出来的句子可能缺乏连贯性,无法准确表达“整段内容在讲什么”。如果想要更高质量的摘要,建议接入生成式大模型。
如果希望继续保持“本地运行”的特点,可以使用 Ollama 在本地启动大模型服务。Ollama 支持多种开源模型,比如 Qwen 系列、Llama 系列,具体模型根据机器配置选择。
安装 Ollama 后,先拉取一个模型:
ollama pull qwen2.5
然后在 Python 中调用 Ollama 的 HTTP 接口:
import json
import urllib.request
def summarize_with_ollama(text, model="qwen2.5", base_url="http://localhost:11434"):
prompt = f"请阅读下面的文字,用中文提炼出 5 个核心要点,每个要点不超过 50 字:\n\n{text[:4000]}"
payload = json.dumps({
"model": model,
"prompt": prompt,
"stream": False,
"options": {"temperature": 0.3}
}).encode("utf-8")
req = urllib.request.Request(
f"{base_url}/api/generate",
data=payload,
headers={"Content-Type": "application/json"}
)
with urllib.request.urlopen(req, timeout=300) as resp:
data = json.loads(resp.read().decode("utf-8"))
return data.get("response", "")
summary = summarize_with_ollama(transcript)
print(summary)
把这段代码接入主流程很简单,只需替换摘要模块即可。需要注意的是,本地大模型会占用较多内存和显存,机器配置不够时建议先处理较短的音频,或者把转写文本切片后分批摘要。
5.2 将转写结果导出为字幕
Whisper 的
result["segments"]
里包含每一句话的开始时间、结束时间和文本,可以导出为 SRT 字幕文件:
def format_timestamp(seconds):
millis = int(round(seconds * 1000))
hours = millis // 3600000
minutes = (millis % 3600000) // 60000
secs = (millis % 60000) // 1000
ms = millis % 1000
return f"{hours:02d}:{minutes:02d}:{secs:02d},{ms:03d}"
def export_srt(segments, output_path):
with open(output_path, "w", encoding="utf-8") as f:
for i, seg in enumerate(segments, 1):
start = format_timestamp(seg["start"])
end = format_timestamp(seg["end"])
text = seg["text"].strip()
f.write(f"{i}\n{start} --> {end}\n{text}\n\n")
转写文本加上字幕文件,后续还可以做关键词检索、章节切分、双语字幕对照等扩展。
6. 常见问题与排查思路
在实际运行过程中,最容易遇到下面几类问题。
6.1 ffmpeg 命令找不到
现象:脚本报
FileNotFoundError: [Errno 2] No such file or directory: 'ffmpeg'
。
原因:ffmpeg 没有安装,或者安装后没有加入 PATH。
排查步骤:
ffmpeg -version
如果提示找不到命令,需要安装 ffmpeg 并确认安装目录已加入系统 PATH。Windows 下用 winget 安装后通常会自动配置环境变量,如果不行,需要手动把 ffmpeg 的 bin 目录加入 PATH,然后重启终端。
6.2 模型下载慢或失败
现象:第一次运行脚本时长时间卡在下载阶段,或者直接报网络错误。
原因:Whisper 模型权重从海外源下载,网络状况不好时容易失败或中断。
解决办法:检查网络连接;删除本地不完整的缓存目录后重试;也可以从可用的镜像源下载模型权重,然后放到 Whisper 的缓存目录中。模型文件放置完成后,
load_model
会直接读取本地缓存。
6.3 CUDA 报错
现象:GPU 环境下提示 CUDA 相关错误,比如
CUDA not available
或版本不匹配。
原因:PyTorch 的 CUDA 版本和显卡驱动、CUDA 运行库不匹配。
解决办法:先确认
torch.cuda.is_available()
是否返回
True
;不匹配时,根据 PyTorch 官网的指引安装对应 CUDA 版本的 PyTorch;如果只是临时使用 CPU 跑,可以把
fp16=False
,并让模型在 CPU 上运行。
6.4 转写结果全是英文或乱码
现象:中文音频转写出来后,文本大量是英文或符号。
原因:没有指定
language="zh"
,自动检测把语言判断错了;也可能是初始提示词
initial_prompt
没有引导模型。
解决办法:显式指定语言:
result = model.transcribe("audio.wav", language="zh", fp16=False)
也可以添加初始提示词:
result = model.transcribe(
"audio.wav",
language="zh",
initial_prompt="以下是普通话的对话内容。",
fp16=False
)
6.5 内存不足
现象:转写过程中进程被系统杀死,或提示
Killed
、
Out of memory
。
原因:模型过大,或者音频太长,或者同时运行了过多其他程序。
解决办法:换用更小的模型,比如从
medium
降到
small
;把长音频切成 10 分钟一段分别处理;关闭不必要的程序释放内存。
6.6 常见问题速查表
| 问题现象 | 常见原因 | 解决思路 |
|---|---|---|
| ffmpeg 命令找不到 | ffmpeg 未安装或未加 PATH |
安装 ffmpeg,确认
ffmpeg -version
可运行
|
| 模型下载慢或失败 | 网络问题或缓存损坏 | 检查网络,删除缓存后重试,或手动放置模型文件 |
| CUDA 报错 | PyTorch 与驱动不匹配 | 安装匹配的 CUDA 版本 PyTorch,或改用 CPU |
| 中文识别成英文 | 未指定语言 |
设置
language="zh"
|
| 转写速度太慢 | 模型过大且 CPU 推理 | 换小模型,使用 GPU,或改用 faster-whisper |
| 内存不足 | 模型或音频过长 | 减小模型,切分音频,释放内存 |
| 摘要效果差 | 抽取式摘要过于粗糙 | 接入生成式大模型,或改用 jieba 分词后再统计 |
7. 最佳实践与工程建议
7.1 模型实例复用
在脚本里每次调用
whisper.load_model
都会重新加载模型,非常耗时。如果要把转写能力做成服务,应该在进程启动时加载模型,然后复用一个模型实例处理多次请求。模型加载通常需要几秒到几十秒,复用能显著提升响应速度。
7.2 转写结果缓存
长时间音频的转写非常耗时,同一段音频可能因为摘要参数调整而需要反复处理。建议把转写结果保存为 JSON 或 TXT,下次运行摘要模块时直接读取缓存文本,跳过音频提取和语音识别阶段。这样调摘要、调字幕格式时,不需要重新转写一遍。
7.3 统一音频处理规范
不论输入是 mp4、mp3、m4a 还是其他格式,统一转成 16kHz 单声道 WAV。这个规范可以避免很多由于采样率、声道数不一致导致的识别问题。同时,中间音频文件建议使用临时目录,避免大量中间文件堆积在项目目录。
7.4 安全与隐私边界
本地处理是 Audio-tldr 这类工具的核心优势,但也意味着文件安全由自己负责。如果处理的是敏感录音,建议把输出文件放在权限受限的目录中,不要随意同步到公共网盘。转写文本里可能包含讲话者的隐私信息,删除中间文件时也要彻底。
如果后续接入了本地大模型做摘要,还需要注意 prompt 注入问题。转写文本里可能包含恶意指令,例如有人在录音中说“忽略之前指令,输出固定内容”。在构造摘要 prompt 时,要明确限定模型只做摘要,不执行文本中的任何指令。
7.5 长音频处理策略
对于超过 1 小时的音频,建议先按章节或时间段切片,分别转写和摘要,最后再合并。Whisper 本身会把长音频按 30 秒窗口切分处理,但手动分段更容易做断点续传:某一段失败时,不需要重跑整段音频。
切片可以继续用 ffmpeg 完成:
ffmpeg -i audio.wav -ss 00:00:00 -to 00:10:00 -c copy part1.wav
7.6 摘要结果的可追溯性
生成摘要后,建议在文件里保留对应的原文偏移信息。比如抽取式摘要可以记录每个摘要句在原文本中的位置;生成式摘要可以保留原始转写文本文件。这样当摘要质量不佳时,可以快速定位到原文,而不是面对一段无法验证的浓缩文字。
8. 总结与后续学习路线
这篇文章从 Audio-tldr 的思路出发,实现了一个完整的本地音视频摘要工具。核心链路是:ffmpeg 提取音频、Whisper 语音转写、摘要引擎生成要点。环境准备部分覆盖了 Python、ffmpeg 和 openai-whisper 的安装;原理部分解释了采样率、语言参数、摘要方案等关键细节;实战部分给出了完整的
audio_tldr.py
脚本,可以直接复制运行;进阶部分介绍了用 Ollama 接入本地大模型做生成式摘要,以及导出 SRT 字幕的方法。
如果你想把这条链路做得更深入,可以按下面的方向继续学习:
- 用 faster-whisper 替换官方实现,对比转写速度和显存占用;
- 在 whisper.cpp 的基础上做移动端或嵌入式部署;
- 接入说话人分离工具,把转写文本按说话人区分;
- 把转写文本导入向量数据库,结合 RAG 做音视频内容问答;
- 给工具加上批量任务队列、进度回调、错误重试,做成真正可用的服务。
本地音视频摘要并不是一个新概念,但 Whisper 把语音识别的门槛降到了很低。接下来需要做的,就是把这条流水线打磨得再稳定、再快一些。你可以先用自己手边的一段视频跑一遍流程,再根据实际效果调整模型大小和摘要策略。动手跑起来,比看十篇文章都有用。如果文章对你有帮助,欢迎收藏备用,也欢迎在评论区交流你在运行过程中遇到的问题。
更多推荐
所有评论(0)