Local TTS 是做什么的?
默认语音:Qwen3-TTS 1.7B CustomVoice,发音人 aiden,8-bit GGUF (Q8_0),由
qwentts.cpp(C++/ggml,MIT 协议)运行,并带有以下风格指令:
温暖、清晰且充满好奇,就像一位友好的科技解说视频旁白。放松、平稳的语速。
它在与 Kokoro、Chatterbox、PyTorch Qwen3-TTS、VoxCPM2 和 Phonon 的盲听测试中胜出(详情见 references/benchmarks.md)。在 8 GB RTX 4070 笔记本 GPU 上,包含模型加载在内,运行速度约为实时速度的 4 倍:一段 18 分钟的旁白约需 4.5 分钟。
设置(每台机器只需一次)
首先运行 python -I <skill>/scripts/say.py --check。如果显示 OK,请跳过此部分。
否则,请自行设置引擎,或请用户帮忙。大约需要 10-20 分钟和约 2.5 GB 磁盘空间。
- 构建 qwentts.cpp。 没有预编译的 Windows 二进制文件,因此需要从源码构建:
在 Windows 上,安装包含 C++ 工作负载的 VS 2022(或更新版本)构建工具、CMake、Ninja 和 CUDA Toolkit,然后在仓库中运行git clone --recurse-submodules https://github.com/ServeurpersoCom/qwentts.cpp ~/tools/qwentts.cpp cd ~/tools/qwentts.cpp ./buildcuda.sh # NVIDIA (Linux);AMD/Intel 使用 ./buildvulkan.sh;仅 CPU 使用 ./buildcpu.sh(例如 macOS)buildcuda.cmd。如果vcvars64.bat在其他位置,请编辑该脚本顶部的路径。要仅针对一个 GPU,请在 cmake 命令行中添加-DCMAKE_CUDA_ARCHITECTURES=89(RTX 40xx;86 = RTX 30xx,120 = RTX 50xx)。 - 下载权重(约 2 GB)来自 Hugging Face
Serveurperso/Qwen3-TTS-GGUF:pip install -U huggingface_hub hf download Serveurperso/Qwen3-TTS-GGUF qwen-talker-1.7b-customvoice-Q8_0.gguf qwen-tokenizer-12hz-Q8_0.gguf --local-dir ~/tools/qwen-gguf - 让 say.py 指向它们:如果使用了其他文件夹,请设置
QWEN_TTS_HOME(检出目录)、QWEN_TTS_EXE(二进制文件)或QWEN_TTS_MODELS(.gguf 文件夹)。在 Windows 上,say.py会 自动将%CUDA_PATH%\bin\x64添加到 PATH 中(使用CUDA_BIN覆盖)。 - 再次运行
say.py --check,然后输入一行测试文本。
硬件:8 GB VRAM 对于 Q8_0 来说绰绰有余(实际使用约 3 GB)。如果没有 GPU,CPU 构建版本的运行速度大约只有 GPU 的四分之一。GGML_BACKEND=CPU 强制 GPU 构建版本使用 CPU 运行。
生成之前
- 有其他程序正在使用 GPU 吗? 如果正在运行游戏或其他 GPU 密集型应用,生成速度会变慢约 4 倍,并且会导致该应用卡顿。在开始长时间任务之前,请先询问用户。如果用户提到了这类应用,请设置
TTS_BUSY_APPS=app1,app2,这样当有应用正在运行时,say.py会发出警告。 - 需要检查是否有更新的模型吗? 请参见下面的“持续寻找更好的模型”。如果距离上次检查已超过约 6 周,并且当前任务是真正的旁白(而非单行测试),请先快速扫描一次。
- 文本听起来像人说的吗? 语音朗读 AI 生成的文本时,每次的语气都一样,因此这种模式会比在纸面上更明显。对于超过几行的任何旁白,请阅读
whiteboard-explainer技能(同一插件,位于references/目录下)中的natural-narration.md,并运行其scripts/aispeak_scan.py,该脚本接受 script.py 或纯文本文件。
生成
该脚本仅使用标准库,因此任何 Python 3.10+ 版本均可使用。<skill> 是此技能所在的文件夹。
python -I <skill>/scripts/say.py "第一行。" "第二行。" -o out.wav # 单个 WAV 文件,0.35 秒间隔
python -I <skill>/scripts/say.py --file lines.txt -o clips/ # 生成 000.wav, 001.wav, ...
选项:
| 标志 | 作用 |
|---|---|
--speaker | aiden(默认);ryan 是另一个原生英语语音 |
--instruct | 风格文本,例如“兴奋,快节奏”或“冷静,严肃” |
--seed N | 生成可重复的结果 |
--gap S | 合并文件时各行之间的静音秒数 |
--srt F | 同时写入与合并的 WAV 文件时间同步的字幕(参见字幕部分) |
--quant | Q8_0(默认,最佳)或 Q4_K_M(更小,音质稍差;请先下载) |
--check | 报告找到的引擎、模型和 CUDA 文件夹 |
工作原理:
- 每行文本都在一个进程中处理,因此模型只加载一次(3–8 秒)。请批量传递文本行,而非逐行调用。
- 输出为 24 kHz 单声道 16 位 WAV。
每次生成的音频都是随机的,因此每次运行都可能不同:
- 保留您喜欢的音频片段。基于文本和设置,为每行文本缓存结果。
- 如果某行音频的长度与每字符约 0.07 秒的预期相差甚远,请重新生成。这意味着模型可能遗漏或编造了单词。
对于视频旁白,还需要:
- 将每个音频片段的静音修剪到约 50 毫秒的填充,然后使用您自己的间隔在时间线上放置各行文本。
- 对最终音轨进行响度归一化:
ffmpeg -af loudnorm=I=-16:TP=-1.5:LRA=11。
字幕 (.srt):在每个旁白视频中提供
您已经知道每行音频的开始和结束时间,因此无需转录。直接根据这些时间信息构建字幕即可。
- 配合
say.py: 添加--srt narration.srt(需要指定.wav的-o输出文件)。字幕将与合并后的音轨完全匹配。 - 在您自己的流程中: 调用
scripts/srt.py,to_srt([(start_s, end_s, text), ...], display=[...]),传入视频时间线上每行口语的开始/结束时间。或者使用 CLI:python srt.py timings.tsv -o video.srt。 - 修正语音拼写。 旁白文本通常是为语音朗读而编写的,例如“D I L”、姓名音译、”runner dot HTML”、数字转为文字。使用
display/--display display.tsv(口语书面语)将它们映射回书面形式。从屏幕上显示的内容获取书面形式,并对数字使用数字表示法(”$232“,”83%”)。 - 检查结果。 搜索其中残留的大写字母加空格模式(
[A-Z] [A-Z])和“ dot ”。 - 模块应用的规则:
- 每个字幕最多 2 行,每行 42 个字符
- 在句子处换行,然后是子句,最后是单词
- 合并过短的句子,以避免一闪而过
- 时间按长度比例分配
- 将字幕文件命名为与视频文件相同(
Video Name.srt放在Video Name.mp4旁边),以便播放器自动加载。在 VLC 中,--sub-file=可强制指定文件。
whiteboard-explainer 技能的 template/narration/build_vo.py 是一个完整的工作流示例:包含逐行缓存、重新生成、静音修剪、停顿塑造、词对齐、提示表以及嘴型同步包络。
注意事项
- CUDA DLL 必须在 PATH 环境变量中(Windows)。 CUDA 13 将它们放在
...\CUDA\v13.x\bin\x64中。如果 PATH 中没有这个文件夹,qwen-tts.exe会静默退出,不产生任何输出。say.py会自动为您添加该路径。 --stream-by-line -o -写入流式 WAV 头(大小为 0x7fffffff),每行一个。在RIFF....WAVEfmt处分割,并丢弃每个 44 字节的头信息。不要相信 size 字段。- Aiden 会误读单个字母和奇怪的词元。 通过一次无提示的 Whisper 审核发现:
- “h star” 变成了 “a hap” 或 “our star”,“y t” 变成了 “VT”,“y” 变成了 “we”,“arXiv” 变成了 “ArcSireFolk”。
- 以下写法可行:大写 “H star” / “under H”,“why tee” / “why tilde”,“archive”,以及 “SMILES check”。
- “aitch” 的表现不稳定。一个名字单独出现时可能没问题,但放在句子中间就可能被扭曲:请在整个句子中测试。
- 句首的 K 可能会丢失(”Kwenputer” → “When Pooter”):可以以 “And” 或 “So” 开头这样的句子。
- 审核方法:将每个音频片段不带脚本文本作为初始提示进行转录,然后与脚本进行对比。使用提示的 Whisper 会“听到”预期的单词并隐藏错误。工具:
whiteboard-explainer/template/narration/audit_vo.py和tts_try.py。
- 冒号处会停顿。 Qwen 在冒号处会停顿 0.6-0.8 秒,有时在短语中间也会停顿。在旁白中,通过其前面的标点符号来限制每次静音(模板 build_vo.py 中的
shape()),并避免使用冒号分隔的片段行(”First: ...”)。 - CPU 后备方案:Kokoro 82M(
kokoro-onnx)音质平淡但 CPU 运行速度快,适合在 GPU 繁忙时用于草稿。
持续寻找更好的模型
本地 TTS 发展很快,当前的选择终将被超越。请将其视为当前冠军,而非最终答案。在大型旁白任务之前,或上次检查(如下)超过约 6 周时,寻找更新更好的模型。
- 扫描。 搜索网络中自上次检查以来发布或更新的开源权重 TTS。查看:
- TTS Arena / Artificial Analysis 语音排行榜
- Hugging Face 趋势文本转语音模型
- r/LocalLLaMA
- 以下引擎的 GitHub 发布版本
- 筛选。 仅当满足以下条件时,才保留候选模型:
- 可以本地免费使用(开源权重、宽松或个人使用许可证)
- 能在用户的 GPU 内存和操作系统上运行
- 能说所需的语言
- 在质量上超越冠军,或者质量相当但速度快得多
- 试用。 使用候选模型生成
eval/lines.txt。使用scripts/voice_metrics.py对其进行评分,并与冠军进行对比(pip install numpy librosa;冠军和基准录音在eval/目录中):- 音高标准差 ≥ ~5 个半音且范围 ≥ ~17 个半音,才能听起来自然。
- 通过每秒生成的音频时长来测量速度。
- 试听。 请用户在候选模型的音频和
eval/champion_*.wav之间进行听感选择(ffplay -nodisp -autoexit file.wav)。指标仅用于筛选候选模型;最终由人耳决定。 - 记录。 无论是否有任何变化,都请更新下面的“上次检查”行和
references/benchmarks.md。如果冠军模型发生变化,请替换eval/champion_*.wav并更新say.py,并在删除旧模型前询问用户。
上次检查:2026-11-02(网络调查以及本地 VoxCPM2 Q8 和 Phonon 31m/Miles 试用)。Aiden 在听感上优于两者。尚未本地尝试的模型:Phonon 40m、OmniVoice、Breeze TTS 2、Drama 3、Orpheus、Dia、Sesame CSM、Spark-TTS、IndexTTS-2、VibeVoice、Higgs Audio、Kyutai TTS 和 Fish/OpenAudio。详情见 references/benchmarks.md。