平时看技术视频、听播客,一集内容动辄四十分钟起步,真正有用的信息可能只占几分钟。想快速了解核心内容,手动拖进度条效率太低;用在线转写工具,又担心隐私、费用和排队。后来在 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/activateffmpeg 是音频提取的关键工具,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.txtoutput目录用来存放中间产物和结果,audio_tldr.py是主脚本。
4.2 创建依赖文件
先创建requirements.txt:
openai-whisper安装依赖:
pip install -r requirements.txt4.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.wav7.6 摘要结果的可追溯性
生成摘要后,建议在文件里保留对应的原文偏移信息。比如抽取式摘要可以记录每个摘要句在原文本中的位置;生成式摘要可以保留原始转写文本文件。这样当摘要质量不佳时,可以快速定位到原文,而不是面对一段无法验证的浓缩文字。
8. 总结与后续学习路线
这篇文章从 Audio-tldr 的思路出发,实现了一个完整的本地音视频摘要工具。核心链路是:ffmpeg 提取音频、Whisper 语音转写、摘要引擎生成要点。环境准备部分覆盖了 Python、ffmpeg 和 openai-whisper 的安装;原理部分解释了采样率、语言参数、摘要方案等关键细节;实战部分给出了完整的audio_tldr.py脚本,可以直接复制运行;进阶部分介绍了用 Ollama 接入本地大模型做生成式摘要,以及导出 SRT 字幕的方法。
如果你想把这条链路做得更深入,可以按下面的方向继续学习:
- 用 faster-whisper 替换官方实现,对比转写速度和显存占用;
- 在 whisper.cpp 的基础上做移动端或嵌入式部署;
- 接入说话人分离工具,把转写文本按说话人区分;
- 把转写文本导入向量数据库,结合 RAG 做音视频内容问答;
- 给工具加上批量任务队列、进度回调、错误重试,做成真正可用的服务。
本地音视频摘要并不是一个新概念,但 Whisper 把语音识别的门槛降到了很低。接下来需要做的,就是把这条流水线打磨得再稳定、再快一些。你可以先用自己手边的一段视频跑一遍流程,再根据实际效果调整模型大小和摘要策略。动手跑起来,比看十篇文章都有用。如果文章对你有帮助,欢迎收藏备用,也欢迎在评论区交流你在运行过程中遇到的问题。