基于Coqui TTS的本地文字转语音(TTS)服务搭建
问题
你是不是也好奇,我怎么会想到在本地把文本转成语音的(其实很多服务商提供了在线API)?现在微信打卡活动特别多,尤其是要求每天在指定时间段往特定群里发一段音频,借此来获取积分,大家大多也就是应付一下。我就琢磨着,要是能把一段文字,用特定人的音色转成语音,再设置定时推送,那可太方便了,简直一个 “爽” 字了得。于是,我就关注起了文字转语音服务,纯粹是觉得好玩。有人可能要问,那怎么自动推送呢?这个问题,咱们下篇再讲。
前言
本文将详细介绍如何从零开始搭建一个本地的文字转语音(Text-to-Speech)服务,使用Coqui TTS框架,并通过Flask提供API服务。
项目介绍
项目地址:https://github.com/bobo-gh/TTS
Coqui TTS(🐸TTS)是一个先进的开源文字转语音(Text-to-Speech)深度学习工具包,专注于提供高质量、多语言的语音合成解决方案。该项目源于Mozilla TTS,现已发展成为一个功能强大、社区驱动的语音合成框架。
主要功能模块
- 文字转语音生成
- 语音克隆
- 多语言支持
- 模型训练工具
- 数据集处理实用程序
我这里主要应用语音克隆、文字转语语音的功能 。
环境准备
我的配置 N卡3060 6G文字转语音还行
1. 克隆项目仓库
# 创建项目目录
mkdir tts-project
cd tts-project
# 克隆Coqui TTS仓库
git clone https://github.com/coqui-ai/TTS.git
cd TTS
2. 创建虚拟环境
这里要注意,在命令行中where python ,看系统默认的python 版本,在我系统中默认3.13, 如果合适3.11,下面python命令应给出绝对路径D:\python3.11.5\python.exe
# 创建虚拟环境(推荐使用 3.11) 这里创建时要注意
D:\python3.11.5\python.exe -m venv venv
# 激活虚拟环境
# Windows
.\venv\Scripts\activate
3. 安装PyTorch(根据您的CUDA版本选择)
# 对于CUDA 11.8
pip install torch==2.2.0+cu118 torchvision==0.17.0+cu118 torchaudio==2.2.0 -f https://download.pytorch.org/whl/torch_stable.html
# 如果没有CUDA,使用CPU版本
pip install torch torchvision torchaudio
4. 安装Coqui TTS及依赖
# 安装依赖
pip install -r requirements.txt
# 安装TTS
pip install TTS
# 安装额外依赖
pip install flask requests
服务器端开发 (tts_server.py)
import os
import torch
from flask import Flask, request, send_file, jsonify
from TTS.api import TTS
import uuid
import logging
# 配置日志
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s: %(message)s')
class TTSService:
_instance = None
def __new__(cls):
if not cls._instance:
cls._instance = super().__new__(cls)
cls._instance._initialize()
return cls._instance
def _initialize(self):
# 设置设备
self.device = "cuda" if torch.cuda.is_available() else "cpu"
logging.info(f"使用设备: {self.device}")
# 获取当前脚本的绝对路径
current_dir = os.path.dirname(os.path.abspath(__file__))
# 创建音频输出文件夹
self.output_dir = os.path.join(current_dir, "generated_audio")
os.makedirs(self.output_dir, exist_ok=True)
# 初始化TTS模型(单例模式)
logging.info("正在加载TTS模型,请稍候...")
self.tts = TTS("tts_models/multilingual/multi-dataset/xtts_v2").to(self.device)
logging.info("TTS模型加载完成!")
# 默认参考音频路径
self.default_speaker_wav = os.path.join(current_dir, "demoWav", "bobovoice.wav")
def generate_speech(self, text, language="zh", speaker_wav=None, speed=1.0):
"""
生成语音
:param text: 文本内容
:param language: 语言
:param speaker_wav: 参考音频
:param speed: 语速调节 (0.5-2.0)
:return: 生成的音频文件路径
"""
try:
# 使用默认参数
if speaker_wav is None:
speaker_wav = self.default_speaker_wav
# 确保语速在合理范围内
speed = max(0.5, min(2.0, speed))
# 生成唯一文件名
filename = f"{language}_{uuid.uuid4().hex[:8]}_speed{speed}.wav"
file_path = os.path.join(self.output_dir, filename)
# 生成语音
self.tts.tts_to_file(
text=text,
speaker_wav=speaker_wav,
language=language,
file_path=file_path,
# 添加语速参数 (XTTS v2特有的参数)
speed=speed
)
logging.info(f"语音已生成:{file_path}")
return file_path
except Exception as e:
logging.error(f"生成语音时发生错误: {str(e)}")
return None
# Flask应用
app = Flask(__name__)
tts_service = TTSService()
@app.route('/tts', methods=['POST'])
def text_to_speech():
try:
# 获取请求参数
data = request.json
text = data.get('text')
language = data.get('language', 'zh')
speaker_wav = data.get('speaker_wav')
speed = data.get('speed', 1.0) # 默认语速为1.0
# 参数验证
if not text:
return jsonify({"error": "文本不能为空"}), 400
# 生成语音
file_path = tts_service.generate_speech(text, language, speaker_wav, speed)
if file_path:
return send_file(file_path, mimetype='audio/wav')
else:
return jsonify({"error": "语音生成失败"}), 500
except Exception as e:
logging.error(f"API调用错误: {str(e)}")
return jsonify({"error": str(e)}), 500
@app.route('/health', methods=['GET'])
def health_check():
return jsonify({"status": "healthy", "device": tts_service.device}), 200
def main():
# 服务器配置
app.run(host='0.0.0.0', port=5000, debug=False)
if __name__ == "__main__":
main()
客户端开发 (tts_client.py)
import requests
import os
import re
import uuid
class TTSClient:
def __init__(self, base_url="http://localhost:5000"):
self.base_url = base_url
self.output_dir = "client_generated_audio"
os.makedirs(self.output_dir, exist_ok=True)
def _sanitize_filename(self, text, language, speed=1.0):
"""
生成安全的文件名
:param text: 原始文本
:param language: 语言
:param speed: 语速
:return: 安全的文件名
"""
# 移除换行符和多余空白
text = text.replace('\n', ' ').strip()
# 截取前20个字符
text = text[:20]
# 移除非法文件名字符
text = re.sub(r'[<>:"/\\|?*]', '', text)
# 如果文本为空,使用UUID
if not text:
text = uuid.uuid4().hex[:8]
# 生成最终文件名
filename = f"{language}_speed{speed}_{text}.wav"
return filename
def generate_speech(self, text, language="zh", speaker_wav=None, speed=1.0):
"""
调用TTS服务生成语音
:param text: 要转换的文本
:param language: 语言,默认中文
:param speaker_wav: 可选的参考音频路径
:param speed: 语速控制,默认1.0(正常速度)
:return: 生成的音频文件路径
"""
try:
# 准备请求数据
payload = {
"text": text,
"language": language,
"speed": speed
}
# 如果提供了speaker_wav,加入请求
if speaker_wav:
payload["speaker_wav"] = speaker_wav
# 发送请求
response = requests.post(
f"{self.base_url}/tts",
json=payload
)
# 检查响应
if response.status_code == 200:
# 生成安全的文件名
filename = self._sanitize_filename(text, language, speed)
filepath = os.path.join(self.output_dir, filename)
# 保存音频文件
with open(filepath, 'wb') as f:
f.write(response.content)
print(f"语音已生成:{filepath}")
return filepath
else:
print(f"错误:{response.json().get('error', '未知错误')}")
return None
except Exception as e:
print(f"发生错误:{str(e)}")
return None
def main():
# 创建客户端
client = TTSClient()
# 测试语音生成(不同语速)
texts = [
"人工智能正在快速发展,改变着我们的世界。",
"盼望着,盼望着,东方的曙光即将到来。",
"我是来自于湖北省仙桃市的一名学生。"
]
# 演示不同语速
speeds = [1.0]
for text in texts:
language = "zh" if any('\u4e00' <= char <= '\u9fff' for char in text) else "en"
for speed in speeds:
client.generate_speech(text, language, speed=speed)
if __name__ == "__main__":
main()
启动服务
1. 启动服务器
python tts_server.py
2. 测试客户端
# 测试之前将服务启动,因为加载模型需要些时间,第一次需要等待,第二次再访问时就快了
python tts_client.py
高级使用示例
Python项目中调用
from tts_client import TTSClient
# 创建TTS客户端
tts_client = TTSClient()
# 生成语音
tts_client.generate_speech("你好,世界!", speed=1.5)
注意事项
- 首次运行需要下载模型,可能需要一些时间
- 确保有足够的磁盘空间和内存
- TTS 目录下应该有目录
demoWav
在此目录中存放参考语音 - 使用GPU加速
下一篇介绍如何按时推送到指定好友或群。