0%

Bestbushiroad计划(2) BangDreamTTS

Bang Dream全员TTS

使用例: 千早爱音
如果挂了梯子可以直接尝试huggingface
如果想合成MyGo+Mujica的全员语音,可以使用基于MyGo剧情训练的模型(仅支持日语)

使用须知

由于bert-vits项目才刚刚支持日语,所以稳定度和效果都会有影响。
参数设置:可以视情况将感情调节和音素控制调节至0.3以下,对应非日常语言(小说等)拥有更好的效果。

使用Galgame引擎进行二创

WebGAL

将AI皮套人部署到服务器或者直播间

轻量化live2d驱动

长文本自动合成:

选择1:自定义文本

与训练用的标记方法相似,用”|”标识符将说话人和内容分割开,参考圣经.txt

1
2
3
4
5
6
素世|我也会好好跟你说明我不得不组乐队的理由
素世|我想如果你能见我一面,你就一定能明白的
素世|我是小祥你的同伴
素世|我好想见你
祥子|真是会虚情假意呢
祥子|想演奏是你们的自由,你们就请便吧

实际上并不需要在意”祥子”或者”素世“这些说话人是什么,名字仅仅是用来标记的。
比如

1
2
3
4
5
6
为|我也会好好跟你说明我不得不组乐队的理由
什|我想如果你能见我一面,你就一定能明白的
么|我是小祥你的同伴
要|我好想见你
演奏|真是会虚情假意呢
春日影|想演奏是你们的自由,你们就请便吧

重要的是在 拓展功能>>>>>>角色对应表中进行手动设置
详见config下的”spk2id”项。对于算法来说,你实际上是在完成一个
“说话人所扮演的角色名”—>”说话人”—>”编号”
作为导演,需要让说话人扮演你所希望的角色:

1
2
3
4
5
6
7
8
そよ|素世
祥子|祥子
ましろ|为
七深|什
透子|么
つくし|要
瑠唯|演奏
そよ|春日影

选择2:游戏脚本自动解读

其实我不知道让她们重新读一遍日语有什么用,但因为不用做任何映射关系,直接去下载一个日语脚本丢进去就好了。
当然,可以让她们读中文脚本,帮助入眠的同时又过了遍剧情()。
下载台服脚本
设置角色对应关系(其实就是译名):

1
2
3
4
5
ましろ|真白
七深|七深
透子|透子
つくし|筑紫
瑠唯|瑠唯

选择3:读小说(建议本地)

支持epub、txt和mobi,但出于稳定性考虑建议先自行用工具转换成txt格式。
当然,在网页端是无法完整合成的,确保在本地运行并且安装了gpu支持(也就是cuda和cudnn)。
所有音频都会被存储在”BangDream-Bert-VITS2/books”文件夹下,记得合成后转移已生成文件,再次合成时将会自动清空。

本地运行:

1
2
3
4
git clone https://huggingface.co/spaces/Mahiruoshi/BangDream-Bert-VITS2
cd BangDream-Bert-VITS2
pip install -r requirements.txt
python app.py

样例API 使用说明

Flask启动代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
logger = logging.getLogger(__name__)
import datetime
import numpy as np
import torch
from ebooklib import epub
import PyPDF2
from PyPDF2 import PdfReader
import zipfile
import shutil
import sys, os
import json
from bs4 import BeautifulSoup
import argparse
import commons
import utils
from models import SynthesizerTrn
from text.symbols import symbols
from text import cleaned_text_to_sequence, get_bert
from text.cleaner import clean_text
import gradio as gr
import webbrowser
import re
from scipy.io.wavfile import write

net_g = None

if sys.platform == "darwin" and torch.backends.mps.is_available():
device = "mps"
os.environ["PYTORCH_ENABLE_MPS_FALLBACK"] = "1"
else:
device = "cuda"

def is_japanese(string):
for ch in string:
if ord(ch) > 0x3040 and ord(ch) < 0x30FF:
return True
return False

def get_text(text, language_str, hps):
norm_text, phone, tone, word2ph = clean_text(text, language_str)
phone, tone, language = cleaned_text_to_sequence(phone, tone, language_str)

if hps.data.add_blank:
phone = commons.intersperse(phone, 0)
tone = commons.intersperse(tone, 0)
language = commons.intersperse(language, 0)
for i in range(len(word2ph)):
word2ph[i] = word2ph[i] * 2
word2ph[0] += 1
bert = get_bert(norm_text, word2ph, language_str, device)
del word2ph
assert bert.shape[-1] == len(phone), phone

if language_str == "ZH":
bert = bert
ja_bert = torch.zeros(768, len(phone))
elif language_str == "JA":
ja_bert = bert
bert = torch.zeros(1024, len(phone))
else:
bert = torch.zeros(1024, len(phone))
ja_bert = torch.zeros(768, len(phone))

