Refactor: Use PathConfig and pathlib instead of paths.yml loading

This commit is contained in:
litagin02
2024-05-25 10:25:22 +09:00
parent 2771fbd209
commit 8976a3ed2f
22 changed files with 266 additions and 253 deletions

10
app.py
View File

@@ -3,7 +3,6 @@ from pathlib import Path
import gradio as gr
import torch
import yaml
from gradio_tabs.dataset import create_dataset_app
from gradio_tabs.inference import create_inference_app
@@ -14,6 +13,7 @@ from style_bert_vits2.constants import GRADIO_THEME, VERSION
from style_bert_vits2.nlp.japanese import pyopenjtalk_worker
from style_bert_vits2.nlp.japanese.user_dict import update_dict
from style_bert_vits2.tts_model import TTSModelHolder
from config import get_path_config
# このプロセスからはワーカーを起動して辞書を使いたいので、ここで初期化
@@ -22,11 +22,6 @@ pyopenjtalk_worker.initialize_worker()
# dict_data/ 以下の辞書データを pyopenjtalk に適用
update_dict()
# Get path settings
with Path("configs/paths.yml").open("r", encoding="utf-8") as f:
path_config: dict[str, str] = yaml.safe_load(f.read())
# dataset_root = path_config["dataset_root"]
assets_root = path_config["assets_root"]
parser = argparse.ArgumentParser()
parser.add_argument("--device", type=str, default="cuda")
@@ -40,7 +35,8 @@ device = args.device
if device == "cuda" and not torch.cuda.is_available():
device = "cpu"
model_holder = TTSModelHolder(Path(assets_root), device)
path_config = get_path_config()
model_holder = TTSModelHolder(Path(path_config.assets_root), device)
with gr.Blocks(theme=GRADIO_THEME) as app:
gr.Markdown(f"# Style-Bert-VITS2 WebUI (version {VERSION})")

View File

@@ -5,13 +5,12 @@ import torch
import torch.multiprocessing as mp
from tqdm import tqdm
from config import config
from config import get_config
from style_bert_vits2.constants import Languages
from style_bert_vits2.logging import logger
from style_bert_vits2.models import commons
from style_bert_vits2.models.hyper_parameters import HyperParameters
from style_bert_vits2.nlp import (
bert_models,
cleaned_text_to_sequence,
extract_bert_feature,
)
@@ -20,6 +19,7 @@ from style_bert_vits2.nlp.japanese.user_dict import update_dict
from style_bert_vits2.utils.stdout_wrapper import SAFE_STDOUT
config = get_config()
# このプロセスからはワーカーを起動して辞書を使いたいので、ここで初期化
pyopenjtalk_worker.initialize_worker()
@@ -61,7 +61,7 @@ def process_line(x: tuple[str, bool]):
bert = torch.load(bert_path)
assert bert.shape[-1] == len(phone)
except Exception:
bert = extract_bert_feature(text, word2ph, language_str, device)
bert = extract_bert_feature(text, word2ph, Languages(language_str), device)
assert bert.shape[-1] == len(phone)
torch.save(bert, bert_path)

View File

@@ -4,7 +4,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"# Style-Bert-VITS2 (ver 2.4.1) のGoogle Colabでの学習\n",
"# Style-Bert-VITS2 (ver 2.5.0) のGoogle Colabでの学習\n",
"\n",
"Google Colab上でStyle-Bert-VITS2の学習を行うことができます。\n",
"\n",

159
config.py
View File

