Spaces:
Running
Running
import os | |
import subprocess | |
import yaml | |
import sys | |
import webbrowser | |
import gradio as gr | |
from ruamel.yaml import YAML | |
import shutil | |
import soundfile | |
import shlex | |
import locale | |
class WebUI: | |
def __init__(self): | |
self.train_config_path = 'configs/train.yaml' | |
self.info = Info() | |
self.names = [] | |
self.names2 = [] | |
self.voice_names = [] | |
self.base_config_path = 'configs/base.yaml' | |
if not os.path.exists(self.train_config_path): | |
shutil.copyfile(self.base_config_path, self.train_config_path) | |
print(i18n("初始化成功")) | |
else: | |
print(i18n("就绪")) | |
self.main_ui() | |
def main_ui(self): | |
with gr.Blocks(theme=gr.themes.Base(primary_hue=gr.themes.colors.green)) as ui: | |
gr.Markdown('# so-vits-svc5.0 WebUI') | |
with gr.Tab(i18n("预处理-训练")): | |
with gr.Accordion(i18n('训练说明'), open=False): | |
gr.Markdown(self.info.train) | |
gr.Markdown(i18n('### 预处理参数设置')) | |
with gr.Row(): | |
self.model_name = gr.Textbox(value='sovits5.0', label='model', info=i18n('模型名称'), interactive=True) #建议设置为不可修改 | |
self.f0_extractor = gr.Textbox(value='crepe', label='f0_extractor', info=i18n('f0提取器'), interactive=False) | |
self.thread_count = gr.Slider(minimum=1, maximum=os.cpu_count(), step=1, value=2, label='thread_count', info=i18n('预处理线程数'), interactive=True) | |
gr.Markdown(i18n('### 训练参数设置')) | |
with gr.Row(): | |
self.learning_rate = gr.Number(value=5e-5, label='learning_rate', info=i18n('学习率'), interactive=True) | |
self.batch_size = gr.Slider(minimum=1, maximum=50, step=1, value=6, label='batch_size', info=i18n('批大小'), interactive=True) | |
with gr.Row(): | |
self.info_interval = gr.Number(value=50, label='info_interval', info=i18n('训练日志记录间隔(step)'), interactive=True) | |
self.eval_interval = gr.Number(value=1, label='eval_interval', info=i18n('验证集验证间隔(epoch)'), interactive=True) | |
self.save_interval = gr.Number(value=5, label='save_interval', info=i18n('检查点保存间隔(epoch)'), interactive=True) | |
self.keep_ckpts = gr.Number(value=0, label='keep_ckpts', info=i18n('保留最新的检查点文件(0保存全部)'),interactive=True) | |
with gr.Row(): | |
self.slow_model = gr.Checkbox(label=i18n("是否添加底模"), value=True, interactive=True) | |
gr.Markdown(i18n('### 开始训练')) | |
with gr.Row(): | |
self.bt_open_dataset_folder = gr.Button(value=i18n('打开数据集文件夹')) | |
self.bt_onekey_train = gr.Button(i18n('一键训练'), variant="primary") | |
self.bt_tb = gr.Button(i18n('启动Tensorboard'), variant="primary") | |
gr.Markdown(i18n('### 恢复训练')) | |
with gr.Row(): | |
self.resume_model = gr.Dropdown(choices=sorted(self.names), label='Resume training progress from checkpoints', info=i18n('从检查点恢复训练进度'), interactive=True) | |
with gr.Column(): | |
self.bt_refersh = gr.Button(i18n('刷新')) | |
self.bt_resume_train = gr.Button(i18n('恢复训练'), variant="primary") | |
with gr.Tab(i18n("推理")): | |
with gr.Accordion(i18n('推理说明'), open=False): | |
gr.Markdown(self.info.inference) | |
gr.Markdown(i18n('### 推理参数设置')) | |
with gr.Row(): | |
with gr.Column(): | |
self.keychange = gr.Slider(-24, 24, value=0, step=1, label=i18n('变调')) | |
self.file_list = gr.Markdown(value="", label=i18n("文件列表")) | |
with gr.Row(): | |
self.resume_model2 = gr.Dropdown(choices=sorted(self.names2), label='Select the model you want to export', | |
info=i18n('选择要导出的模型'), interactive=True) | |
with gr.Column(): | |
self.bt_refersh2 = gr.Button(value=i18n('刷新模型和音色')) | |
self.bt_out_model = gr.Button(value=i18n('导出模型'), variant="primary") | |
with gr.Row(): | |
self.resume_voice = gr.Dropdown(choices=sorted(self.voice_names), label='Select the sound file', | |
info=i18n('选择音色文件'), interactive=True) | |
with gr.Row(): | |
self.input_wav = gr.Audio(type='filepath', label=i18n('选择待转换音频'), source='upload') | |
with gr.Row(): | |
self.bt_infer = gr.Button(value=i18n('开始转换'), variant="primary") | |
with gr.Row(): | |
self.output_wav = gr.Audio(label=i18n('输出音频'), interactive=False) | |
self.bt_open_dataset_folder.click(fn=self.openfolder) | |
self.bt_onekey_train.click(fn=self.onekey_training,inputs=[self.model_name, self.thread_count,self.learning_rate,self.batch_size, self.info_interval, self.eval_interval,self.save_interval, self.keep_ckpts, self.slow_model]) | |
self.bt_out_model.click(fn=self.out_model, inputs=[self.model_name, self.resume_model2]) | |
self.bt_tb.click(fn=self.tensorboard) | |
self.bt_refersh.click(fn=self.refresh_model, inputs=[self.model_name], outputs=[self.resume_model]) | |
self.bt_resume_train.click(fn=self.resume_train, inputs=[self.model_name, self.resume_model, self.learning_rate,self.batch_size, self.info_interval, self.eval_interval,self.save_interval, self.keep_ckpts, self.slow_model]) | |
self.bt_infer.click(fn=self.inference, inputs=[self.input_wav, self.resume_voice, self.keychange], outputs=[self.output_wav]) | |
self.bt_refersh2.click(fn=self.refresh_model_and_voice, inputs=[self.model_name],outputs=[self.resume_model2, self.resume_voice]) | |
ui.launch(inbrowser=True, server_port=2333, share=True) | |
def openfolder(self): | |
try: | |
if sys.platform.startswith('win'): | |
os.startfile('dataset_raw') | |
elif sys.platform.startswith('linux'): | |
subprocess.call(['xdg-open', 'dataset_raw']) | |
elif sys.platform.startswith('darwin'): | |
subprocess.call(['open', 'dataset_raw']) | |
else: | |
print(i18n('打开文件夹失败!')) | |
except BaseException: | |
print(i18n('打开文件夹失败!')) | |
def preprocessing(self, thread_count): | |
print(i18n('开始预处理')) | |
train_process = subprocess.Popen('python -u svc_preprocessing.py -t ' + str(thread_count), stdout=subprocess.PIPE) | |
while train_process.poll() is None: | |
output = train_process.stdout.readline().decode('utf-8') | |
print(output, end='') | |
def create_config(self, model_name, learning_rate, batch_size, info_interval, eval_interval, save_interval, | |
keep_ckpts, slow_model): | |
yaml = YAML() | |
yaml.preserve_quotes = True | |
yaml.width = 1024 | |
with open("configs/train.yaml", "r") as f: | |
config = yaml.load(f) | |
config['train']['model'] = model_name | |
config['train']['learning_rate'] = learning_rate | |
config['train']['batch_size'] = batch_size | |
config["log"]["info_interval"] = int(info_interval) | |
config["log"]["eval_interval"] = int(eval_interval) | |
config["log"]["save_interval"] = int(save_interval) | |
config["log"]["keep_ckpts"] = int(keep_ckpts) | |
if slow_model: | |
config["train"]["pretrain"] = "vits_pretrain\sovits5.0.pretrain.pth" | |
else: | |
config["train"]["pretrain"] = "" | |
with open("configs/train.yaml", "w") as f: | |
yaml.dump(config, f) | |
return f"{config['log']}" | |
def training(self, model_name): | |
print(i18n('开始训练')) | |
train_process = subprocess.Popen('python -u svc_trainer.py -c ' + self.train_config_path + ' -n ' + str(model_name), stdout=subprocess.PIPE, creationflags=subprocess.CREATE_NEW_CONSOLE) | |
while train_process.poll() is None: | |
output = train_process.stdout.readline().decode('utf-8') | |
print(output, end='') | |
def onekey_training(self, model_name, thread_count, learning_rate, batch_size, info_interval, eval_interval, save_interval, keep_ckpts, slow_model): | |
print(self, model_name, thread_count, learning_rate, batch_size, info_interval, eval_interval, | |
save_interval, keep_ckpts) | |
self.create_config(model_name, learning_rate, batch_size, info_interval, eval_interval, save_interval, keep_ckpts, slow_model) | |
self.preprocessing(thread_count) | |
self.training(model_name) | |
def out_model(self, model_name, resume_model2): | |
print(i18n('开始导出模型')) | |
try: | |
subprocess.Popen('python -u svc_export.py -c {} -p "chkpt/{}/{}"'.format(self.train_config_path, model_name, resume_model2),stdout=subprocess.PIPE) | |
print(i18n('导出模型成功')) | |
except Exception as e: | |
print(i18n("出现错误:"), e) | |
def tensorboard(self): | |
if sys.platform.startswith('win'): | |
tb_process = subprocess.Popen('tensorboard --logdir=logs --port=6006', stdout=subprocess.PIPE) | |
webbrowser.open("http://localhost:6006") | |
else: | |
p1 = subprocess.Popen(["ps", "-ef"], stdout=subprocess.PIPE) #ps -ef | grep tensorboard | awk '{print $2}' | xargs kill -9 | |
p2 = subprocess.Popen(["grep", "tensorboard"], stdin=p1.stdout, stdout=subprocess.PIPE) | |
p3 = subprocess.Popen(["awk", "{print $2}"], stdin=p2.stdout, stdout=subprocess.PIPE) | |
p4 = subprocess.Popen(["xargs", "kill", "-9"], stdin=p3.stdout) | |
p1.stdout.close() | |
p2.stdout.close() | |
p3.stdout.close() | |
p4.communicate() | |
tb_process = subprocess.Popen('tensorboard --logdir=logs --port=6007', stdout=subprocess.PIPE) # AutoDL端口设置为6007 | |
while tb_process.poll() is None: | |
output = tb_process.stdout.readline().decode('utf-8') | |
print(output) | |
def refresh_model(self, model_name): | |
self.script_dir = os.path.dirname(os.path.abspath(__file__)) | |
self.model_root = os.path.join(self.script_dir, f"chkpt/{model_name}") | |
self.names = [] | |
try: | |
for self.name in os.listdir(self.model_root): | |
if self.name.endswith(".pt"): | |
self.names.append(self.name) | |
return {"choices": sorted(self.names), "__type__": "update"} | |
except FileNotFoundError: | |
return {"label": i18n("缺少模型文件"), "__type__": "update"} | |
def refresh_model2(self, model_name): | |
self.script_dir = os.path.dirname(os.path.abspath(__file__)) | |
self.model_root = os.path.join(self.script_dir, f"chkpt/{model_name}") | |
self.names2 = [] | |
try: | |
for self.name in os.listdir(self.model_root): | |
if self.name.endswith(".pt"): | |
self.names2.append(self.name) | |
return {"choices": sorted(self.names2), "__type__": "update"} | |
except FileNotFoundError: | |
return {"label": i18n("缺少模型文件"), "__type__": "update"} | |
def refresh_voice(self): | |
self.script_dir = os.path.dirname(os.path.abspath(__file__)) | |
self.model_root = os.path.join(self.script_dir, "data_svc/singer") | |
self.voice_names = [] | |
try: | |
for self.name in os.listdir(self.model_root): | |
if self.name.endswith(".npy"): | |
self.voice_names.append(self.name) | |
return {"choices": sorted(self.voice_names), "__type__": "update"} | |
except FileNotFoundError: | |
return {"label": i18n("缺少文件"), "__type__": "update"} | |
def refresh_model_and_voice(self, model_name): | |
model_update = self.refresh_model2(model_name) | |
voice_update = self.refresh_voice() | |
return model_update, voice_update | |
def resume_train(self, model_name, resume_model ,learning_rate, batch_size, info_interval, eval_interval, save_interval, keep_ckpts, slow_model): | |
print(i18n('开始恢复训练')) | |
self.create_config(model_name, learning_rate, batch_size, info_interval, eval_interval, save_interval,keep_ckpts, slow_model) | |
train_process = subprocess.Popen('python -u svc_trainer.py -c {} -n {} -p "chkpt/{}/{}"'.format(self.train_config_path, model_name, model_name, resume_model), stdout=subprocess.PIPE, creationflags=subprocess.CREATE_NEW_CONSOLE) | |
while train_process.poll() is None: | |
output = train_process.stdout.readline().decode('utf-8') | |
print(output, end='') | |
def inference(self, input, resume_voice, keychange): | |
if os.path.exists("test.wav"): | |
os.remove("test.wav") | |
print(i18n("已清理残留文件")) | |
else: | |
print(i18n("无需清理残留文件")) | |
self.train_config_path = 'configs/train.yaml' | |
print(i18n('开始推理')) | |
shutil.copy(input, ".") | |
input_name = os.path.basename(input) | |
os.rename(input_name, "test.wav") | |
input_name = "test.wav" | |
if not input_name.endswith(".wav"): | |
data, samplerate = soundfile.read(input_name) | |
input_name = input_name.rsplit(".", 1)[0] + ".wav" | |
soundfile.write(input_name, data, samplerate) | |
train_config_path = shlex.quote(self.train_config_path) | |
keychange = shlex.quote(str(keychange)) | |
cmd = ["python", "-u", "svc_inference.py", "--config", train_config_path, "--model", "sovits5.0.pth", "--spk", | |
f"data_svc/singer/{resume_voice}", "--wave", "test.wav", "--shift", keychange] | |
train_process = subprocess.run(cmd, shell=False, capture_output=True, text=True) | |
print(train_process.stdout) | |
print(train_process.stderr) | |
print(i18n("推理成功")) | |
return "svc_out.wav" | |
class Info: | |
def __init__(self) -> None: | |
self.train = i18n('### 2023.7.11|[@OOPPEENN](https://github.com/OOPPEENN)第一次编写|[@thestmitsuk](https://github.com/thestmitsuki)二次补完') | |
self.inference = i18n('### 2023.7.11|[@OOPPEENN](https://github.com/OOPPEENN)第一次编写|[@thestmitsuk](https://github.com/thestmitsuki)二次补完') | |
LANGUAGE_LIST = ['zh_CN', 'en_US'] | |
LANGUAGE_ALL = { | |
'zh_CN': { | |
'SUPER': 'END', | |
'LANGUAGE': 'zh_CN', | |
'初始化成功': '初始化成功', | |
'就绪': '就绪', | |
'预处理-训练': '预处理-训练', | |
'训练说明': '训练说明', | |
'### 预处理参数设置': '### 预处理参数设置', | |
'模型名称': '模型名称', | |
'f0提取器': 'f0提取器', | |
'预处理线程数': '预处理线程数', | |
'### 训练参数设置': '### 训练参数设置', | |
'学习率': '学习率', | |
'批大小': '批大小', | |
'训练日志记录间隔(step)': '训练日志记录间隔(step)', | |
'验证集验证间隔(epoch)': '验证集验证间隔(epoch)', | |
'检查点保存间隔(epoch)': '检查点保存间隔(epoch)', | |
'保留最新的检查点文件(0保存全部)': '保留最新的检查点文件(0保存全部)', | |
'是否添加底模': '是否添加底模', | |
'### 开始训练': '### 开始训练', | |
'打开数据集文件夹': '打开数据集文件夹', | |
'一键训练': '一键训练', | |
'启动Tensorboard': '启动Tensorboard', | |
'### 恢复训练': '### 恢复训练', | |
'从检查点恢复训练进度': '从检查点恢复训练进度', | |
'刷新': '刷新', | |
'恢复训练': '恢复训练', | |
'推理': '推理', | |
'推理说明': '推理说明', | |
'### 推理参数设置': '### 推理参数设置', | |
'变调': '变调', | |
'文件列表': '文件列表', | |
'选择要导出的模型': '选择要导出的模型', | |
'刷新模型和音色': '刷新模型和音色', | |
'导出模型': '导出模型', | |
'选择音色文件': '选择音色文件', | |
'选择待转换音频': '选择待转换音频', | |
'开始转换': '开始转换', | |
'输出音频': '输出音频', | |
'打开文件夹失败!': '打开文件夹失败!', | |
'开始预处理': '开始预处理', | |
'开始训练': '开始训练', | |
'开始导出模型': '开始导出模型', | |
'导出模型成功': '导出模型成功', | |
'出现错误:': '出现错误:', | |
'缺少模型文件': '缺少模型文件', | |
'缺少文件': '缺少文件', | |
'已清理残留文件': '已清理残留文件', | |
'无需清理残留文件': '无需清理残留文件', | |
'开始推理': '开始推理', | |
'推理成功': '推理成功', | |
'### 2023.7.11|[@OOPPEENN](https://github.com/OOPPEENN)第一次编写|[@thestmitsuk](https://github.com/thestmitsuki)二次补完': '### 2023.7.11|[@OOPPEENN](https://github.com/OOPPEENN)第一次编写|[@thestmitsuk](https://github.com/thestmitsuki)二次补完' | |
}, | |
'en_US': { | |
'SUPER': 'zh_CN', | |
'LANGUAGE': 'en_US', | |
'初始化成功': 'Initialization successful', | |
'就绪': 'Ready', | |
'预处理-训练': 'Preprocessing-Training', | |
'训练说明': 'Training instructions', | |
'### 预处理参数设置': '### Preprocessing parameter settings', | |
'模型名称': 'Model name', | |
'f0提取器': 'f0 extractor', | |
'预处理线程数': 'Preprocessing thread number', | |
'### 训练参数设置': '### Training parameter settings', | |
'学习率': 'Learning rate', | |
'批大小': 'Batch size', | |
'训练日志记录间隔(step)': 'Training log recording interval (step)', | |
'验证集验证间隔(epoch)': 'Validation set validation interval (epoch)', | |
'检查点保存间隔(epoch)': 'Checkpoint save interval (epoch)', | |
'保留最新的检查点文件(0保存全部)': 'Keep the latest checkpoint file (0 save all)', | |
'是否添加底模': 'Whether to add the base model', | |
'### 开始训练': '### Start training', | |
'打开数据集文件夹': 'Open the dataset folder', | |
'一键训练': 'One-click training', | |
'启动Tensorboard': 'Start Tensorboard', | |
'### 恢复训练': '### Resume training', | |
'从检查点恢复训练进度': 'Restore training progress from checkpoint', | |
'刷新': 'Refresh', | |
'恢复训练': 'Resume training', | |
"推理": "Inference", | |
"推理说明": "Inference instructions", | |
"### 推理参数设置": "### Inference parameter settings", | |
"变调": "Pitch shift", | |
"文件列表": "File list", | |
"选择要导出的模型": "Select the model to export", | |
"刷新模型和音色": "Refresh model and timbre", | |
"导出模型": "Export model", | |
"选择音色文件": "Select timbre file", | |
"选择待转换音频": "Select audio to be converted", | |
"开始转换": "Start conversion", | |
"输出音频": "Output audio", | |
"打开文件夹失败!": "Failed to open folder!", | |
"开始预处理": "Start preprocessing", | |
"开始训练": "Start training", | |
"开始导出模型": "Start exporting model", | |
"导出模型成功": "Model exported successfully", | |
"出现错误:": "An error occurred:", | |
"缺少模型文件": "Missing model file", | |
'缺少文件': 'Missing file', | |
"已清理残留文件": "Residual files cleaned up", | |
"无需清理残留文件": "No need to clean up residual files", | |
"开始推理": "Start inference", | |
'### 2023.7.11|[@OOPPEENN](https://github.com/OOPPEENN)第一次编写|[@thestmitsuk](https://github.com/thestmitsuki)二次补完': '### 2023.7.11|[@OOPPEENN](https://github.com/OOPPEENN)first writing|[@thestmitsuk](https://github.com/thestmitsuki)second completion' | |
} | |
} | |
class I18nAuto: | |
def __init__(self, language=None): | |
self.language_list = LANGUAGE_LIST | |
self.language_all = LANGUAGE_ALL | |
self.language_map = {} | |
self.language = language or locale.getdefaultlocale()[0] | |
if self.language not in self.language_list: | |
self.language = 'zh_CN' | |
self.read_language(self.language_all['zh_CN']) | |
while self.language_all[self.language]['SUPER'] != 'END': | |
self.read_language(self.language_all[self.language]) | |
self.language = self.language_all[self.language]['SUPER'] | |
def read_language(self, lang_dict: dict): | |
self.language_map.update(lang_dict) | |
def __call__(self, key): | |
return self.language_map[key] | |
if __name__ == "__main__": | |
i18n = I18nAuto() | |
webui = WebUI() | |