给 Claude Code 的 AI 助手配上语音:从零到会说话的完整教程
给 Claude Code 的 AI 助手配上语音:从零到会说话的完整教程
作者:Nicc29jl
日期:2026-05-12
效果:每次 AI 回复后自动生成声音并播放
一、整体架构
Claude Code 回复 → Stop Hook → tts.sh → cc_voice_auto.py → 星瞳TTS模型 → WAV文件 → 自动播放
核心思路:利用 Claude Code 的 Stop Hook 机制,在每次 AI 回复完成后自动触发脚本,将回复文本转为语音并播放。
二、环境准备
2.1 硬件要求
- NVIDIA 显卡(本教程使用 RTX 4060 Laptop 8GB)
- 至少 6GB 显存(推理最低需求)
2.2 软件安装
1. 安装 Miniconda / Anaconda
创建 Python 3.9 环境:
conda create -n sovits python=3.9
conda activate sovits
2. 安装 FFmpeg
winget install FFmpeg
安装后确保 FFmpeg 在系统 PATH 中。
3. 安装 PyTorch(CUDA 版本)
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu126
2.3 获取 GPT-SoVITS
从 gitcode 克隆 GPT-SoVITS V4 仓库:
git clone https://gitcode.com/GPT-SoVITS/GPT-SoVITS.git
cd GPT-SoVITS
2.4 下载预训练模型
需要下载以下模型文件:
- GPT-SoVITS V4 预训练模型:
gsv-v4-pretrained(含s2Gv4.pth和vocoder.pth) - BERT 模型:
chinese-roberta-wwm-ext-large - 中文 HuBERT:
chinese-hubert-base
将模型放置到对应目录:
GPT-SoVITS/
GPT_SoVITS/
pretrained_models/
gsv-v4-pretrained/
chinese-roberta-wwm-ext-large/
chinese-hubert-base/
2.5 Windows 兼容性修复
GPT-SoVITS 在 Windows 上需要以下代码修改:
1. jieba_fast → jieba
文件:GPT_SoVITS/text/chinese.py、chinese2.py、tone_sandhi.py
将 import jieba_fast 改为 import jieba,将 jieba_fast.xxx 改为 jieba.xxx。
(jieba_fast 需要 C++ 编译,Windows 上容易失败)
2. opencc 改为可选
文件:GPT_SoVITS/text/g2pw/onnx_api.py
将繁简转换改为 try/except 可选导入。
3. x_transformers 降级
Python 3.9 不支持新版 x_transformers 语法:
pip install x_transformers==1.42.0
4. 单卡训练修复(如需训练)
文件:GPT_SoVITS/s2_train.py 和 s2_train_v3.py
Windows 不支持 GLOO 分布式训练,单卡时需要跳过 dist.init_process_group 和 DDP 包装。
三、获取语音模型
3.1 方案一:使用现成模型(推荐)
本教程使用**星瞳(XingTong)**预训练模型,中文女声效果优秀。
将模型文件放置到:
D:\GPT-SoVITS\GPT_SoVITS\pretrained_models\XingTong\XingTong\
├── gpt.ckpt
├── sovits.pth
└── ref.wav
3.2 方案二:自己训练
如果你想要定制音色,需要准备:
- 目标说话人的音频(建议 30 分钟以上清晰干声)
- 使用 WebUI 进行数据预处理(切割 → ASR 识别 → BERT/HuBERT 特征提取 → 语义 Token)
- 修改训练代码支持 Windows 单卡训练
- 训练 GPT 模型和 SoVITS 模型
四、TTS 脚本
4.1 语音生成脚本 cc_voice_auto.py
"""AI语音自动生成 - 输入文字,输出语音并播放"""
import sys, os, soundfile as sf, subprocess, re, time
def strip_emoji(text):
"""过滤 emoji 等非 TTS 友好字符"""
emoji_pat = re.compile("["
u"\U0001F300-\U0001FAFF"
u"\U0001FB00-\U0001FFFF"
u"\U00002700-\U000027BF"
u"\U00002600-\U000026FF"
u"\U0001F000-\U0001F02F"
u"\U0001F0A0-\U0001F0FF"
u"\U0000FE00-\U0000FE0F"
u"\U0000200D"
"]+", flags=re.UNICODE)
return emoji_pat.sub('', text).strip()
def clean_for_tts(text):
"""清洗文本:只保留中文、数字、中文标点(星瞳模型不擅长英文)"""
text = re.sub(r'`[^`]*`', '', text)
text = re.sub(r'\*\*[^*]*\*\*', '', text)
text = re.sub(r'\*[^*]*\*', '', text)
text = re.sub(r'https?://\S+', '', text)
# 保留:中文、CJK符号、全角字符、数字、空格、中文标点
text = re.sub(r"[^一-鿿 -〿-0-9 \n,。!?;:、“”‘’()…—~~]+", '', text)
text = re.sub(r'\n+', ',', text)
text = re.sub(r'\s+', ' ', text)
return text.strip()
BASE = r"D:\GPT-SoVITS"
GPT = BASE + r"\GPT_SoVITS\pretrained_models\XingTong\XingTong\gpt.ckpt"
SOVITS = BASE + r"\GPT_SoVITS\pretrained_models\XingTong\XingTong\sovits.pth"
REF = BASE + r"\GPT_SoVITS\pretrained_models\XingTong\XingTong\ref.wav"
OUT_DIR = r"C:\Users\你的用户名\Desktop\CC语音"
os.chdir(BASE)
sys.path.insert(0, "GPT_SoVITS"); sys.path.insert(0, ".")
os.environ["is_half"] = "True"
os.environ["gpt_path"] = GPT
os.environ["sovits_path"] = SOVITS
os.environ["bert_path"] = BASE + r"\GPT_SoVITS\pretrained_models\chinese-roberta-wwm-ext-large"
os.environ["cnhubert_base_path"] = BASE + r"\GPT_SoVITS\pretrained_models\chinese-hubert-base"
from GPT_SoVITS.inference_webui import change_gpt_weights, change_sovits_weights, get_tts_wav
change_gpt_weights(gpt_path=GPT)
change_sovits_weights(sovits_path=SOVITS)
def speak(text):
text = strip_emoji(text.strip()).replace("CC", "西西").replace("cc", "西西")
text = clean_for_tts(text)
if not text or len(text) < 2:
return None
try:
result = list(get_tts_wav(
ref_wav_path=REF, prompt_text="", prompt_language="中文",
text=text, text_language="中文",
top_k=20, top_p=0.6, temperature=0.6, sample_steps=16, speed=1))
if result:
sr, audio = result[-1]
ts = time.strftime("%H%M%S")
filename = f"CC_{ts}.wav"
filepath = os.path.join(OUT_DIR, filename)
sf.write(filepath, audio, sr)
return filepath
except Exception as e:
print(f"[语音错误] {e}", file=sys.stderr)
return None
if __name__ == "__main__":
if len(sys.argv) >= 3 and sys.argv[1] == "--file":
with open(sys.argv[2], "r", encoding="utf-8") as f:
text = f.read().strip()
elif len(sys.argv) > 1:
text = sys.argv[1]
else:
text = sys.stdin.read().strip()
if text:
filepath = speak(text)
if filepath:
print(filepath)
# 自动播放
subprocess.Popen(['powershell', '-c',
f'(New-Object System.Media.SoundPlayer "{filepath}").PlaySync();'],
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
else:
print("FAILED", file=sys.stderr)
4.2 Hook 脚本 tts.sh
保存到 C:\Users\你的用户名\.claude\hooks\tts.sh:
#!/usr/bin/env bash
# Claude Code Stop Hook - AI 回复后自动生成语音
LOGF="$HOME/.claude/hooks/tts_debug.log"
STDIN_FILE="C:/Users/你的用户名/.claude/hooks/.tts_stdin.json"
MSG_FILE="C:/Users/你的用户名/.claude/hooks/.tts_msg.txt"
# 保存 Claude Code 传来的 JSON 数据
cat > "$STDIN_FILE"
# 从 JSON 中提取 AI 回复文本,写入 UTF-8 文件(避免 CLI 编码问题)
python路径/python -c "
import json
d = json.load(open('C:/Users/你的用户名/.claude/hooks/.tts_stdin.json', 'r', encoding='utf-8'))
msg = d.get('last_assistant_message', '')
if msg:
with open('C:/Users/你的用户名/.claude/hooks/.tts_msg.txt', 'w', encoding='utf-8') as f:
f.write(msg)
print('OK')
else:
print('EMPTY')
" >>"$LOGF" 2>>"$LOGF"
# 生成语音
if [ -s "$MSG_FILE" ]; then
echo "$(date): generating..." >> "$LOGF"
python路径/python C:/Users/你的用户名/cc_voice_auto.py --file "$MSG_FILE" 2>>"$LOGF"
else
echo "$(date): no message" >> "$LOGF"
fi
4.3 配置 Claude Code Hook
编辑 C:\Users\你的用户名\.claude\settings.json:
{
"permissions": {
"allow": [
"Bash(你的python路径/python C:/Users/你的用户名/cc_voice_auto.py *)",
"Bash(find */CC语音/* -name *.wav -mmin *)"
]
},
"hooks": {
"Stop": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "bash ~/.claude/hooks/tts.sh",
"timeout": 120
}
]
}
]
}
}
注意:
"matcher": ""不可省略,否则 hook 格式校验不通过。
五、踩坑记录与解决方案
以下是实际配置过程中遇到的 5 个关键问题:
坑 1:stdin 管道在命令替换中不稳定
症状:Hook 收到数据但 Python 读不到 stdin
原因:$(python -c "sys.stdin.read()") 中 $() 命令替换可能不继承管道 stdin
解决:先用 cat > file 把 stdin 保存到临时文件,再让 Python 读取文件
坑 2:2>/dev/null 吞掉所有错误
症状:Hook 静默失败,无从排查
解决:将所有 2>/dev/null 改为 2>>debug.log,建立调试日志体系
坑 3:Emoji 导致 GBK 编码崩溃
症状:'gbk' codec can't encode character '\U0001f3a4'
原因:Windows 中文系统 CLI 使用 GBK 编码,emoji(如 🎤)无法编码
解决:TTS 前用正则过滤所有 emoji 和特殊 Unicode 符号
坑 4:中英混合文本让模型产出垃圾音频(最隐蔽)
症状:生成 WAV 文件但只听见几个字,其余全是杂音——没有任何异常抛出
原因:星瞳是纯中文 TTS 模型,英文单词/技术术语(API、JSON、stdin 等)让它生成无意义波形但不报错
解决:添加 clean_for_tts() 函数,TTS 前将英文、特殊符号全部过滤,只保留中文+数字+中文标点。
教训:不要依赖"先试原文、失败再清洗"的两步策略——模型对中英混合不抛异常但产出垃圾,无法触发重试。直接全量清洗。
坑 5:CLI 参数编码损耗(根因)
症状:中文文本经 bash 传给 Windows Python 后变成乱码,clean_for_tts() 返回空字符串
原因:MSYS2/Git Bash 传参给 Windows 原生程序时,UTF-8 文本经过系统 GBK 代码页转换,部分字符丢失
解决:改用 --file 参数,将文本写入 UTF-8 文件,Python 直接从文件读取。彻底绕开 CLI 编码转换。
六、调试技巧
6.1 手动测试 Hook
echo '{"last_assistant_message":"你好这是一条测试"}' | bash ~/.claude/hooks/tts.sh
6.2 直接测试 TTS 引擎
cd D:/GPT-SoVITS
python -c "
import sys; sys.path.insert(0, 'GPT_SoVITS')
# ... 完整推理代码见上文
"
6.3 检查产物
- 桌面
CC语音/文件夹下的 WAV 文件 ~/.claude/hooks/tts_debug.log调试日志
6.4 常见失败原因速查
| 症状 | 可能原因 | 检查方法 |
|---|---|---|
| 无任何语音文件 | Hook 未触发 | 检查 settings.json 格式 |
| 有日志但显示 MSG empty | JSON 解析失败 | 检查 .tts_stdin.json 内容 |
| 日志显示 FAILED | TTS 生成失败 | 检查 tts_text_debug.txt 确认文本是否正常 |
| 有 WAV 但音质差/少字 | 中英混合文本 | 确认 clean_for_tts() 已过滤英文 |
七、资源链接
- GPT-SoVITS 仓库:
https://gitcode.com/GPT-SoVITS/GPT-SoVITS - 预训练模型合集 HuggingFace:
https://huggingface.co/zhaijifu67/so-vits-4.0-collect - B站 GPT-SoVITS-V4 教程:搜索 “GPT-SoVITS V4 推理”
后记
从"AI 只能打字"到"AI 能跟我说话",中间的每一步都是踩着 Bug 走过来的。希望这篇教程能帮你少走弯路,让你的 AI 助手也快点开口说话!
如果你配好了自己的 AI 语音,欢迎分享你的音色选择和效果~
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)