kwau/sovits-isla
0
1import ast2import datetime3import glob4import json5import logging6import multiprocessing7import os8import re9import shutil10import subprocess11import traceback12import zipfile13from itertools import chain14from pathlib import Path15 16import gradio as gr17import librosa18import numpy as np19import soundfile as sf20import torch21import yaml22 23import utils24from auto_slicer import AutoSlicer25from compress_model import removeOptimizer26from inference.infer_tool import Svc27from onnxexport.model_onnx import SynthesizerTrn28from tts_voices import SUPPORTED_LANGUAGES29from utils import mix_model30 31os.environ["PATH"] += os.pathsep + os.path.join(os.getcwd(), "ffmpeg", "bin")32 33logging.getLogger('numba').setLevel(logging.WARNING)34logging.getLogger('markdown_it').setLevel(logging.WARNING)35logging.getLogger('urllib3').setLevel(logging.WARNING)36logging.getLogger('matplotlib').setLevel(logging.WARNING)37 38# Some directories39workdir = "logs/44k"40second_dir = "models"41diff_second_dir = "models/diffusion"42diff_workdir = "logs/44k/diffusion"43config_dir = "configs/"44dataset_dir = "dataset/44k"45raw_path = "dataset_raw"46raw_wavs_path = "raw"47models_backup_path = 'models_backup'48root_dir = "checkpoints"49default_settings_file = "settings.yaml"50current_mode = ""51# Some global variables52debug = False53precheck_ok = False54model = None55sovits_params = {}56diff_params = {}57# Some dicts for mapping58MODEL_TYPE = {59 "vec768l12": 768,60 "vec256l9": 256,61 "hubertsoft": 256,62 "whisper-ppg": 1024,63 "cnhubertlarge": 1024,64 "dphubert": 768,65 "wavlmbase+": 768,66 "whisper-ppg-large": 128067}68ENCODER_PRETRAIN = {69 "vec256l9": "pretrain/checkpoint_best_legacy_500.pt",70 "vec768l12": "pretrain/checkpoint_best_legacy_500.pt",71 "hubertsoft": "pretrain/hubert-soft-0d54a1f4.pt",72 "whisper-ppg": "pretrain/medium.pt",73 "cnhubertlarge": "pretrain/chinese-hubert-large-fairseq-ckpt.pt",74 "dphubert": "pretrain/DPHuBERT-sp0.75.pth",75 "wavlmbase+": "pretrain/WavLM-Base+.pt",76 "whisper-ppg-large": "pretrain/large-v2.pt"77}78 79class Config:80 def __init__(self, path, type):81 self.path = path82 self.type = type83 84 def read(self):85 if self.type == "json":86 with open(self.path, 'r') as f:87 return json.load(f)88 if self.type == "yaml":89 with open(self.path, 'r') as f:90 return yaml.safe_load(f)91 92 def save(self, content):93 if self.type == "json":94 with open(self.path, 'w') as f:95 json.dump(content, f, indent=4)96 if self.type == "yaml":97 with open(self.path, 'w') as f:98 yaml.safe_dump(content, f, default_flow_style=False, sort_keys=False)99 100 101class ReleasePacker:102 def __init__(self, speaker, model):103 self.speaker = speaker104 self.model = model105 self.output_path = os.path.join("release_packs", f"{speaker}_release.zip")106 self.file_list = []107 108 def remove_temp(self, path):109 for filename in os.listdir(path):110 file_path = os.path.join(path, filename)111 if os.path.isfile(file_path) and not filename.endswith(".zip"):112 os.remove(file_path)113 elif os.path.isdir(file_path):114 shutil.rmtree(file_path, ignore_errors=True)115 116 def add_file(self, file_paths):117 self.file_list.extend(file_paths)118 119 def spk_to_dict(self):120 spk_string = self.speaker.replace(',', ',')121 spk_string = spk_string.replace(' ', '')122 _spk = spk_string.split(',')123 return {_spk: index for index, _spk in enumerate(_spk)}124 125 def generate_config(self, diff_model, config_origin):126 _config_origin = Config(os.path.join(config_read_dir, config_origin), "json")127 _template = Config("release_packs/config_template.json", "json")128 _d_template = Config("release_packs/diffusion_template.yaml", "yaml")129 orig_config = _config_origin.read()130 config_template = _template.read()131 diff_config_template = _d_template.read()132 spk_dict = self.spk_to_dict()133 _net = torch.load(os.path.join(ckpt_read_dir, self.model), map_location='cpu')134 emb_dim, model_dim = _net['model'].get('emb_g.weight', torch.empty(0, 0)).size()135 vol_emb = _net['model'].get('emb_vol.weight')136 if vol_emb is not None:137 config_template["train"]["vol_aug"] = config_template["model"]["vol_embedding"] = True138 #Keep the spk_dict length same as emb_dim139 if emb_dim > len(spk_dict):140 for i in range(emb_dim - len(spk_dict)):141 spk_dict[f"spk{i}"] = len(spk_dict)142 if emb_dim < len(spk_dict):143 for i in range(len(spk_dict) - emb_dim):144 spk_dict.popitem()145 self.speaker = ','.join(spk_dict.keys())146 config_template['model']['ssl_dim'] = config_template["model"]["filter_channels"] = config_template["model"]["gin_channels"] = model_dim147 config_template['model']['n_speakers'] = diff_config_template['model']['n_spk'] = emb_dim148 config_template['spk'] = diff_config_template['spk'] = spk_dict149 encoder = [k for k, v in MODEL_TYPE.items() if v == model_dim]150 if orig_config['model']['speech_encoder'] in encoder:151 config_template['model']['speech_encoder'] = orig_config['model']['speech_encoder']152 else:153 raise Exception("Config is not compatible with the model")154 155 if diff_model != "no_diff":156 _diff = torch.load(os.path.join(diff_read_dir, diff_model), map_location='cpu')157 _, diff_dim = _diff["model"].get("unit_embed.weight", torch.empty(0, 0)).size()158 if diff_dim == 256:159 diff_config_template['data']['encoder'] = 'hubertsoft'160 diff_config_template['data']['encoder_out_channels'] = 256161 elif diff_dim == 768:162 diff_config_template['data']['encoder'] = 'vec768l12'163 diff_config_template['data']['encoder_out_channels'] = 768164 elif diff_dim == 1024:165 diff_config_template['data']['encoder'] = 'whisper-ppg'166 diff_config_template['data']['encoder_out_channels'] = 1024167 168 with open("release_packs/install.txt", 'w') as f:169 f.write(str(self.file_list) + '#' + str(self.speaker))170 171 _template.save(config_template)172 _d_template.save(diff_config_template)173 174 def unpack(self, zip_file):175 with zipfile.ZipFile(zip_file, 'r') as zipf:176 zipf.extractall("release_packs")177 178 def formatted_install(self, install_txt):179 with open(install_txt, 'r') as f:180 content = f.read()181 file_list, speaker = content.split('#')182 self.speaker = speaker183 file_list = ast.literal_eval(file_list)184 self.file_list = file_list185 for _, target_path in self.file_list:186 if target_path != "install.txt" and target_path != "":187 shutil.move(os.path.join("release_packs", target_path), target_path)188 self.remove_temp("release_packs")189 return self.speaker190 191 def pack(self):192 with zipfile.ZipFile(self.output_path, 'w', zipfile.ZIP_DEFLATED) as zipf:193 for file_path, target_path in self.file_list:194 if os.path.isfile(file_path):195 zipf.write(file_path, arcname=target_path)196 197 198def debug_change():199 global debug200 debug = debug_button.value201 202def get_default_settings():203 global sovits_params, diff_params, second_dir_enable204 config_file = Config(default_settings_file, "yaml")205 default_settings = config_file.read()206 sovits_params = default_settings['sovits_params']207 diff_params = default_settings['diff_params']208 webui_settings = default_settings['webui_settings']209 second_dir_enable = webui_settings['second_dir']210 return sovits_params, diff_params, second_dir_enable211 212def webui_change(read_second_dir):213 global second_dir_enable214 config_file = Config(default_settings_file, "yaml")215 default_settings = config_file.read()216 second_dir_enable = default_settings['webui_settings']['second_dir'] = read_second_dir217 config_file.save(default_settings)218 219def get_current_mode():220 global current_mode221 current_mode = "当前模式:独立目录模式,将从'./models/'读取模型文件" if second_dir_enable else "当前模式:工作目录模式,将从'./logs/44k'读取模型文件" 222 return current_mode223 224def save_default_settings(log_interval,eval_interval,keep_ckpts,batch_size,learning_rate,amp_dtype,all_in_mem,num_workers,cache_all_data,cache_device,diff_amp_dtype,diff_batch_size,diff_lr,diff_interval_log,diff_interval_val,diff_force_save,diff_k_step_max):225 config_file = Config(default_settings_file, "yaml")226 default_settings = config_file.read()227 default_settings['sovits_params']['log_interval'] = int(log_interval)228 default_settings['sovits_params']['eval_interval'] = int(eval_interval)229 default_settings['sovits_params']['keep_ckpts'] = int(keep_ckpts)230 default_settings['sovits_params']['batch_size'] = int(batch_size)231 default_settings['sovits_params']['learning_rate'] = float(learning_rate)232 default_settings['sovits_params']['amp_dtype'] = str(amp_dtype)233 default_settings['sovits_params']['all_in_mem'] = all_in_mem234 default_settings['diff_params']['num_workers'] = int(num_workers)235 default_settings['diff_params']['cache_all_data'] = cache_all_data236 default_settings['diff_params']['cache_device'] = str(cache_device)237 default_settings['diff_params']['amp_dtype'] = str(diff_amp_dtype)238 default_settings['diff_params']['diff_batch_size'] = int(diff_batch_size)239 default_settings['diff_params']['diff_lr'] = float(diff_lr)240 default_settings['diff_params']['diff_interval_log'] = int(diff_interval_log)241 default_settings['diff_params']['diff_interval_val'] = int(diff_interval_val)242 default_settings['diff_params']['diff_force_save'] = int(diff_force_save)243 default_settings['diff_params']['diff_k_step_max'] = diff_k_step_max244 config_file.save(default_settings)245 return "成功保存默认配置"246 247def get_model_info(choice_ckpt):248 pthfile = os.path.join(ckpt_read_dir, choice_ckpt)249 net = torch.load(pthfile, map_location=torch.device('cpu')) #cpu load to avoid using gpu memory250 spk_emb = net["model"].get("emb_g.weight")251 if spk_emb is None:252 return "所选模型缺少emb_g.weight,你可能选择了一个底模"253 _layer = spk_emb.size(1)254 encoder = [k for k, v in MODEL_TYPE.items() if v == _layer] #通过维度对应编码器255 encoder.sort()256 if encoder == ["hubertsoft", "vec256l9"]:257 encoder = ["vec256l9 / hubertsoft"]258 if encoder == ["cnhubertlarge", "whisper-ppg"]:259 encoder = ["whisper-ppg / cnhubertlarge"]260 if encoder == ["dphubert", "vec768l12", "wavlmbase+"]:261 encoder = ["vec768l12 / dphubert / wavlmbase+"]262 return encoder[0]263 264def load_json_encoder(config_choice, choice_ckpt):265 if config_choice == "no_config":266 return "未启用自动加载,请手动选择配置文件"267 if choice_ckpt == "no_model":268 return "请先选择模型"269 config_file = Config(os.path.join(config_read_dir, config_choice), "json")270 config = config_file.read()271 try:272 #比对配置文件中的模型维度与该encoder的实际维度是否对应,防止古神语273 config_encoder = config["model"].get("speech_encoder", "no_encoder")274 config_dim = config["model"]["ssl_dim"]275 #旧版配置文件自动匹配276 if config_encoder == "no_encoder":277 config_encoder = config["model"]["speech_encoder"] = "vec256l9" if config_dim == 256 else "vec768l12"278 config_file.save(config)279 correct_dim = MODEL_TYPE.get(config_encoder, "unknown")280 if config_dim != correct_dim:281 return "配置文件中的编码器与模型维度不匹配"282 return config_encoder283 except Exception as e:284 return f"出错了: {e}"285 286def auto_load(choice_ckpt):287 global second_dir_enable288 model_output_msg = get_model_info(choice_ckpt)289 json_output_msg = config_choice = ""290 choice_ckpt_name, _ = os.path.splitext(choice_ckpt)291 if second_dir_enable:292 all_config = [json for json in os.listdir(second_dir) if json.endswith(".json")]293 for config in all_config:294 config_fname, _ = os.path.splitext(config)295 if config_fname == choice_ckpt_name:296 config_choice = config297 json_output_msg = load_json_encoder(config, choice_ckpt)298 if json_output_msg != "":299 return model_output_msg, config_choice, json_output_msg300 else:301 return model_output_msg, "no_config", ""302 else:303 return model_output_msg, "no_config", ""304 305def auto_load_diff(diff_model):306 global second_dir_enable307 if second_dir_enable is False:308 return "no_diff_config"309 all_diff_config = [yaml for yaml in os.listdir(second_dir) if yaml.endswith(".yaml")]310 for config in all_diff_config:311 config_fname, _ = os.path.splitext(config)312 diff_fname, _ = os.path.splitext(diff_model)313 if config_fname == diff_fname:314 return config315 return "no_diff_config"316 317def load_model_func(ckpt_name,cluster_name,config_name,enhance,diff_model_name,diff_config_name,only_diffusion,use_spk_mix,using_device,method,speedup):318 global model319 config_path = os.path.join(config_read_dir, config_name) if not only_diffusion else "configs/config.json"320 diff_config_path = os.path.join(config_read_dir, diff_config_name) if diff_config_name != "no_diff_config" else "configs/diffusion.yaml"321 ckpt_path = os.path.join(ckpt_read_dir, ckpt_name)322 cluster_path = os.path.join(ckpt_read_dir, cluster_name)323 diff_model_path = os.path.join(diff_read_dir, diff_model_name)324 k_step_max = 1000325 if not only_diffusion:326 config = Config(config_path, "json").read()327 if diff_model_name != "no_diff":328 _diff = Config(diff_config_path, "yaml")329 _content = _diff.read()330 diff_spk = _content.get('spk', {})331 diff_spk_choice = spk_choice = next(iter(diff_spk), "未检测到音色")332 if not only_diffusion:333 if _content['data'].get('encoder_out_channels') != config["model"].get('ssl_dim'):334 return "扩散模型维度与主模型不匹配,请确保两个模型使用的是同一个编码器", gr.Dropdown.update(choices=[], value=""), 0, None335 _content["infer"]["speedup"] = int(speedup)336 _content["infer"]["method"] = str(method)337 k_step_max = _content["model"].get('k_step_max', 0) if _content["model"].get('k_step_max', 0) != 0 else 1000338 _diff.save(_content)339 if not only_diffusion:340 net = torch.load(ckpt_path, map_location=torch.device('cpu'))341 #读取模型各维度并比对,还有小可爱无视提示硬要加载底模的就返回个未初始张量342 emb_dim, model_dim = net["model"].get("emb_g.weight", torch.empty(0, 0)).size() 343 if emb_dim > config["model"]["n_speakers"]:344 return "模型说话人数量与emb维度不匹配", gr.Dropdown.update(choices=[], value=""), 0, None345 if model_dim != config["model"]["ssl_dim"]: 346 return "配置文件与模型不匹配", gr.Dropdown.update(choices=[], value=""), 0, None347 encoder = config["model"]["speech_encoder"]348 spk_dict = config.get('spk', {})349 spk_choice = next(iter(spk_dict), "未检测到音色")350 else:351 spk_dict = diff_spk352 spk_choice = diff_spk_choice353 fr = cluster_name.endswith(".pkl") #如果是pkl后缀就启用特征检索354 shallow_diffusion = diff_model_name != "no_diff" #加载了扩散模型就启用浅扩散355 device = cuda[using_device] if "CUDA" in using_device else using_device356 model = Svc(ckpt_path,357 config_path,358 device=device if device != "Auto" else None,359 cluster_model_path=cluster_path,360 nsf_hifigan_enhance=enhance,361 diffusion_model_path=diff_model_path,362 diffusion_config_path=diff_config_path,363 shallow_diffusion=shallow_diffusion,364 only_diffusion=only_diffusion,365 spk_mix_enable=use_spk_mix,366 feature_retrieval=fr)367 spk_list = list(spk_dict.keys())368 if not only_diffusion:369 clip = 25 if encoder == "whisper-ppg" or encoder == "whisper-ppg-large" else 0 #Whisper必须强制切片25秒370 device_name = torch.cuda.get_device_properties(model.dev).name if "cuda" in str(model.dev) else str(model.dev)371 sovits_msg = f"模型被成功加载到了{device_name}上\n"372 else: 373 clip = 0374 sovits_msg = "启用全扩散推理,未加载So-VITS模型\n"375 index_or_kmeans = "特征索引" if fr else "聚类模型"376 clu_load = "未加载" if cluster_name == "no_clu" else cluster_name377 diff_load = "未加载" if diff_model_name == "no_diff" else f"{diff_model_name} | 采样器: {method} | 加速倍数:{int(speedup)} | 最大浅扩散步数:{k_step_max}"378 output_msg = f"{sovits_msg}{index_or_kmeans}:{clu_load}\n扩散模型:{diff_load}"379 return (380 output_msg, 381 gr.Dropdown.update(choices=spk_list, value=spk_choice), 382 clip, 383 gr.Slider.update(value=100 if k_step_max>100 else k_step_max, minimum=speedup, maximum=k_step_max)384 )385 386def model_empty_cache():387 global model388 if model is None:389 return sid.update(choices = [],value=""),"没有模型需要卸载!"390 else:391 model.unload_model()392 model = None393 torch.cuda.empty_cache()394 return sid.update(choices = [],value=""),"模型卸载完毕!"395 396def get_file_options(directory, extension):397 return [file for file in os.listdir(directory) if file.endswith(extension)]398 399def load_options():400 ckpt_list = [file for file in get_file_options(ckpt_read_dir, ".pth") if not file.startswith("D_") or file == "G_0.pth"]401 config_list = get_file_options(config_read_dir, ".json")402 cluster_list = ["no_clu"] + get_file_options(ckpt_read_dir, ".pt") + get_file_options(ckpt_read_dir, ".pkl") # 聚类和特征检索模型403 diff_list = ["no_diff"] + get_file_options(diff_read_dir, ".pt")404 diff_config_list = ["no_diff_config"] + get_file_options(config_read_dir, ".yaml")405 return ckpt_list, config_list, cluster_list, diff_list, diff_config_list406 407def refresh_options():408 global ckpt_read_dir, config_read_dir, diff_read_dir, current_mode409 ckpt_read_dir = second_dir if second_dir_enable else workdir410 config_read_dir = second_dir if second_dir_enable else config_dir411 diff_read_dir = diff_second_dir if second_dir_enable else diff_workdir412 ckpt_list, config_list, cluster_list, diff_list, diff_config_list = load_options()413 current_mode = get_current_mode()414 return (415 choice_ckpt.update(choices=ckpt_list),416 config_choice.update(choices=config_list),417 cluster_choice.update(choices=cluster_list),418 diff_choice.update(choices=diff_list),419 diff_config_choice.update(choices=diff_config_list),420 mode_caption.update(value=f"""{current_mode},可在页面底端切换模式""")421 )422 423def vc_infer(output_format, sid, input_audio, input_audio_path, vc_transform, auto_f0, cluster_ratio, slice_db, noise_scale, pad_seconds, cl_num, lg_num, lgr_num, f0_predictor, enhancer_adaptive_key, cr_threshold, k_step, use_spk_mix, second_encoding, loudness_envelope_adjustment):424 if np.issubdtype(input_audio.dtype, np.integer):425 input_audio = (input_audio / np.iinfo(input_audio.dtype).max).astype(np.float32)426 if len(input_audio.shape) > 1:427 input_audio = librosa.to_mono(input_audio.transpose(1, 0))428 sf.write("temp.wav", input_audio, 44100, format="wav")429 _audio = model.slice_inference(430 "temp.wav",431 sid,432 vc_transform,433 slice_db,434 cluster_ratio,435 auto_f0,436 noise_scale,437 pad_seconds,438 cl_num,439 lg_num,440 lgr_num,441 f0_predictor,442 enhancer_adaptive_key,443 cr_threshold,444 k_step,445 use_spk_mix,446 second_encoding,447 loudness_envelope_adjustment448 ) 449 model.clear_empty()450 if not os.path.exists("results"):451 os.makedirs("results")452 key = "auto" if auto_f0 else f"{int(vc_transform)}key"453 cluster = "_" if cluster_ratio == 0 else f"_{cluster_ratio}_"454 isdiffusion = "sovits"455 if model.shallow_diffusion:456 isdiffusion = "sovdiff"457 if model.only_diffusion:458 isdiffusion = "diff"459 #Gradio上传的filepath因为未知原因会有一个无意义的固定后缀,这里去掉460 truncated_basename = Path(input_audio_path).stem[:-6] if Path(input_audio_path).stem[-6:] == "-0-100" else Path(input_audio_path).stem461 output_file_name = f'{truncated_basename}_{sid}_{key}{cluster}{isdiffusion}.{output_format}'462 output_file_path = os.path.join("results", output_file_name)463 if os.path.exists(output_file_path):464 count = 1465 while os.path.exists(output_file_path):466 output_file_name = f'{truncated_basename}_{sid}_{key}{cluster}{isdiffusion}_{str(count)}.{output_format}'467 output_file_path = os.path.join("results", output_file_name)468 count += 1 469 sf.write(output_file_path, _audio, model.target_sample, format=output_format)470 return output_file_path471 472def vc_fn(output_format, sid, input_audio, vc_transform, auto_f0, cluster_ratio, slice_db, noise_scale, pad_seconds, cl_num, lg_num, lgr_num, f0_predictor, enhancer_adaptive_key, cr_threshold, k_step, use_spk_mix, second_encoding, loudness_envelope_adjustment):473 global model474 try:475 if input_audio is None:476 return "你还没有上传音频", None477 if model is None:478 return "你还没有加载模型", None479 if getattr(model, 'cluster_model', None) is None and model.feature_retrieval is False:480 if cluster_ratio != 0:481 return "你还未加载聚类或特征检索模型,无法启用聚类/特征检索混合比例", None482 audio, _ = sf.read(input_audio)483 output_file_path = vc_infer(output_format, sid, audio, input_audio, vc_transform, auto_f0, cluster_ratio, slice_db, noise_scale, pad_seconds, cl_num, lg_num, lgr_num, f0_predictor, enhancer_adaptive_key, cr_threshold, k_step, use_spk_mix, second_encoding, loudness_envelope_adjustment)484 os.remove("temp.wav")485 return "Success", output_file_path486 except Exception as e:487 if debug:488 traceback.print_exc()489 raise gr.Error(e)490 491def vc_batch_fn(output_format, sid, input_audio_files, vc_transform, auto_f0, cluster_ratio, slice_db, noise_scale, pad_seconds, cl_num, lg_num, lgr_num, f0_predictor, enhancer_adaptive_key, cr_threshold, k_step, use_spk_mix, second_encoding, loudness_envelope_adjustment):492 global model493 try:494 if input_audio_files is None or len(input_audio_files) == 0:495 return "你还没有上传音频"496 if model is None:497 return "你还没有加载模型"498 if getattr(model, 'cluster_model', None) is None and model.feature_retrieval is False:499 if cluster_ratio != 0:500 return "你还未加载聚类或特征检索模型,无法启用聚类/特征检索混合比例", None501 _output = []502 for file_obj in input_audio_files:503 print(f"Start processing: {file_obj.name}")504 input_audio_path = file_obj.name505 audio, _ = sf.read(input_audio_path)506 output_file_path = vc_infer(output_format, sid, audio, input_audio_path, vc_transform, auto_f0, cluster_ratio, slice_db, noise_scale, pad_seconds, cl_num, lg_num, lgr_num, f0_predictor, enhancer_adaptive_key, cr_threshold, k_step, use_spk_mix, second_encoding, loudness_envelope_adjustment)507 _output.append(output_file_path)508 return "批量推理完成,音频已经被保存到results文件夹"509 except Exception as e:510 if debug:511 traceback.print_exc()512 raise gr.Error(e)513 514def tts_fn(_text, _gender, _lang, _rate, _volume, output_format, sid, vc_transform, auto_f0,cluster_ratio, slice_db, noise_scale,pad_seconds,cl_num,lg_num,lgr_num,f0_predictor,enhancer_adaptive_key,cr_threshold, k_step,use_spk_mix,second_encoding,loudness_envelope_adjustment):515 global model516 try:517 if model is None:518 return "你还没有加载模型", None519 if getattr(model, 'cluster_model', None) is None and model.feature_retrieval is False:520 if cluster_ratio != 0:521 return "你还未加载聚类或特征检索模型,无法启用聚类/特征检索混合比例", None522 _rate = f"+{int(_rate*100)}%" if _rate >= 0 else f"{int(_rate*100)}%"523 _volume = f"+{int(_volume*100)}%" if _volume >= 0 else f"{int(_volume*100)}%"524 if _lang == "Auto":525 _gender = "Male" if _gender == "男" else "Female"526 subprocess.run([r".\workenv\python.exe", "tts.py", _text, _lang, _rate, _volume, _gender])527 else:528 subprocess.run([r".\workenv\python.exe", "tts.py", _text, _lang, _rate, _volume])529 target_sr = 44100530 y, sr = librosa.load("tts.wav")531 resampled_y = librosa.resample(y, orig_sr=sr, target_sr=target_sr)532 sf.write("tts.wav", resampled_y, target_sr, subtype = "PCM_16")533 input_audio = "tts.wav"534 audio, _ = sf.read(input_audio)535 output_file_path = vc_infer(output_format, sid, audio, input_audio, vc_transform, auto_f0, cluster_ratio, slice_db, noise_scale, pad_seconds, cl_num, lg_num, lgr_num, f0_predictor, enhancer_adaptive_key, cr_threshold, k_step, use_spk_mix, second_encoding, loudness_envelope_adjustment)536 #os.remove("tts.wav")537 return "Success", output_file_path538 except Exception as e:539 if debug:540 traceback.print_exc()541 raise gr.Error(e)542 543def load_raw_dirs():544 global precheck_ok545 precheck_ok = False546 allowed_pattern = re.compile(r'^[a-zA-Z0-9_@#$%^&()_+\-=\s\.]*$')547 illegal_files = illegal_dataset = []548 for root, dirs, files in os.walk(raw_path):549 for dir in dirs:550 if not allowed_pattern.match(dir):551 illegal_dataset.append(dir)552 if illegal_dataset:553 return f"数据集文件夹名只能包含数字、字母、下划线,以下文件夹不符合要求,请改名后再试:\n{illegal_dataset}"554 if root != raw_path: # 只处理子文件夹内的文件555 for file in files:556 if not allowed_pattern.match(file) and file not in illegal_files:557 illegal_files.append(file)558 if not file.lower().endswith('.wav') and file not in illegal_files:559 illegal_files.append(file)560 if illegal_files:561 return f"数据集文件名只能包含数字、字母、下划线,且必须是.wav格式,以下文件不符合要求,请改名后再试:\n{illegal_files}"562 spk_dirs = [entry.name for entry in os.scandir(raw_path) if entry.is_dir()]563 if spk_dirs:564 precheck_ok = True565 return spk_dirs566 else:567 return "未找到数据集,请检查dataset_raw文件夹"568 569def dataset_preprocess(encoder, f0_predictor, use_diff, vol_aug, skip_loudnorm, num_processes):570 if precheck_ok:571 diff_arg = "--use_diff" if use_diff else ""572 vol_aug_arg = "--vol_aug" if vol_aug else ""573 skip_loudnorm_arg = "--skip_loudnorm" if skip_loudnorm else ""574 preprocess_commands = [575 r".\workenv\python.exe resample.py %s" % (skip_loudnorm_arg),576 r".\workenv\python.exe preprocess_flist_config.py --speech_encoder %s %s" % (encoder, vol_aug_arg),577 r".\workenv\python.exe preprocess_hubert_f0.py --num_processes %s --f0_predictor %s %s" % (num_processes ,f0_predictor, diff_arg)578 ]579 accumulated_output = ""580 #清空dataset581 dataset = os.listdir(dataset_dir)582 if len(dataset) != 0:583 for dir in dataset:584 dataset_spk_dir = os.path.join(dataset_dir, str(dir))585 if os.path.isdir(dataset_spk_dir):586 shutil.rmtree(dataset_spk_dir)587 accumulated_output += f"Deleting previous dataset: {dir}\n"588 for command in preprocess_commands:589 try:590 result = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=True, text=True)591 accumulated_output += f"Command: {command}, Using Encoder: {encoder}, Using f0 Predictor: {f0_predictor}\n"592 yield accumulated_output, None593 progress_line = None594 for line in result.stdout:595 if r"it/s" in line or r"s/it" in line: #防止进度条刷屏596 progress_line = line597 else:598 accumulated_output += line599 if progress_line is None:600 yield accumulated_output, None601 else:602 yield accumulated_output + progress_line, None603 result.communicate()604 except subprocess.CalledProcessError as e:605 result = e.output606 accumulated_output += f"Error: {result}\n"607 yield accumulated_output, None608 if progress_line is not None:609 accumulated_output += progress_line610 accumulated_output += '-' * 50 + '\n'611 yield accumulated_output, None612 config_path = "configs/config.json"613 with open(config_path, 'r') as f:614 config = json.load(f)615 spk_name = config.get('spk', None)616 yield accumulated_output, gr.Textbox.update(value=spk_name)617 else:618 yield "数据集识别未通过,请先识别数据集并确保没有报错信息", None619 620def regenerate_config(encoder, vol_aug):621 if precheck_ok is False:622 return "数据集识别未通过,请检查识别结果的报错信息"623 vol_aug_arg = "--vol_aug" if vol_aug else ""624 cmd = r".\workenv\python.exe preprocess_flist_config.py --speech_encoder %s %s" % (encoder, vol_aug_arg)625 output = ""626 try:627 result = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=True, text=True)628 for line in result.stdout:629 output += line630 output += "Regenerate config file successfully."631 except subprocess.CalledProcessError as e:632 result = e.output633 output += f"Error: {result}\n"634 return output635 636def clear_output():637 return gr.Textbox.update(value="Cleared!>_<")638 639def get_available_encoder():640 current_pretrain = os.listdir("pretrain")641 current_pretrain = [("pretrain/" + model) for model in current_pretrain]642 encoder_list = []643 for encoder, path in ENCODER_PRETRAIN.items():644 if path in current_pretrain:645 encoder_list.append(encoder)646 return encoder_list647 648def config_fn(log_interval, eval_interval, keep_ckpts, batch_size, lr, amp_dtype, all_in_mem, diff_num_workers, diff_cache_all_data, diff_batch_size, diff_lr, diff_interval_log, diff_interval_val, diff_cache_device, diff_amp_dtype, diff_force_save, diff_k_step_max):649 if amp_dtype == "fp16" or amp_dtype == "bf16":650 fp16_run = True651 else:652 fp16_run = False653 amp_dtype = "fp16"654 config_origin = Config("configs/config.json", "json")655 diff_config = Config("configs/diffusion.yaml", "yaml")656 config_data = config_origin.read()657 config_data['train']['log_interval'] = int(log_interval)658 config_data['train']['eval_interval'] = int(eval_interval)659 config_data['train']['keep_ckpts'] = int(keep_ckpts)660 config_data['train']['batch_size'] = int(batch_size)661 config_data['train']['learning_rate'] = float(lr)662 config_data['train']['fp16_run'] = fp16_run663 config_data['train']['half_type'] = str(amp_dtype)664 config_data['train']['all_in_mem'] = all_in_mem665 config_origin.save(config_data)666 diff_config_data = diff_config.read()667 diff_config_data['train']['num_workers'] = int(diff_num_workers)668 diff_config_data['train']['cache_all_data'] = diff_cache_all_data669 diff_config_data['train']['batch_size'] = int(diff_batch_size)670 diff_config_data['train']['lr'] = float(diff_lr)671 diff_config_data['train']['interval_log'] = int(diff_interval_log)672 diff_config_data['train']['interval_val'] = int(diff_interval_val)673 diff_config_data['train']['cache_device'] = str(diff_cache_device)674 diff_config_data['train']['amp_dtype'] = str(diff_amp_dtype)675 diff_config_data['train']['interval_force_save'] = int(diff_force_save)676 diff_config_data['model']['k_step_max'] = 100 if diff_k_step_max else 0677 diff_config.save(diff_config_data)678 return "配置文件写入完成"679 680def check_dataset(dataset_path):681 if not os.listdir(dataset_path):682 return "数据集不存在,请检查dataset文件夹"683 no_npy_pt_files = True684 for root, dirs, files in os.walk(dataset_path):685 for file in files:686 if file.endswith('.npy') or file.endswith('.pt'):687 no_npy_pt_files = False688 break689 if no_npy_pt_files:690 return "数据集中未检测到f0和hubert文件,可能是预处理未完成"691 return None692 693def training(gpu_selection, encoder):694 config_file = Config("configs/config.json", "json")695 config_data = config_file.read()696 vol_emb = config_data["model"]["vol_embedding"]697 dataset_warn = check_dataset(dataset_dir)698 if dataset_warn is not None:699 return dataset_warn700 PRETRAIN = { 701 "vec256l9": ("D_0.pth", "G_0.pth", "pre_trained_model"),702 "vec768l12": ("D_0.pth", "G_0.pth", "pre_trained_model/768l12/vol_emb" if vol_emb else "pre_trained_model/768l12"),703 "hubertsoft": ("D_0.pth", "G_0.pth", "pre_trained_model/hubertsoft"),704 "whisper-ppg": ("D_0.pth", "G_0.pth", "pre_trained_model/whisper-ppg"),705 "cnhubertlarge": ("D_0.pth", "G_0.pth", "pre_trained_model/cnhubertlarge"),706 "dphubert": ("D_0.pth", "G_0.pth", "pre_trained_model/dphubert"),707 "wavlmbase+": ("D_0.pth", "G_0.pth", "pre_trained_model/wavlmbase+"),708 "whisper-ppg-large": ("D_0.pth", "G_0.pth", "pre_trained_model/whisper-ppg-large")709 }710 if encoder not in PRETRAIN:711 return "未知编码器"712 d_0_file, g_0_file, encoder_model_path = PRETRAIN[encoder]713 d_0_path = os.path.join(encoder_model_path, d_0_file)714 g_0_path = os.path.join(encoder_model_path, g_0_file)715 timestamp = datetime.datetime.now().strftime('%Y_%m_%d_%H_%M')716 new_backup_folder = os.path.join(models_backup_path, str(timestamp))717 output_msg = ""718 if os.listdir(workdir) != ['diffusion']:719 os.makedirs(new_backup_folder, exist_ok=True)720 for file in os.listdir(workdir):721 if file != "diffusion":722 shutil.move(os.path.join(workdir, file), os.path.join(new_backup_folder, file))723 if os.path.isfile(g_0_path) and os.path.isfile(d_0_path):724 shutil.copy(d_0_path, os.path.join(workdir, "D_0.pth"))725 shutil.copy(g_0_path, os.path.join(workdir, "G_0.pth"))726 output_msg += f"成功装载预训练模型,编码器:{encoder}\n"727 else:728 output_msg += f"{encoder}的预训练模型不存在,未装载预训练模型\n"729 cmd = r"set CUDA_VISIBLE_DEVICES=%s && .\workenv\python.exe train.py -c configs/config.json -m 44k" % (gpu_selection)730 subprocess.Popen(["cmd", "/c", "start", "cmd", "/k", cmd])731 output_msg += "已经在新的终端窗口开始训练,请监看终端窗口的训练日志。在终端中按Ctrl+C可暂停训练。"732 return output_msg733 734def continue_training(gpu_selection, encoder):735 dataset_warn = check_dataset(dataset_dir)736 if dataset_warn is not None:737 return dataset_warn738 if encoder == "":739 return "请先选择预处理对应的编码器"740 all_files = os.listdir(workdir)741 model_files = [f for f in all_files if f.startswith('G_') and f.endswith('.pth')]742 if len(model_files) == 0:743 return "你还没有已开始的训练"744 cmd = r"set CUDA_VISIBLE_DEVICES=%s && .\workenv\python.exe train.py -c configs/config.json -m 44k" % (gpu_selection)745 subprocess.Popen(["cmd", "/c", "start", "cmd", "/k", cmd])746 return "已经在新的终端窗口开始训练,请监看终端窗口的训练日志。在终端中按Ctrl+C可暂停训练。"747 748def kmeans_training(kmeans_gpu):749 if not os.listdir(dataset_dir):750 return "数据集不存在,请检查dataset文件夹"751 cmd = r".\workenv\python.exe cluster/train_cluster.py --gpu" if kmeans_gpu else r".\workenv\python.exe cluster/train_cluster.py"752 subprocess.Popen(["cmd", "/c", "start", "cmd", "/k", cmd])753 return "已经在新的终端窗口开始训练,训练聚类模型不会输出日志,CPU训练一般需要5-10分钟左右"754 755def index_training():756 if not os.listdir(dataset_dir):757 return "数据集不存在,请检查dataset文件夹"758 cmd = r".\workenv\python.exe train_index.py -c configs/config.json"759 subprocess.Popen(["cmd", "/c", "start", "cmd", "/k", cmd])760 return "已经在新的终端窗口开始训练"761 762def diff_training(encoder, k_step_max):763 if not os.listdir(dataset_dir):764 return "数据集不存在,请检查dataset文件夹"765 timestamp = datetime.datetime.now().strftime('%Y_%m_%d_%H_%M')766 new_backup_folder = os.path.join(models_backup_path, "diffusion", str(timestamp))767 if len(os.listdir(diff_workdir)) != 0:768 os.makedirs(new_backup_folder, exist_ok=True)769 for file in os.listdir(diff_workdir):770 shutil.move(os.path.join(diff_workdir, file), os.path.join(new_backup_folder, file))771 DIFF_PRETRAIN = {772 "768-kstepmax100": "pre_trained_model/diffusion/768l12/max100/model_0.pt",773 "vec768l12": "pre_trained_model/diffusion/768l12/model_0.pt",774 "hubertsoft": "pre_trained_model/diffusion/hubertsoft/model_0.pt",775 "whisper-ppg": "pre_trained_model/diffusion/whisper-ppg/model_0.pt"776 }777 if encoder not in DIFF_PRETRAIN:778 return "你所选的编码器暂时不支持训练扩散模型"779 if k_step_max:780 encoder = "768-kstepmax100"781 diff_pretrained_model = DIFF_PRETRAIN[encoder]782 shutil.copy(diff_pretrained_model, os.path.join(diff_workdir, "model_0.pt"))783 subprocess.Popen(["cmd", "/c", "start", "cmd", "/k", r".\workenv\python.exe train_diff.py -c configs/diffusion.yaml"])784 output_message = "已经在新的终端窗口开始训练,请监看终端窗口的训练日志。在终端中按Ctrl+C可暂停训练。"785 if encoder == "768-kstepmax100":786 output_message += "\n正在进行100步深度的浅扩散训练,已加载底模"787 else:788 output_message += f"\n正在进行完整深度的扩散训练,编码器{encoder}"789 return output_message790 791def diff_continue_training(encoder):792 if not os.listdir(dataset_dir):793 return "数据集不存在,请检查dataset文件夹"794 if encoder == "":795 return "请先选择预处理对应的编码器"796 all_files = os.listdir(diff_workdir)797 model_files = [f for f in all_files if f.endswith('.pt')]798 if len(model_files) == 0:799 return "你还没有已开始的训练"800 subprocess.Popen(["cmd", "/c", "start", "cmd", "/k", r".\workenv\python.exe train_diff.py -c configs/diffusion.yaml"])801 return "已经在新的终端窗口开始训练,请监看终端窗口的训练日志。在终端中按Ctrl+C可暂停训练。"802 803def upload_mix_append_file(files,sfiles):804 try:805 if(sfiles is None):806 file_paths = [file.name for file in files]807 else:808 file_paths = [file.name for file in chain(files,sfiles)]809 p = {file:100 for file in file_paths}810 return file_paths,mix_model_output1.update(value=json.dumps(p,indent=2))811 except Exception as e:812 if debug:813 traceback.print_exc()814 raise gr.Error(e)815 816def mix_submit_click(js,mode):817 try:818 assert js.lstrip()!=""819 modes = {"凸组合":0, "线性组合":1}820 mode = modes[mode]821 data = json.loads(js)822 data = list(data.items())823 model_path,mix_rate = zip(*data)824 path = mix_model(model_path,mix_rate,mode)825 return f"成功,文件被保存在了{path}"826 except Exception as e:827 if debug:828 traceback.print_exc()829 raise gr.Error(e)830 831def updata_mix_info(files):832 try:833 if files is None:834 return mix_model_output1.update(value="")835 p = {file.name:100 for file in files}836 return mix_model_output1.update(value=json.dumps(p,indent=2))837 except Exception as e:838 if debug:839 traceback.print_exc()840 raise gr.Error(e)841 842def pth_identify():843 if not os.path.exists(root_dir):844 return f"未找到{root_dir}文件夹,请先创建一个{root_dir}文件夹并按第一步流程操作"845 model_dirs = [d for d in os.listdir(root_dir) if os.path.isdir(os.path.join(root_dir, d))]846 if not model_dirs:847 return f"未在{root_dir}文件夹中找到模型文件夹,请确保每个模型和配置文件都被放置在单独的文件夹中"848 valid_model_dirs = []849 for path in model_dirs:850 pth_files = glob.glob(f"{root_dir}/{path}/*.pth")851 json_files = glob.glob(f"{root_dir}/{path}/*.json")852 if len(pth_files) != 1 or len(json_files) != 1:853 return f"错误: 在{root_dir}/{path}中找到了{len(pth_files)}个.pth文件和{len(json_files)}个.json文件。应当确保每个文件夹内有且只有一个.pth文件和.json文件"854 valid_model_dirs.append(path)855 856 return f"成功识别了{len(valid_model_dirs)}个模型:{valid_model_dirs}"857 858def onnx_export():859 model_dirs = [d for d in os.listdir(root_dir) if os.path.isdir(os.path.join(root_dir, d))]860 try:861 for path in model_dirs:862 pth_files = glob.glob(f"{root_dir}/{path}/*.pth")863 json_files = glob.glob(f"{root_dir}/{path}/*.json")864 model_file = pth_files[0]865 json_file = json_files[0]866 device = torch.device("cpu")867 hps = utils.get_hparams_from_file(json_file)868 SVCVITS = SynthesizerTrn(869 hps.data.filter_length // 2 + 1,870 hps.train.segment_size // hps.data.hop_length,871 **hps.model)872 _ = utils.load_checkpoint(model_file, SVCVITS, None)873 _ = SVCVITS.eval().to(device)874 for i in SVCVITS.parameters():875 i.requires_grad = False 876 n_frame = 10877 test_hidden_unit = torch.rand(1, n_frame, 256)878 test_pitch = torch.rand(1, n_frame)879 test_mel2ph = torch.arange(0, n_frame, dtype=torch.int64)[None] # torch.LongTensor([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]).unsqueeze(0)880 test_uv = torch.ones(1, n_frame, dtype=torch.float32)881 test_noise = torch.randn(1, 192, n_frame)882 test_sid = torch.LongTensor([0])883 input_names = ["c", "f0", "mel2ph", "uv", "noise", "sid"]884 output_names = ["audio", ]885 onnx_file = os.path.splitext(model_file)[0] + ".onnx"886 torch.onnx.export(SVCVITS,887 (888 test_hidden_unit.to(device),889 test_pitch.to(device),890 test_mel2ph.to(device),891 test_uv.to(device),892 test_noise.to(device),893 test_sid.to(device)894 ),895 onnx_file,896 dynamic_axes={897 "c": [0, 1],898 "f0": [1],899 "mel2ph": [1],900 "uv": [1],901 "noise": [2],902 },903 do_constant_folding=False,904 opset_version=16,905 verbose=False,906 input_names=input_names,907 output_names=output_names)908 return "转换成功,模型被保存在了checkpoints下的对应目录"909 except Exception as e:910 if debug:911 traceback.print_exc()912 raise gr.Error(e)913 914def load_raw_audio(audio_path):915 if not os.path.isdir(audio_path):916 return "请输入正确的目录", None917 files = os.listdir(audio_path)918 wav_files = [file for file in files if file.lower().endswith('.wav')]919 if not wav_files:920 return "未在目录中找到.wav音频文件", None921 return "成功加载", wav_files922 923def slicer_fn(input_dir, output_dir, process_method, max_sec, min_sec):924 if output_dir == "":925 return "请先选择输出的文件夹"926 if output_dir == input_dir:927 return "输出目录不能和输入目录相同"928 slicer = AutoSlicer()929 if os.path.exists(output_dir) is not True:930 os.makedirs(output_dir)931 for filename in os.listdir(input_dir):932 if filename.lower().endswith(".wav"):933 slicer.auto_slice(filename, input_dir, output_dir, max_sec)934 if process_method == "丢弃":935 for filename in os.listdir(output_dir):936 if filename.endswith(".wav"):937 filepath = os.path.join(output_dir, filename)938 audio, sr = librosa.load(filepath, sr=None, mono=False)939 if librosa.get_duration(y=audio, sr=sr) < min_sec:940 os.remove(filepath)941 elif process_method == "将过短音频整合为长音频":942 slicer.merge_short(output_dir, max_sec, min_sec)943 file_count, max_duration, min_duration, orig_duration, final_duration = slicer.slice_count(input_dir, output_dir)944 hrs = int(final_duration / 3600)945 mins = int((final_duration % 3600) / 60)946 sec = format(float(final_duration % 60), '.2f')947 rate = format(100 * (final_duration / orig_duration), '.2f') if orig_duration != 0 else 0948 rate_msg = f"为原始音频时长的{rate}%" if rate != 0 else "因未知问题,无法计算切片时长的占比"949 return f"成功将音频切分为{file_count}条片段,其中最长{max_duration}秒,最短{min_duration}秒,切片后的音频总时长{hrs:02d}小时{mins:02d}分{sec}秒,{rate_msg}"950 951def model_compression(_model):952 if _model == "":953 return "请先选择要压缩的模型"954 else:955 model_path = os.path.join(ckpt_read_dir, _model)956 filename, extension = os.path.splitext(_model)957 output_model_name = f"{filename}_compressed{extension}"958 output_path = os.path.join(ckpt_read_dir, output_model_name)959 removeOptimizer(model_path, output_path)960 return f"模型已成功被保存在了{output_path}"961 962def pack_autoload(model_to_pack):963 _, config_name, _ = auto_load(model_to_pack)964 if config_name == "no_config":965 return "未找到对应的配置文件,请手动选择", None966 else:967 _config = Config(os.path.join(config_read_dir, config_name), "json")968 _content = _config.read()969 spk_dict = _content["spk"]970 spk_list = ",".join(spk_dict.keys())971 return config_name, spk_list972 973def release_packing(model_to_pack, model_config, speaker, diff_to_pack, cluster_to_pack):974 model_path = diff_path = cluster_path = ""975 basename = os.path.splitext(model_to_pack)[0]976 diff_basename = os.path.splitext(diff_to_pack)[0]977 if model_to_pack == "" or model_config == "" or speaker == "":978 return "存在必选项为空,请检查后重试"979 released_pack = ReleasePacker(speaker, model_to_pack)980 released_pack.remove_temp("release_packs")981 model_path = os.path.join(ckpt_read_dir, model_to_pack)982 if os.stat(model_path).st_size > 300000000:983 removeOptimizer(model_path, os.path.join("release_packs", model_to_pack))984 model_path = os.path.join("release_packs", model_to_pack)985 if diff_to_pack != "no_diff":986 diff_path = os.path.join(diff_read_dir, diff_to_pack)987 if cluster_to_pack != "no_cluster":988 cluster_path = os.path.join(ckpt_read_dir, cluster_to_pack)989 shutil.copyfile("configs_template/config_template.json", "release_packs/config_template.json")990 shutil.copyfile("configs_template/diffusion_template.yaml", "release_packs/diffusion_template.yaml")991 files_to_pack = [992 (model_path, f"models/{model_to_pack}"),993 (diff_path, f"models/diffusion/{diff_to_pack}") if diff_to_pack != "no_diff" else ("", ""),994 (cluster_path, f"models/{cluster_to_pack}") if cluster_to_pack != "no_cluster" else ("", ""),995 (f"release_packs/{basename}.json", f"models/{basename}.json"),996 (f"release_packs/{diff_basename}.yaml", f"models/{diff_basename}.yaml") if diff_to_pack != "no_diff" else ("", ""),997 ("release_packs/install.txt", "install.txt")998 ]999 released_pack.add_file(files_to_pack)1000 released_pack.generate_config(diff_to_pack, model_config)1001 os.rename("release_packs/config_template.json", f"release_packs/{basename}.json")1002 os.rename("release_packs/diffusion_template.yaml", f"release_packs/{diff_basename}.yaml")1003 released_pack.pack()1004 to_remove = [file for file in os.listdir("release_packs") if not file.endswith(".zip")]1005 for file in to_remove:1006 os.remove(os.path.join("release_packs", file))1007 return "打包成功, 请在release_packs目录下查看"1008 1009def release_install(model_zip_path):1010 model_zip = ReleasePacker("", "")1011 model_zip.unpack(model_zip_path)1012 for file in os.listdir("release_packs"):1013 if file.endswith(".txt"):1014 install_txt = os.path.join("release_packs", file)1015 break1016 else:1017 model_zip.remove_temp("release_packs")1018 return "非格式化安装包,无法安装"1019 _spk = model_zip.formatted_install(install_txt)1020 model_zip.remove_temp("release_packs")1021 return f"安装成功,可用说话人{_spk},请启用独立目录模式加载模型"1022 1023#read default params1024sovits_params, diff_params, second_dir_enable = get_default_settings()1025ckpt_read_dir = second_dir if second_dir_enable else workdir1026config_read_dir = second_dir if second_dir_enable else config_dir1027diff_read_dir = diff_second_dir if second_dir_enable else diff_workdir1028current_mode = get_current_mode()1029 1030# create dirs if they don't exist1031dirs_to_check = [1032 workdir,1033 second_dir,1034 diff_workdir,1035 diff_second_dir,1036 dataset_dir,1037]1038for dir in dirs_to_check:1039 if not os.path.exists(dir):1040 os.makedirs(dir)1041 1042# read ckpt list1043ckpt_list, config_list, cluster_list, diff_list, diff_config_list = load_options()1044 1045# read available encoder list1046encoder_list = get_available_encoder()1047 1048#read GPU info1049ngpu=torch.cuda.device_count()1050gpu_infos=[]1051if(torch.cuda.is_available() is False or ngpu==0):1052 if_gpu_ok=False1053else:1054 if_gpu_ok = False1055 for i in range(ngpu):1056 gpu_name=torch.cuda.get_device_name(i)1057 if("MX"in gpu_name):1058 continue1059 if("RTX" in gpu_name.upper() or "10"in gpu_name or "16"in gpu_name or "20"in gpu_name or "30"in gpu_name or "40"in gpu_name or "A50"in gpu_name.upper() or "70"in gpu_name or "80"in gpu_name or "90"in gpu_name or "M4"in gpu_name or"P4"in gpu_name or "T4"in gpu_name or "TITAN"in gpu_name.upper()):#A10#A100#V100#A40#P40#M40#K801060 if_gpu_ok=True#至少有一张能用的N卡1061 gpu_infos.append("%s\t%s"%(i,gpu_name))1062gpu_info="\n".join(gpu_infos)if if_gpu_ok is True and len(gpu_infos)>0 else "很遗憾您这没有能用的显卡来支持您训练"1063gpus="-".join([i[0]for i in gpu_infos])1064 1065#read cuda info for inference1066cuda = {}1067if torch.cuda.is_available():1068 for i in range(torch.cuda.device_count()):1069 device_name = torch.cuda.get_device_properties(i).name1070 cuda[f"CUDA:{i} {device_name}"] = f"cuda:{i}"1071 1072#Check BF16 support1073amp_options = ["fp32", "fp16", "bf16"] if torch.cuda.is_bf16_supported() else ["fp32", "fp16"]1074 1075#Get F0 Options1076f0_options = ["crepe","pm","dio","harvest","rmvpe"] if os.path.exists("pretrain/rmvpe.pt") else ["crepe","pm","dio","harvest"]1077 1078app = gr.Blocks()1079with app:1080 gr.Markdown(value="""1081 ### So-VITS-SVC 4.1-Stable WebUI 推理&训练 v2.3.81082 1083 制作协力:bilibili@麦哲云1084 1085 仅供个人娱乐和非商业用途,禁止用于血腥、暴力、性相关、政治相关内容1086 1087 [使用文档和常见报错解答](https://www.yuque.com/umoubuton/ueupp5)1088 1089 整合包作者:bilibili@羽毛布団 | 技术交流群:742817595 | 交流二群:168254971 | 交流三群:4166561751090 1091 """)1092 with gr.Tabs():1093 with gr.TabItem("推理") as inference_tab:1094 mode_caption = gr.Markdown(value=f"""1095 {current_mode},可在页面底端切换模式1096 """)1097 with gr.Row():1098 choice_ckpt = gr.Dropdown(label="模型选择", choices=ckpt_list, value="no_model")1099 model_branch = gr.Textbox(label="模型编码器", placeholder="请先选择模型", interactive=False)1100 with gr.Row():1101 config_choice = gr.Dropdown(label="配置文件", choices=config_list, value="no_config")1102 config_info = gr.Textbox(label="配置文件编码器", placeholder="请选择配置文件")1103 gr.Markdown(value="""**请检查模型和配置文件的编码器是否匹配**""")1104 with gr.Row():1105 diff_choice = gr.Dropdown(label="(可选)选择扩散模型", choices=diff_list, value="no_diff", interactive=True)1106 diff_config_choice = gr.Dropdown(label="扩散模型配置文件", choices=diff_config_list, value="no_diff_config", interactive=True)1107 cluster_choice = gr.Dropdown(label="(可选)选择聚类模型/特征检索模型", choices=cluster_list, value="no_clu")1108 refresh = gr.Button("刷新选项")1109 with gr.Row():1110 enhance = gr.Checkbox(label="是否使用NSF_HIFIGAN增强,该选项对部分训练集少的模型有一定的音质增强效果,但是对训练好的模型有反面效果,默认关闭", value=False)1111 only_diffusion = gr.Checkbox(label="是否使用全扩散推理,开启后将不使用So-VITS模型,仅使用扩散模型进行完整扩散推理,不建议使用", value=False)1112 with gr.Row():1113 diffusion_method = gr.Dropdown(label="扩散模型采样器", choices=["dpm-solver++","dpm-solver","pndm","ddim","unipc"], value="dpm-solver")1114 diffusion_speedup = gr.Number(label="扩散加速倍数,默认为10倍", value=10)1115 using_device = gr.Dropdown(label="推理设备,默认为自动选择", choices=["Auto",*cuda.keys(),"cpu"], value="Auto")1116 with gr.Row():1117 loadckpt = gr.Button("加载模型", variant="primary")1118 unload = gr.Button("卸载模型", variant="primary")1119 with gr.Row():1120 model_message = gr.Textbox(label="Output Message")1121 sid = gr.Dropdown(label="So-VITS说话人", value="speaker0")1122 1123 inference_tab.select(refresh_options,[],[choice_ckpt,config_choice,cluster_choice,diff_choice,diff_config_choice])1124 choice_ckpt.change(auto_load, [choice_ckpt], [model_branch, config_choice, config_info]) 1125 config_choice.change(load_json_encoder, [config_choice, choice_ckpt], [config_info])1126 diff_choice.change(auto_load_diff, [diff_choice], [diff_config_choice])1127 refresh.click(refresh_options,[],[choice_ckpt,config_choice,cluster_choice,diff_choice,diff_config_choice,mode_caption])1128 1129 gr.Markdown(value="""1130 请稍等片刻,模型加载大约需要10秒。后续操作不需要重新加载模型1131 """)1132 with gr.Tabs():1133 with gr.TabItem("单个音频上传"):1134 vc_input3 = gr.Audio(label="单个音频上传", type="filepath")1135 with gr.TabItem("批量音频上传"):1136 vc_batch_files = gr.Files(label="批量音频上传", file_types=["audio"], file_count="multiple")1137 with gr.TabItem("文字转语音"):1138 gr.Markdown("""1139 文字转语音(TTS)说明:使用edge_tts服务生成音频,并转换为So-VITS模型音色。1140 """)1141 text_input = gr.Textbox(label = "在此输入需要转译的文字(建议打开自动f0预测)",)1142 with gr.Row():1143 tts_gender = gr.Radio(label = "说话人性别", choices = ["男","女"], value = "男")1144 tts_lang = gr.Dropdown(label = "选择语言,Auto为根据输入文字自动识别", choices=SUPPORTED_LANGUAGES, value = "Auto")1145 with gr.Row():1146 tts_rate = gr.Slider(label = "TTS语音变速(倍速相对值)", minimum = -1, maximum = 3, value = 0, step = 0.1)1147 tts_volume = gr.Slider(label = "TTS语音音量(相对值)", minimum = -1, maximum = 1.5, value = 0, step = 0.1)1148 1149 with gr.Row():1150 auto_f0 = gr.Checkbox(label="自动f0预测,配合聚类模型f0预测效果更好,会导致变调功能失效(仅限转换语音,歌声不要勾选此项会跑调)", value=False)1151 f0_predictor = gr.Radio(label="f0预测器选择(如遇哑音可以更换f0预测器解决,crepe为原F0使用均值滤波器)", choices=f0_options, value="pm")1152 cr_threshold = gr.Number(label="F0过滤阈值,只有使用crepe时有效. 数值范围从0-1. 降低该值可减少跑调概率,但会增加哑音", value=0.05)1153 with gr.Row():1154 vc_transform = gr.Number(label="变调(整数,可以正负,半音数量,升高八度就是12)", value=0)1155 cluster_ratio = gr.Number(label="聚类模型/特征检索混合比例,0-1之间,默认为0不启用聚类或特征检索,能提升音色相似度,但会导致咬字下降", value=0)1156 k_step = gr.Slider(label="浅扩散步数,只有使用了扩散模型才有效,步数越大越接近扩散模型的结果", value=100, minimum = 1, maximum = 1000)1157 with gr.Row():1158 output_format = gr.Radio(label="音频输出格式", choices=["wav", "flac", "mp3"], value = "wav")1159 enhancer_adaptive_key = gr.Number(label="使NSF-HIFIGAN增强器适应更高的音域(单位为半音数)|默认为0", value=0)1160 slice_db = gr.Number(label="切片阈值", value=-50)1161 cl_num = gr.Number(label="音频自动切片,0为按默认方式切片,单位为秒/s,爆显存可以设置此处强制切片", value=0)1162 with gr.Accordion("高级设置(一般不需要动)", open=False):1163 noise_scale = gr.Number(label="noise_scale 建议不要动,会影响音质,玄学参数", value=0.4)1164 pad_seconds = gr.Number(label="推理音频pad秒数,由于未知原因开头结尾会有异响,pad一小段静音段后就不会出现", value=0.5)1165 lg_num = gr.Number(label="两端音频切片的交叉淡入长度,如果自动切片后出现人声不连贯可调整该数值,如果连贯建议采用默认值0,注意,该设置会影响推理速度,单位为秒/s", value=1)1166 lgr_num = gr.Number(label="自动音频切片后,需要舍弃每段切片的头尾。该参数设置交叉长度保留的比例,范围0-1,左开右闭", value=0.75)1167 second_encoding = gr.Checkbox(label = "二次编码,浅扩散前会对原始音频进行二次编码,玄学选项,效果时好时差,默认关闭", value=False)1168 loudness_envelope_adjustment = gr.Number(label="输入源响度包络替换输出响度包络融合比例,越靠近1越使用输出响度包络", value = 0)1169 use_spk_mix = gr.Checkbox(label="动态声线融合,需要手动编辑角色混合轨道,没做完暂时不要开启", value=False, interactive=False)1170 with gr.Row():1171 vc_submit = gr.Button("音频转换", variant="primary")1172 vc_batch_submit = gr.Button("批量转换", variant="primary")1173 vc_tts_submit = gr.Button("文本转语音", variant="primary")1174 vc_output1 = gr.Textbox(label="Output Message")1175 vc_output2 = gr.Audio(label="Output Audio")1176 1177 loadckpt.click(load_model_func,[choice_ckpt,cluster_choice,config_choice,enhance,diff_choice,diff_config_choice,only_diffusion,use_spk_mix,using_device,diffusion_method,diffusion_speedup],[model_message, sid, cl_num, k_step])1178 unload.click(model_empty_cache, [], [sid, model_message])1179 vc_submit.click(vc_fn, [output_format, sid, vc_input3, vc_transform,auto_f0,cluster_ratio, slice_db, noise_scale,pad_seconds,cl_num,lg_num,lgr_num,f0_predictor,enhancer_adaptive_key,cr_threshold,k_step,use_spk_mix,second_encoding,loudness_envelope_adjustment], [vc_output1, vc_output2])1180 vc_batch_submit.click(vc_batch_fn, [output_format, sid, vc_batch_files, vc_transform,auto_f0,cluster_ratio, slice_db, noise_scale,pad_seconds,cl_num,lg_num,lgr_num,f0_predictor,enhancer_adaptive_key,cr_threshold,k_step,use_spk_mix,second_encoding,loudness_envelope_adjustment], [vc_output1])1181 vc_tts_submit.click(tts_fn, [text_input, tts_gender, tts_lang, tts_rate, tts_volume, output_format, sid, vc_transform,auto_f0,cluster_ratio, slice_db, noise_scale,pad_seconds,cl_num,lg_num,lgr_num,f0_predictor,enhancer_adaptive_key,cr_threshold,k_step,use_spk_mix,second_encoding,loudness_envelope_adjustment], [vc_output1, vc_output2])1182 1183 1184 with gr.TabItem("训练"):1185 gr.Markdown(value="""请将数据集文件夹放置在dataset_raw文件夹下,确认放置正确后点击下方获取数据集名称""")1186 raw_dirs_list=gr.Textbox(label="Raw dataset directory(s):")1187 get_raw_dirs=gr.Button("识别数据集", variant="primary")1188 gr.Markdown(value="""确认数据集正确识别后请选择训练使用的特征编码器和f0预测器,**如果要训练扩散模型,请选择Vec768l12或hubertsoft或whisper-ppg,并确保So-VITS和扩散模型使用同一个编码器**""")1189 with gr.Row():1190 gr.Markdown(value="""**vec256l9**: ContentVec(256Layer9),旧版本叫v1,So-VITS-SVC 4.0的基础版本,**不推荐使用**1191 **vec768l12**: 特征输入更换为ContentVec的第12层Transformer输出,模型理论上会更加还原训练集音色1192 **hubertsoft**: So-VITS-SVC 3.0使用的编码器,咬字更为准确,但可能存在多说话人音色泄露问题1193 **whisper-ppg**: 来自OpenAI,咬字最为准确,但和Hubertsoft一样存在多说话人音色泄露,且显存占用和训练时间有明显增加。1194 解锁更多编码器选项,请见[这里](https://www.yuque.com/umoubuton/ueupp5/kmui02dszo5zrqkz)1195 """)1196 gr.Markdown(value="""**crepe**: 抗噪能力最强,但预处理速度慢(不过如果你的显卡很强的话速度会很快)1197 **pm**: 预处理速度快,但抗噪能力较弱1198 **dio**: 先前版本预处理默认使用的f0预测器1199 **harvest**: 有一定抗噪能力,预处理显存占用友好,速度比较慢1200 """)