@@ -2,9 +2,9 @@
@Desc: 全局配置文件读取
"""
import os
import shutil
from typing import Dict, List
from pathlib import Path
from typing import Any
import torch
import yaml
@@ -12,6 +12,12 @@ import yaml
from style_bert_vits2.logging import logger
class PathConfig:
def __init__(self, dataset_root: str, assets_root: str):
self.dataset_root = Path(dataset_root)
self.assets_root = Path(assets_root)
# If not cuda available, set possible devices to cpu
cuda_available = torch.cuda.is_available()
@@ -20,17 +26,17 @@ class Resample_config:
"""重采样配置"""
def __init__(self, in_dir: str, out_dir: str, sampling_rate: int = 44100):
self.sampling_rate: int = sampling_rate # 目标采样率
self.in_dir: str = in_dir # 待处理音频目录路径
self.out_dir: str = out_dir # 重采样输出路径
self.sampling_rate = sampling_rate # 目标采样率
self.in_dir = Path(in_dir) # 待处理音频目录路径
self.out_dir = Path(out_dir) # 重采样输出路径
@classmethod
def from_dict(cls, dataset_path: str, data: Dict[str, any]):
def from_dict(cls, dataset_path: Path, data: dict[str, Any]):
"""从字典中生成实例"""
# 不检查路径是否有效此逻辑在resample.py中处理
data["in_dir"] = os.path.join(dataset_path, data["in_dir"])
data["out_dir"] = os.path.join(dataset_path, data["out_dir"])
data["in_dir"] = dataset_path / data["in_dir"]
data["out_dir"] = dataset_path / data["out_dir"]
return cls(**data)
@@ -49,39 +55,27 @@ class Preprocess_text_config:
max_val_total: int = 10000,
clean: bool = True,
):
self.transcription_path: str = (
transcription_path # 原始文本文件路径,文本格式应为{wav_path}|{speaker_name}|{language}|{text}。
)
self.cleaned_path: str = (
cleaned_path # 数据清洗后文本路径,可以不填。不填则将在原始文本目录生成
)
self.train_path: str = (
train_path # 训练集路径,可以不填。不填则将在原始文本目录生成
)
self.val_path: str = (
val_path # 验证集路径,可以不填。不填则将在原始文本目录生成
)
self.config_path: str = config_path # 配置文件路径
self.val_per_lang: int = val_per_lang # 每个speaker的验证集条数
self.max_val_total: int = (
max_val_total # 验证集最大条数,多于的会被截断并放到训练集中
)
self.clean: bool = clean # 是否进行数据清洗
self.transcription_path = Path(transcription_path)
self.cleaned_path = Path(cleaned_path)
self.train_path = Path(train_path)
self.val_path = Path(val_path)
self.config_path = Path(config_path)
self.val_per_lang = val_per_lang
self.max_val_total = max_val_total
self.clean = clean
@classmethod
def from_dict(cls, dataset_path: str, data: Dict[str, any]):
def from_dict(cls, dataset_path: Path, data: dict[str, Any]):
"""从字典中生成实例"""
data["transcription_path"] = os.path.join(
dataset_path, data["transcription_path"]
)
data["transcription_path"] = dataset_path / data["transcription_path"]
if data["cleaned_path"] == "" or data["cleaned_path"] is None:
data["cleaned_path"] = None
else:
data["cleaned_path"] = os.path.join(dataset_path, data["cleaned_path"])
data["train_path"] = os.path.join(dataset_path, data["train_path"])
data["val_path"] = os.path.join(dataset_path, data["val_path"])
data["config_path"] = os.path.join(dataset_path, data["config_path"])
data["cleaned_path"] = dataset_path / data["cleaned_path"]
data["train_path"] = dataset_path / data["train_path"]
data["val_path"] = dataset_path / data["val_path"]
data["config_path"] = dataset_path / data["config_path"]
return cls(**data)
@@ -96,7 +90,7 @@ class Bert_gen_config:
device: str = "cuda",
use_multi_device: bool = False,
):
self.config_path = config_path
self.config_path = Path(config_path)
self.num_processes = num_processes
if not cuda_available:
device = "cpu"
@@ -104,8 +98,8 @@ class Bert_gen_config:
self.use_multi_device = use_multi_device
@classmethod
def from_dict(cls, dataset_path: str, data: Dict[str, any]):
data["config_path"] = os.path.join(dataset_path, data["config_path"])
def from_dict(cls, dataset_path: Path, data: dict[str, Any]):
data["config_path"] = dataset_path / data["config_path"]
return cls(**data)
@@ -119,15 +113,15 @@ class Style_gen_config:
num_processes: int = 4,
device: str = "cuda",
):
self.config_path = config_path
self.config_path = Path(config_path)
self.num_processes = num_processes
if not cuda_available:
device = "cpu"
self.device = device
@classmethod
def from_dict(cls, dataset_path: str, data: Dict[str, any]):
data["config_path"] = os.path.join(dataset_path, data["config_path"])
def from_dict(cls, dataset_path: Path, data: dict[str, Any]):
data["config_path"] = dataset_path / data["config_path"]
return cls(**data)
@@ -138,7 +132,7 @@ class Train_ms_config:
def __init__(
self,
config_path: str,
env: Dict[str, any],
env: dict[str, Any],
# base: Dict[str, any],
model_dir: str,
num_workers: int,
@@ -147,16 +141,18 @@ class Train_ms_config:
):
self.env = env # 需要加载的环境变量
# self.base = base # 底模配置
self.model_dir = model_dir # 训练模型存储目录该路径为相对于dataset_path的路径而非项目根目录
self.config_path = config_path # 配置文件路径
self.model_dir = Path(
model_dir
) # 训练模型存储目录该路径为相对于dataset_path的路径而非项目根目录
self.config_path = Path(config_path) # 配置文件路径
self.num_workers = num_workers # worker数量
self.spec_cache = spec_cache # 是否启用spec缓存
self.keep_ckpts = keep_ckpts # ckpt数量
@classmethod
def from_dict(cls, dataset_path: str, data: Dict[str, any]):
def from_dict(cls, dataset_path: Path, data: dict[str, Any]):
# data["model"] = os.path.join(dataset_path, data["model"])
data["config_path"] = os.path.join(dataset_path, data["config_path"])
data["config_path"] = dataset_path / data["config_path"]
return cls(**data)
@@ -176,20 +172,18 @@ class Webui_config:
):
if not cuda_available:
device = "cpu"
self.device: str = device
self.model: str = model # 端口号
self.config_path: str = config_path # 是否公开部署,对外网开放
self.port: int = port # 是否开启debug模式
self.share: bool = share # 模型路径
self.debug: bool = debug # 配置文件路径
self.language_identification_library: str = (
language_identification_library # 语种识别库
)
self.device = device
self.model = Path(model)
self.config_path = Path(config_path)
self.port: int = port
self.share: bool = share
self.debug: bool = debug
self.language_identification_library: str = language_identification_library
@classmethod
def from_dict(cls, dataset_path: str, data: Dict[str, any]):
data["config_path"] = os.path.join(dataset_path, data["config_path"])
data["model"] = os.path.join(dataset_path, data["model"])
def from_dict(cls, dataset_path: Path, data: dict[str, Any]):
data["config_path"] = dataset_path / data["config_path"]
data["model"] = dataset_path / data["model"]
return cls(**data)
@@ -200,7 +194,7 @@ class Server_config:
device: str = "cuda",
limit: int = 100,
language: str = "JP",
origins: List[str] = None,
origins: list[str] = ["*"],
):
self.port: int = port
if not cuda_available:
@@ -208,10 +202,10 @@ class Server_config:
self.device: str = device
self.language: str = language
self.limit: int = limit
self.origins: List[str] = origins
self.origins: list[str] = origins
@classmethod
def from_dict(cls, data: Dict[str, any]):
def from_dict(cls, data: dict[str, Any]):
return cls(**data)
@@ -223,32 +217,32 @@ class Translate_config:
self.secret_key = secret_key
@classmethod
def from_dict(cls, data: Dict[str, any]):
def from_dict(cls, data: dict[str, Any]):
return cls(**data)
class Config:
def __init__(self, config_path: str, path_config: dict[str, str]):
if not os.path.isfile(config_path) and os.path.isfile("default_config.yml"):
def __init__(self, config_path: str, path_config: PathConfig):
if not Path(config_path).exists():
shutil.copy(src="default_config.yml", dst=config_path)
logger.info(
f"A configuration file {config_path} has been generated based on the default configuration file default_config.yml."
)
logger.info(
"If you have no special needs, please do not modify default_config.yml."
"Please do not modify default_config.yml. Instead, modify config.yml."
)
# sys.exit(0)
with open(config_path, "r", encoding="utf-8") as file:
yaml_config: Dict[str, any] = yaml.safe_load(file.read())
yaml_config: dict[str, Any] = yaml.safe_load(file.read())
model_name: str = yaml_config["model_name"]
self.model_name: str = model_name
if "dataset_path" in yaml_config:
dataset_path = yaml_config["dataset_path"]
dataset_path = Path(yaml_config["dataset_path"])
else:
dataset_path = os.path.join(path_config["dataset_root"], model_name)
self.dataset_path: str = dataset_path
self.assets_root: str = path_config["assets_root"]
self.out_dir = os.path.join(self.assets_root, model_name)
dataset_path = path_config.dataset_root / model_name
self.dataset_path = dataset_path
self.assets_root = path_config.assets_root
self.out_dir = self.assets_root / model_name
self.resample_config: Resample_config = Resample_config.from_dict(
dataset_path, yaml_config["resample"]
)
@@ -277,16 +271,31 @@ class Config:
# )
with open(os.path.join("configs", "paths.yml"), "r", encoding="utf-8") as f:
path_config: dict[str, str] = yaml.safe_load(f.read())
# Should contain the following keys:
# - dataset_root: the root directory of the dataset, default to "Data"
# - assets_root: the root directory of the assets, default to "model_assets"
# Load and initialize the configuration
def get_path_config() -> PathConfig:
path_config_path = Path("configs/paths.yml")
if not path_config_path.exists():
shutil.copy(src="configs/default_paths.yml", dst=path_config_path)
logger.info(
f"A configuration file {path_config_path} has been generated based on the default configuration file default_paths.yml."
)
logger.info(
"Please do not modify configs/default_paths.yml. Instead, modify configs/paths.yml."
)
with open(path_config_path, "r", encoding="utf-8") as file:
path_config_dict: dict[str, str] = yaml.safe_load(file.read())
return PathConfig(**path_config_dict)
def get_config() -> Config:
path_config = get_path_config()
try:
config = Config("config.yml", path_config)
except (TypeError, KeyError):
logger.warning("Old config.yml found. Replace it with default_config.yml.")
shutil.copy(src="default_config.yml", dst="config.yml")
config = Config("config.yml", path_config)
return config

View File

@@ -69,5 +69,5 @@
"use_spectral_norm": false,
"gin_channels": 256
},
"version": "2.4.1"
"version": "2.5.0"
}

View File

@@ -76,5 +76,5 @@
"initial_channel": 64
}
},
"version": "2.4.1-JP-Extra"
"version": "2.5.0-JP-Extra"
}

View File

@@ -1,5 +1,4 @@
import json
import os
from pathlib import Path
from typing import Union

View File

@@ -1,14 +1,13 @@
import json
import os
from pathlib import Path
import gradio as gr
import numpy as np
import torch
import yaml
from safetensors import safe_open
from safetensors.torch import save_file
from config import get_path_config
from style_bert_vits2.constants import DEFAULT_STYLE, GRADIO_THEME
from style_bert_vits2.logging import logger
from style_bert_vits2.tts_model import TTSModel, TTSModelHolder
@@ -20,15 +19,17 @@ speech_style_keys = ["enc_p"]
tempo_keys = ["sdp", "dp"]
device = "cuda" if torch.cuda.is_available() else "cpu"
# Get path settings
with open(os.path.join("configs", "paths.yml"), "r", encoding="utf-8") as f:
path_config: dict[str, str] = yaml.safe_load(f.read())
# dataset_root = path_config["dataset_root"]
assets_root = path_config["assets_root"]
path_config = get_path_config()
assets_root = path_config.assets_root
def merge_style(model_name_a, model_name_b, weight, output_name, style_triple_list):
def merge_style(
model_name_a: str,
model_name_b: str,
weight: float,
output_name: str,
style_triple_list: list[tuple[str, str, str]],
) -> tuple[Path, list[str]]:
"""
style_triple_list: list[(model_aでのスタイル名, model_bでのスタイル名, 出力するスタイル名)]
"""
@@ -41,18 +42,14 @@ def merge_style(model_name_a, model_name_b, weight, output_name, style_triple_li
raise ValueError(f"No element with {DEFAULT_STYLE} output style name found.")
style_vectors_a = np.load(
os.path.join(assets_root, model_name_a, "style_vectors.npy")
assets_root / model_name_a / "style_vectors.npy"
) # (style_num_a, 256)
style_vectors_b = np.load(
os.path.join(assets_root, model_name_b, "style_vectors.npy")
assets_root / model_name_b / "style_vectors.npy"
) # (style_num_b, 256)
with open(
os.path.join(assets_root, model_name_a, "config.json"), "r", encoding="utf-8"
) as f:
with open(assets_root / model_name_a / "config.json", "r", encoding="utf-8") as f:
config_a = json.load(f)
with open(
os.path.join(assets_root, model_name_b, "config.json"), "r", encoding="utf-8"
) as f:
with open(assets_root / model_name_b / "config.json", "r", encoding="utf-8") as f:
config_b = json.load(f)
style2id_a = config_a["data"]["style2id"]
style2id_b = config_b["data"]["style2id"]
@@ -73,21 +70,19 @@ def merge_style(model_name_a, model_name_b, weight, output_name, style_triple_li
new_style2id[style_out] = len(new_style_vecs) - 1
new_style_vecs = np.array(new_style_vecs)
output_style_path = os.path.join(assets_root, output_name, "style_vectors.npy")
output_style_path = assets_root / output_name / "style_vectors.npy"
np.save(output_style_path, new_style_vecs)
new_config = config_a.copy()
new_config["data"]["num_styles"] = len(new_style2id)
new_config["data"]["style2id"] = new_style2id
new_config["model_name"] = output_name
with open(
os.path.join(assets_root, output_name, "config.json"), "w", encoding="utf-8"
) as f:
with open(assets_root / output_name / "config.json", "w", encoding="utf-8") as f:
json.dump(new_config, f, indent=2, ensure_ascii=False)
# recipe.jsonを読み込んで、style_triple_listを追記
info_path = os.path.join(assets_root, output_name, "recipe.json")
if os.path.exists(info_path):
info_path = assets_root / output_name / "recipe.json"
if info_path.exists():
with open(info_path, "r", encoding="utf-8") as f:
info = json.load(f)
else:
@@ -99,11 +94,13 @@ def merge_style(model_name_a, model_name_b, weight, output_name, style_triple_li
return output_style_path, list(new_style2id.keys())
def lerp_tensors(t, v0, v1):
def lerp_tensors(t: float, v0: torch.Tensor, v1: torch.Tensor):
return v0 * (1 - t) + v1 * t
def slerp_tensors(t, v0, v1, dot_thres=0.998):
def slerp_tensors(
t: float, v0: torch.Tensor, v1: torch.Tensor, dot_thres: float = 0.998
):
device = v0.device
v0c = v0.cpu().numpy()
v1c = v1.cpu().numpy()
@@ -123,23 +120,23 @@ def slerp_tensors(t, v0, v1, dot_thres=0.998):
def merge_models(
model_path_a,
model_path_b,
voice_weight,
voice_pitch_weight,
speech_style_weight,
tempo_weight,
output_name,
use_slerp_instead_of_lerp,
model_path_a: str,
model_path_b: str,
voice_weight: float,
voice_pitch_weight: float,
speech_style_weight: float,
tempo_weight: float,
output_name: str,
use_slerp_instead_of_lerp: bool,
):
"""model Aを起点に、model Bの各要素を重み付けしてマージする。
safetensors形式を前提とする。"""
model_a_weight = {}
model_a_weight: dict[str, torch.Tensor] = {}
with safe_open(model_path_a, framework="pt", device="cpu") as f:
for k in f.keys():
model_a_weight[k] = f.get_tensor(k)
model_b_weight = {}
model_b_weight: dict[str, torch.Tensor] = {}
with safe_open(model_path_b, framework="pt", device="cpu") as f:
for k in f.keys():
model_b_weight[k] = f.get_tensor(k)
@@ -161,10 +158,8 @@ def merge_models(
slerp_tensors if use_slerp_instead_of_lerp else lerp_tensors
)(weight, model_a_weight[key], model_b_weight[key])
merged_model_path = os.path.join(
assets_root, output_name, f"{output_name}.safetensors"
)
os.makedirs(os.path.dirname(merged_model_path), exist_ok=True)
merged_model_path = assets_root / output_name / f"{output_name}.safetensors"
merged_model_path.parent.mkdir(parents=True, exist_ok=True)
save_file(merged_model_weight, merged_model_path)
info = {
@@ -175,24 +170,22 @@ def merge_models(
"speech_style_weight": speech_style_weight,
"tempo_weight": tempo_weight,
}
with open(
os.path.join(assets_root, output_name, "recipe.json"), "w", encoding="utf-8"
) as f:
with open(assets_root / output_name / "recipe.json", "w", encoding="utf-8") as f:
json.dump(info, f, indent=2, ensure_ascii=False)
return merged_model_path
def merge_models_gr(
model_name_a,
model_path_a,
model_name_b,
model_path_b,
output_name,
voice_weight,
voice_pitch_weight,
speech_style_weight,
tempo_weight,
use_slerp_instead_of_lerp,
model_name_a: str,
model_path_a: str,
model_name_b: str,
model_path_b: str,
output_name: str,
voice_weight: float,
voice_pitch_weight: float,
speech_style_weight: float,
tempo_weight: float,
use_slerp_instead_of_lerp: bool,
):
if output_name == "":
return "Error: 新しいモデル名を入力してください。"
@@ -210,10 +203,10 @@ def merge_models_gr(
def merge_style_gr(
model_name_a,
model_name_b,
weight,
output_name,
model_name_a: str,
model_name_b: str,
weight: float,
output_name: str,
style_triple_list_str: str,
):
if output_name == "":
@@ -245,12 +238,14 @@ def merge_style_gr(
)
def simple_tts(model_name, text, style=DEFAULT_STYLE, style_weight=1.0):
model_path = os.path.join(assets_root, model_name, f"{model_name}.safetensors")
config_path = os.path.join(assets_root, model_name, "config.json")
style_vec_path = os.path.join(assets_root, model_name, "style_vectors.npy")
def simple_tts(
model_name: str, text: str, style: str = DEFAULT_STYLE, style_weight: float = 1.0
):
model_path = assets_root / model_name / f"{model_name}.safetensors"
config_path = assets_root / model_name / "config.json"
style_vec_path = assets_root / model_name / "style_vectors.npy"
model = TTSModel(Path(model_path), Path(config_path), Path(style_vec_path), device)
model = TTSModel(model_path, config_path, style_vec_path, device)
return model.infer(text, style=style, style_weight=style_weight)
@@ -259,13 +254,13 @@ def update_two_model_names_dropdown(model_holder: TTSModelHolder):
return new_names, new_files, new_names, new_files
def load_styles_gr(model_name_a, model_name_b):
config_path_a = os.path.join(assets_root, model_name_a, "config.json")
def load_styles_gr(model_name_a: str, model_name_b: str):
config_path_a = assets_root / model_name_a / "config.json"
with open(config_path_a, "r", encoding="utf-8") as f:
config_a = json.load(f)
styles_a = list(config_a["data"]["style2id"].keys())
config_path_b = os.path.join(assets_root, model_name_b, "config.json")
config_path_b = assets_root / model_name_b / "config.json"
with open(config_path_b, "r", encoding="utf-8") as f:
config_b = json.load(f)
styles_b = list(config_b["data"]["style2id"].keys())
@@ -336,7 +331,10 @@ def create_merge_app(model_holder: TTSModelHolder) -> gr.Blocks:
)
return app
initial_id = 0
initial_model_files = model_holder.model_files_dict[model_names[initial_id]]
# initial_model_files = model_holder.model_files_dict[model_names[initial_id]]
initial_model_files = [
str(f) for f in model_holder.model_files_dict[model_names[initial_id]]
]
with gr.Blocks(theme=GRADIO_THEME) as app:
gr.Markdown(

View File

@@ -1,27 +1,23 @@
import json
import os
import shutil
from pathlib import Path
import gradio as gr
import matplotlib.pyplot as plt
import numpy as np
import yaml
from scipy.spatial.distance import pdist, squareform
from sklearn.cluster import DBSCAN, AgglomerativeClustering, KMeans
from sklearn.manifold import TSNE
from umap import UMAP
from config import config
from config import get_path_config
from style_bert_vits2.constants import DEFAULT_STYLE, GRADIO_THEME
from style_bert_vits2.logging import logger
# Get path settings
with open(os.path.join("configs", "paths.yml"), "r", encoding="utf-8") as f:
path_config: dict[str, str] = yaml.safe_load(f.read())
dataset_root = Path(path_config["dataset_root"])
# assets_root = path_config["assets_root"]
path_config = get_path_config()
dataset_root = path_config.dataset_root
assets_root = path_config.assets_root
MAX_CLUSTER_NUM = 10
MAX_AUDIO_NUM = 10
@@ -39,11 +35,7 @@ centroids = []
def load(model_name: str, reduction_method: str):
global wav_files, x, x_reduced, mean
# wavs_dir = os.path.join(dataset_root, model_name, "wavs")
wavs_dir = dataset_root / model_name / "wavs"
# style_vector_files = [
# os.path.join(wavs_dir, f) for f in os.listdir(wavs_dir) if f.endswith(".npy")
# ]
style_vector_files = [f for f in wavs_dir.rglob("*.npy") if f.is_file()]
# foo.wav.npy -> foo.wav
wav_files = [f.with_suffix("") for f in style_vector_files]
@@ -196,21 +188,21 @@ def do_clustering_gradio(n_clusters=4, method="KMeans"):
] * MAX_AUDIO_NUM
def save_style_vectors_from_clustering(model_name, style_names_str: str):
def save_style_vectors_from_clustering(model_name: str, style_names_str: str):
"""centerとcentroidsを保存する"""
result_dir = os.path.join(config.assets_root, model_name)
os.makedirs(result_dir, exist_ok=True)
result_dir = assets_root / model_name
result_dir.mkdir(parents=True, exist_ok=True)
style_vectors = np.stack([mean] + centroids)
style_vector_path = os.path.join(result_dir, "style_vectors.npy")
if os.path.exists(style_vector_path):
style_vector_path = result_dir / "style_vectors.npy"
if style_vector_path.exists():
logger.info(f"Backup {style_vector_path} to {style_vector_path}.bak")
shutil.copy(style_vector_path, f"{style_vector_path}.bak")
np.save(style_vector_path, style_vectors)
logger.success(f"Saved style vectors to {style_vector_path}")
# config.jsonの更新
config_path = os.path.join(result_dir, "config.json")
if not os.path.exists(config_path):
config_path = result_dir / "config.json"
if not config_path.exists():
return f"{config_path}が存在しません。"
style_names = [name.strip() for name in style_names_str.split(",")]
style_name_list = [DEFAULT_STYLE] + style_names
@@ -233,7 +225,7 @@ def save_style_vectors_from_clustering(model_name, style_names_str: str):
def save_style_vectors_from_files(
model_name, audio_files_str: str, style_names_str: str
model_name: str, audio_files_str: str, style_names_str: str
):
"""音声ファイルからスタイルベクトルを作成して保存する"""
global mean
@@ -241,8 +233,8 @@ def save_style_vectors_from_files(
return "Error: スタイルベクトルを読み込んでください。"
mean = np.mean(x, axis=0)
result_dir = os.path.join(config.assets_root, model_name)
os.makedirs(result_dir, exist_ok=True)
result_dir = assets_root / model_name
result_dir.mkdir(parents=True, exist_ok=True)
audio_files = [name.strip() for name in audio_files_str.split(",")]
style_names = [name.strip() for name in style_names_str.split(",")]
if len(audio_files) != len(style_names):
@@ -252,23 +244,23 @@ def save_style_vectors_from_files(
return "スタイル名が重複しています。"
style_vectors = [mean]
wavs_dir = os.path.join(dataset_root, model_name, "wavs")
wavs_dir = dataset_root / model_name / "wavs"
for audio_file in audio_files:
path = os.path.join(wavs_dir, audio_file)
if not os.path.exists(path):
path = wavs_dir / audio_file
if not path.exists():
return f"{path}が存在しません。"
style_vectors.append(np.load(f"{path}.npy"))
style_vectors = np.stack(style_vectors)
assert len(style_name_list) == len(style_vectors)
style_vector_path = os.path.join(result_dir, "style_vectors.npy")
if os.path.exists(style_vector_path):
style_vector_path = result_dir / "style_vectors.npy"
if style_vector_path.exists():
logger.info(f"Backup {style_vector_path} to {style_vector_path}.bak")
shutil.copy(style_vector_path, f"{style_vector_path}.bak")
np.save(style_vector_path, style_vectors)
# config.jsonの更新
config_path = os.path.join(result_dir, "config.json")
if not os.path.exists(config_path):
config_path = result_dir / "config.json"
if not config_path.exists():
return f"{config_path}が存在しません。"
logger.info(f"Backup {config_path} to {config_path}.bak")
shutil.copy(config_path, f"{config_path}.bak")

View File

@@ -1,5 +1,4 @@
import json
import os
import shutil
import socket
import subprocess
@@ -12,7 +11,8 @@ from pathlib import Path
import gradio as gr
import yaml
from dataclasses import dataclass
from config import get_path_config
from style_bert_vits2.logging import logger
from style_bert_vits2.utils.stdout_wrapper import SAFE_STDOUT
from style_bert_vits2.utils.subprocess import run_script_with_log, second_elem_of
@@ -21,20 +21,27 @@ from style_bert_vits2.utils.subprocess import run_script_with_log, second_elem_o
logger_handler = None
tensorboard_executed = False
# Get path settings
with open(os.path.join("configs", "paths.yml"), "r", encoding="utf-8") as f:
path_config: dict[str, str] = yaml.safe_load(f.read())
dataset_root = Path(path_config["dataset_root"])
path_config = get_path_config()
dataset_root = path_config.dataset_root
def get_path(model_name: str) -> tuple[Path, Path, Path, Path, Path]:
@dataclass
class PathsForPreprocess:
dataset_path: Path
esd_path: Path
train_path: Path
val_path: Path
config_path: Path
def get_path(model_name: str) -> PathsForPreprocess:
assert model_name != "", "モデル名は空にできません"
dataset_path = dataset_root / model_name
lbl_path = dataset_path / "esd.list"
esd_path = dataset_path / "esd.list"
train_path = dataset_path / "train.list"
val_path = dataset_path / "val.list"
config_path = dataset_path / "config.json"
return dataset_path, lbl_path, train_path, val_path, config_path
return PathsForPreprocess(dataset_path, esd_path, train_path, val_path, config_path)
def initialize(
@@ -51,14 +58,14 @@ def initialize(
log_interval: int,
):
global logger_handler
dataset_path, _, train_path, val_path, config_path = get_path(model_name)
paths = get_path(model_name)
# 前処理のログをファイルに保存する
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
file_name = f"preprocess_{timestamp}.log"
if logger_handler is not None:
logger.remove(logger_handler)
logger_handler = logger.add(os.path.join(dataset_path, file_name))
logger_handler = logger.add(paths.dataset_path / file_name)
logger.info(
f"Step 1: start initialization...\nmodel_name: {model_name}, batch_size: {batch_size}, epochs: {epochs}, save_every_steps: {save_every_steps}, freeze_ZH_bert: {freeze_ZH_bert}, freeze_JP_bert: {freeze_JP_bert}, freeze_EN_bert: {freeze_EN_bert}, freeze_style: {freeze_style}, freeze_decoder: {freeze_decoder}, use_jp_extra: {use_jp_extra}"
@@ -71,8 +78,8 @@ def initialize(
with open(default_config_path, "r", encoding="utf-8") as f:
config = json.load(f)
config["model_name"] = model_name
config["data"]["training_files"] = str(train_path)
config["data"]["validation_files"] = str(val_path)
config["data"]["training_files"] = str(paths.train_path)
config["data"]["validation_files"] = str(paths.val_path)
config["train"]["batch_size"] = batch_size
config["train"]["epochs"] = epochs
config["train"]["eval_interval"] = save_every_steps
@@ -89,14 +96,14 @@ def initialize(
# 今はデフォルトであるが、以前は非JP-Extra版になくバグの原因になるので念のため
config["data"]["use_jp_extra"] = use_jp_extra
model_path = dataset_path / "models"
model_path = paths.dataset_path / "models"
if model_path.exists():
logger.warning(
f"Step 1: {model_path} already exists, so copy it to backup to {model_path}_backup"
)
shutil.copytree(
src=model_path,
dst=dataset_path / "models_backup",
dst=paths.dataset_path / "models_backup",
dirs_exist_ok=True,
)
shutil.rmtree(model_path)
@@ -110,14 +117,14 @@ def initialize(
logger.error(f"Step 1: {pretrained_dir} folder not found.")
return False, f"Step 1, Error: {pretrained_dir}フォルダが見つかりません。"
with open(config_path, "w", encoding="utf-8") as f:
with open(paths.config_path, "w", encoding="utf-8") as f:
json.dump(config, f, indent=2, ensure_ascii=False)
if not Path("config.yml").exists():
shutil.copy(src="default_config.yml", dst="config.yml")
with open("config.yml", "r", encoding="utf-8") as f:
yml_data = yaml.safe_load(f)
yml_data["model_name"] = model_name
yml_data["dataset_path"] = str(dataset_path)
yml_data["dataset_path"] = str(paths.dataset_path)
with open("config.yml", "w", encoding="utf-8") as f:
yaml.dump(yml_data, f, allow_unicode=True)
logger.success("Step 1: initialization finished.")
@@ -126,7 +133,7 @@ def initialize(
def resample(model_name: str, normalize: bool, trim: bool, num_processes: int):
logger.info("Step 2: start resampling...")
dataset_path, _, _, _, _ = get_path(model_name)
dataset_path = get_path(model_name).dataset_path
input_dir = dataset_path / "raw"
output_dir = dataset_path / "wavs"
cmd = [
@@ -159,21 +166,24 @@ def preprocess_text(
model_name: str, use_jp_extra: bool, val_per_lang: int, yomi_error: str
):
logger.info("Step 3: start preprocessing text...")
_, lbl_path, train_path, val_path, config_path = get_path(model_name)
if not lbl_path.exists():
logger.error(f"Step 3: {lbl_path} not found.")
return False, f"Step 3, Error: 書き起こしファイル {lbl_path} が見つかりません。"
paths = get_path(model_name)
if not paths.esd_path.exists():
logger.error(f"Step 3: {paths.esd_path} not found.")
return (
False,
f"Step 3, Error: 書き起こしファイル {paths.esd_path} が見つかりません。",
)
cmd = [
"preprocess_text.py",
"--config-path",
str(config_path),
str(paths.config_path),
"--transcription-path",
str(lbl_path),
str(paths.esd_path),
"--train-path",
str(train_path),
str(paths.train_path),
"--val-path",
str(val_path),
str(paths.val_path),
"--val-per-lang",
str(val_per_lang),
"--yomi_error",
@@ -201,7 +211,7 @@ def preprocess_text(
def bert_gen(model_name: str):
logger.info("Step 4: start bert_gen...")
_, _, _, _, config_path = get_path(model_name)
config_path = get_path(model_name).config_path
success, message = run_script_with_log(
["bert_gen.py", "--config", str(config_path)]
)
@@ -220,7 +230,7 @@ def bert_gen(model_name: str):
def style_gen(model_name: str, num_processes: int):
logger.info("Step 5: start style_gen...")
_, _, _, _, config_path = get_path(model_name)
config_path = get_path(model_name).config_path
success, message = run_script_with_log(
[
"style_gen.py",
@@ -319,17 +329,23 @@ def train(
use_jp_extra: bool = True,
speedup: bool = False,
):
dataset_path, _, _, _, config_path = get_path(model_name)
paths = get_path(model_name)
# 学習再開の場合を考えて念のためconfig.ymlの名前等を更新
with open("config.yml", "r", encoding="utf-8") as f:
yml_data = yaml.safe_load(f)
yml_data["model_name"] = model_name
yml_data["dataset_path"] = str(dataset_path)
yml_data["dataset_path"] = str(paths.dataset_path)
with open("config.yml", "w", encoding="utf-8") as f:
yaml.dump(yml_data, f, allow_unicode=True)
train_py = "train_ms.py" if not use_jp_extra else "train_ms_jp_extra.py"
cmd = [train_py, "--config", str(config_path), "--model", str(dataset_path)]
cmd = [
train_py,
"--config",
str(paths.config_path),
"--model",
str(paths.dataset_path),
]
if skip_style:
cmd.append("--skip_default_style")
if speedup:

View File

@@ -7,7 +7,7 @@ from typing import Optional
from tqdm import tqdm
from config import Preprocess_text_config, config
from config import get_config
from style_bert_vits2.logging import logger
from style_bert_vits2.nlp import clean_text
from style_bert_vits2.nlp.japanese import pyopenjtalk_worker
@@ -22,7 +22,7 @@ pyopenjtalk_worker.initialize_worker()
update_dict()
preprocess_text_config: Preprocess_text_config = config.preprocess_text_config
preprocess_text_config = get_config().preprocess_text_config
# Count lines for tqdm

View File

@@ -12,6 +12,7 @@ matplotlib
num2words
numba
numpy
protobuf==4.25
psutil
pyannote.audio>=3.1.0
pydantic>=2.0

View File

@@ -10,7 +10,7 @@ import soundfile
from numpy.typing import NDArray
from tqdm import tqdm
from config import config
from config import get_config
from style_bert_vits2.logging import logger
from style_bert_vits2.utils.stdout_wrapper import SAFE_STDOUT
@@ -70,6 +70,7 @@ def resample(
if __name__ == "__main__":
config = get_config()
parser = argparse.ArgumentParser()
parser.add_argument(
"--sr",

View File

@@ -22,7 +22,6 @@ import numpy as np
import requests
import torch
import uvicorn
import yaml
from fastapi import APIRouter, FastAPI, HTTPException, status
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse, Response
@@ -30,6 +29,7 @@ from fastapi.staticfiles import StaticFiles
from pydantic import BaseModel
from scipy.io import wavfile
from config import get_path_config
from style_bert_vits2.constants import (
DEFAULT_ASSIST_TEXT_WEIGHT,
DEFAULT_NOISE,
@@ -174,22 +174,14 @@ origins = [
"http://127.0.0.1:8000",
]
# Get path settings
with open(Path("configs/paths.yml"), "r", encoding="utf-8") as f:
path_config: dict[str, str] = yaml.safe_load(f.read())
# dataset_root = path_config["dataset_root"]
assets_root = path_config["assets_root"]
path_config = get_path_config()
parser = argparse.ArgumentParser()
parser.add_argument("--model_dir", type=str, default="model_assets/")
parser.add_argument("--model_dir", type=str, default=path_config.assets_root)
parser.add_argument("--device", type=str, default="cuda")
parser.add_argument("--port", type=int, default=8000)
parser.add_argument("--inbrowser", action="store_true")
parser.add_argument("--line_length", type=int, default=None)
parser.add_argument("--line_count", type=int, default=None)
parser.add_argument(
"--dir", "-d", type=str, help="Model directory", default=assets_root
)
args = parser.parse_args()
device = args.device

View File

@@ -20,7 +20,7 @@ from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import FileResponse, Response
from scipy.io import wavfile
from config import config
from config import get_config
from style_bert_vits2.constants import (
DEFAULT_ASSIST_TEXT_WEIGHT,
DEFAULT_LENGTH,
@@ -40,6 +40,7 @@ from style_bert_vits2.nlp.japanese.user_dict import update_dict
from style_bert_vits2.tts_model import TTSModel, TTSModelHolder
config = get_config()
ln = config.server_config.language
@@ -113,6 +114,9 @@ if __name__ == "__main__":
load_models(model_holder)
limit = config.server_config.limit
logger.info(
f"The maximum length of the text is {limit}. If you want to change it, modify config.yml"
)
app = FastAPI()
allow_origins = config.server_config.origins
if allow_origins:

View File

@@ -10,6 +10,7 @@ import torch
import yaml
from tqdm import tqdm
from config import get_path_config
from style_bert_vits2.logging import logger
from style_bert_vits2.utils.stdout_wrapper import SAFE_STDOUT
@@ -150,13 +151,12 @@ if __name__ == "__main__":
)
args = parser.parse_args()
with open(Path("configs/paths.yml"), "r", encoding="utf-8") as f:
path_config: dict[str, str] = yaml.safe_load(f.read())
dataset_root = path_config["dataset_root"]
path_config = get_path_config()
dataset_root = path_config.dataset_root
model_name = str(args.model_name)
input_dir = Path(args.input_dir)
output_dir = Path(dataset_root) / model_name / "raw"
output_dir = dataset_root / model_name / "raw"
min_sec: float = args.min_sec
max_sec: float = args.max_sec
min_silence_dur_ms: int = args.min_silence_dur_ms

View File

@@ -10,7 +10,7 @@ import pandas as pd
import torch
from tqdm import tqdm
from config import config
from config import get_path_config
from style_bert_vits2.logging import logger
from style_bert_vits2.tts_model import TTSModel
@@ -35,6 +35,8 @@ test_texts = [
"この分野の最新の研究成果を使うと、より自然で表現豊かな音声の生成が可能である。深層学習の応用により、感情やアクセントを含む声質の微妙な変化も再現することが出来る。",
]
path_config = get_path_config()
predictor = torch.hub.load(
"tarepan/SpeechMOS:v1.2.0", "utmos22_strong", trust_repo=True
)
@@ -48,17 +50,16 @@ args = parser.parse_args()
model_name: str = args.model_name
device: str = args.device
model_path = Path(config.assets_root) / model_name
model_path = path_config.assets_root / model_name
# .safetensorsファイルを検索
safetensors_files = model_path.glob("*.safetensors")
def get_model(model_file: Path):
return TTSModel(
model_path=str(model_file),
config_path=str(model_file.parent / "config.json"),
style_vec_path=str(model_file.parent / "style_vectors.npy"),
model_path=model_file,
config_path=model_file.parent / "config.json",
style_vec_path=model_file.parent / "style_vectors.npy",
device=device,
)

View File

@@ -4,7 +4,7 @@ from style_bert_vits2.utils.strenum import StrEnum
# Style-Bert-VITS2 のバージョン
VERSION = "2.4.1"
VERSION = "2.5.0"
# Style-Bert-VITS2 のベースディレクトリ
BASE_DIR = Path(__file__).parent.parent

View File

@@ -8,12 +8,14 @@ from numpy.typing import NDArray
from pyannote.audio import Inference, Model
from tqdm import tqdm
from config import config
from config import get_config
from style_bert_vits2.logging import logger
from style_bert_vits2.models.hyper_parameters import HyperParameters
from style_bert_vits2.utils.stdout_wrapper import SAFE_STDOUT
config = get_config()
model = Model.from_pretrained("pyannote/wespeaker-voxceleb-resnet34-LM")
inference = Inference(model, window="whole")
device = torch.device(config.style_gen_config.device)

View File

@@ -16,7 +16,7 @@ from tqdm import tqdm
# logging.getLogger("numba").setLevel(logging.WARNING)
import default_style
from config import config
from config import get_config
from data_utils import (
DistributedBucketSampler,
TextAudioSpeakerCollate,
@@ -48,7 +48,7 @@ torch.backends.cuda.enable_mem_efficient_sdp(
) # Not available if torch version is lower than 2.0
torch.backends.cuda.enable_math_sdp(True)
config = get_config()
global_step = 0
api = HfApi()

View File

@@ -16,7 +16,7 @@ from tqdm import tqdm
# logging.getLogger("numba").setLevel(logging.WARNING)
import default_style
from config import config
from config import get_config
from data_utils import (
DistributedBucketSampler,
TextAudioSpeakerCollate,
@@ -48,6 +48,8 @@ torch.backends.cuda.enable_flash_sdp(True)
torch.backends.cuda.enable_mem_efficient_sdp(
True
) # Not available if torch version is lower than 2.0
config = get_config()
global_step = 0
api = HfApi()

View File

@@ -8,6 +8,7 @@ import yaml
from torch.utils.data import Dataset
from tqdm import tqdm
from config import get_path_config
from style_bert_vits2.constants import Languages
from style_bert_vits2.logging import logger
from style_bert_vits2.utils.stdout_wrapper import SAFE_STDOUT
@@ -124,9 +125,8 @@ if __name__ == "__main__":
parser.add_argument("--no_repeat_ngram_size", type=int, default=10)
args = parser.parse_args()
with open(os.path.join("configs", "paths.yml"), "r", encoding="utf-8") as f:
path_config: dict[str, str] = yaml.safe_load(f.read())
dataset_root = Path(path_config["dataset_root"])
path_config = get_path_config()
dataset_root = path_config.dataset_root
model_name = str(args.model_name)