assert bert.shape[-1] == len(
phone
), f"Bert seq len {bert.shape[-1]} != {len(phone)}"

phone = torch.LongTensor(phone)
tone = torch.LongTensor(tone)
language = torch.LongTensor(language)
return bert, ja_bert, phone, tone, language


def infer(text, sdp_ratio, noise_scale, noise_scale_w, length_scale, sid, language):
global net_g
bert, ja_bert, phones, tones, lang_ids = get_text(text, language, hps)
with torch.no_grad():
x_tst = phones.to(device).unsqueeze(0)
tones = tones.to(device).unsqueeze(0)
lang_ids = lang_ids.to(device).unsqueeze(0)
bert = bert.to(device).unsqueeze(0)
ja_bert = ja_bert.to(device).unsqueeze(0)
x_tst_lengths = torch.LongTensor([phones.size(0)]).to(device)
del phones
speakers = torch.LongTensor([hps.data.spk2id[sid]]).to(device)
audio = (
net_g.infer(
x_tst,
x_tst_lengths,
speakers,
tones,
lang_ids,
bert,
ja_bert,
sdp_ratio=sdp_ratio,
noise_scale=noise_scale,
noise_scale_w=noise_scale_w,
length_scale=length_scale,
)[0][0, 0]
.data.cpu()
.float()
.numpy()
)
del x_tst, tones, lang_ids, bert, x_tst_lengths, speakers
return audio


def tts_fn(
text, speaker, sdp_ratio, noise_scale, noise_scale_w, length_scale, language
):
with torch.no_grad():
audio = infer(
text,
sdp_ratio=sdp_ratio,
noise_scale=noise_scale,
noise_scale_w=noise_scale_w,
length_scale=length_scale,
sid=speaker,
language= "JP" if is_japanese(text) else "ZH",
)
torch.cuda.empty_cache()
write("books/temp.wav", 44100, audio)
return "Success"

app = Flask(__name__)

@app.route('/tts')
def tts_api():
# 从请求中获取参数
text = request.args.get('text')
speaker = request.args.get('speaker')
sdp_ratio = float(request.args.get('sdp_ratio', 0.2))
noise_scale = float(request.args.get('noise_scale', 0.6))
noise_scale_w = float(request.args.get('noise_scale_w', 0.8))
length_scale = float(request.args.get('length_scale', 1))

status = tts_fn(text, speaker='つくし', sdp_ratio=0.2, noise_scale=0.6, noise_scale_w=0.8, length_scale=1)
with open('books/temp.wav','rb') as bit:
wav_bytes = bit.read()

headers = {
'Content-Type': 'audio/wav',
'Text': status.encode('utf-8')}
return wav_bytes, 200, headers

if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument(
"-m", "--model", default="./logs/BangDream/G_47000.pth", help="path of your model"
)
parser.add_argument(
"-c",
"--config",
default="./logs/BangDream/config.json",
help="path of your config file",
)
parser.add_argument(
"--share", default=True, help="make link public", action="store_true"
)
parser.add_argument(
"-d", "--debug", action="store_true", help="enable DEBUG-LEVEL log"
)

args = parser.parse_args()
if args.debug:
logger.info("Enable DEBUG-LEVEL log")
logging.basicConfig(level=logging.DEBUG)
hps = utils.get_hparams_from_file(args.config)

device = (
"cuda:0"
if torch.cuda.is_available()
else (
"mps"
if sys.platform == "darwin" and torch.backends.mps.is_available()
else "cpu"
)
)
net_g = SynthesizerTrn(
len(symbols),
hps.data.filter_length // 2 + 1,
hps.train.segment_size // hps.data.hop_length,
n_speakers=hps.data.n_speakers,
**hps.model,
).to(device)
_ = net_g.eval()

_ = utils.load_checkpoint(args.model, net_g, None, skip_optimizer=True)

app.run(host="0.0.0.0", port=8080)

API 请求路径:

1
/tts

请求方法:

GET

请求参数:

text (必须) - 您想要转化为语音的文本。
speaker (可选) - 选择的发音人,默认为 ‘つくし’。
sdp_ratio (可选) - 参数值,默认为 0.2。
noise_scale (可选) - 噪音比例,默认为 0.6。
noise_scale_w (可选) - 噪音比例宽度,默认为 0.8。
length_scale (可选) - 长度比例,默认为 1。

响应:

响应为 WAV 格式的音频文件,可以直接播放。

响应头:

Content-Type: ‘audio/wav’ - 表示响应内容为 WAV 格式的音频文件。
Text: 转化的状态,编码为 UTF-8。

调用示例:

1
GET /tts?text=你好,世界!&speaker=つくし&sdp_ratio=0.3&noise_scale=0.5