如何在本地部署ChatTTS?
前言
最近,24-05-27号,github上出现了一个新项目,ChatTTS。该项目提供了一个文本转语音(Text To Speech)的开源方案,同时支持中文和英文。在官网的演示视频中,可以看到合成效果高度接近真人。
到目前(06-04)为止,已经有18.3k的star。
那我们就来看看这个模型的基本部署和使用方法吧。
环境说明
Ubuntu22.04,python版本为默认的3.8.10
安装pip和换清华源
sudo apt install pip
pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple
安装mpv
用于音频播放
sudo apt install mpv
本地安装
💫下载modelscope和SDK模型
pip install modelscope
from modelscope import snapshot_download
model_dir = snapshot_download(pzc163/chatTTS')
💫获取源码并安装依赖
git clone https://github.com/2noise/ChatTTS.git
cd ChatTTS
pip install -r requirement.txt
💫测试脚本
官网给出的版本会提示torchaudio的相关错误,这里采用材料4的方式,在ChatTTS
文件夹下新建py
文件并执行:
import ChatTTS
from IPython.display import Audio
from modelscope import snapshot_download
# 注意your-user-path换成你自己的用户目录,这个参数意思是你的本地模型下载目录
model_dir = "/<your-user-path>/.cache/modelscope/hub/pzc163/chatTTS"
chat = ChatTTS.Chat()
# compile设置为True可以获得更好的表现
# source 和 local_path 是对本地部署的设置
chat.load_models(compile=True, source='local', local_path=model_dir)
rand_spk = chat.sample_random_speaker()
params_infer_code = {
'spk_emb': rand_spk, # add sampled speaker
'temperature': .3, # using custom temperature
'top_P': 0.7, # top P decode
'top_K': 20, # top K decode
}
params_refine_text = {
'prompt': '[oral_2][laugh_2][break_6]'
}
# 你要转换的文本
texts = ["幸福是把灵魂安放在最适当的位置。",]
wavs = chat.infer(texts, skip_refine_text=True, params_infer_code=params_infer_code, use_decoder=True)
Audio(wavs[0], rate=24_000, autoplay=True)
#保存Audio
import soundfile as sf
audio_data = wavs[0]
if len(audio_data.shape) > 1:
audio_data = audio_data.flatten()
output_file='./output_audio2.wav'
sf.write(output_file, audio_data, 24000)
注意model_path
换成你自己的用户目录。如果提示缺少其他依赖,根据错误提示用pip
安装即可。
编译好的音频用mpv
进行播放即可。
mpv output_audio2.wav
使用
💫笑声和停顿的控制
🌟基本形式
在chat.infer()
函数中,确保use_decoder=True
。
然后在文本中,[uv_break]
和[lbreak]
表示停顿;[laugh]
表示笑声。
params_refine_text
中可以进一步配置oral``break
和laugh
的类型。
在默认值中,0是关闭的意思。
注意oral指定语气助词,不为零会出现“那个”之类的语气助词。
另外还有两个问题:
-
不支持分号、问号和省略号等符号。
-
对“地”作为“de”的发音并不支持。
🌟多音调的测试
给出一个测试脚本:
from ChatTTS import Chat
from IPython.display import Audio
import ChatTTS
import torch
import csv
import soundfile as sf
model_dir = "/<your-user-path>/.cache/modelscope/hub/pzc163/chatTTS"
chat = ChatTTS.Chat()
chat.load_models(compile=True, source='local', local_path=model_dir)
torch.manual_seed(2215)
rand_spk = chat.sample_random_speaker()
params_infer_code = {
'spk_emb': rand_spk, # add sampled speaker
'temperature': .001, # using custom temperature
'top_P': 0.7, # top P decode
'top_K': 20, # top K decode
}
params_refine_text = {
'prompt': '[oral_2][laugh_1][break_1]'
}
text = "幸福是把[uv_break]灵魂安放在最合适[laugh]的位置。"
test_example = []
oral = 10
laugh = 3
lbreak = 8
for i in range(oral):
for j in range(laugh):
for k in range(lbreak):
row = [i, j, k]
test_example.append(row)
#print(test_example)
for i in test_example:
params_refine_text = {
'prompt': '[oral_' + str(i[0]) + '][laugh_' + str(i[1]) + '][break_' + str(i[2]) + ']'
}
wav = chat.infer(text,params_infer_code=params_infer_code, params_refine_text=params_refine_text, use_decoder=True)
output_file = '/<your-user-path>/data/tts_test/n/fl-2215-' + str(i[0]) + str(i[1]) + str(i[2]) + '.wav'
audio_data = wav[0]
if len(audio_data.shape) > 1:
audio_data = audio_data.flatten()
sf.write(output_file, audio_data, 24000)
print(f"Audio saved to {output_file}")
💫音色固定问题
🌟方法一、保存张量
在材料五中可以看到。本质上是把rand_spk 随机生成的值进行了保存。具体的代码实现这里给出两个:
保存代码:
from ChatTTS import Chat
from IPython.display import Audio
import ChatTTS
import torch
import csv
import soundfile as sf
model_dir = "/<your-user-path>/.cache/modelscope/hub/pzc163/chatTTS"
chat = ChatTTS.Chat()
chat.load_models(compile=True, source='local', local_path=model_dir)
torch.manual_seed(0)
rand_spk = chat.sample_random_speaker()
def writeToCsv(csv_file_path,data):
with open(csv_file_path, mode='w', newline='') as file:
writer = csv.writer(file)
writer.writerow(data.tolist())
writeToCsv(f"saved.csv",rand_spk.detach().numpy())
params_infer_code = {
'spk_emb': rand_spk, # add sampled speaker
'temperature': .001, # using custom temperature
'top_P': 0.7, # top P decode
'top_K': 20, # top K decode
}
# use oral_(0-9), laugh_(0-2), break_(0-7)
# to generate special token in text to synthesize.
params_refine_text = {
'prompt': '[oral_0][laugh_0][break_7]'
}
text = "幸福是[uv_break]把灵魂安放在最合适的位置。"
wav = chat.infer(text,params_infer_code=params_infer_code, params_refine_text=params_refine_text, use_decoder=True)
audio_data = wav[0]
if len(audio_data.shape) > 1:
audio_data = audio_data.flatten()
output_file = './output_audio2.wav'
sf.write(output_file, audio_data, 24000)
print(f"Audio saved to {output_file}")
加载代码:
from ChatTTS import Chat
from IPython.display import Audio
from modelscope import snapshot_download
import ChatTTS
import torch
import csv
import pandas as pd
model_dir = "/<your-user-path>/.cache/modelscope/hub/pzc163/chatTTS"
chat = ChatTTS.Chat()
chat.load_models(compile=True, source='local', local_path=model_dir)
data = pd.read_csv(f"./saved/f-8.csv", header=None)
rand_spk = torch.tensor(data.iloc[0], dtype=torch.float32)
params_infer_code = {
'spk_emb': rand_spk, # add sampled speaker
'temperature': .1, # using custom temperature
'top_P': 0.7, # top P decode
'top_K': 20, # top K decode
}
params_refine_text = {
'prompt': '[oral_1][laugh_1][break_1]'
}
with open('./test.txt', 'r') as file:
text = file.readlines()
wav = chat.infer(text,params_infer_code=params_infer_code, params_refine_text=params_refine_text, use_decoder=True)
import soundfile as sf
audio_data = wav[0]
if len(audio_data.shape) > 1:
audio_data = audio_data.flatten()
#记得换成自己的目录
output_file = '/<your-user-path>/data/tts_test/output_audio2.wav'
sf.write(output_file, audio_data, 24000)
print(f"Audio saved to {output_file}")
🌟方法二、设置种子值(推荐)
核心命令在于通过torch.manual_seed
来设置种子值:
torch.manual_seed(seed)
其中seed
应该是一个整数。
那么我们就可以得到一个根据种子固定音色的通用代码:
import ChatTTS
from IPython.display import Audio
from modelscope import snapshot_download
# 注意your-user-path换成你自己的用户目录,这个参数意思是你的本地模型下载目录
model_dir = "/<your-user-path>/.cache/modelscope/hub/pzc163/chatTTS"
#seed应该设置为一个整数。
torch.manual_seed(seed)
chat = ChatTTS.Chat()
# compile设置为True可以获得更好的表现
# source 和 local_path 是对本地部署的设置
chat.load_models(compile=True, source='local', local_path=model_dir)
rand_spk = chat.sample_random_speaker()
params_infer_code = {
'spk_emb': rand_spk, # add sampled speaker
'temperature': .3, # using custom temperature
'top_P': 0.7, # top P decode
'top_K': 20, # top K decode
}
params_refine_text = {
'prompt': '[oral_2][laugh_2][break_6]'
}
# 你要转换的文本
texts = ["幸福是把灵魂安放在最适当的位置。",]
wavs = chat.infer(texts, skip_refine_text=True, params_infer_code=params_infer_code, use_decoder=True)
Audio(wavs[0], rate=24_000, autoplay=True)
#保存Audio
import soundfile as sf
audio_data = wavs[0]
if len(audio_data.shape) > 1:
audio_data = audio_data.flatten()
output_file='./output_audio2.wav'
sf.write(output_file, audio_data, 24000)
注意把模型目录换成自己的,并且给seed一个初始值。
🌟多种子的测试
给出一个测试1-999种子的代码,如果需要,改变for循环的值即可:
from ChatTTS import Chat
from IPython.display import Audio
import ChatTTS
import torch
import csv
import soundfile as sf
model_dir = "/<your-home-path>/.cache/modelscope/hub/pzc163/chatTTS"
chat = ChatTTS.Chat()
chat.load_models(compile=True, source='local', local_path=model_dir)
params_refine_text = {
'prompt': '[oral_0][laugh_1][break_7]'
}
text = "幸福是把[uv_break]灵魂安放在最合适[laugh]的位置。"
for seed in range(1, 1000):
torch.manual_seed(seed)
rand_spk = chat.sample_random_speaker()
params_infer_code = {
'spk_emb': rand_spk, # add sampled speaker
'temperature': .001, # using custom temperature
'top_P': 0.7, # top P decode
'top_K': 20, # top K decode
}
wav = chat.infer(text,params_infer_code=params_infer_code, params_refine_text=params_refine_text, use_decoder=True)
output_file = '/<your-home-path>/data/tts_test/seed/' + str(seed) + '.wav'
#save audio
audio_data = wav[0]
if len(audio_data.shape) > 1:
audio_data = audio_data.flatten()
sf.write(output_file, audio_data, 24000)
print(f"Audio saved to {output_file}")
💫文本的切割合成
ChatTTS本身不适合长文本的生成。如果要生成长文本,可以通过切割文本分别生成音频文件再加以连接。
这里给出实现代码。
🌟单句合成代码
版本一:给出种子,文本和目标路径进行合成
### 本文件用于tts的行合成。接受三个参数。第一个是种子值,为int数字。第二个为合成音频的文本。第三个为目标文件路径。
from ChatTTS import Chat
from IPython.display import Audio
import ChatTTS
import torch
import sys
import soundfile as sf
args=sys.argv
seed=int(args[1])
text=args[2]
aim_path=args[3]
model_dir = "/<your-user-path>/.cache/modelscope/hub/pzc163/chatTTS"
chat = ChatTTS.Chat()
chat.load_models(compile=True, source='local', local_path=model_dir)
torch.manual_seed(seed)
rand_spk = chat.sample_random_speaker()
params_infer_code = {
'spk_emb': rand_spk, # add sampled speaker
'temperature': .3, # using custom temperature
'top_P': 0.7, # top P decode
'top_K': 20, # top K decode
}
# use oral_(0-9), laugh_(0-2), break_(0-7)
# to generate special token in text to synthesize.
params_refine_text = {
'prompt': '[oral_0][laugh_0][break_7]'
}
wav = chat.infer(text,params_infer_code=params_infer_code, params_refine_text=params_refine_text, use_decoder=True)
audio_data = wav[0]
if len(audio_data.shape) > 1:
audio_data = audio_data.flatten()
sf.write(aim_path, audio_data, 24000)
print(f"Audio saved to {aim_path}")
版本二:给出文件,源文件路径和目标路径进行音频合成。
### 本文件用于tts的行合成。接受三个参数。第一个是种子值,为int数字。第二个为源文件路径。第三个为目标文件路径。
from ChatTTS import Chat
from IPython.display import Audio
import ChatTTS
import torch
import sys
import soundfile as sf
args=sys.argv
seed=int(args[1])
source_path=args[2]
aim_path=args[3]
model_dir = "/<your-user-path>/.cache/modelscope/hub/pzc163/chatTTS"
chat = ChatTTS.Chat()
chat.load_models(compile=True, source='local', local_path=model_dir)
torch.manual_seed(seed)
rand_spk = chat.sample_random_speaker()
params_infer_code = {
'spk_emb': rand_spk, # add sampled speaker
'temperature': .3, # using custom temperature
'top_P': 0.7, # top P decode
'top_K': 20, # top K decode
}
# use oral_(0-9), laugh_(0-2), break_(0-7)
# to generate special token in text to synthesize.
params_refine_text = {
'prompt': '[oral_0][laugh_0][break_7]'
}
with open(source_path, 'r') as file:
text = file.readlines()
wav = chat.infer(text,params_infer_code=params_infer_code, params_refine_text=params_refine_text, use_decoder=True)
audio_data = wav[0]
if len(audio_data.shape) > 1:
audio_data = audio_data.flatten()
sf.write(aim_path, audio_data, 24000)
print(f"Audio saved to {aim_path}")
🌟切割文本合成
文本切割通过readlines
函数进行操作即可。而音频合成则可以通过pydub
中的AudioSegment
模块来实现。而对于每一行之间的间隔,可以用sox
命令生成一段0.5s的空白:
sox -n -r 44100 -c 2 silence-0.5s.wav trim 0 0.5
### 本文件用于tts的行合成。接受三个参数。第一个是种子值,为int数字。第二个为源文件路径。第三个为目标文件夹路径。
from ChatTTS import Chat
from IPython.display import Audio
import ChatTTS
import torch
import sys
import soundfile as sf
from pydub import AudioSegment
args=sys.argv
seed=int(args[1])
source_path=args[2]
tmp_path = "/<your-home-path>/data/tmp"
aim_path=args[3]
model_dir = "/<your-home-path>/.cache/modelscope/hub/pzc163/chatTTS"
chat = ChatTTS.Chat()
chat.load_models(compile=True, source='local', local_path=model_dir)
torch.manual_seed(seed)
rand_spk = chat.sample_random_speaker()
params_infer_code = {
'spk_emb': rand_spk, # add sampled speaker
'temperature': .3, # using custom temperature
'top_P': 0.7, # top P decode
'top_K': 20, # top K decode
}
params_refine_text = {
'prompt': '[oral_0][laugh_0][break_7]'
}
with open(source_path, 'r') as file:
text = file.readlines()
i=0
for row in text:
i+=1
wav = chat.infer(row.replace("\n",""),params_infer_code=params_infer_code, params_refine_text=params_refine_text, use_decoder=True)
tmp_file=tmp_path + "/line-" + str(i) + ".wav"
audio_data = wav[0]
if len(audio_data.shape) > 1:
audio_data = audio_data.flatten()
sf.write(tmp_file, audio_data, 24000)
print(f"Audio saved to {tmp_file}")
j=0
audio_silence = AudioSegment.from_file("/<your-home-path>/data/tts_other/silence-0.5s.wav", format="wav")
audio = AudioSegment.from_file("/<your-home-path>/data/tts_other/silence-0.5s.wav", format="wav")
for x in range(1, i+1):
j+=1
audio_now = AudioSegment.from_file(tmp_path + "/line-" + str(j) + ".wav", format="wav")
audio = audio + audio_silence + audio_now
audio = audio + audio_silence
audio.export(aim_path, format="wav")
其中为了保存文本生成结果用到了tmp
文件夹。并且在生成之后没有删除中间文本。有需要可以增加删除命令。
使用方式还是,第一个参数,种子值;第二个参数,原文本文档;第三个参数,目标路径;
🌟bash和alias的快捷命令
在~/src/mybash
中增加bash脚本:
#!bin/bash
python3 ~/src/chattts/ChatTTS/text-trans.py $1 $2 $3
在~/.bashrc
中增加如下行:
alias text-trans='bash ~/src/mybash/text-trans.sh'
应用配置
source ~/.bashrc
以后就有了一个基于ChatTTS进行文本切割合成的命令行工具:
text-trans <seed> <source-file> <aim-file>
🌟添加背景音
通过ffmpeg
命令可以对音频进行合成,为合成的语音增加背景音乐。
用法如下:
ffmpeg -i background.mp3 -i foreground.wav -filter_complex amix=inputs=2:duration=shortest output.wav
如果生成的wav文件过大,可以用lame
压缩为mp3文件。
lame input.wav output.mp3
结语
本文介绍了ChatTTS的本地化部署方式,并对文本的切割合成和音色的固定,以及背景音的增加进行了相应介绍。对于ChatTTS,未来可以考虑更多的应用场景。