Spaces:
Running on Zero
Running on Zero
| import os | |
| import shutil | |
| import html | |
| # Offline WebUI keeps the CUDA Graph implementation available, but remains | |
| # eager by default. Set RVC_OFFLINE_CUDA_GRAPH=1 to opt in for benchmarking or | |
| # controlled deployments. | |
| _offline_cuda_graph = os.environ.get("RVC_OFFLINE_CUDA_GRAPH", "0") == "1" | |
| os.environ["RVC_CUDA_GRAPH"] = "1" if _offline_cuda_graph else "0" | |
| os.environ.setdefault("OPENBLAS_NUM_THREADS", "1") | |
| os.environ.setdefault("no_proxy", "localhost, 127.0.0.1, ::1") | |
| os.environ.setdefault("weight_root", "assets/weights") | |
| os.environ.setdefault("weight_pymss_root", "assets/pymss_weights") | |
| os.environ.setdefault("index_root", "logs") | |
| os.environ.setdefault("outside_index_root", "assets/indices") | |
| os.environ.setdefault("rmvpe_root", "assets/rmvpe") | |
| now_dir = os.getcwd() | |
| tmp = os.path.join(now_dir, "TEMP") | |
| os.makedirs(tmp, exist_ok=True) | |
| os.environ["TEMP"] = tmp | |
| for name in os.listdir(tmp): | |
| if name == "jieba.cache": | |
| continue | |
| path = os.path.join(tmp, name) | |
| delete = ( | |
| os.remove if os.path.isfile(path) or os.path.islink(path) else shutil.rmtree | |
| ) | |
| try: | |
| delete(path) | |
| except Exception as error: | |
| print(str(error)) | |
| # Import `spaces` first — it must be imported before any CUDA-initializing packages. | |
| try: | |
| import spaces # noqa: F401 | |
| except Exception: | |
| # Non-fatal: continue even if `spaces` is not available in this environment. | |
| pass | |
| from configs.config import Config, GPU_INDEX, GPU_INFOS, GPU_MEMORY, IS_GPU | |
| from infer.vc.modules import VC | |
| # PyMSS functionality temporarily disabled. | |
| # from tools.pymss_webui import ( | |
| # PYMSS_MODEL_CHOICES, | |
| # get_model_info, | |
| # pymss_separate as _pymss_separate_core, | |
| # stop_pymss_separation as _stop_pymss_separation_core, | |
| # ) | |
| from tools.file_io import read_text | |
| from tools.process_utils import kill_process_tree | |
| from train.process_ckpt import ( | |
| change_info, | |
| extract_small_model, | |
| merge, | |
| show_info, | |
| ) | |
| from i18n.i18n import I18nAuto | |
| import torch, platform | |
| import numpy as np | |
| import gradio as gr | |
| import pathlib | |
| import json | |
| from time import sleep | |
| from subprocess import Popen | |
| from random import shuffle | |
| import warnings | |
| import traceback | |
| import threading | |
| import logging | |
| import socket | |
| import subprocess | |
| import time | |
| try: | |
| from huggingface_hub import HfApi | |
| except ImportError: | |
| HfApi = None | |
| logging.getLogger("numba").setLevel(logging.WARNING) | |
| logging.getLogger("httpx").setLevel(logging.WARNING) | |
| logger = logging.getLogger(__name__) | |
| def find_available_port(start_port, host="0.0.0.0"): | |
| """Return the first bindable TCP port at or above ``start_port``.""" | |
| if not 1 <= start_port <= 65535: | |
| raise ValueError(f"Port must be between 1 and 65535, got {start_port}.") | |
| for port in range(start_port, 65536): | |
| try: | |
| with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: | |
| sock.bind((host, port)) | |
| return port | |
| except OSError: | |
| continue | |
| raise OSError( | |
| f"No available TCP port from {start_port} through 65535; WebUI was not started." | |
| ) | |
| def is_gradio_port_in_use_error(error, port): | |
| """Recognize Gradio's explicit-port conflict without hiding other launch errors.""" | |
| return str(error).startswith(f"Port {port} is in use.") | |
| def launch_webui_with_port_fallback(app, config): | |
| """Launch Gradio, increasing the requested port until startup succeeds.""" | |
| next_port = config.listen_port | |
| queued_app = app.queue(max_size=1022) | |
| while True: | |
| config.listen_port = int(os.environ.get("PORT", 7860)) | |
| if config.listen_port != next_port: | |
| logger.warning( | |
| "Port %s is occupied; trying port %s instead.", | |
| next_port, | |
| config.listen_port, | |
| ) | |
| try: | |
| print( | |
| f"Starting Gradio on port {config.listen_port} (inbrowser={not config.noautoopen})", | |
| flush=True, | |
| ) | |
| queued_app.launch( | |
| server_name="0.0.0.0", | |
| inbrowser=False, | |
| share=True, | |
| server_port=config.listen_port, | |
| ) | |
| return config.listen_port | |
| except OSError as error: | |
| if not is_gradio_port_in_use_error(error, config.listen_port): | |
| raise | |
| if config.listen_port == 65535: | |
| raise OSError( | |
| "No available TCP port through 65535; WebUI was not started." | |
| ) from error | |
| logger.warning( | |
| "Port %s became occupied while Gradio was starting; trying the next port.", | |
| config.listen_port, | |
| ) | |
| next_port = config.listen_port + 1 | |
| runtime_dirs = ( | |
| os.path.join(now_dir, "logs"), | |
| os.environ["weight_root"], | |
| os.environ["weight_pymss_root"], | |
| os.environ["index_root"], | |
| os.environ["outside_index_root"], | |
| os.environ["rmvpe_root"], | |
| os.path.join(now_dir, "assets", "hubert_base"), | |
| os.path.join(now_dir, "assets", "pretrained"), | |
| os.path.join(now_dir, "assets", "pretrained_v2"), | |
| ) | |
| for runtime_dir in runtime_dirs: | |
| os.makedirs(runtime_dir, exist_ok=True) | |
| warnings.filterwarnings("ignore") | |
| torch.manual_seed(114514) | |
| config = Config() | |
| vc = VC(config) | |
| i18n = I18nAuto() | |
| logger.info(i18n) | |
| print( | |
| i18n("当前设备:%s | 推理精度:%s") % (config.device, config.dtype), | |
| flush=True, | |
| ) | |
| # GPU filtering and precision rules are shared with inference/extraction/training. | |
| gpu_infos = list(GPU_INFOS) | |
| gpu_indices = sorted(GPU_INDEX) | |
| if_gpu_ok = IS_GPU | |
| if if_gpu_ok: | |
| gpu_info = "\n".join(gpu_infos) | |
| default_batch_size = max(1, int(min(GPU_MEMORY[i] for i in gpu_indices)) // 2) | |
| else: | |
| gpu_info = i18n("很遗憾您这没有能用的显卡来支持您训练") | |
| default_batch_size = 1 | |
| gpus = "-".join(str(i) for i in gpu_indices) | |
| # Simple HTTP health-check endpoint when Gradio is running as a server. | |
| try: | |
| # Import only when available to avoid hard dependency at module import time. | |
| from http.server import BaseHTTPRequestHandler, HTTPServer | |
| class _HealthHandler(BaseHTTPRequestHandler): | |
| def do_GET(self): | |
| if self.path == "/healthz": | |
| self.send_response(200) | |
| self.send_header("Content-Type", "text/plain; charset=utf-8") | |
| self.end_headers() | |
| self.wfile.write(b"ok") | |
| else: | |
| self.send_response(404) | |
| self.end_headers() | |
| # Expose a function to start the health server on demand. | |
| def start_health_server(port): | |
| try: | |
| server = HTTPServer(("0.0.0.0", port), _HealthHandler) | |
| except Exception: | |
| return None | |
| def _serve(): | |
| try: | |
| server.serve_forever() | |
| except Exception: | |
| pass | |
| t = threading.Thread(target=_serve, daemon=True) | |
| t.start() | |
| return server | |
| except Exception: | |
| start_health_server = None | |
| class ToolButton(gr.Button, gr.components.FormComponent): | |
| """Small button with single emoji as text, fits inside gradio forms""" | |
| def __init__(self, **kwargs): | |
| super().__init__(variant="tool", **kwargs) | |
| def get_block_name(self): | |
| return "button" | |
| weight_root = os.getenv("weight_root") | |
| weight_pymss_root = os.getenv("weight_pymss_root") | |
| outside_index_root = os.getenv("outside_index_root") | |
| # PyMSS functionality is temporarily disabled. | |
| def weight_names(): | |
| return sorted( | |
| name for name in os.listdir(weight_root) if name.endswith(".pth") | |
| ) | |
| def refresh_weight_choices(previous_names=None, force=False): | |
| current_names = tuple(weight_names()) | |
| if force or current_names != previous_names: | |
| return current_names, change_choices() | |
| return current_names, {"__type__": "update"} | |
| # Names and PyMSS model choices are not used outside training flows. | |
| names = weight_names() | |
| # pymss_names = PYMSS_MODEL_CHOICES | |
| def change_choices(): | |
| return {"choices": weight_names(), "__type__": "update"} | |
| def get_huggingface_token(token): | |
| return token or os.getenv("HUGGINGFACE_TOKEN", "") | |
| def save_uploaded_weight(model_file): | |
| if not model_file: | |
| return i18n("请上传一个 .pth 模型文件"), {"__type__": "update"} | |
| if isinstance(model_file, (list, tuple)): | |
| model_file = model_file[0] | |
| if not model_file.lower().endswith(".pth"): | |
| return i18n("仅支持 .pth 模型文件"), {"__type__": "update"} | |
| target_path = os.path.join(weight_root, os.path.basename(model_file)) | |
| try: | |
| shutil.copy(model_file, target_path) | |
| return i18n("模型已保存到本地"), change_choices() | |
| except Exception as error: | |
| return "%s: %s" % (i18n("保存模型失败"), error), {"__type__": "update"} | |
| def upload_model_to_huggingface(model_name, repo_id, token): | |
| if HfApi is None: | |
| return i18n("huggingface_hub 未安装,请先安装依赖。"), "" | |
| if not model_name: | |
| return i18n("请先选择一个本地模型进行上传"), "" | |
| if not repo_id: | |
| return i18n("请填写 Hugging Face 仓库 ID,例如 username/repo-name"), "" | |
| hf_token = get_huggingface_token(token) | |
| if not hf_token: | |
| return i18n( | |
| "请提供 Hugging Face 访问令牌或设置 HUGGINGFACE_TOKEN 环境变量" | |
| ), "" | |
| model_path = os.path.join(weight_root, model_name) | |
| if not os.path.exists(model_path): | |
| return "%s: %s" % (i18n("模型文件不存在"), model_path), "" | |
| try: | |
| api = HfApi(token=hf_token) | |
| api.upload_file( | |
| path_or_fileobj=model_path, | |
| path_in_repo=model_name, | |
| repo_id=repo_id, | |
| repo_type="model", | |
| token=hf_token, | |
| ) | |
| return "%s %s %s: %s" % ( | |
| i18n("成功上传"), model_name, i18n("到 Hugging Face 仓库"), | |
| f"https://huggingface.co/{repo_id}/blob/main/{model_name}", | |
| ) | |
| except Exception as error: | |
| return "%s: %s" % (i18n("上传失败"), error) | |
| def clean(): | |
| return {"value": "", "__type__": "update"} | |
| sr_dict = { | |
| "32k": 32000, | |
| "40k": 40000, | |
| "48k": 48000, | |
| } | |
| TRAIN_TASK_LOCK = threading.Lock() | |
| TRAIN_TASK = None | |
| def button_update(value=None, variant=None, visible=None): | |
| update = {"__type__": "update"} | |
| if value is not None: | |
| update["value"] = value | |
| if variant is not None: | |
| update["variant"] = variant | |
| if visible is not None: | |
| update["visible"] = visible | |
| return update | |
| def render_pymss_progress(percent=0, label="等待开始", state="idle"): | |
| percent = max(0.0, min(100.0, float(percent or 0))) | |
| colors = { | |
| "idle": "#64748b", | |
| "running": "#2563eb", | |
| "done": "#15803d", | |
| "stopped": "#b45309", | |
| "failed": "#b91c1c", | |
| } | |
| color = colors.get(state, colors["running"]) | |
| safe_label = html.escape(str(label or "")) | |
| return ( | |
| '<div style="min-height:52px;padding:6px 0;">' | |
| '<div style="display:flex;justify-content:space-between;gap:12px;' | |
| 'align-items:center;margin-bottom:7px;font-size:14px;line-height:20px;">' | |
| '<span style="overflow-wrap:anywhere;">%s</span>' | |
| '<strong style="flex:0 0 auto;color:%s;">%.1f%%</strong>' | |
| "</div>" | |
| '<div role="progressbar" aria-valuemin="0" aria-valuemax="100" ' | |
| 'aria-valuenow="%.1f" style="height:10px;width:100%%;overflow:hidden;' | |
| 'border-radius:4px;background:#e2e8f0;">' | |
| '<div style="height:100%%;width:%.3f%%;background:%s;"></div>' | |
| "</div></div>" | |
| ) % (safe_label, color, percent, percent, percent, color) | |
| # PyMSS functionality is temporarily disabled. | |
| # def run_pymss_separation( | |
| # model_name, | |
| # inp_root, | |
| # save_root_vocal, | |
| # paths, | |
| # save_root_ins, | |
| # format0, | |
| # ): | |
| # progress_state = { | |
| # "percent": 0.0, | |
| # "label": "正在准备 PyMSS 分离任务", | |
| # "state": "running", | |
| # } | |
| # busy = False | |
| # | |
| # def update_progress(event): | |
| # nonlocal busy | |
| # event_type = event.get("event") | |
| # file_count = max(1, int(event.get("file_count") or 1)) | |
| # file_index = max(1, int(event.get("file_index") or 1)) | |
| # message = str(event.get("message") or "") | |
| # | |
| # if event_type == "progress": | |
| # done = max(0.0, float(event.get("done") or 0)) | |
| # total = max(1.0, float(event.get("total") or 1)) | |
| # file_fraction = min(1.0, done / total) | |
| # progress_state["percent"] = ( | |
| # (file_index - 1 + file_fraction) / file_count * 100 | |
| # ) | |
| # progress_state["label"] = "文件 %s/%s · %s · %.0f/%.0f 秒" % ( | |
| # file_index, | |
| # file_count, | |
| # message or "正在处理音频", | |
| # done, | |
| # total, | |
| # ) | |
| # progress_state["state"] = "running" | |
| # elif event_type == "file_start": | |
| # progress_state["percent"] = (file_index - 1) / file_count * 100 | |
| # progress_state["label"] = message | |
| # progress_state["state"] = "running" | |
| # elif event_type == "file": | |
| # progress_state["percent"] = file_index / file_count * 100 | |
| # progress_state["label"] = ( | |
| # message.splitlines()[0] if message else "文件处理结束" | |
| # ) | |
| # progress_state["state"] = "running" if event.get("ok") else "failed" | |
| # elif event_type == "done": | |
| # successful = int(event.get("successful") or 0) | |
| # failed = int(event.get("failed") or 0) | |
| # progress_state["percent"] = 100.0 | |
| # progress_state["label"] = "分离完成:成功 %s,失败 %s" % ( | |
| # successful, | |
| # failed, | |
| # ) | |
| # progress_state["state"] = "done" if failed == 0 else "failed" | |
| # elif event_type == "retry_fp32": | |
| # progress_state["percent"] = 0.0 | |
| # progress_state["label"] = message | |
| # progress_state["state"] = "running" | |
| # elif event_type == "cancelled": | |
| # progress_state["label"] = message or "PyMSS 分离任务已停止" | |
| # progress_state["state"] = "stopped" | |
| # elif event_type in {"fatal", "busy"}: | |
| # progress_state["label"] = ( | |
| # message.splitlines()[0] if message else "PyMSS 分离任务失败" | |
| # ) | |
| # progress_state["state"] = "failed" | |
| # busy = event_type == "busy" | |
| # elif event_type in {"preparing", "precision_attempt", "status"}: | |
| # progress_state["label"] = message | |
| # progress_state["state"] = "running" | |
| # | |
| # last_info = "" | |
| # try: | |
| # for info in _pymss_separate_core( | |
| # model_name, | |
| # inp_root, | |
| # save_root_vocal, | |
| # paths, | |
| # save_root_ins, | |
| # format0, | |
| # event_callback=update_progress, | |
| # ): | |
| # last_info = info | |
| # start_button = button_update() | |
| # stop_button = button_update() | |
| # if not busy: | |
| # start_button = button_update(visible=False) | |
| # stop_button = button_update(visible=True) | |
| # yield ( | |
| # info, | |
| # render_pymss_progress(**progress_state), | |
| # start_button, | |
| # stop_button, | |
| # ) | |
| # except Exception: | |
| # last_info = "失败\n%s" % traceback.format_exc() | |
| # progress_state.update( | |
| # {"label": "PyMSS 分离任务失败", "state": "failed"} | |
| # ) | |
| # logger.exception("PyMSS WebUI task failed") | |
| # | |
| # start_button = button_update() | |
| # stop_button = button_update() | |
| # if not busy: | |
| # start_button = button_update(visible=True) | |
| # stop_button = button_update(visible=False) | |
| # yield (last_info, render_pymss_progress(**progress_state), start_button, stop_button) | |
| # | |
| # | |
| # PyMSS functionality is temporarily disabled. | |
| # def stop_pymss_webui(): | |
| # return ( | |
| # _stop_pymss_separation_core(), | |
| # render_pymss_progress(0, "PyMSS 分离任务已停止", "stopped"), | |
| # button_update(visible=True), | |
| # button_update(visible=False), | |
| # ) | |
| # | |
| # | |
| def format_status(title, state, detail=""): | |
| lines = ["【%s】" % i18n(title), "%s:%s" % (i18n("状态"), i18n(state))] | |
| if detail: | |
| lines.extend(["", detail.strip()]) | |
| return "\n".join(lines) | |
| def format_workflow_status(step, detail="", completed_steps=None, state="运行中"): | |
| completed_steps = completed_steps or [] | |
| detail = str(detail).strip() | |
| lines = [] | |
| if completed_steps: | |
| lines.append("%s:" % i18n("已完成阶段")) | |
| lines.extend( | |
| "✓ %s:%s" % (i18n(completed_step), i18n("已成功")) | |
| for completed_step in completed_steps | |
| ) | |
| if step: | |
| if lines: | |
| lines.append("") | |
| lines.append("%s:%s" % (i18n("当前阶段"), i18n(step))) | |
| if detail: | |
| lines.extend(["", detail]) | |
| return format_status( | |
| "一键训练", | |
| state, | |
| "\n".join(lines), | |
| ) | |
| def read_log(path, max_lines=40): | |
| try: | |
| lines = [line.rstrip() for line in read_text(path, errors="ignore").splitlines()] | |
| lines = [line for line in lines if line.strip()] | |
| if len(lines) > max_lines: | |
| tail_count = max(0, max_lines - 1) | |
| omitted = len(lines) - tail_count | |
| tail = lines[-tail_count:] if tail_count else [] | |
| lines = [i18n("……已省略前%s行,仅显示最新状态") % omitted] | |
| lines.extend(tail) | |
| return "\n".join(lines) | |
| except FileNotFoundError: | |
| return "" | |
| def artifact_names(directory, suffix): | |
| if not os.path.isdir(directory): | |
| return set() | |
| return { | |
| name.split(".")[0] | |
| for name in os.listdir(directory) | |
| if name.lower().endswith(suffix) | |
| } | |
| def validate_preprocess_outputs(exp_dir): | |
| exp_path = os.path.join(now_dir, "logs", exp_dir) | |
| gt_names = artifact_names(os.path.join(exp_path, "0_gt_wavs"), ".wav") | |
| wav16_names = artifact_names(os.path.join(exp_path, "1_16k_wavs"), ".wav") | |
| if not gt_names: | |
| raise RuntimeError(i18n("数据切分没有生成有效训练音频,请检查训练集和数据切分日志")) | |
| if not wav16_names: | |
| raise RuntimeError(i18n("数据切分没有生成16k音频,已停止后续特征提取和训练")) | |
| if not gt_names & wav16_names: | |
| raise RuntimeError(i18n("数据切分输出文件不匹配,已停止后续特征提取和训练")) | |
| def validate_feature_outputs(exp_dir, version, if_f0): | |
| exp_path = os.path.join(now_dir, "logs", exp_dir) | |
| wav16_names = artifact_names(os.path.join(exp_path, "1_16k_wavs"), ".wav") | |
| feature_name = "3_feature256" if version == "v1" else "3_feature768" | |
| feature_names = artifact_names(os.path.join(exp_path, feature_name), ".npy") | |
| matched = wav16_names & feature_names | |
| if not feature_names or not matched: | |
| raise RuntimeError(i18n("HuBERT特征提取没有生成有效结果,已停止训练")) | |
| if if_f0: | |
| f0_names = artifact_names(os.path.join(exp_path, "2a_f0"), ".npy") | |
| f0nsf_names = artifact_names(os.path.join(exp_path, "2b-f0nsf"), ".npy") | |
| matched &= f0_names & f0nsf_names | |
| if not f0_names or not f0nsf_names or not matched: | |
| raise RuntimeError(i18n("F0提取没有生成有效结果,已停止训练")) | |
| return matched | |
| def begin_train_task(name): | |
| global TRAIN_TASK | |
| with TRAIN_TASK_LOCK: | |
| if TRAIN_TASK is None: | |
| state = { | |
| "name": name, | |
| "processes": [], | |
| "stop_requested": False, | |
| } | |
| TRAIN_TASK = state | |
| return "start", state | |
| return "busy", TRAIN_TASK | |
| def stop_train_task(name): | |
| with TRAIN_TASK_LOCK: | |
| if TRAIN_TASK is None: | |
| return ( | |
| format_status(name, "未运行"), | |
| button_update(visible=True), | |
| button_update(visible=False), | |
| ) | |
| if TRAIN_TASK["name"] != name: | |
| return ( | |
| format_status( | |
| name, | |
| "无法停止", | |
| i18n("%s运行中,请先停止该任务") % i18n(TRAIN_TASK["name"]), | |
| ), | |
| button_update(), | |
| button_update(), | |
| ) | |
| state = TRAIN_TASK | |
| state["stop_requested"] = True | |
| processes = list(state["processes"]) | |
| for process in processes: | |
| kill_process_tree(process, name, logger) | |
| return ( | |
| format_status(name, "已停止"), | |
| button_update(visible=True), | |
| button_update(visible=False), | |
| ) | |
| def finish_train_task(state): | |
| global TRAIN_TASK | |
| with TRAIN_TASK_LOCK: | |
| if TRAIN_TASK is state: | |
| TRAIN_TASK = None | |
| def train_task_stopped(state): | |
| with TRAIN_TASK_LOCK: | |
| return state["stop_requested"] | |
| def start_train_process(state, cmd): | |
| kwargs = {"shell": True, "cwd": now_dir} | |
| if "train/train.py" in cmd.replace("\\", "/"): | |
| training_env = os.environ.copy() | |
| training_env["RVC_CUDA_GRAPH"] = "0" | |
| kwargs["env"] = training_env | |
| if platform.system() == "Windows": | |
| kwargs["creationflags"] = subprocess.CREATE_NEW_PROCESS_GROUP | |
| else: | |
| kwargs["start_new_session"] = True | |
| logger.info("%s: %s", i18n("执行命令"), cmd) | |
| process = Popen(cmd, **kwargs) | |
| with TRAIN_TASK_LOCK: | |
| state["processes"].append(process) | |
| stopped = state["stop_requested"] | |
| if stopped: | |
| kill_process_tree(process, state["name"], logger) | |
| return process | |
| def wait_train_processes( | |
| state, | |
| processes, | |
| log_path=None, | |
| title="任务", | |
| format_output=True, | |
| watch_weights=False, | |
| ): | |
| last_snapshot = None | |
| last_emit_time = 0 | |
| last_weight_names = tuple(weight_names()) if watch_weights else () | |
| while any(process.poll() is None for process in processes): | |
| if train_task_stopped(state): | |
| for process in processes: | |
| kill_process_tree(process, state["name"], logger) | |
| break | |
| if log_path: | |
| snapshot = read_log(log_path) | |
| current_time = time.monotonic() | |
| current_weight_names = tuple(weight_names()) if watch_weights else () | |
| weights_changed = watch_weights and current_weight_names != last_weight_names | |
| if ( | |
| snapshot != last_snapshot | |
| or weights_changed | |
| or current_time - last_emit_time >= 5 | |
| ): | |
| yield ( | |
| format_status(title, "运行中", snapshot) | |
| if format_output | |
| else snapshot | |
| ) | |
| last_snapshot = snapshot | |
| last_emit_time = current_time | |
| last_weight_names = current_weight_names | |
| sleep(1) | |
| with TRAIN_TASK_LOCK: | |
| for process in processes: | |
| if process in state["processes"]: | |
| state["processes"].remove(process) | |
| if log_path: | |
| final_state = "已停止" if train_task_stopped(state) else "正在收尾" | |
| snapshot = read_log(log_path) | |
| yield ( | |
| format_status(title, final_state, snapshot) | |
| if format_output | |
| else snapshot | |
| ) | |
| if not train_task_stopped(state): | |
| failed = [process.returncode for process in processes if process.returncode != 0] | |
| if failed: | |
| raise RuntimeError(i18n("子进程执行失败,返回码:%s") % failed) | |
| def run_preprocess_dataset(trainset_dir, exp_dir, sr, n_p, state, format_output=True): | |
| sr = sr_dict[sr] | |
| os.makedirs("%s/logs/%s" % (now_dir, exp_dir), exist_ok=True) | |
| log_path = "%s/logs/%s/preprocess.log" % (now_dir, exp_dir) | |
| with open(log_path, "w", encoding="utf8"): | |
| pass | |
| cmd = '"%s" train/preprocess.py "%s" %s %s "%s/logs/%s" %s %.1f' % ( | |
| config.python_cmd, | |
| trainset_dir, | |
| sr, | |
| n_p, | |
| now_dir, | |
| exp_dir, | |
| config.noparallel, | |
| config.preprocess_per, | |
| ) | |
| extract_start_time = time.time() | |
| requested_workers = max(int(n_p), 1) | |
| actual_workers = 1 if config.noparallel else requested_workers | |
| print( | |
| i18n( | |
| "数据提取开始:start_time=%.6f,请求并行数=%s,实际并行数上限=%s" | |
| ) | |
| % (extract_start_time, requested_workers, actual_workers), | |
| flush=True, | |
| ) | |
| try: | |
| process = start_train_process(state, cmd) | |
| yield from wait_train_processes( | |
| state, [process], log_path, "数据切分", format_output | |
| ) | |
| finally: | |
| extract_end_time = time.time() | |
| print( | |
| i18n("数据提取结束:end_time=%.6f,总耗时=%.3f秒") | |
| % (extract_end_time, extract_end_time - extract_start_time), | |
| flush=True, | |
| ) | |
| if not train_task_stopped(state): | |
| validate_preprocess_outputs(exp_dir) | |
| def preprocess_dataset(trainset_dir, exp_dir, sr, n_p): | |
| action, state = begin_train_task("数据切分") | |
| if action == "busy": | |
| yield ( | |
| format_status( | |
| "数据切分", | |
| "等待中", | |
| i18n("%s运行中,请先停止该任务") % i18n(state["name"]), | |
| ), | |
| button_update(), | |
| button_update(), | |
| ) | |
| return | |
| final_info = None | |
| try: | |
| yield ( | |
| format_status("数据切分", "正在启动"), | |
| button_update(visible=False), | |
| button_update(visible=True), | |
| ) | |
| for info in run_preprocess_dataset(trainset_dir, exp_dir, sr, n_p, state): | |
| yield info, button_update(visible=False), button_update(visible=True) | |
| if train_task_stopped(state): | |
| final_info = format_status("数据切分", "已停止") | |
| except Exception: | |
| final_info = format_status("数据切分", "失败", traceback.format_exc()) | |
| finally: | |
| finish_train_task(state) | |
| if final_info is None: | |
| final_info = format_status("数据切分", "已完成") | |
| yield final_info, button_update(visible=True), button_update(visible=False) | |
| def stop_preprocess_dataset(): | |
| return stop_train_task("数据切分") | |
| # but2.click(extract_f0,[gpus6,np7,f0method8,if_f0_3,trainset_dir4],[info2]) | |
| def run_extract_f0_feature( | |
| gpus, | |
| n_p, | |
| f0method, | |
| if_f0, | |
| exp_dir, | |
| version19, | |
| gpus_rmvpe, | |
| state, | |
| format_output=True, | |
| ): | |
| if f0method not in ("pm", "rmvpe"): | |
| raise ValueError(i18n("仅支持pm和rmvpe音高提取算法")) | |
| log_path = "%s/logs/%s/extract_f0_feature.log" % (now_dir, exp_dir) | |
| os.makedirs("%s/logs/%s" % (now_dir, exp_dir), exist_ok=True) | |
| validate_preprocess_outputs(exp_dir) | |
| with open(log_path, "w", encoding="utf8"): | |
| pass | |
| if if_f0: | |
| processes = [] | |
| rmvpe_devices = [gpu for gpu in gpus_rmvpe.split("-") if gpu != ""] | |
| if f0method == "pm" or ( | |
| f0method == "rmvpe" and not rmvpe_devices and not config.dml | |
| ): | |
| cmd = ( | |
| '"%s" train/dataset/extract_f0.py cpu "%s/logs/%s" %s %s' | |
| % (config.python_cmd, now_dir, exp_dir, n_p, f0method) | |
| ) | |
| processes.append(start_train_process(state, cmd)) | |
| elif rmvpe_devices: | |
| count = len(rmvpe_devices) | |
| for index, gpu in enumerate(rmvpe_devices): | |
| cmd = ( | |
| '"%s" train/dataset/extract_f0.py cuda %s %s %s "%s/logs/%s" %s' | |
| % ( | |
| config.python_cmd, | |
| count, | |
| index, | |
| gpu, | |
| now_dir, | |
| exp_dir, | |
| config.is_half, | |
| ) | |
| ) | |
| processes.append(start_train_process(state, cmd)) | |
| else: | |
| cmd = ( | |
| '"%s" train/dataset/extract_f0.py dml "%s/logs/%s"' | |
| % (config.python_cmd, now_dir, exp_dir) | |
| ) | |
| processes.append(start_train_process(state, cmd)) | |
| yield from wait_train_processes( | |
| state, processes, log_path, "F0提取", format_output | |
| ) | |
| if train_task_stopped(state): | |
| return | |
| with open(log_path, "w", encoding="utf8"): | |
| pass | |
| feature_gpus = [gpu for gpu in gpus.split("-") if gpu != ""] | |
| processes = [] | |
| if feature_gpus: | |
| count = len(feature_gpus) | |
| for index, gpu in enumerate(feature_gpus): | |
| cmd = ( | |
| '"%s" train/dataset/extract_hubert_feature.py %s %s %s %s "%s/logs/%s" %s %s' | |
| % ( | |
| config.python_cmd, | |
| config.device, | |
| count, | |
| index, | |
| gpu, | |
| now_dir, | |
| exp_dir, | |
| version19, | |
| config.is_half, | |
| ) | |
| ) | |
| processes.append(start_train_process(state, cmd)) | |
| else: | |
| cmd = ( | |
| '"%s" train/dataset/extract_hubert_feature.py %s 1 0 "%s/logs/%s" %s %s' | |
| % ( | |
| config.python_cmd, | |
| config.device, | |
| now_dir, | |
| exp_dir, | |
| version19, | |
| config.is_half, | |
| ) | |
| ) | |
| processes.append(start_train_process(state, cmd)) | |
| yield from wait_train_processes( | |
| state, processes, log_path, "HuBERT特征", format_output | |
| ) | |
| if not train_task_stopped(state): | |
| validate_feature_outputs(exp_dir, version19, if_f0) | |
| def extract_f0_feature(gpus, n_p, f0method, if_f0, exp_dir, version19, gpus_rmvpe): | |
| action, state = begin_train_task("特征提取") | |
| if action == "busy": | |
| yield ( | |
| format_status( | |
| "特征提取", | |
| "等待中", | |
| i18n("%s运行中,请先停止该任务") % i18n(state["name"]), | |
| ), | |
| button_update(), | |
| button_update(), | |
| ) | |
| return | |
| final_info = None | |
| try: | |
| yield ( | |
| format_status("特征提取", "正在启动"), | |
| button_update(visible=False), | |
| button_update(visible=True), | |
| ) | |
| for info in run_extract_f0_feature( | |
| gpus, n_p, f0method, if_f0, exp_dir, version19, gpus_rmvpe, state | |
| ): | |
| yield info, button_update(visible=False), button_update(visible=True) | |
| if train_task_stopped(state): | |
| final_info = format_status("特征提取", "已停止") | |
| except Exception: | |
| final_info = format_status("特征提取", "失败", traceback.format_exc()) | |
| finally: | |
| finish_train_task(state) | |
| if final_info is None: | |
| final_info = format_status("特征提取", "已完成") | |
| yield final_info, button_update(visible=True), button_update(visible=False) | |
| def stop_extract_f0_feature(): | |
| return stop_train_task("特征提取") | |
| def get_pretrained_models(path_str, f0_str, sr2): | |
| if_pretrained_generator_exist = os.access( | |
| "assets/pretrained%s/%sG%s.pth" % (path_str, f0_str, sr2), os.F_OK | |
| ) | |
| if_pretrained_discriminator_exist = os.access( | |
| "assets/pretrained%s/%sD%s.pth" % (path_str, f0_str, sr2), os.F_OK | |
| ) | |
| if not if_pretrained_generator_exist: | |
| logger.warning( | |
| i18n("生成器预训练模型不存在,将不使用:assets/pretrained%s/%sG%s.pth"), | |
| path_str, | |
| f0_str, | |
| sr2, | |
| ) | |
| if not if_pretrained_discriminator_exist: | |
| logger.warning( | |
| i18n("判别器预训练模型不存在,将不使用:assets/pretrained%s/%sD%s.pth"), | |
| path_str, | |
| f0_str, | |
| sr2, | |
| ) | |
| return ( | |
| ( | |
| "assets/pretrained%s/%sG%s.pth" % (path_str, f0_str, sr2) | |
| if if_pretrained_generator_exist | |
| else "" | |
| ), | |
| ( | |
| "assets/pretrained%s/%sD%s.pth" % (path_str, f0_str, sr2) | |
| if if_pretrained_discriminator_exist | |
| else "" | |
| ), | |
| ) | |
| def change_sr2(sr2, if_f0_3, version19): | |
| path_str = "" if version19 == "v1" else "_v2" | |
| f0_str = "f0" if if_f0_3 else "" | |
| return get_pretrained_models(path_str, f0_str, sr2) | |
| def change_version19(sr2, if_f0_3, version19): | |
| path_str = "" if version19 == "v1" else "_v2" | |
| if sr2 == "32k" and version19 == "v1": | |
| sr2 = "40k" | |
| to_return_sr2 = ( | |
| {"choices": ["40k", "48k"], "__type__": "update", "value": sr2} | |
| if version19 == "v1" | |
| else {"choices": ["40k", "48k", "32k"], "__type__": "update", "value": sr2} | |
| ) | |
| f0_str = "f0" if if_f0_3 else "" | |
| return ( | |
| *get_pretrained_models(path_str, f0_str, sr2), | |
| to_return_sr2, | |
| ) | |
| def change_f0(if_f0_3, sr2, version19): # f0method8,pretrained_G14,pretrained_D15 | |
| path_str = "" if version19 == "v1" else "_v2" | |
| return ( | |
| {"visible": if_f0_3, "__type__": "update"}, | |
| {"visible": if_f0_3, "__type__": "update"}, | |
| *get_pretrained_models(path_str, "f0" if if_f0_3 == True else "", sr2), | |
| ) | |
| # but3.click(click_train,[exp_dir1,sr2,if_f0_3,save_epoch10,total_epoch11,batch_size12,if_save_latest13,pretrained_G14,pretrained_D15,gpus16]) | |
| def run_train_model( | |
| exp_dir1, | |
| sr2, | |
| if_f0_3, | |
| spk_id5, | |
| save_epoch10, | |
| total_epoch11, | |
| batch_size12, | |
| if_save_latest13, | |
| pretrained_G14, | |
| pretrained_D15, | |
| gpus16, | |
| if_cache_gpu17, | |
| if_save_every_weights18, | |
| version19, | |
| state, | |
| format_output=True, | |
| ): | |
| # 生成filelist | |
| exp_dir = "%s/logs/%s" % (now_dir, exp_dir1) | |
| os.makedirs(exp_dir, exist_ok=True) | |
| gt_wavs_dir = "%s/0_gt_wavs" % (exp_dir) | |
| feature_dir = ( | |
| "%s/3_feature256" % (exp_dir) | |
| if version19 == "v1" | |
| else "%s/3_feature768" % (exp_dir) | |
| ) | |
| if if_f0_3: | |
| f0_dir = "%s/2a_f0" % (exp_dir) | |
| f0nsf_dir = "%s/2b-f0nsf" % (exp_dir) | |
| names = ( | |
| set([name.split(".")[0] for name in os.listdir(gt_wavs_dir)]) | |
| & set([name.split(".")[0] for name in os.listdir(feature_dir)]) | |
| & set([name.split(".")[0] for name in os.listdir(f0_dir)]) | |
| & set([name.split(".")[0] for name in os.listdir(f0nsf_dir)]) | |
| ) | |
| else: | |
| names = set([name.split(".")[0] for name in os.listdir(gt_wavs_dir)]) & set( | |
| [name.split(".")[0] for name in os.listdir(feature_dir)] | |
| ) | |
| if not names: | |
| raise RuntimeError(i18n("没有可用于训练的有效音频,请先完成数据切分和特征提取")) | |
| opt = [] | |
| for name in names: | |
| if if_f0_3: | |
| opt.append( | |
| "%s/%s.wav|%s/%s.npy|%s/%s.wav.npy|%s/%s.wav.npy|%s" | |
| % ( | |
| gt_wavs_dir.replace("\\", "\\\\"), | |
| name, | |
| feature_dir.replace("\\", "\\\\"), | |
| name, | |
| f0_dir.replace("\\", "\\\\"), | |
| name, | |
| f0nsf_dir.replace("\\", "\\\\"), | |
| name, | |
| spk_id5, | |
| ) | |
| ) | |
| else: | |
| opt.append( | |
| "%s/%s.wav|%s/%s.npy|%s" | |
| % ( | |
| gt_wavs_dir.replace("\\", "\\\\"), | |
| name, | |
| feature_dir.replace("\\", "\\\\"), | |
| name, | |
| spk_id5, | |
| ) | |
| ) | |
| fea_dim = 256 if version19 == "v1" else 768 | |
| if if_f0_3: | |
| for _ in range(2): | |
| opt.append( | |
| "%s/logs/mute/0_gt_wavs/mute%s.wav|%s/logs/mute/3_feature%s/mute.npy|%s/logs/mute/2a_f0/mute.wav.npy|%s/logs/mute/2b-f0nsf/mute.wav.npy|%s" | |
| % (now_dir, sr2, now_dir, fea_dim, now_dir, now_dir, spk_id5) | |
| ) | |
| else: | |
| for _ in range(2): | |
| opt.append( | |
| "%s/logs/mute/0_gt_wavs/mute%s.wav|%s/logs/mute/3_feature%s/mute.npy|%s" | |
| % (now_dir, sr2, now_dir, fea_dim, spk_id5) | |
| ) | |
| shuffle(opt) | |
| with open("%s/filelist.txt" % exp_dir, "w", encoding="utf8") as f: | |
| f.write("\n".join(opt)) | |
| logger.debug(i18n("训练文件列表写入完成")) | |
| # 生成config#无需生成config | |
| # cmd = python_cmd + " train_nsf_sim_cache_sid_load_pretrain.py -e mi-test -sr 40k -f0 1 -bs 4 -g 0 -te 10 -se 5 -pg pretrained/f0G40k.pth -pd pretrained/f0D40k.pth -l 1 -c 0" | |
| logger.info(i18n("使用显卡:%s"), str(gpus16)) | |
| if pretrained_G14 == "": | |
| logger.info(i18n("未使用生成器预训练模型")) | |
| if pretrained_D15 == "": | |
| logger.info(i18n("未使用判别器预训练模型")) | |
| if version19 == "v1" or sr2 == "40k": | |
| config_path = "v1/%s.json" % sr2 | |
| else: | |
| config_path = "v2/%s.json" % sr2 | |
| config_save_path = os.path.join(exp_dir, "config.json") | |
| if not pathlib.Path(config_save_path).exists(): | |
| with open(config_save_path, "w", encoding="utf8") as f: | |
| json.dump( | |
| config.json_config[config_path], | |
| f, | |
| ensure_ascii=False, | |
| indent=4, | |
| sort_keys=True, | |
| ) | |
| f.write("\n") | |
| if gpus16: | |
| cmd = ( | |
| '"%s" train/train.py -e "%s" -sr %s -f0 %s -bs %s -g %s -te %s -se %s %s %s -l %s -c %s -sw %s -v %s' | |
| % ( | |
| config.python_cmd, | |
| exp_dir1, | |
| sr2, | |
| 1 if if_f0_3 else 0, | |
| batch_size12, | |
| gpus16, | |
| total_epoch11, | |
| save_epoch10, | |
| "-pg %s" % pretrained_G14 if pretrained_G14 != "" else "", | |
| "-pd %s" % pretrained_D15 if pretrained_D15 != "" else "", | |
| 1 if if_save_latest13 == i18n("是") else 0, | |
| 1 if if_cache_gpu17 == i18n("是") else 0, | |
| 1 if if_save_every_weights18 == i18n("是") else 0, | |
| version19, | |
| ) | |
| ) | |
| else: | |
| cmd = ( | |
| '"%s" train/train.py -e "%s" -sr %s -f0 %s -bs %s -te %s -se %s %s %s -l %s -c %s -sw %s -v %s' | |
| % ( | |
| config.python_cmd, | |
| exp_dir1, | |
| sr2, | |
| 1 if if_f0_3 else 0, | |
| batch_size12, | |
| total_epoch11, | |
| save_epoch10, | |
| "-pg %s" % pretrained_G14 if pretrained_G14 != "" else "", | |
| "-pd %s" % pretrained_D15 if pretrained_D15 != "" else "", | |
| 1 if if_save_latest13 == i18n("是") else 0, | |
| 1 if if_cache_gpu17 == i18n("是") else 0, | |
| 1 if if_save_every_weights18 == i18n("是") else 0, | |
| version19, | |
| ) | |
| ) | |
| logger.info("%s: %s", i18n("执行命令"), cmd) | |
| process = start_train_process(state, cmd) | |
| yield from wait_train_processes( | |
| state, | |
| [process], | |
| os.path.join(exp_dir, "train.log"), | |
| "模型训练", | |
| format_output, | |
| True, | |
| ) | |
| def click_train( | |
| exp_dir1, | |
| sr2, | |
| if_f0_3, | |
| spk_id5, | |
| save_epoch10, | |
| total_epoch11, | |
| batch_size12, | |
| if_save_latest13, | |
| pretrained_G14, | |
| pretrained_D15, | |
| gpus16, | |
| if_cache_gpu17, | |
| if_save_every_weights18, | |
| version19, | |
| ): | |
| known_models = tuple(weight_names()) | |
| action, state = begin_train_task("模型训练") | |
| if action == "busy": | |
| yield ( | |
| format_status( | |
| "模型训练", | |
| "等待中", | |
| i18n("%s运行中,请先停止该任务") % i18n(state["name"]), | |
| ), | |
| button_update(), | |
| button_update(), | |
| button_update(), | |
| ) | |
| return | |
| final_info = None | |
| try: | |
| yield ( | |
| format_status("模型训练", "正在启动"), | |
| button_update(visible=False), | |
| button_update(visible=True), | |
| button_update(), | |
| ) | |
| for info in run_train_model( | |
| exp_dir1, | |
| sr2, | |
| if_f0_3, | |
| spk_id5, | |
| save_epoch10, | |
| total_epoch11, | |
| batch_size12, | |
| if_save_latest13, | |
| pretrained_G14, | |
| pretrained_D15, | |
| gpus16, | |
| if_cache_gpu17, | |
| if_save_every_weights18, | |
| version19, | |
| state, | |
| ): | |
| known_models, model_update = refresh_weight_choices(known_models) | |
| yield ( | |
| info, | |
| button_update(visible=False), | |
| button_update(visible=True), | |
| model_update, | |
| ) | |
| if train_task_stopped(state): | |
| final_info = format_status("模型训练", "已停止") | |
| except Exception: | |
| final_info = format_status("模型训练", "失败", traceback.format_exc()) | |
| finally: | |
| finish_train_task(state) | |
| if final_info is None: | |
| final_info = format_status("模型训练", "已完成") | |
| model_update = change_choices() | |
| yield ( | |
| final_info, | |
| button_update(visible=True), | |
| button_update(visible=False), | |
| model_update, | |
| ) | |
| def stop_train_model(): | |
| return stop_train_task("模型训练") | |
| # but4.click(train_index, [exp_dir1], info3) | |
| def run_train_index(exp_dir1, version19, state, format_output=True): | |
| exp_dir = os.path.join(now_dir, "logs", exp_dir1) | |
| os.makedirs(exp_dir, exist_ok=True) | |
| log_path = os.path.join(exp_dir, "train_index.log") | |
| with open(log_path, "w", encoding="utf8"): | |
| pass | |
| cmd = ( | |
| '"%s" train/train_index.py "%s" %s "%s" %s' | |
| % ( | |
| config.python_cmd, | |
| exp_dir1, | |
| version19, | |
| outside_index_root, | |
| config.n_cpu, | |
| ) | |
| ) | |
| process = start_train_process(state, cmd) | |
| yield from wait_train_processes( | |
| state, [process], log_path, "索引训练", format_output | |
| ) | |
| def train_index(exp_dir1, version19): | |
| action, state = begin_train_task("索引训练") | |
| if action == "busy": | |
| yield ( | |
| format_status( | |
| "索引训练", | |
| "等待中", | |
| i18n("%s运行中,请先停止该任务") % i18n(state["name"]), | |
| ), | |
| button_update(), | |
| button_update(), | |
| ) | |
| return | |
| final_info = None | |
| try: | |
| yield ( | |
| format_status("索引训练", "正在启动"), | |
| button_update(visible=False), | |
| button_update(visible=True), | |
| ) | |
| for info in run_train_index(exp_dir1, version19, state): | |
| yield info, button_update(visible=False), button_update(visible=True) | |
| if train_task_stopped(state): | |
| final_info = format_status("索引训练", "已停止") | |
| except Exception: | |
| final_info = format_status("索引训练", "失败", traceback.format_exc()) | |
| finally: | |
| finish_train_task(state) | |
| if final_info is None: | |
| final_info = format_status("索引训练", "已完成") | |
| yield final_info, button_update(visible=True), button_update(visible=False) | |
| def stop_train_index(): | |
| return stop_train_task("索引训练") | |
| # but5.click(train1key, [exp_dir1, sr2, if_f0_3, trainset_dir4, spk_id5, gpus6, np7, f0method8, save_epoch10, total_epoch11, batch_size12, if_save_latest13, pretrained_G14, pretrained_D15, gpus16, if_cache_gpu17], info3) | |
| def train1key( | |
| exp_dir1, | |
| sr2, | |
| if_f0_3, | |
| trainset_dir4, | |
| spk_id5, | |
| np7, | |
| f0method8, | |
| save_epoch10, | |
| total_epoch11, | |
| batch_size12, | |
| if_save_latest13, | |
| pretrained_G14, | |
| pretrained_D15, | |
| gpus16, | |
| if_cache_gpu17, | |
| if_save_every_weights18, | |
| version19, | |
| gpus_rmvpe, | |
| ): | |
| known_models = tuple(weight_names()) | |
| action, state = begin_train_task("一键训练") | |
| if action == "busy": | |
| yield ( | |
| format_status( | |
| "一键训练", | |
| "等待中", | |
| i18n("%s运行中,请先停止该任务") % i18n(state["name"]), | |
| ), | |
| button_update(), | |
| button_update(), | |
| button_update(), | |
| ) | |
| return | |
| completed_steps = [] | |
| step = "" | |
| final_info = None | |
| running = True | |
| start_button = button_update(visible=False) | |
| stop_button = button_update(visible=True) | |
| try: | |
| yield ( | |
| format_status("一键训练", "正在启动"), | |
| start_button, | |
| stop_button, | |
| button_update(), | |
| ) | |
| step = "数据切分" | |
| yield ( | |
| format_workflow_status(step, completed_steps=completed_steps), | |
| start_button, | |
| stop_button, | |
| button_update(), | |
| ) | |
| for info in run_preprocess_dataset( | |
| trainset_dir4, exp_dir1, sr2, np7, state, False | |
| ): | |
| yield ( | |
| format_workflow_status(step, info, completed_steps), | |
| start_button, | |
| stop_button, | |
| button_update(), | |
| ) | |
| running = not train_task_stopped(state) | |
| if running: | |
| completed_steps.append(step) | |
| if running: | |
| step = "F0与HuBERT特征提取" | |
| yield ( | |
| format_workflow_status(step, completed_steps=completed_steps), | |
| start_button, | |
| stop_button, | |
| button_update(), | |
| ) | |
| for info in run_extract_f0_feature( | |
| gpus16, | |
| np7, | |
| f0method8, | |
| if_f0_3, | |
| exp_dir1, | |
| version19, | |
| gpus_rmvpe, | |
| state, | |
| False, | |
| ): | |
| yield ( | |
| format_workflow_status(step, info, completed_steps), | |
| start_button, | |
| stop_button, | |
| button_update(), | |
| ) | |
| running = not train_task_stopped(state) | |
| if running: | |
| completed_steps.append(step) | |
| if running: | |
| step = "模型训练" | |
| yield ( | |
| format_workflow_status(step, completed_steps=completed_steps), | |
| start_button, | |
| stop_button, | |
| button_update(), | |
| ) | |
| for info in run_train_model( | |
| exp_dir1, | |
| sr2, | |
| if_f0_3, | |
| spk_id5, | |
| save_epoch10, | |
| total_epoch11, | |
| batch_size12, | |
| if_save_latest13, | |
| pretrained_G14, | |
| pretrained_D15, | |
| gpus16, | |
| if_cache_gpu17, | |
| if_save_every_weights18, | |
| version19, | |
| state, | |
| False, | |
| ): | |
| known_models, model_update = refresh_weight_choices(known_models) | |
| yield ( | |
| format_workflow_status(step, info, completed_steps), | |
| start_button, | |
| stop_button, | |
| model_update, | |
| ) | |
| if not train_task_stopped(state): | |
| yield ( | |
| format_workflow_status(step, completed_steps=completed_steps), | |
| start_button, | |
| stop_button, | |
| change_choices(), | |
| ) | |
| running = not train_task_stopped(state) | |
| if running: | |
| completed_steps.append(step) | |
| if running: | |
| step = "索引训练" | |
| yield ( | |
| format_workflow_status(step, completed_steps=completed_steps), | |
| start_button, | |
| stop_button, | |
| button_update(), | |
| ) | |
| for info in run_train_index(exp_dir1, version19, state, False): | |
| yield ( | |
| format_workflow_status(step, info, completed_steps), | |
| start_button, | |
| stop_button, | |
| button_update(), | |
| ) | |
| running = not train_task_stopped(state) | |
| if running: | |
| completed_steps.append(step) | |
| if not running: | |
| final_info = format_workflow_status( | |
| step, completed_steps=completed_steps, state="已停止" | |
| ) | |
| else: | |
| final_info = format_workflow_status( | |
| "", completed_steps=completed_steps, state="已完成" | |
| ) | |
| except Exception: | |
| final_info = format_workflow_status( | |
| step, | |
| traceback.format_exc(), | |
| completed_steps, | |
| "失败", | |
| ) | |
| finally: | |
| finish_train_task(state) | |
| model_update = change_choices() | |
| yield ( | |
| final_info, | |
| button_update(visible=True), | |
| button_update(visible=False), | |
| model_update, | |
| ) | |
| def stop_train1key(): | |
| return stop_train_task("一键训练") | |
| # ckpt_path2.change(change_info_,[ckpt_path2],[sr__,if_f0__]) | |
| def change_info_(ckpt_path): | |
| if not os.path.exists(ckpt_path.replace(os.path.basename(ckpt_path), "train.log")): | |
| return {"__type__": "update"}, {"__type__": "update"}, {"__type__": "update"} | |
| try: | |
| info = eval( | |
| read_text( | |
| ckpt_path.replace(os.path.basename(ckpt_path), "train.log") | |
| ) | |
| .strip("\n") | |
| .split("\n")[0] | |
| .split("\t")[-1] | |
| ) | |
| sr, f0 = info["sample_rate"], info["if_f0"] | |
| version = "v2" if ("version" in info and info["version"] == "v2") else "v1" | |
| return sr, str(f0), version | |
| except Exception: | |
| traceback.print_exc() | |
| return {"__type__": "update"}, {"__type__": "update"}, {"__type__": "update"} | |
| F0GPUVisible = IS_GPU | |
| TRAINING_INFO_CSS = """ | |
| #training-info-step2a textarea, | |
| #training-info-step2b textarea, | |
| #training-info-step3 textarea { | |
| height: 12rem !important; | |
| min-height: 12rem !important; | |
| max-height: 12rem !important; | |
| overflow-y: auto !important; | |
| resize: none !important; | |
| } | |
| """ | |
| def change_f0_method(f0method8): | |
| if f0method8 == "rmvpe": | |
| visible = F0GPUVisible | |
| else: | |
| visible = False | |
| return {"visible": visible, "__type__": "update"} | |
| with gr.Blocks(title="RVC WebUI", css=TRAINING_INFO_CSS) as app: | |
| gr.Markdown("## RVC WebUI") | |
| gr.Markdown( | |
| value=i18n( | |
| "本软件以MIT协议开源, 作者不对软件具备任何控制力, 使用软件者、传播软件导出的声音者自负全责. <br>如不认可该条款, 则不能使用或引用软件包内任何代码和文件. 详见根目录<b>LICENSE</b>." | |
| ) | |
| ) | |
| with gr.Tabs(): | |
| with gr.TabItem(i18n("模型推理")): | |
| with gr.Row(): | |
| sid0 = gr.Dropdown(label=i18n("推理音色"), choices=sorted(names)) | |
| with gr.Column(): | |
| refresh_button = gr.Button( | |
| i18n("刷新音色列表"), variant="primary" | |
| ) | |
| clean_button = gr.Button(i18n("卸载音色省显存"), variant="primary") | |
| with gr.Column(): | |
| gr.Markdown(i18n("上传本地模型并发布到 Hugging Face")) | |
| model_upload_file = gr.File( | |
| label=i18n("选择本地 .pth 模型文件"), | |
| file_types=[".pth"], | |
| type="filepath", | |
| ) | |
| model_upload_button = gr.Button( | |
| i18n("保存本地模型"), variant="secondary" | |
| ) | |
| model_upload_info = gr.Textbox( | |
| label=i18n("保存信息"), interactive=False | |
| ) | |
| hf_repo_input = gr.Textbox( | |
| label=i18n("Hugging Face 仓库 ID"), | |
| placeholder="username/repo-name", | |
| ) | |
| hf_token_input = gr.Textbox( | |
| label=i18n("Hugging Face 访问令牌"), | |
| placeholder=i18n( | |
| "可选:设置 HUGGINGFACE_TOKEN 环境变量" | |
| ), | |
| type="password", | |
| interactive=True, | |
| ) | |
| hf_upload_button = gr.Button( | |
| i18n("上传到 Hugging Face"), variant="secondary" | |
| ) | |
| hf_upload_info = gr.Textbox( | |
| label=i18n("上传信息"), interactive=False | |
| ) | |
| spk_item = gr.Slider( | |
| minimum=0, | |
| maximum=2333, | |
| step=1, | |
| label=i18n("请选择说话人id"), | |
| value=0, | |
| visible=False, | |
| interactive=True, | |
| ) | |
| clean_button.click( | |
| fn=clean, inputs=[], outputs=[sid0], api_name="infer_clean" | |
| ) | |
| model_upload_button.click( | |
| fn=save_uploaded_weight, | |
| inputs=[model_upload_file], | |
| outputs=[model_upload_info, sid0], | |
| api_name="infer_save_model", | |
| ) | |
| hf_upload_button.click( | |
| fn=upload_model_to_huggingface, | |
| inputs=[sid0, hf_repo_input, hf_token_input], | |
| outputs=[hf_upload_info], | |
| api_name="infer_upload_hf", | |
| ) | |
| with gr.TabItem(i18n("单次推理")): | |
| with gr.Group(): | |
| with gr.Row(): | |
| with gr.Column(): | |
| with gr.Row(equal_height=True): | |
| with gr.Column(scale=1, min_width=120): | |
| vc_transform0 = gr.Number( | |
| label=i18n("变调(整数, 半音数量, 升八度12降八度-12)"), | |
| value=0, | |
| ) | |
| with gr.Column(scale=2, min_width=200): | |
| f0method0 = gr.Radio( | |
| label=i18n("选择音高提取算法"), | |
| choices=["pm", "rmvpe", "fcpe"], | |
| value="rmvpe", | |
| interactive=True, | |
| ) | |
| input_audio0 = gr.Audio( | |
| label=i18n("拖拽或点击上传待处理音频"), | |
| type="filepath", | |
| interactive=True, | |
| ) | |
| with gr.Column(): | |
| resample_sr0 = gr.Slider( | |
| minimum=0, | |
| maximum=48000, | |
| label=i18n("后处理重采样至最终采样率,0为不进行重采样"), | |
| value=0, | |
| step=1, | |
| interactive=True, | |
| ) | |
| rms_mix_rate0 = gr.Slider( | |
| minimum=0, | |
| maximum=1, | |
| label=i18n( | |
| "输入源音量包络替换输出音量包络融合比例,越靠近1越使用输出包络" | |
| ), | |
| value=0.25, | |
| interactive=True, | |
| ) | |
| protect0 = gr.Slider( | |
| minimum=0, | |
| maximum=0.5, | |
| label=i18n( | |
| "保护清辅音和呼吸声,防止电音撕裂等artifact,拉满0.5不开启,调低加大保护力度但可能降低索引效果" | |
| ), | |
| value=0.33, | |
| step=0.01, | |
| interactive=True, | |
| ) | |
| index_rate1 = gr.Slider( | |
| minimum=0, | |
| maximum=1, | |
| label=i18n("检索特征占比"), | |
| value=0.75, | |
| interactive=True, | |
| ) | |
| file_index1 = gr.Textbox( | |
| label=i18n("特征检索库文件路径(选择模型后自动匹配,可手动修改)"), | |
| placeholder="C:\\Users\\Desktop\\model_example.index", | |
| interactive=True, | |
| ) | |
| refresh_button.click( | |
| fn=change_choices, | |
| inputs=[], | |
| outputs=sid0, | |
| api_name="infer_refresh", | |
| ) | |
| with gr.Group(): | |
| with gr.Column(): | |
| but0 = gr.Button(i18n("转换"), variant="primary") | |
| with gr.Row(): | |
| vc_output1 = gr.Textbox(label=i18n("输出信息")) | |
| vc_output2 = gr.Audio( | |
| label=i18n("输出音频(右下角三个点,点了可以下载)") | |
| ) | |
| but0.click( | |
| vc.vc_single, | |
| [ | |
| spk_item, | |
| input_audio0, | |
| vc_transform0, | |
| f0method0, | |
| file_index1, | |
| index_rate1, | |
| resample_sr0, | |
| rms_mix_rate0, | |
| protect0, | |
| ], | |
| [vc_output1, vc_output2], | |
| api_name="infer_convert", | |
| ) | |
| with gr.TabItem(i18n("批量推理")): | |
| gr.Markdown( | |
| value=i18n( | |
| "批量转换, 输入待转换音频文件夹, 或上传多个音频文件, 在指定文件夹(默认opt)下输出转换的音频. " | |
| ) | |
| ) | |
| with gr.Row(): | |
| with gr.Column(): | |
| vc_transform1 = gr.Number( | |
| label=i18n("变调(整数, 半音数量, 升八度12降八度-12)"), | |
| value=0, | |
| ) | |
| opt_input = gr.Textbox( | |
| label=i18n("指定输出文件夹"), value="opt" | |
| ) | |
| file_index3 = gr.Textbox( | |
| label=i18n("特征检索库文件路径(选择模型后自动匹配,可手动修改)"), | |
| value="", | |
| interactive=True, | |
| ) | |
| f0method1 = gr.Radio( | |
| label=i18n("选择音高提取算法"), | |
| choices=["pm", "rmvpe", "fcpe"], | |
| value="rmvpe", | |
| interactive=True, | |
| ) | |
| format1 = gr.Radio( | |
| label=i18n("导出文件格式"), | |
| choices=["wav", "flac", "mp3", "m4a"], | |
| value="wav", | |
| interactive=True, | |
| ) | |
| with gr.Column(): | |
| resample_sr1 = gr.Slider( | |
| minimum=0, | |
| maximum=48000, | |
| label=i18n("后处理重采样至最终采样率,0为不进行重采样"), | |
| value=0, | |
| step=1, | |
| interactive=True, | |
| ) | |
| rms_mix_rate1 = gr.Slider( | |
| minimum=0, | |
| maximum=1, | |
| label=i18n( | |
| "输入源音量包络替换输出音量包络融合比例,越靠近1越使用输出包络" | |
| ), | |
| value=1, | |
| interactive=True, | |
| ) | |
| protect1 = gr.Slider( | |
| minimum=0, | |
| maximum=0.5, | |
| label=i18n( | |
| "保护清辅音和呼吸声,防止电音撕裂等artifact,拉满0.5不开启,调低加大保护力度但可能降低索引效果" | |
| ), | |
| value=0.33, | |
| step=0.01, | |
| interactive=True, | |
| ) | |
| index_rate2 = gr.Slider( | |
| minimum=0, | |
| maximum=1, | |
| label=i18n("检索特征占比"), | |
| value=1, | |
| interactive=True, | |
| ) | |
| with gr.Row(): | |
| dir_input = gr.Textbox( | |
| label=i18n( | |
| "输入待处理音频文件夹路径(去文件管理器地址栏拷就行了)" | |
| ), | |
| placeholder="C:\\Users\\Desktop\\input_vocal_dir", | |
| ) | |
| inputs = gr.File( | |
| file_count="multiple", | |
| label=i18n("也可批量输入音频文件, 二选一, 优先读文件夹"), | |
| ) | |
| with gr.Row(): | |
| but1 = gr.Button(i18n("转换"), variant="primary") | |
| vc_output3 = gr.Textbox(label=i18n("输出信息")) | |
| but1.click( | |
| vc.vc_multi, | |
| [ | |
| spk_item, | |
| dir_input, | |
| opt_input, | |
| inputs, | |
| vc_transform1, | |
| f0method1, | |
| file_index3, | |
| index_rate2, | |
| resample_sr1, | |
| rms_mix_rate1, | |
| protect1, | |
| format1, | |
| ], | |
| [vc_output3], | |
| api_name="infer_convert_batch", | |
| ) | |
| sid0.change( | |
| fn=vc.get_vc, | |
| inputs=[sid0, protect0, protect1], | |
| outputs=[spk_item, protect0, protect1, file_index1, file_index3], | |
| api_name="infer_change_voice", | |
| ) | |
| with gr.TabItem(i18n("人声伴奏分离&去混响")): | |
| with gr.Group(): | |
| gr.Markdown( | |
| value=i18n( | |
| "人声、伴奏与混响批量处理,使用pymss/MSST模型。" | |
| ) | |
| ) | |
| with gr.Row(): | |
| with gr.Column(): | |
| dir_wav_input = gr.Textbox( | |
| label=i18n("输入待处理音频文件夹路径"), | |
| placeholder="C:\\Users\\Desktop\\todo-songs", | |
| ) | |
| wav_inputs = gr.File( | |
| file_count="multiple", | |
| label=i18n("也可批量输入音频文件, 二选一, 优先读文件夹"), | |
| ) | |
| with gr.Column(): | |
| gr.Markdown(value=i18n("PyMSS 功能已暂时禁用。")) | |
| with gr.Row(): | |
| gr.Markdown(value=i18n("请稍后再试或使用其他功能。")) | |
| with gr.TabItem(i18n("训练")): | |
| gr.Markdown( | |
| value=i18n( | |
| "step1: 填写实验配置. 实验数据放在logs下, 每个实验一个文件夹, 需手工输入实验名路径, 内含实验配置, 日志, 训练得到的模型文件. " | |
| ) | |
| ) | |
| with gr.Row(): | |
| exp_dir1 = gr.Textbox(label=i18n("输入实验名"), value="mi-test") | |
| sr2 = gr.Radio( | |
| label=i18n("目标采样率"), | |
| choices=["40k", "48k"], | |
| value="40k", | |
| interactive=True, | |
| ) | |
| if_f0_3 = gr.Radio( | |
| label=i18n("模型是否带音高指导(唱歌一定要, 语音可以不要)"), | |
| choices=[True, False], | |
| value=True, | |
| interactive=True, | |
| ) | |
| version19 = gr.Radio( | |
| label=i18n("版本"), | |
| choices=["v1", "v2"], | |
| value="v2", | |
| interactive=True, | |
| visible=True, | |
| ) | |
| np7 = gr.Slider( | |
| minimum=0, | |
| maximum=config.n_cpu, | |
| step=1, | |
| label=i18n("提取音高和处理数据使用的CPU进程数"), | |
| value=int(np.ceil(config.n_cpu / 1.5)), | |
| interactive=True, | |
| ) | |
| with gr.Group(): # 暂时单人的, 后面支持最多4人的#数据处理 | |
| gr.Markdown( | |
| value=i18n( | |
| "step2a: 自动遍历训练文件夹下所有可解码成音频的文件并进行切片归一化, 在实验目录下生成2个wav文件夹; 暂时只支持单人训练. " | |
| ) | |
| ) | |
| with gr.Row(): | |
| trainset_dir4 = gr.Textbox( | |
| label=i18n("输入训练文件夹路径"), | |
| value=i18n("E:\\语音音频+标注\\米津玄师\\src"), | |
| ) | |
| spk_id5 = gr.Slider( | |
| minimum=0, | |
| maximum=4, | |
| step=1, | |
| label=i18n("请指定说话人id"), | |
| value=0, | |
| interactive=True, | |
| ) | |
| but1 = gr.Button(i18n("处理数据"), variant="primary") | |
| stop_but1 = gr.Button( | |
| i18n("停止处理数据"), variant="stop", visible=False | |
| ) | |
| info1 = gr.Textbox( | |
| label=i18n("输出信息"), | |
| value="", | |
| lines=8, | |
| max_lines=8, | |
| elem_id="training-info-step2a", | |
| ) | |
| but1.click( | |
| preprocess_dataset, | |
| [trainset_dir4, exp_dir1, sr2, np7], | |
| [info1, but1, stop_but1], | |
| api_name="train_preprocess", | |
| ) | |
| stop_but1.click( | |
| stop_preprocess_dataset, | |
| [], | |
| [info1, but1, stop_but1], | |
| queue=False, | |
| ) | |
| with gr.Group(): | |
| gr.Markdown( | |
| value=i18n( | |
| "step2b: 使用CPU提取音高(如果模型带音高), 使用GPU提取特征(选择卡号)" | |
| ) | |
| ) | |
| with gr.Row(): | |
| with gr.Column(): | |
| gpus6 = gr.Textbox( | |
| label=i18n( | |
| "以-分隔输入使用的卡号, 例如 0-1-2 使用卡0和卡1和卡2" | |
| ), | |
| value=gpus, | |
| interactive=True, | |
| visible=F0GPUVisible, | |
| ) | |
| gpu_info9 = gr.Textbox( | |
| label=i18n("显卡信息"), value=gpu_info, visible=F0GPUVisible | |
| ) | |
| with gr.Column(): | |
| f0method8 = gr.Radio( | |
| label=i18n("选择音高提取算法"), | |
| choices=["pm", "rmvpe"], | |
| value="rmvpe", | |
| interactive=True, | |
| ) | |
| gpus_rmvpe = gr.Textbox( | |
| label=i18n( | |
| "rmvpe卡号配置:以-分隔输入使用的不同进程卡号,例如0-0-1使用在卡0上跑2个进程并在卡1上跑1个进程" | |
| ), | |
| value="%s-%s" % (gpus, gpus), | |
| interactive=True, | |
| visible=F0GPUVisible, | |
| ) | |
| with gr.Row(): | |
| but2 = gr.Button(i18n("特征提取"), variant="primary") | |
| stop_but2 = gr.Button( | |
| i18n("停止特征提取"), variant="stop", visible=False | |
| ) | |
| info2 = gr.Textbox( | |
| label=i18n("输出信息"), | |
| value="", | |
| lines=8, | |
| max_lines=8, | |
| elem_id="training-info-step2b", | |
| ) | |
| f0method8.change( | |
| fn=change_f0_method, | |
| inputs=[f0method8], | |
| outputs=[gpus_rmvpe], | |
| ) | |
| but2.click( | |
| extract_f0_feature, | |
| [ | |
| gpus6, | |
| np7, | |
| f0method8, | |
| if_f0_3, | |
| exp_dir1, | |
| version19, | |
| gpus_rmvpe, | |
| ], | |
| [info2, but2, stop_but2], | |
| api_name="train_extract_f0_feature", | |
| ) | |
| stop_but2.click( | |
| stop_extract_f0_feature, | |
| [], | |
| [info2, but2, stop_but2], | |
| queue=False, | |
| ) | |
| with gr.Group(): | |
| gr.Markdown(value=i18n("step3: 填写训练设置, 开始训练模型和索引")) | |
| with gr.Row(): | |
| save_epoch10 = gr.Slider( | |
| minimum=1, | |
| maximum=50, | |
| step=1, | |
| label=i18n("保存频率save_every_epoch"), | |
| value=5, | |
| interactive=True, | |
| ) | |
| total_epoch11 = gr.Slider( | |
| minimum=2, | |
| maximum=1200, | |
| step=1, | |
| label=i18n("总训练轮数total_epoch"), | |
| value=20, | |
| interactive=True, | |
| ) | |
| batch_size12 = gr.Slider( | |
| minimum=1, | |
| maximum=40, | |
| step=1, | |
| label=i18n("每张显卡的batch_size"), | |
| value=default_batch_size, | |
| interactive=True, | |
| ) | |
| if_save_latest13 = gr.Radio( | |
| label=i18n("是否仅保存最新的ckpt文件以节省硬盘空间"), | |
| choices=[i18n("是"), i18n("否")], | |
| value=i18n("否"), | |
| interactive=True, | |
| ) | |
| if_cache_gpu17 = gr.Radio( | |
| label=i18n( | |
| "是否缓存所有训练集至显存. 10min以下小数据可缓存以加速训练, 大数据缓存会炸显存也加不了多少速" | |
| ), | |
| choices=[i18n("是"), i18n("否")], | |
| value=i18n("否"), | |
| interactive=True, | |
| ) | |
| if_save_every_weights18 = gr.Radio( | |
| label=i18n( | |
| "是否在每次保存时间点将最终小模型保存至weights文件夹" | |
| ), | |
| choices=[i18n("是"), i18n("否")], | |
| value=i18n("否"), | |
| interactive=True, | |
| ) | |
| with gr.Row(): | |
| pretrained_G14 = gr.Textbox( | |
| label=i18n("加载预训练底模G路径"), | |
| value="assets/pretrained_v2/f0G40k.pth", | |
| interactive=True, | |
| ) | |
| pretrained_D15 = gr.Textbox( | |
| label=i18n("加载预训练底模D路径"), | |
| value="assets/pretrained_v2/f0D40k.pth", | |
| interactive=True, | |
| ) | |
| sr2.change( | |
| change_sr2, | |
| [sr2, if_f0_3, version19], | |
| [pretrained_G14, pretrained_D15], | |
| ) | |
| version19.change( | |
| change_version19, | |
| [sr2, if_f0_3, version19], | |
| [pretrained_G14, pretrained_D15, sr2], | |
| ) | |
| if_f0_3.change( | |
| change_f0, | |
| [if_f0_3, sr2, version19], | |
| [f0method8, gpus_rmvpe, pretrained_G14, pretrained_D15], | |
| ) | |
| gpus16 = gr.Textbox( | |
| label=i18n( | |
| "以-分隔输入使用的卡号, 例如 0-1-2 使用卡0和卡1和卡2" | |
| ), | |
| value=gpus, | |
| interactive=True, | |
| ) | |
| but3 = gr.Button(i18n("训练模型"), variant="primary") | |
| stop_but3 = gr.Button( | |
| i18n("停止训练模型"), variant="stop", visible=False | |
| ) | |
| but4 = gr.Button(i18n("训练特征索引"), variant="primary") | |
| stop_but4 = gr.Button( | |
| i18n("停止训练索引"), variant="stop", visible=False | |
| ) | |
| but5 = gr.Button(i18n("一键训练"), variant="primary") | |
| stop_but5 = gr.Button( | |
| i18n("停止一键训练"), variant="stop", visible=False | |
| ) | |
| info3 = gr.Textbox( | |
| label=i18n("输出信息"), | |
| value="", | |
| lines=8, | |
| max_lines=8, | |
| elem_id="training-info-step3", | |
| ) | |
| but3.click( | |
| click_train, | |
| [ | |
| exp_dir1, | |
| sr2, | |
| if_f0_3, | |
| spk_id5, | |
| save_epoch10, | |
| total_epoch11, | |
| batch_size12, | |
| if_save_latest13, | |
| pretrained_G14, | |
| pretrained_D15, | |
| gpus16, | |
| if_cache_gpu17, | |
| if_save_every_weights18, | |
| version19, | |
| ], | |
| [info3, but3, stop_but3, sid0], | |
| api_name="train_start", | |
| ) | |
| stop_but3.click( | |
| stop_train_model, | |
| [], | |
| [info3, but3, stop_but3], | |
| queue=False, | |
| ) | |
| but4.click( | |
| train_index, | |
| [exp_dir1, version19], | |
| [info3, but4, stop_but4], | |
| ) | |
| stop_but4.click( | |
| stop_train_index, | |
| [], | |
| [info3, but4, stop_but4], | |
| queue=False, | |
| ) | |
| but5.click( | |
| train1key, | |
| [ | |
| exp_dir1, | |
| sr2, | |
| if_f0_3, | |
| trainset_dir4, | |
| spk_id5, | |
| np7, | |
| f0method8, | |
| save_epoch10, | |
| total_epoch11, | |
| batch_size12, | |
| if_save_latest13, | |
| pretrained_G14, | |
| pretrained_D15, | |
| gpus16, | |
| if_cache_gpu17, | |
| if_save_every_weights18, | |
| version19, | |
| gpus_rmvpe, | |
| ], | |
| [info3, but5, stop_but5, sid0], | |
| api_name="train_start_all", | |
| ) | |
| stop_but5.click( | |
| stop_train1key, | |
| [], | |
| [info3, but5, stop_but5], | |
| queue=False, | |
| ) | |
| with gr.TabItem(i18n("ckpt处理")): | |
| with gr.Group(): | |
| gr.Markdown(value=i18n("模型融合, 可用于测试音色融合")) | |
| with gr.Row(): | |
| ckpt_a = gr.Textbox( | |
| label=i18n("A模型路径"), value="", interactive=True | |
| ) | |
| ckpt_b = gr.Textbox( | |
| label=i18n("B模型路径"), value="", interactive=True | |
| ) | |
| alpha_a = gr.Slider( | |
| minimum=0, | |
| maximum=1, | |
| label=i18n("A模型权重"), | |
| value=0.5, | |
| interactive=True, | |
| ) | |
| with gr.Row(): | |
| sr_ = gr.Radio( | |
| label=i18n("目标采样率"), | |
| choices=["40k", "48k"], | |
| value="40k", | |
| interactive=True, | |
| ) | |
| if_f0_ = gr.Radio( | |
| label=i18n("模型是否带音高指导"), | |
| choices=[i18n("是"), i18n("否")], | |
| value=i18n("是"), | |
| interactive=True, | |
| ) | |
| info__ = gr.Textbox( | |
| label=i18n("要置入的模型信息"), | |
| value="", | |
| max_lines=8, | |
| interactive=True, | |
| ) | |
| name_to_save0 = gr.Textbox( | |
| label=i18n("保存的模型名不带后缀"), | |
| value="", | |
| max_lines=1, | |
| interactive=True, | |
| ) | |
| version_2 = gr.Radio( | |
| label=i18n("模型版本型号"), | |
| choices=["v1", "v2"], | |
| value="v1", | |
| interactive=True, | |
| ) | |
| with gr.Row(): | |
| but6 = gr.Button(i18n("融合"), variant="primary") | |
| info4 = gr.Textbox(label=i18n("输出信息"), value="", max_lines=8) | |
| but6.click( | |
| merge, | |
| [ | |
| ckpt_a, | |
| ckpt_b, | |
| alpha_a, | |
| sr_, | |
| if_f0_, | |
| info__, | |
| name_to_save0, | |
| version_2, | |
| ], | |
| info4, | |
| api_name="ckpt_merge", | |
| ) # def merge(path1,path2,alpha1,sr,f0,info): | |
| with gr.Group(): | |
| gr.Markdown( | |
| value=i18n("修改模型信息(仅支持weights文件夹下提取的小模型文件)") | |
| ) | |
| with gr.Row(): | |
| ckpt_path0 = gr.Textbox( | |
| label=i18n("模型路径"), value="", interactive=True | |
| ) | |
| info_ = gr.Textbox( | |
| label=i18n("要改的模型信息"), | |
| value="", | |
| max_lines=8, | |
| interactive=True, | |
| ) | |
| name_to_save1 = gr.Textbox( | |
| label=i18n("保存的文件名, 默认空为和源文件同名"), | |
| value="", | |
| max_lines=8, | |
| interactive=True, | |
| ) | |
| with gr.Row(): | |
| but7 = gr.Button(i18n("修改"), variant="primary") | |
| info5 = gr.Textbox(label=i18n("输出信息"), value="", max_lines=8) | |
| but7.click( | |
| change_info, | |
| [ckpt_path0, info_, name_to_save1], | |
| info5, | |
| api_name="ckpt_modify", | |
| ) | |
| with gr.Group(): | |
| gr.Markdown( | |
| value=i18n("查看模型信息(仅支持weights文件夹下提取的小模型文件)") | |
| ) | |
| with gr.Row(): | |
| ckpt_path1 = gr.Textbox( | |
| label=i18n("模型路径"), value="", interactive=True | |
| ) | |
| but8 = gr.Button(i18n("查看"), variant="primary") | |
| info6 = gr.Textbox(label=i18n("输出信息"), value="", max_lines=8) | |
| but8.click(show_info, [ckpt_path1], info6, api_name="ckpt_show") | |
| with gr.Group(): | |
| gr.Markdown( | |
| value=i18n( | |
| "模型提取(输入logs文件夹下大文件模型路径),适用于训一半不想训了模型没有自动提取保存小文件模型,或者想测试中间模型的情况" | |
| ) | |
| ) | |
| with gr.Row(): | |
| ckpt_path2 = gr.Textbox( | |
| label=i18n("模型路径"), | |
| value="E:\\codes\\py39\\logs\\mi-test_f0_48k\\G_23333.pth", | |
| interactive=True, | |
| ) | |
| save_name = gr.Textbox( | |
| label=i18n("保存名"), value="", interactive=True | |
| ) | |
| sr__ = gr.Radio( | |
| label=i18n("目标采样率"), | |
| choices=["32k", "40k", "48k"], | |
| value="40k", | |
| interactive=True, | |
| ) | |
| if_f0__ = gr.Radio( | |
| label=i18n("模型是否带音高指导,1是0否"), | |
| choices=["1", "0"], | |
| value="1", | |
| interactive=True, | |
| ) | |
| version_1 = gr.Radio( | |
| label=i18n("模型版本型号"), | |
| choices=["v1", "v2"], | |
| value="v2", | |
| interactive=True, | |
| ) | |
| info___ = gr.Textbox( | |
| label=i18n("要置入的模型信息"), | |
| value="", | |
| max_lines=8, | |
| interactive=True, | |
| ) | |
| but9 = gr.Button(i18n("提取"), variant="primary") | |
| info7 = gr.Textbox(label=i18n("输出信息"), value="", max_lines=8) | |
| ckpt_path2.change( | |
| change_info_, [ckpt_path2], [sr__, if_f0__, version_1] | |
| ) | |
| but9.click( | |
| extract_small_model, | |
| [ckpt_path2, save_name, sr__, if_f0__, info___, version_1], | |
| info7, | |
| api_name="ckpt_extract", | |
| ) | |
| tab_faq = i18n("常见问题解答") | |
| with gr.TabItem(tab_faq): | |
| try: | |
| if tab_faq == "常见问题解答": | |
| info = read_text("docs/cn/faq.md") | |
| else: | |
| info = read_text("docs/en/faq_en.md") | |
| gr.Markdown(value=info) | |
| except Exception: | |
| gr.Markdown(traceback.format_exc()) | |
| if config.iscolab: | |
| app.queue(max_size=1022).launch(share=True) | |
| else: | |
| launch_webui_with_port_fallback(app, config) | |