适用场景
这套方案适合三类人:一是会议内容涉及合同、财务、人事、未公开的技术方案,不允许上传到任何云端服务的团队;二是办公室网络不稳定,或者经常在飞机、客户现场等无网环境下做记录的人;三是手里已经有一台内存还算宽裕的电脑,想把历史录音批量转成文字和纪要,而不是按分钟数或订阅制付费。
整套流程全部在本机完成:音频不出磁盘,转写用本地模型,纪要由本地运行的 Gemma 生成。拔掉网线照样工作。
环境与前置条件
操作系统:Linux(Ubuntu 22.04 及以上)、macOS(Apple Silicon 体验更好)、Windows 建议用 WSL2。
运行时:Python 3.10 或以上。
硬件建议:
- 内存:转写环节 8GB 起;本地纪要模型跑 7B~8B 参数量化版建议空闲内存 8GB 以上,13B 以上建议 16GB 以上。
- 磁盘:模型文件合计预留 10~20GB。
- 显卡:有 NVIDIA 显卡可以明显加速,纯 CPU 也能跑通,只是慢一些。
- 麦克风,或者现成的录音文件(wav / mp3 / m4a 均可)。
授权与许可:
- 本地模型的使用条款需要单独确认。从 Hugging Face 等平台下载 Gemma 系列模型时,通常要先接受对应的使用条款,具体以官方模型页面为准。
- 如果直接用命令行录音,需要给当前用户音频设备访问权限;桌面环境首次录音会弹麦克风授权,选允许。
- 具体模型的名称、大小、量化方式,以官方文档当前版本为准,本文命令里的模型名只是示例。
分步骤部署
步骤 1:建立工作目录
```bash
mkdir -p ~/offline-notes/{audio,work,out,models}
cd ~/offline-notes
ls
```
预期看到 audio、work、out、models 四个目录。audio 放原始录音,work 放逐字稿,out 放最终纪要。
步骤 2:创建 Python 虚拟环境并安装转写引擎
```bash
cd ~/offline-notes
python3 -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade pip
pip install faster-whisper
```
这一步装的是本地语音转写库,基于 CTranslate2 推理后端,纯 CPU 也能跑。装完后执行 pip show faster-whisper 能看到版本信息即为成功。
步骤 3:把转写模型预先下载到本地
本地优先的关键是“模型先落地,之后不再联网”。
```bash
cd ~/offline-notes
source .venv/bin/activate
python - <<'PY'
from faster_whisper import WhisperModel
WhisperModel("medium", device="cpu", compute_type="int8")
print("转写模型已缓存到本机")
PY
```
第一次运行会下载模型权重,几百 MB 到 1GB 级别不等。看到“转写模型已缓存到本机”说明完成。模型默认落在用户缓存目录,想换位置可以设置 HF_HOME 环境变量。
内存紧张就把 medium 换成 small;追求准确率可以换成 large 系列,代价是速度。具体可用型号以官方仓库说明为准。
步骤 4:安装本地模型运行器并拉取 Gemma
这一步负责“自动纪要”。运行器只负责加载模型,模型本身是 Google 开放的 Gemma 系列。
```bash
安装命令以运行器官方文档当前版本为准
curl -fsSL https://ollama.com/install.sh | sh
```
安装完成后确认服务在监听:
```bash
curl http://127.0.0.1:11434/api/tags
```
拉取模型:
```bash
ollama pull gemma3
```
拉完后 ollama list 能看到模型名称。第一次拉取需要联网,之后完全离线可用。
> 如果你的环境更适合用 Google AI Edge 的端侧推理组件(例如 LiteRT-LM、MediaPipe LLM AI 词典:Inference">Inference),可以把这一步替换掉,只要保证有一个监听在本机、不访问外网的推理接口即可,参数以官方文档当前版本为准。
步骤 5:写转写脚本
新建 transcribe.py:
```python
import sys
from pathlib import Path
from faster_whisper import WhisperModel
src = Path(sys.argv[1])
Path("work").mkdir(exist_ok=True)
model = WhisperModel("medium", device="cpu", compute_type="int8")
segments, info = model.transcribe(
str(src),
language="zh",
vad_filter=True,
initial_prompt="以下是普通话会议录音,可能包含技术术语和英文缩写。",
)
lines = []
for i, seg in enumerate(segments, 1):
lines.append(f"[{seg.start:8.2f} -> {seg.end:8.2f}] {seg.text.strip()}")
out = Path("work") / (src.stem + ".txt")
out.write_text("\n".join(lines), encoding="utf-8")
print(f"已写入 {out},共 {len(lines)} 段")
```
说明:vad_filter=True 会先做静音检测,去掉长时间停顿,能减少模型“幻觉”出重复句子。initial_prompt 用来提示语言和领域,中文会议建议写上。
运行:
```bash
python transcribe.py audio/meeting.m4a
```
成功输出形如 已写入 work/meeting.txt,共 412 段。
步骤 6:写自动纪要脚本
新建 minutes.py:
```python
import json
import sys
import urllib.request
from pathlib import Path
transcript = Path(sys.argv[1]).read_text(encoding="utf-8")
Path("out").mkdir(exist_ok=True)
prompt = f"""你是一名严谨的会议记录员。下面是一段会议的逐字稿。
请输出 Markdown 格式的会议纪要,结构如下:
会议主题
参会人
讨论要点
达成的决议
待办事项(表格:负责人 / 事项 / 截止时间)
要求:
1. 只使用逐字稿中出现的信息,逐字稿没提到的写“未提及”或“待确认”。
2. 不要补充任何推测。
3. 决议和待办按重要性排序。
逐字稿:
{transcript}
"""
payload = json.dumps({
"model": "gemma3",
"prompt": prompt,
"stream": False,
"options": {"temperature": 0.2, "num_ctx": 8192},
}).encode()
req = urllib.request.Request(
"http://127.0.0.1:11434/api/generate",
data=payload,
headers={"Content-Type": "application/json"},
)
with urllib.request.urlopen(req, timeout=1800) as resp:
result = json.loads(resp.read().decode())
out = Path("out") / (Path(sys.argv[1]).stem + ".minutes.md")
out.write_text(result["response"], encoding="utf-8")
print(f"纪要已写入 {out}")
```
运行:
```bash
python minutes.py work/meeting.txt
```
temperature 调低是为了让输出更稳定;num_ctx 决定上下文窗口,逐字稿很长时要相应调大,否则会被截断。
步骤 7:断网跑通全流程
先确认断网:
```bash
curl -m 5 https://example.com && echo "仍然联网" || echo "已离线"
```
在离线状态下重跑:
```bash
python transcribe.py audio/meeting.m4a
python minutes.py work/meeting.txt
```
两条命令都能正常结束并生成文件,说明整条链路不依赖外网。
步骤 8:导出与归档
```bash
mkdir -p out/$(date +%Y-%m-%d)
cp work/meeting.txt out/$(date +%Y-%m-%d)/
cp out/meeting.minutes.md out/$(date +%Y-%m-%d)/
```
纪要就是标准 Markdown,可以直接贴进笔记软件、Wiki,或者用 pandoc 转成 docx:
```bash
pandoc out/meeting.minutes.md -o out/meeting.minutes.docx
```
验证部署是否成功
按顺序执行下面几条:
```bash
1. 转写引擎可用
source ~/offline-notes/.venv/bin/activate && python -c "import faster_whisper; print('ok')"
2. 本地推理服务在监听
curl -s http://127.0.0.1:11434/api/tags | head -c 200
3. 端到端跑一遍
cd ~/offline-notes
python transcribe.py audio/meeting.m4a
python minutes.py work/meeting.txt
4. 检查产物
wc -l work/meeting.txt
head -n 20 out/meeting.minutes.md
```
预期结果:第 1 条打印 ok;第 2 条返回包含模型名的 JSON;第 3 条两个脚本都正常退出;第 4 条逐字稿行数不为 0,纪要文件开头能看到“会议主题”等小节标题。
如果 work/meeting.txt 是空文件,多半是音频声道或采样率的问题,用 ffmpeg -i 原文件 看一下信息,必要时转成 16kHz 单声道。
常见报错与解决
1. ModuleNotFoundError: No module named 'faster_whisper'
原因:没有激活虚拟环境,或者装到了系统 Python。
解决:
```bash
source ~/offline-notes/.venv/bin/activate
pip install faster-whisper
```
2. Connection refused 或 urlopen error [Errno 111] Connection refused
原因:本地模型运行器没有启动,或者监听端口变了。
解决:
```bash
systemctl --user start ollama
没有 systemd 的环境直接前台启动
ollama serve
curl http://127.0.0.1:11434/api/tags
```
3. Invalid data found when processing input 或转写结果为空
原因:音频编码或采样率不被解码器接受,常见于某些会议软件导出的 m4a、opus。
解决:先统一转成 16kHz 单声道 wav。
```bash
ffmpeg -i audio/meeting.m4a -ar 16000 -ac 1 audio/meeting.wav
```
4. CUDA out of memory,或者进程被系统直接杀掉
原因:模型太大,显存或内存不够。
解决:换小一号模型,或者强制走 CPU 并使用 int8 量化。把 transcribe.py 里的模型初始化改成:
```python
WhisperModel("small", device="cpu", compute_type="int8")
```
5. 中文被识别成繁体,或夹杂大量无关英文
原因:语言识别不稳定,或音频里中英混杂。
解决:显式指定语言,并在 initial_prompt 里说明是普通话会议。
```python
segments, info = model.transcribe(
str(src), language="zh", vad_filter=True,
initial_prompt="以下是普通话会议录音。",
)
```
6. 纪要输出到一半被截断
原因:逐字稿长度超过上下文窗口。
解决:调大 num_ctx,或者先按章节分块总结,再做一次汇总。
本地方案与云端方案对照
| 维度 | 本地离线方案 | 云端会议笔记 |
|---|---|---|
| 隐私 | 音频、逐字稿、纪要都在本机磁盘,断网也能跑 | 音频和文本需要上传到服务方,受其数据条款约束 |
| 准确率 | 取决于模型规模和音频质量,口音重、多人抢话时下降明显 | 通常由服务方使用更大的模型和更完整的后处理,整体更稳 |
| 延迟 | 与硬件强相关,一小时录音在普通笔记本上可能要几分钟到十几分钟 | 与网络和排队有关,短音频往往更快 |
| 成本结构 | 主要是硬件与电费,处理量越大平均成本通常越低 | 按订阅或用量计费,处理量越大支出越接近线性增长 |
| 网络依赖 | 无 | 必须 |
| 可定制 | 提示词、术语表、输出格式都能自己改 | 通常只能在服务方给出的选项里选 |
| 维护成本 | 自己管模型、环境和升级 | 由服务方维护 |
选型思路:涉密会议、批量处理历史录音,选本地;追求开箱即用的准确率、不介意上云,选云端。两者也可以混用——敏感会议走本地,公开的内部周会走云端。
后续维护
备份:模型文件可以从官方渠道重新下载,不必备份;audio/、work/、out/ 三个目录要定期备份,可以用 rsync,或者放进 Git 仓库(注意仓库权限,涉密内容不要推到公开远端)。
```bash
rsync -av --delete ~/offline-notes/out/ /mnt/backup/offline-notes/out/
```
升级:转写库和运行器升级前,先复制一份环境出来试跑一段旧录音,确认输出格式和效果没退化,再动生产环境。
```bash
pip install --upgrade faster-whisper
ollama pull gemma3 # 模型名以官方模型库当前版本为准
```
日志与监控:运行器日志可以用 journalctl -u ollama -f 跟随查看;脚本层面建议把每次运行的耗时、音频时长、输出字数追加到 run.log,方便横向对比不同模型的实际表现。模型目录增长较快,加一条定时检查:
```bash
df -h ~/offline-notes
du -sh ~/offline-notes/models
```
日常习惯:新模型上线前,先拿同一段十分钟的样本录音做基线,比对逐字稿的错误率和纪要的可用性,再决定要不要换。会议纪要是给没参会的人看的,花两分钟核对“待办事项”这一节,比反复换模型更划算。
