Refactor and add merge

This commit is contained in:
litagin02
2023-12-31 14:55:04 +09:00
parent 62d360e777
commit ef4e82defc
34 changed files with 898 additions and 370 deletions

13
Merge.bat Normal file
View File

@@ -0,0 +1,13 @@
chcp 65001 > NUL
@echo off
pushd %~dp0
echo Running webui_merge.py...
venv\Scripts\python webui_merge.py
if %errorlevel% neq 0 ( pause & popd & exit /b %errorlevel% )
popd
pause

View File

@@ -44,8 +44,6 @@ Windowsを前提としています。
#### GitやPython使える人
Windowsの通常環境のPython 3.10で動作確認していますが、他の環境でも動くと思います。
```bash
git clone https://github.com/litagin02/Style-Bert-VITS2.git
cd Style-Bert-VITS2

275
app.py
View File

@@ -2,225 +2,30 @@ import argparse
import datetime
import os
import sys
import warnings
import enum
import gradio as gr
import numpy as np
import torch
from gradio.processing_utils import convert_to_16_bit_wav
from typing import Dict, List
import utils
from common.constants import (
DEFAULT_ASSIST_TEXT_WEIGHT,
DEFAULT_LENGTH,
DEFAULT_LINE_SPLIT,
DEFAULT_NOISE,
DEFAULT_NOISEW,
DEFAULT_SDP_RATIO,
DEFAULT_SPLIT_INTERVAL,
DEFAULT_STYLE,
DEFAULT_STYLE_WEIGHT,
Languages,
)
from common.log import logger
from common.tts_model import ModelHolder
from config import config
from infer import get_net_g, infer
from tools.log import logger
class Languages(str, enum.Enum):
JP = "JP"
EN = "EN"
ZH = "ZH"
languages = [l.value for l in Languages]
DEFAULT_SDP_RATIO: float = 0.2
DEFAULT_NOISE: float = 0.6
DEFAULT_NOISEW: float = 0.8
DEFAULT_LENGTH: float = 1
DEFAULT_LINE_SPLIT: bool = True
DEFAULT_SPLIT_INTERVAL: float = 0.5
DEFAULT_STYLE_WEIGHT: float = 0.7
DEFAULT_EMOTION_WEIGHT: float = 1.0
class Model:
def __init__(self, model_path, config_path, style_vec_path, device):
self.model_path = model_path
self.config_path = config_path
self.device = device
self.style_vec_path = style_vec_path
self.hps = utils.get_hparams_from_file(self.config_path)
self.spk2id: Dict[str, int] = self.hps.data.spk2id
self.id2spk: Dict[int, str] = {v: k for k, v in self.spk2id.items()}
self.num_styles = self.hps.data.num_styles
if hasattr(self.hps.data, "style2id"):
self.style2id = self.hps.data.style2id
else:
self.style2id = {str(i): i for i in range(self.num_styles)}
self.style_vectors = np.load(self.style_vec_path)
self.net_g = None
def load_net_g(self):
self.net_g = get_net_g(
model_path=self.model_path,
version=self.hps.version,
device=self.device,
hps=self.hps,
)
def get_style_vector(self, style_id, weight=1.0):
mean = self.style_vectors[0]
style_vec = self.style_vectors[style_id]
style_vec = mean + (style_vec - mean) * weight
return style_vec
def get_style_vector_from_audio(self, audio_path, weight=1.0):
from style_gen import extract_style_vector
xvec = extract_style_vector(audio_path)
mean = self.style_vectors[0]
xvec = mean + (xvec - mean) * weight
return xvec
def infer(
self,
text,
language="JP",
sid=0,
reference_audio_path=None,
sdp_ratio=DEFAULT_SDP_RATIO,
noise=DEFAULT_NOISE,
noisew=DEFAULT_NOISEW,
length=DEFAULT_LENGTH,
line_split=DEFAULT_LINE_SPLIT,
split_interval=DEFAULT_SPLIT_INTERVAL,
style_text="",
style_weight=DEFAULT_STYLE_WEIGHT,
use_style_text=False,
style="0",
emotion_weight=DEFAULT_EMOTION_WEIGHT,
):
if reference_audio_path == "":
reference_audio_path = None
if style_text == "" or not use_style_text:
style_text = None
if self.net_g is None:
self.load_net_g()
if reference_audio_path is None:
style_id = self.style2id[style]
style_vector = self.get_style_vector(style_id, emotion_weight)
else:
style_vector = self.get_style_vector_from_audio(
reference_audio_path, emotion_weight
)
if not line_split:
with torch.no_grad():
audio = infer(
text=text,
sdp_ratio=sdp_ratio,
noise_scale=noise,
noise_scale_w=noisew,
length_scale=length,
sid=sid,
language=language,
hps=self.hps,
net_g=self.net_g,
device=self.device,
style_text=style_text,
style_weight=style_weight,
style_vec=style_vector,
)
else:
texts = text.split("\n")
texts = [t for t in texts if t != ""]
audios = []
with torch.no_grad():
for i, t in enumerate(texts):
audios.append(
infer(
text=t,
sdp_ratio=sdp_ratio,
noise_scale=noise,
noise_scale_w=noisew,
length_scale=length,
sid=sid,
language=language,
hps=self.hps,
net_g=self.net_g,
device=self.device,
style_text=style_text,
style_weight=style_weight,
style_vec=style_vector,
)
)
if i != len(texts) - 1:
audios.append(np.zeros(int(44100 * split_interval)))
audio = np.concatenate(audios)
with warnings.catch_warnings():
warnings.simplefilter("ignore")
audio = convert_to_16_bit_wav(audio)
return (self.hps.data.sampling_rate, audio)
class ModelHolder:
def __init__(self, root_dir, device):
self.root_dir = root_dir
self.device = device
self.model_files_dict = {}
self.current_model = None
self.model_names = []
self.models = []
self.refresh()
def refresh(self):
self.model_files_dict: Dict[str, List[str]] = {}
self.model_names: List[str] = []
self.current_model = None
model_dirs = [
d
for d in os.listdir(self.root_dir)
if os.path.isdir(os.path.join(self.root_dir, d))
]
for model_name in model_dirs:
model_dir = os.path.join(self.root_dir, model_name)
model_files = [
os.path.join(model_dir, f)
for f in os.listdir(model_dir)
if f.endswith(".pth") or f.endswith(".pt") or f.endswith(".safetensors")
]
if len(model_files) == 0:
logger.info(
f"No model files found in {self.root_dir}/{model_name}, so skip it"
)
continue
self.model_files_dict[model_name] = model_files
self.model_names.append(model_name)
def load_model(self, model_name, model_path):
if model_name not in self.model_files_dict:
raise Exception(f"モデル名{model_name}は存在しません")
if model_path not in self.model_files_dict[model_name]:
raise Exception(f"pthファイル{model_path}は存在しません")
self.current_model = Model(
model_path=model_path,
config_path=os.path.join(self.root_dir, model_name, "config.json"),
style_vec_path=os.path.join(self.root_dir, model_name, "style_vectors.npy"),
device=self.device,
)
styles = list(self.current_model.style2id.keys())
return (
gr.Dropdown(choices=styles, value=styles[0]),
gr.update(interactive=True, value="音声合成"),
)
def update_model_files_dropdown(self, model_name):
model_files = self.model_files_dict[model_name]
return gr.Dropdown(choices=model_files, value=model_files[0])
def update_model_names_dropdown(self):
self.refresh()
initial_model_name = self.model_names[0]
initial_model_files = self.model_files_dict[initial_model_name]
return (
gr.Dropdown(choices=self.model_names, value=initial_model_name),
gr.Dropdown(choices=initial_model_files, value=initial_model_files[0]),
gr.update(interactive=False), # For tts_button
)
def tts_fn(
text,
@@ -232,11 +37,11 @@ def tts_fn(
length_scale,
line_split,
split_interval,
style_text,
assist_text,
assist_text_weight,
use_assist_text,
style,
style_weight,
use_style_text,
emotion,
emotion_weight,
):
assert model_holder.current_model is not None
@@ -252,11 +57,11 @@ def tts_fn(
length=length_scale,
line_split=line_split,
split_interval=split_interval,
style_text=style_text,
assist_text=assist_text,
assist_text_weight=assist_text_weight,
use_assist_text=use_assist_text,
style=style,
style_weight=style_weight,
use_style_text=use_style_text,
style=emotion,
emotion_weight=emotion_weight,
)
end_time = datetime.datetime.now()
@@ -349,9 +154,9 @@ model_assets
TODO: 現在のところはspeaker_id = 0に固定しており複数話者の合成には対応していません。
"""
style_md = """
style_md = f"""
- プリセットまたは音声ファイルから読み上げの声音・感情・スタイルのようなものを制御できます。
- デフォルトのNeutralでも、十分に読み上げる文に応じた感情で感情豊かに読み上げられます。このスタイル制御は、それを重み付きで上書きするような感じです。
- デフォルトの{DEFAULT_STYLE}でも、十分に読み上げる文に応じた感情で感情豊かに読み上げられます。このスタイル制御は、それを重み付きで上書きするような感じです。
- 強さを大きくしすぎると発音が変になったり声にならなかったりと崩壊することがあります。
- どのくらいに強さがいいかはモデルやスタイルによって異なるようです。
- 音声ファイルを入力する場合は、学習データと似た声音の話者(特に同じ性別)でないとよい効果が出ないかもしれません。
@@ -459,25 +264,25 @@ if __name__ == "__main__":
step=0.1,
label="Length",
)
use_style_text = gr.Checkbox(label="Style textを使う", value=False)
style_text = gr.Textbox(
label="Style text",
use_assist_text = gr.Checkbox(label="Assist textを使う", value=False)
assist_text = gr.Textbox(
label="Assist text",
placeholder="どうして私の意見を無視するの?許せない、ムカつく!死ねばいいのに。",
info="このテキストの読み上げと似た声音・感情になりやすくなります。ただ抑揚やテンポ等が犠牲になる傾向があります。",
visible=False,
)
style_text_weight = gr.Slider(
assist_text_weight = gr.Slider(
minimum=0,
maximum=1,
value=DEFAULT_STYLE_WEIGHT,
value=DEFAULT_ASSIST_TEXT_WEIGHT,
step=0.1,
label="Style textの強さ",
visible=False,
)
use_style_text.change(
use_assist_text.change(
lambda x: (gr.Textbox(visible=x), gr.Slider(visible=x)),
inputs=[use_style_text],
outputs=[style_text, style_text_weight],
inputs=[use_assist_text],
outputs=[assist_text, assist_text_weight],
)
with gr.Column():
with gr.Accordion("スタイルについて詳細", open=False):
@@ -488,14 +293,14 @@ if __name__ == "__main__":
value="プリセットから選ぶ",
)
style = gr.Dropdown(
label="スタイル(Neutralが平均スタイル)",
label="スタイル({DEFAULT_STYLE}が平均スタイル)",
choices=["モデルをロードしてください"],
value="モデルをロードしてください",
)
style_weight = gr.Slider(
minimum=0,
maximum=50,
value=DEFAULT_EMOTION_WEIGHT,
value=DEFAULT_STYLE_WEIGHT,
step=0.1,
label="スタイルの強さ",
)
@@ -520,9 +325,9 @@ if __name__ == "__main__":
length_scale,
line_split,
split_interval,
style_text,
style_text_weight,
use_style_text,
assist_text,
assist_text_weight,
use_assist_text,
style,
style_weight,
],
@@ -530,7 +335,7 @@ if __name__ == "__main__":
)
model_name.change(
model_holder.update_model_files_dropdown,
model_holder.update_model_files_gr,
inputs=[model_name],
outputs=[model_path],
)
@@ -538,12 +343,12 @@ if __name__ == "__main__":
model_path.change(make_non_interactive, outputs=[tts_button])
refresh_button.click(
model_holder.update_model_names_dropdown,
model_holder.update_model_names_gr,
outputs=[model_name, model_path, tts_button],
)
load_button.click(
model_holder.load_model,
model_holder.load_model_gr,
inputs=[model_name, model_path],
outputs=[style, tts_button],
)

View File

@@ -4,7 +4,7 @@ from torch import nn
from torch.nn import functional as F
import commons
from tools.log import logger as logging
from common.log import logger as logging
class LayerNorm(nn.Module):

View File

@@ -9,7 +9,7 @@ import commons
import utils
from config import config
from text import cleaned_text_to_sequence, get_bert
from tools.stdout_wrapper import SAFE_STDOUT
from common.stdout_wrapper import SAFE_STDOUT
def process_line(x):

20
common/constants.py Normal file
View File

@@ -0,0 +1,20 @@
import enum
DEFAULT_STYLE: str = "Neutral"
DEFAULT_STYLE_WEIGHT: float = 5.0
class Languages(str, enum.Enum):
JP = "JP"
EN = "EN"
ZH = "ZH"
DEFAULT_SDP_RATIO: float = 0.2
DEFAULT_NOISE: float = 0.6
DEFAULT_NOISEW: float = 0.8
DEFAULT_LENGTH: float = 1.0
DEFAULT_LINE_SPLIT: bool = True
DEFAULT_SPLIT_INTERVAL: float = 0.5
DEFAULT_ASSIST_TEXT_WEIGHT: float = 0.7
DEFAULT_ASSIST_TEXT_WEIGHT: float = 1.0

View File

@@ -1,34 +1,34 @@
import subprocess
import sys
from .log import logger
from .stdout_wrapper import SAFE_STDOUT
python = sys.executable
def run_script_with_log(cmd: list[str]) -> tuple[bool, str]:
logger.info(f"Running: {' '.join(cmd)}")
result = subprocess.run(
[python] + cmd,
stdout=SAFE_STDOUT, # type: ignore
stderr=subprocess.PIPE,
text=True,
)
if result.returncode != 0:
logger.error(f"Error: {' '.join(cmd)}")
print(result.stderr)
return False, result.stderr
elif result.stderr:
logger.warning(f"Warning: {' '.join(cmd)}")
print(result.stderr)
return True, result.stderr
logger.success(f"Success: {' '.join(cmd)}")
return True, ""
def second_elem_of(original_function):
def inner_function(*args, **kwargs):
return original_function(*args, **kwargs)[1]
return inner_function
import subprocess
import sys
from .log import logger
from .stdout_wrapper import SAFE_STDOUT
python = sys.executable
def run_script_with_log(cmd: list[str]) -> tuple[bool, str]:
logger.info(f"Running: {' '.join(cmd)}")
result = subprocess.run(
[python] + cmd,
stdout=SAFE_STDOUT, # type: ignore
stderr=subprocess.PIPE,
text=True,
)
if result.returncode != 0:
logger.error(f"Error: {' '.join(cmd)}")
print(result.stderr)
return False, result.stderr
elif result.stderr:
logger.warning(f"Warning: {' '.join(cmd)}")
print(result.stderr)
return True, result.stderr
logger.success(f"Success: {' '.join(cmd)}")
return True, ""
def second_elem_of(original_function):
def inner_function(*args, **kwargs):
return original_function(*args, **kwargs)[1]
return inner_function

219
common/tts_model.py Normal file
View File

@@ -0,0 +1,219 @@
import numpy as np
import gradio as gr
import torch
import os
import warnings
from gradio.processing_utils import convert_to_16_bit_wav
from typing import Dict, List, Optional
import utils
from infer import get_net_g, infer
from models import SynthesizerTrn
from .log import logger
from .constants import (
DEFAULT_ASSIST_TEXT_WEIGHT,
DEFAULT_LENGTH,
DEFAULT_LINE_SPLIT,
DEFAULT_NOISE,
DEFAULT_NOISEW,
DEFAULT_SDP_RATIO,
DEFAULT_SPLIT_INTERVAL,
DEFAULT_STYLE,
DEFAULT_STYLE_WEIGHT,
)
class Model:
def __init__(
self, model_path: str, config_path: str, style_vec_path: str, device: str
):
self.model_path: str = model_path
self.config_path: str = config_path
self.device: str = device
self.style_vec_path: str = style_vec_path
self.hps: utils.HParams = utils.get_hparams_from_file(self.config_path)
self.spk2id: Dict[str, int] = self.hps.data.spk2id
self.id2spk: Dict[int, str] = {v: k for k, v in self.spk2id.items()}
self.num_styles: int = self.hps.data.num_styles
if hasattr(self.hps.data, "style2id"):
self.style2id: Dict[str, int] = self.hps.data.style2id
else:
self.style2id: Dict[str, int] = {str(i): i for i in range(self.num_styles)}
self.style_vectors: np.ndarray = np.load(self.style_vec_path)
self.net_g: Optional[SynthesizerTrn] = None
def load_net_g(self):
self.net_g = get_net_g(
model_path=self.model_path,
version=self.hps.version,
device=self.device,
hps=self.hps,
)
def get_style_vector(self, style_id: int, weight: float = 1.0) -> np.ndarray:
mean = self.style_vectors[0]
style_vec = self.style_vectors[style_id]
style_vec = mean + (style_vec - mean) * weight
return style_vec
def get_style_vector_from_audio(
self, audio_path: str, weight: float = 1.0
) -> np.ndarray:
from style_gen import extract_style_vector
xvec = extract_style_vector(audio_path)
mean = self.style_vectors[0]
xvec = mean + (xvec - mean) * weight
return xvec
def infer(
self,
text: str,
language: str = "JP",
sid: int = 0,
reference_audio_path: Optional[str] = None,
sdp_ratio: float = DEFAULT_SDP_RATIO,
noise: float = DEFAULT_NOISE,
noisew: float = DEFAULT_NOISEW,
length: float = DEFAULT_LENGTH,
line_split: bool = DEFAULT_LINE_SPLIT,
split_interval: float = DEFAULT_SPLIT_INTERVAL,
assist_text: Optional[str] = None,
assist_text_weight: float = DEFAULT_ASSIST_TEXT_WEIGHT,
use_assist_text: bool = False,
style: str = DEFAULT_STYLE,
style_weight: float = DEFAULT_STYLE_WEIGHT,
) -> tuple[int, np.ndarray]:
logger.info(f"Start generating audio data from text:\n{text}")
if reference_audio_path == "":
reference_audio_path = None
if assist_text == "" or not use_assist_text:
assist_text = None
if self.net_g is None:
self.load_net_g()
if reference_audio_path is None:
style_id = self.style2id[style]
style_vector = self.get_style_vector(style_id, style_weight)
else:
style_vector = self.get_style_vector_from_audio(
reference_audio_path, style_weight
)
if not line_split:
with torch.no_grad():
audio = infer(
text=text,
sdp_ratio=sdp_ratio,
noise_scale=noise,
noise_scale_w=noisew,
length_scale=length,
sid=sid,
language=language,
hps=self.hps,
net_g=self.net_g,
device=self.device,
assist_text=assist_text,
assist_text_weight=assist_text_weight,
style_vec=style_vector,
)
else:
texts = text.split("\n")
texts = [t for t in texts if t != ""]
audios = []
with torch.no_grad():
for i, t in enumerate(texts):
audios.append(
infer(
text=t,
sdp_ratio=sdp_ratio,
noise_scale=noise,
noise_scale_w=noisew,
length_scale=length,
sid=sid,
language=language,
hps=self.hps,
net_g=self.net_g,
device=self.device,
assist_text=assist_text,
assist_text_weight=assist_text_weight,
style_vec=style_vector,
)
)
if i != len(texts) - 1:
audios.append(np.zeros(int(44100 * split_interval)))
audio = np.concatenate(audios)
with warnings.catch_warnings():
warnings.simplefilter("ignore")
audio = convert_to_16_bit_wav(audio)
logger.info("Audio data generated successfully")
return (self.hps.data.sampling_rate, audio)
class ModelHolder:
def __init__(self, root_dir: str, device: str):
self.root_dir: str = root_dir
self.device: str = device
self.model_files_dict: Dict[str, List[str]] = {}
self.current_model: Optional[Model] = None
self.model_names: List[str] = []
self.models: List[Model] = []
self.refresh()
def refresh(self):
self.model_files_dict = {}
self.model_names = []
self.current_model = None
model_dirs = [
d
for d in os.listdir(self.root_dir)
if os.path.isdir(os.path.join(self.root_dir, d))
]
for model_name in model_dirs:
model_dir = os.path.join(self.root_dir, model_name)
model_files = [
os.path.join(model_dir, f)
for f in os.listdir(model_dir)
if f.endswith(".pth") or f.endswith(".pt") or f.endswith(".safetensors")
]
if len(model_files) == 0:
logger.info(
f"No model files found in {self.root_dir}/{model_name}, so skip it"
)
continue
self.model_files_dict[model_name] = model_files
self.model_names.append(model_name)
def load_model_gr(
self, model_name: str, model_path: str
) -> tuple[gr.Dropdown, gr.Button]:
if model_name not in self.model_files_dict:
raise Exception(f"モデル名{model_name}は存在しません")
if model_path not in self.model_files_dict[model_name]:
raise Exception(f"pthファイル{model_path}は存在しません")
self.current_model = Model(
model_path=model_path,
config_path=os.path.join(self.root_dir, model_name, "config.json"),
style_vec_path=os.path.join(self.root_dir, model_name, "style_vectors.npy"),
device=self.device,
)
styles = list(self.current_model.style2id.keys())
return (
gr.Dropdown(choices=styles, value=styles[0]),
gr.Button(interactive=True, value="音声合成"),
)
def update_model_files_gr(self, model_name: str) -> gr.Dropdown:
model_files = self.model_files_dict[model_name]
return gr.Dropdown(choices=model_files, value=model_files[0])
def update_model_names_gr(self) -> tuple[gr.Dropdown, gr.Dropdown, gr.Button]:
self.refresh()
initial_model_name = self.model_names[0]
initial_model_files = self.model_files_dict[initial_model_name]
return (
gr.Dropdown(choices=self.model_names, value=initial_model_name),
gr.Dropdown(choices=initial_model_files, value=initial_model_files[0]),
gr.Button(interactive=False), # For tts_button
)

View File

@@ -8,7 +8,7 @@ from typing import Dict, List
import yaml
from tools.log import logger
from common.log import logger
class Resample_config:

View File

@@ -11,7 +11,7 @@ import commons
from config import config
from mel_processing import mel_spectrogram_torch, spectrogram_torch
from text import cleaned_text_to_sequence
from tools.log import logger
from common.log import logger
from utils import load_filepaths_and_text, load_wav_to_torch
"""Multi speaker version"""

View File

@@ -1,5 +1,6 @@
import os
from tools.log import logger
from common.log import logger
from common.constants import DEFAULT_STYLE
import numpy as np
import json
@@ -9,10 +10,10 @@ def set_style_config(json_path, output_path):
with open(json_path, "r") as f:
json_dict = json.load(f)
json_dict["data"]["num_styles"] = 1
json_dict["data"]["style2id"] = {"Neutral": 0}
json_dict["data"]["style2id"] = {DEFAULT_STYLE: 0}
with open(output_path, "w") as f:
json.dump(json_dict, f, indent=2)
logger.info(f"Update style config (only Neutral style) to {output_path}")
logger.info(f"Save style config (only {DEFAULT_STYLE}) to {output_path}")
def save_mean_vector(wav_dir, output_path):

View File

@@ -1,11 +1,13 @@
# Changelog
## v1.2 (2023-12-30)
## v1.2 (2023-12-31)
- グラボがないCPUユーザーで音声合成機能は使えるように
- Google colabでの学習をサポート、ートブックを追加
- グラボがないユーザーで音声合成をサポート、`Install-Style-Bert-VITS2-CPU.bat`でインストール。
- Google Colabでの学習をサポート、[ノートブック](../colab.ipynb)を追加
- 前処理のリサンプリング時に音声ファイルの開始・終了部分の無音を削除するオプションを追加(デフォルトでオン)
- 学習時に自動的にデフォルトスタイル Neutral を生成するように。これで学習したらそのまま音声合成を試せます。
- 学習時に自動的にデフォルトスタイル Neutral を生成するように。特にスタイル指定が必要のない方は、学習したらそのまま音声合成を試せます。これまで通りスタイルを自分で作ることもできます。
- マージ機能の新規追加: `Merge.bat`, `webui_merge.py`
- 音声合成のAPIサーバーを追加、`python server_fastapi.py`で起動します。API仕様は起動後に`/docs`にて確認ください。( @darai0512 様によるPRです、ありがとうございます
## v1.1 (2023-12-29)
- TrainとDatasetのWebUIの改良・調整一括事前処理ボタン等

View File

@@ -23,13 +23,13 @@ def get_net_g(model_path: str, version: str, device: str, hps):
if model_path.endswith(".pth") or model_path.endswith(".pt"):
_ = utils.load_checkpoint(model_path, net_g, None, skip_optimizer=True)
elif model_path.endswith(".safetensors"):
_ = utils.load_safetensors(model_path, net_g, device)
_ = utils.load_safetensors(model_path, net_g, True)
else:
raise ValueError(f"Unknown model format: {model_path}")
return net_g
def get_text(text, language_str, hps, device, style_text=None, style_weight=0.7):
def get_text(text, language_str, hps, device, assist_text=None, assist_text_weight=0.7):
# 在此处实现当前版本的get_text
norm_text, phone, tone, word2ph = clean_text(text, language_str)
phone, tone, language = cleaned_text_to_sequence(phone, tone, language_str)
@@ -42,7 +42,7 @@ def get_text(text, language_str, hps, device, style_text=None, style_weight=0.7)
word2ph[i] = word2ph[i] * 2
word2ph[0] += 1
bert_ori = get_bert(
norm_text, word2ph, language_str, device, style_text, style_weight
norm_text, word2ph, language_str, device, assist_text, assist_text_weight
)
del word2ph
assert bert_ori.shape[-1] == len(phone), phone
@@ -86,16 +86,16 @@ def infer(
device,
skip_start=False,
skip_end=False,
style_text=None,
style_weight=0.7,
assist_text=None,
assist_text_weight=0.7,
):
bert, ja_bert, en_bert, phones, tones, lang_ids = get_text(
text,
language,
hps,
device,
style_text=style_text,
style_weight=style_weight,
assist_text=assist_text,
assist_text_weight=assist_text_weight,
)
if skip_start:
phones = phones[3:]

View File

@@ -3,7 +3,7 @@ from pathlib import Path
from huggingface_hub import hf_hub_download
from tools.log import logger
from common.log import logger
def download_bert_models():

View File

@@ -9,7 +9,7 @@ from tqdm import tqdm
from config import config
from text.cleaner import clean_text
from tools.stdout_wrapper import SAFE_STDOUT
from common.stdout_wrapper import SAFE_STDOUT
preprocess_text_config = config.preprocess_text_config

View File

@@ -8,8 +8,8 @@ import soundfile
from tqdm import tqdm
from config import config
from tools.log import logger
from tools.stdout_wrapper import SAFE_STDOUT
from common.log import logger
from common.stdout_wrapper import SAFE_STDOUT
def normalize_audio(data, sr):

View File

@@ -1,40 +1,43 @@
"""
api服务 多版本多模型 fastapi实现
API server for TTS
"""
import argparse
from fastapi import FastAPI, Query, Request, status, HTTPException
from fastapi.responses import Response, FileResponse
from fastapi.middleware.cors import CORSMiddleware
import os
import sys
from io import BytesIO
from scipy.io import wavfile
import uvicorn
import torch
import psutil
import GPUtil
from typing import Dict, Optional, List, Union
import os, sys
from tools.log import logger
from typing import Dict, Optional, Union
from urllib.parse import unquote
from config import config
from app import (
Model,
ModelHolder,
Languages,
DEFAULT_SDP_RATIO,
DEFAULT_NOISE,
DEFAULT_NOISEW,
import GPUtil
import psutil
import torch
import uvicorn
from fastapi import FastAPI, HTTPException, Query, Request, status
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import FileResponse, Response
from scipy.io import wavfile
from common.constants import (
DEFAULT_ASSIST_TEXT_WEIGHT,
DEFAULT_LENGTH,
DEFAULT_LINE_SPLIT,
DEFAULT_NOISE,
DEFAULT_NOISEW,
DEFAULT_SDP_RATIO,
DEFAULT_SPLIT_INTERVAL,
DEFAULT_STYLE,
DEFAULT_STYLE_WEIGHT,
DEFAULT_EMOTION_WEIGHT,
Languages,
)
from webui_style_vectors import DEFAULT_EMOTION
from common.log import logger
from common.tts_model import Model, ModelHolder
from config import config
ln = config.server_config.language
def raise_validation_error(msg: str, param: str):
logger.warning(f"Validation error: {msg}")
raise HTTPException(
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
detail=[dict(type="invalid_params", msg=msg, loc=["query", param])],
@@ -125,15 +128,15 @@ if __name__ == "__main__":
split_interval: float = Query(
DEFAULT_SPLIT_INTERVAL, description="分けた場合に挟む無音の長さ(秒)"
),
style_text: Optional[str] = Query(
assist_text: Optional[str] = Query(
None, description="このテキストの読み上げと似た声音・感情になりやすくなる。ただし抑揚やテンポ等が犠牲になる傾向がある"
),
style_weight: float = Query(DEFAULT_STYLE_WEIGHT, description="style_textの強さ"),
emotion: Optional[Union[int, str]] = Query(DEFAULT_EMOTION, description="スタイル"),
emotion_weight: float = Query(DEFAULT_EMOTION_WEIGHT, description="emotionの強さ"),
reference_audio_path: Optional[str] = Query(
None, description="emotionを音声ファイルで行う"
assist_text_weight: float = Query(
DEFAULT_ASSIST_TEXT_WEIGHT, description="assist_textの強さ"
),
style: Optional[Union[int, str]] = Query(DEFAULT_STYLE, description="スタイル"),
style_weight: float = Query(DEFAULT_STYLE_WEIGHT, description="スタイルの強さ"),
reference_audio_path: Optional[str] = Query(None, description="スタイルを音声ファイルで行う"),
):
"""Infer text to speech(テキストから感情付き音声を生成する)"""
logger.info(
@@ -154,8 +157,8 @@ if __name__ == "__main__":
f"speaker_name={speaker_name} not found", "speaker_name"
)
speaker_id = model.spk2id[speaker_name]
if emotion not in model.style2id.keys():
raise_validation_error(f"emotion={emotion} not found", "emotion")
if style not in model.style2id.keys():
raise_validation_error(f"style={style} not found", "style")
if encoding is not None:
text = unquote(text, encoding=encoding)
sr, audio = model.infer(
@@ -169,12 +172,13 @@ if __name__ == "__main__":
length=length,
line_split=auto_split,
split_interval=split_interval,
style_text=style_text,
assist_text=assist_text,
assist_text_weight=assist_text_weight,
use_assist_text=bool(assist_text),
style=style,
style_weight=style_weight,
use_style_text=bool(style_text),
style=emotion,
emotion_weight=emotion_weight,
)
logger.success("Audio data generated and sent successfully")
with BytesIO() as wavContent:
wavfile.write(wavContent, sr, audio)
return Response(content=wavContent.getvalue(), media_type="audio/wav")

74
server_test.ipynb Normal file

File diff suppressed because one or more lines are too long

View File

@@ -6,7 +6,7 @@ import soundfile as sf
import torch
from tqdm import tqdm
from tools.stdout_wrapper import SAFE_STDOUT
from common.stdout_wrapper import SAFE_STDOUT
vad_model, utils = torch.hub.load(
repo_or_dir="snakers4/silero-vad",

View File

@@ -8,7 +8,7 @@ from tqdm import tqdm
import utils
from config import config
from tools.stdout_wrapper import SAFE_STDOUT
from common.stdout_wrapper import SAFE_STDOUT
warnings.filterwarnings("ignore", category=UserWarning)
from pyannote.audio import Inference, Model

View File

@@ -18,13 +18,15 @@ def cleaned_text_to_sequence(cleaned_text, tones, language):
return phones, tones, lang_ids
def get_bert(norm_text, word2ph, language, device, style_text=None, style_weight=0.7):
def get_bert(
norm_text, word2ph, language, device, assist_text=None, assist_text_weight=0.7
):
from .chinese_bert import get_bert_feature as zh_bert
from .english_bert_mock import get_bert_feature as en_bert
from .japanese_bert import get_bert_feature as jp_bert
lang_bert_func_map = {"ZH": zh_bert, "EN": en_bert, "JP": jp_bert}
bert = lang_bert_func_map[language](
norm_text, word2ph, device, style_text, style_weight
norm_text, word2ph, device, assist_text, assist_text_weight
)
return bert

View File

@@ -16,8 +16,8 @@ def get_bert_feature(
text,
word2ph,
device=config.bert_gen_config.device,
style_text=None,
style_weight=0.7,
assist_text=None,
assist_text_weight=0.7,
):
if (
sys.platform == "darwin"
@@ -37,8 +37,8 @@ def get_bert_feature(
inputs[i] = inputs[i].to(device)
res = models[device](**inputs, output_hidden_states=True)
res = torch.cat(res["hidden_states"][-3:-2], -1)[0].cpu()
if style_text:
style_inputs = tokenizer(style_text, return_tensors="pt")
if assist_text:
style_inputs = tokenizer(assist_text, return_tensors="pt")
for i in style_inputs:
style_inputs[i] = style_inputs[i].to(device)
style_res = models[device](**style_inputs, output_hidden_states=True)
@@ -48,10 +48,10 @@ def get_bert_feature(
word2phone = word2ph
phone_level_feature = []
for i in range(len(word2phone)):
if style_text:
if assist_text:
repeat_feature = (
res[i].repeat(word2phone[i], 1) * (1 - style_weight)
+ style_res_mean.repeat(word2phone[i], 1) * style_weight
res[i].repeat(word2phone[i], 1) * (1 - assist_text_weight)
+ style_res_mean.repeat(word2phone[i], 1) * assist_text_weight
)
else:
repeat_feature = res[i].repeat(word2phone[i], 1)

View File

@@ -17,8 +17,8 @@ def get_bert_feature(
text,
word2ph,
device=config.bert_gen_config.device,
style_text=None,
style_weight=0.7,
assist_text=None,
assist_text_weight=0.7,
):
if (
sys.platform == "darwin"
@@ -38,8 +38,8 @@ def get_bert_feature(
inputs[i] = inputs[i].to(device)
res = models[device](**inputs, output_hidden_states=True)
res = torch.cat(res["hidden_states"][-3:-2], -1)[0].cpu()
if style_text:
style_inputs = tokenizer(style_text, return_tensors="pt")
if assist_text:
style_inputs = tokenizer(assist_text, return_tensors="pt")
for i in style_inputs:
style_inputs[i] = style_inputs[i].to(device)
style_res = models[device](**style_inputs, output_hidden_states=True)
@@ -49,10 +49,10 @@ def get_bert_feature(
word2phone = word2ph
phone_level_feature = []
for i in range(len(word2phone)):
if style_text:
if assist_text:
repeat_feature = (
res[i].repeat(word2phone[i], 1) * (1 - style_weight)
+ style_res_mean.repeat(word2phone[i], 1) * style_weight
res[i].repeat(word2phone[i], 1) * (1 - assist_text_weight)
+ style_res_mean.repeat(word2phone[i], 1) * assist_text_weight
)
else:
repeat_feature = res[i].repeat(word2phone[i], 1)

View File

@@ -17,12 +17,12 @@ def get_bert_feature(
text,
word2ph,
device=config.bert_gen_config.device,
style_text=None,
style_weight=0.7,
assist_text=None,
assist_text_weight=0.7,
):
text = "".join(text2sep_kata(text)[0])
if style_text:
style_text = "".join(text2sep_kata(style_text)[0])
if assist_text:
assist_text = "".join(text2sep_kata(assist_text)[0])
if (
sys.platform == "darwin"
and torch.backends.mps.is_available()
@@ -41,8 +41,8 @@ def get_bert_feature(
inputs[i] = inputs[i].to(device)
res = models[device](**inputs, output_hidden_states=True)
res = torch.cat(res["hidden_states"][-3:-2], -1)[0].cpu()
if style_text:
style_inputs = tokenizer(style_text, return_tensors="pt")
if assist_text:
style_inputs = tokenizer(assist_text, return_tensors="pt")
for i in style_inputs:
style_inputs[i] = style_inputs[i].to(device)
style_res = models[device](**style_inputs, output_hidden_states=True)
@@ -53,10 +53,10 @@ def get_bert_feature(
word2phone = word2ph
phone_level_feature = []
for i in range(len(word2phone)):
if style_text:
if assist_text:
repeat_feature = (
res[i].repeat(word2phone[i], 1) * (1 - style_weight)
+ style_res_mean.repeat(word2phone[i], 1) * style_weight
res[i].repeat(word2phone[i], 1) * (1 - assist_text_weight)
+ style_res_mean.repeat(word2phone[i], 1) * assist_text_weight
)
else:
repeat_feature = res[i].repeat(word2phone[i], 1)

View File

@@ -29,8 +29,8 @@ from losses import discriminator_loss, feature_loss, generator_loss, kl_loss
from mel_processing import mel_spectrogram_torch, spec_to_mel_torch
from models import DurationDiscriminator, MultiPeriodDiscriminator, SynthesizerTrn
from text.symbols import symbols
from tools.log import logger
from tools.stdout_wrapper import SAFE_STDOUT
from common.log import logger
from common.stdout_wrapper import SAFE_STDOUT
torch.backends.cuda.matmul.allow_tf32 = True
torch.backends.cudnn.allow_tf32 = (
@@ -157,6 +157,7 @@ def run():
if rank == 0:
# logger = utils.get_logger(hps.model_dir)
# logger.info(hps)
logger.add(os.path.join(hps.model_dir, "train.log"))
utils.check_git_hash(hps.model_dir)
writer = SummaryWriter(log_dir=hps.model_dir)
writer_eval = SummaryWriter(log_dir=os.path.join(hps.model_dir, "eval"))

View File

@@ -5,7 +5,7 @@ import sys
from faster_whisper import WhisperModel
from tqdm import tqdm
from tools.stdout_wrapper import SAFE_STDOUT
from common.stdout_wrapper import SAFE_STDOUT
def transcribe(wav_path, initial_prompt=None):

View File

@@ -13,7 +13,7 @@ from safetensors import safe_open
from safetensors.torch import save_file
from scipy.io.wavfile import read
from tools.log import logger
from common.log import logger
MATPLOTLIB_FLAG = False

View File

@@ -71,8 +71,8 @@ def generate_audio(
device=device,
skip_start=skip_start,
skip_end=skip_end,
style_text=style_text,
style_weight=style_weight,
assist_text=style_text,
assist_text_weight=style_weight,
)
audio16bit = gr.processing_utils.convert_to_16_bit_wav(audio)
audio_list.append(audio16bit)

View File

@@ -2,8 +2,8 @@ import os
import gradio as gr
from tools.log import logger
from tools.subprocess_utils import run_script_with_log, second_elem_of
from common.log import logger
from common.subprocess_utils import run_script_with_log, second_elem_of
def do_slice(model_name, normalize):

389
webui_merge.py Normal file
View File

@@ -0,0 +1,389 @@
import json
import os
import sys
import gradio as gr
import numpy as np
import torch
from safetensors import safe_open
from safetensors.torch import save_file
from config import config
from common.tts_model import Model, ModelHolder
from common.log import logger
from common.constants import DEFAULT_STYLE
voice_keys = ["dec", "flow"]
speech_style_keys = ["enc_p"]
tempo_keys = ["sdp", "dp"]
device = "cuda" if torch.cuda.is_available() else "cpu"
model_dir = config.out_dir
model_holder = ModelHolder(model_dir, device)
def merge_style(model_name_a, model_name_b, weight, output_name, style_triple_list):
"""
style_triple_list: list[(model_aでのスタイル名, model_bでのスタイル名, 出力するスタイル名)]
"""
# 新スタイル名リストにNeutralが含まれているか確認し、Neutralを先頭に持ってくる
if any(triple[2] == DEFAULT_STYLE for triple in style_triple_list):
# 存在する場合、リストをソート
sorted_list = sorted(style_triple_list, key=lambda x: x[2] != DEFAULT_STYLE)
else:
# 存在しない場合、エラーを発生
raise ValueError("No element with {DEFAULT_STYLE} output style name found.")
style_vectors_a = np.load(
os.path.join(model_dir, model_name_a, "style_vectors.npy")
) # (style_num_a, 256)
style_vectors_b = np.load(
os.path.join(model_dir, model_name_b, "style_vectors.npy")
) # (style_num_b, 256)
with open(os.path.join(model_dir, model_name_a, "config.json")) as f:
config_a = json.load(f)
with open(os.path.join(model_dir, model_name_b, "config.json")) as f:
config_b = json.load(f)
style2id_a = config_a["data"]["style2id"]
style2id_b = config_b["data"]["style2id"]
new_style_vecs = []
new_style2id = {}
for style_a, style_b, style_out in sorted_list:
if style_a not in style2id_a:
logger.error(f"{style_a} is not in {model_name_a}.")
raise ValueError(f"{style_a}{model_name_a} にありません。")
if style_b not in style2id_b:
logger.error(f"{style_b} is not in {model_name_b}.")
raise ValueError(f"{style_b}{model_name_b} にありません。")
new_style = (
style_vectors_a[style2id_a[style_a]] * (1 - weight)
+ style_vectors_b[style2id_b[style_b]] * weight
)
new_style_vecs.append(new_style)
new_style2id[style_out] = len(new_style_vecs) - 1
new_style_vecs = np.array(new_style_vecs)
output_style_path = os.path.join(model_dir, 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(model_dir, output_name, "config.json"), "w") as f:
json.dump(new_config, f, indent=2)
return output_style_path, list(new_style2id.keys())
def merge_models(
model_path_a,
model_path_b,
voice_weight,
speech_style_weight,
tempo_weight,
output_name,
):
"""model Aを起点に、model Bの各要素を重み付けしてマージする。
safetensors形式を前提とする。"""
model_a_weight = {}
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 = {}
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)
merged_model_weight = model_a_weight.copy()
for key in model_a_weight.keys():
if any([key.startswith(prefix) for prefix in voice_keys]):
weight = voice_weight
elif any([key.startswith(prefix) for prefix in speech_style_keys]):
weight = speech_style_weight
elif any([key.startswith(prefix) for prefix in tempo_keys]):
weight = tempo_weight
else:
continue
merged_model_weight[key] = (
model_a_weight[key] * (1 - weight) + model_b_weight[key] * weight
)
merged_model_path = os.path.join(
model_dir, output_name, f"{output_name}.safetensors"
)
os.makedirs(os.path.dirname(merged_model_path), exist_ok=True)
save_file(merged_model_weight, merged_model_path)
return merged_model_path
def merge_models_gr(
model_name_a,
model_path_a,
model_name_b,
model_path_b,
output_name,
voice_weight,
speech_style_weight,
tempo_weight,
):
merged_model_path = merge_models(
model_path_a,
model_path_b,
voice_weight,
speech_style_weight,
tempo_weight,
output_name,
)
return f"Success: モデルを{merged_model_path}に保存しました。"
def merge_style_gr(
model_name_a,
model_name_b,
weight,
output_name,
style_triple_list_str: str,
):
style_triple_list = []
for line in style_triple_list_str.split("\n"):
if not line:
continue
style_triple = line.split(",")
if len(style_triple) != 3:
logger.error(f"Invalid style triple: {line}")
return f"Error: スタイルを3つのカンマ区切りで入力してください:\n{line}", None
style_a, style_b, style_out = style_triple
style_a = style_a.strip()
style_b = style_b.strip()
style_out = style_out.strip()
style_triple_list.append((style_a, style_b, style_out))
try:
new_style_path, new_styles = merge_style(
model_name_a, model_name_b, weight, output_name, style_triple_list
)
except ValueError as e:
return f"Error: {e}"
return f"Success: スタイルを{new_style_path}に保存しました。", gr.Dropdown(
choices=new_styles, value=new_styles[0]
)
def simple_tts(model_name, text, style=DEFAULT_STYLE, emotion_weight=1.0):
model_path = os.path.join(model_dir, model_name, f"{model_name}.safetensors")
config_path = os.path.join(model_dir, model_name, "config.json")
style_vec_path = os.path.join(model_dir, model_name, "style_vectors.npy")
model = Model(model_path, config_path, style_vec_path, device)
return model.infer(text, style=style, style_weight=emotion_weight)
def update_two_model_names_dropdown():
new_names, new_files, _ = model_holder.update_model_names_gr()
return new_names, new_files, new_names, new_files
def get_styles_gr(model_name_a, model_name_b):
config_path_a = os.path.join(model_dir, model_name_a, "config.json")
with open(config_path_a) as f:
config_a = json.load(f)
styles_a = list(config_a["data"]["style2id"].keys())
config_path_b = os.path.join(model_dir, model_name_b, "config.json")
with open(config_path_b) as f:
config_b = json.load(f)
styles_b = list(config_b["data"]["style2id"].keys())
return gr.Textbox(value=", ".join(styles_a)), gr.Textbox(value=", ".join(styles_b))
initial_md = """
# Style-Bert-VITS2 モデルマージツール
2つのStyle-Bert-VITS2モデルから、声質・話し方・話す速さを取り替えたり混ぜたりできます。
## 使い方
1. マージしたい2つのモデルを選択してください`model_assets`フォルダの中から選ばれます)。
2. マージ後のモデルの名前を入力してください。
3. マージ後のモデルの声質・話し方・話す速さを調整してください。
4. 「モデルファイルのマージ」ボタンを押してくださいsafetensorsファイルがマージされる
5. スタイルベクトルファイルも生成する必要があるので、指示に従ってマージ方法を入力後、「スタイルのマージ」ボタンを押してください。
以上でマージは完了で、`model_assets/マージ後のモデル名`にマージ後のモデルが保存され、音声合成のときに使えます。
一番下にマージしたモデルによる簡易的な音声合成機能もつけています。
"""
style_merge_md = f"""
## スタイルベクトルのマージ
1行に「モデルAのスタイル名, モデルBのスタイル名, 左の2つを混ぜて出力するスタイル名」
という形式で入力してください。例えば、
```
{DEFAULT_STYLE}, {DEFAULT_STYLE}, {DEFAULT_STYLE}
Happy, Surprise, HappySurprise
```
と入力すると、マージ後のスタイルベクトルは、
- `{DEFAULT_STYLE}`: モデルAの`{DEFAULT_STYLE}`とモデルBの`{DEFAULT_STYLE}`を混ぜたもの
- `HappySurprise`: モデルAの`Happy`とモデルBの`Surprise`を混ぜたもの
の2つになります。
### 注意
- 必ず「{DEFAULT_STYLE}」という名前のスタイルを作ってください。これは、マージ後のモデルの平均スタイルになります。
- 構造上の相性の関係で、スタイルベクトルを混ぜる重みは、上の「話し方」と同じ比率で混ぜられます。例えば「話し方」が0のときはモデルAのみしか使われません。
"""
model_names = model_holder.model_names
if len(model_names) == 0:
logger.error(f"モデルが見つかりませんでした。{model_dir}にモデルを置いてください。")
sys.exit(1)
initial_id = 0
initial_model_files = model_holder.model_files_dict[model_names[initial_id]]
with gr.Blocks(theme="NoCrypt/miku") as app:
gr.Markdown(initial_md)
with gr.Accordion(label="使い方", open=False):
gr.Markdown(initial_md)
with gr.Row():
with gr.Column(scale=3):
model_name_a = gr.Dropdown(
label="モデルA",
choices=model_names,
value=model_names[initial_id],
)
model_path_a = gr.Dropdown(
label="モデルファイル",
choices=initial_model_files,
value=initial_model_files[0],
)
with gr.Column(scale=3):
model_name_b = gr.Dropdown(
label="モデルB",
choices=model_names,
value=model_names[initial_id],
)
model_path_b = gr.Dropdown(
label="モデルファイル",
choices=initial_model_files,
value=initial_model_files[0],
)
refresh_button = gr.Button("更新", scale=1, visible=True)
with gr.Column(variant="panel"):
new_name = gr.Textbox(label="新しいモデル名", placeholder="new_model")
with gr.Row():
voice_slider = gr.Slider(
label="声質",
value=0,
minimum=0,
maximum=1,
step=0.1,
)
speech_style_slider = gr.Slider(
label="話し方(抑揚・感情表現等)",
value=0,
minimum=0,
maximum=1,
step=0.1,
)
tempo_slider = gr.Slider(
label="話す速さ・リズム・テンポ",
value=0,
minimum=0,
maximum=1,
step=0.1,
)
with gr.Column(variant="panel"):
gr.Markdown("## モデルファイルsafetensorsのマージ")
model_merge_button = gr.Button("モデルファイルのマージ", variant="primary")
info_model_merge = gr.Textbox(label="情報")
with gr.Column(variant="panel"):
gr.Markdown(style_merge_md)
with gr.Row():
load_button = gr.Button("スタイル一覧をロード", scale=1)
styles_a = gr.Textbox(label="モデルAのスタイル一覧")
styles_b = gr.Textbox(label="モデルBのスタイル一覧")
style_triple_list = gr.TextArea(
label="スタイルのマージリスト",
placeholder=f"{DEFAULT_STYLE}, {DEFAULT_STYLE},{DEFAULT_STYLE}\nAngry, Angry, Angry",
value=f"{DEFAULT_STYLE}, {DEFAULT_STYLE}, {DEFAULT_STYLE}",
)
style_merge_button = gr.Button("スタイルのマージ", variant="primary")
info_style_merge = gr.Textbox(label="情報")
text_input = gr.TextArea(label="テキスト", value="これはテストです。聞こえていますか?")
style = gr.Dropdown(
label="スタイル",
choices=["スタイルをマージしてください"],
value="スタイルをマージしてください",
)
emotion_weight = gr.Slider(
minimum=0,
maximum=50,
value=1,
step=0.1,
label="スタイルの強さ",
)
tts_button = gr.Button("音声合成", variant="primary")
audio_output = gr.Audio(label="結果")
model_name_a.change(
model_holder.update_model_files_gr,
inputs=[model_name_a],
outputs=[model_path_a],
)
model_name_b.change(
model_holder.update_model_files_gr,
inputs=[model_name_b],
outputs=[model_path_b],
)
refresh_button.click(
update_two_model_names_dropdown,
outputs=[model_name_a, model_path_a, model_name_b, model_path_b],
)
load_button.click(
get_styles_gr,
inputs=[model_name_a, model_name_b],
outputs=[styles_a, styles_b],
)
model_merge_button.click(
merge_models_gr,
inputs=[
model_name_a,
model_path_a,
model_name_b,
model_path_b,
new_name,
voice_slider,
speech_style_slider,
tempo_slider,
],
outputs=[info_model_merge],
)
style_merge_button.click(
merge_style_gr,
inputs=[
model_name_a,
model_name_b,
speech_style_slider,
new_name,
style_triple_list,
],
outputs=[info_style_merge, style],
)
tts_button.click(
simple_tts,
inputs=[new_name, text_input, style, emotion_weight],
outputs=[audio_output],
)
app.launch(inbrowser=True)

View File

@@ -7,9 +7,9 @@ import numpy as np
from sklearn.cluster import AgglomerativeClustering, KMeans
from sklearn.manifold import TSNE
from config import config
from common.constants import DEFAULT_STYLE
MAX_CLUSTER_NUM = 10
DEFAULT_EMOTION: str = "Neutral"
tsne = TSNE(n_components=2, random_state=42, metric="cosine")
@@ -125,7 +125,7 @@ def save_style_vectors(model_name, style_names: str):
config_path = os.path.join(result_dir, "config.json")
if not os.path.exists(config_path):
return f"{config_path}が存在しません。"
style_name_list = [DEFAULT_EMOTION]
style_name_list = [DEFAULT_STYLE]
style_name_list = style_name_list + style_names.split(",")
if len(style_name_list) != len(centroids) + 1:
return f"スタイルの数が合いません。`,`で正しく{len(centroids)}個に区切られているか確認してください: {style_names}"
@@ -174,7 +174,7 @@ def save_style_vectors_from_files(model_name, audio_files_text, style_names_text
config_path = os.path.join(result_dir, "config.json")
if not os.path.exists(config_path):
return f"{config_path}が存在しません。"
style_name_list = [DEFAULT_EMOTION]
style_name_list = [DEFAULT_STYLE]
style_name_list = style_name_list + style_names
assert len(style_name_list) == len(style_vectors)
@@ -194,7 +194,7 @@ initial_md = f"""
Style-Bert-VITS2でこまかくスタイルを指定して音声合成するには、モデルごとにスタイルベクトルのファイル`style_vectors.npy`を手動で作成する必要があります。
ただし、学習の過程で自動的に平均スタイル「Neutral」のみは作成されるので、それをそのまま使うこともできますその場合はこのWebUIは使いません
ただし、学習の過程で自動的に平均スタイル「{DEFAULT_STYLE}」のみは作成されるので、それをそのまま使うこともできますその場合はこのWebUIは使いません
このプロセスは学習とは全く関係がないので、何回でも独立して繰り返して試せます。また学習中にもたぶん軽いので動くはずです。
@@ -275,7 +275,7 @@ with gr.Blocks(theme="NoCrypt/miku") as app:
style_names = gr.Textbox(
"Angry, Sad, Happy",
label="スタイルの名前",
info=f"スタイルの名前を`,`で区切って入力してください(日本語可)。例: `Angry, Sad, Happy`や`怒り, 悲しみ, 喜び`など。平均音声は{DEFAULT_EMOTION}として自動的に保存されます。",
info=f"スタイルの名前を`,`で区切って入力してください(日本語可)。例: `Angry, Sad, Happy`や`怒り, 悲しみ, 喜び`など。平均音声は{DEFAULT_STYLE}として自動的に保存されます。",
)
with gr.Row():
save_button = gr.Button("スタイルベクトルを保存", variant="primary")
@@ -288,7 +288,7 @@ with gr.Blocks(theme="NoCrypt/miku") as app:
gr.Markdown("下のテキスト欄に、各スタイルの代表音声のファイル名を`,`区切りで、その横に対応するスタイル名を`,`区切りで入力してください。")
gr.Markdown("例: `angry.wav, sad.wav, happy.wav`と`Angry, Sad, Happy`")
gr.Markdown(
f"注意: {DEFAULT_EMOTION}スタイルは自動的に保存されます、手動では{DEFAULT_EMOTION}という名前のスタイルは指定しないでください。"
f"注意: {DEFAULT_STYLE}スタイルは自動的に保存されます、手動では{DEFAULT_STYLE}という名前のスタイルは指定しないでください。"
)
with gr.Row():
audio_files_text = gr.Textbox(

View File

@@ -7,8 +7,8 @@ from multiprocessing import cpu_count
import gradio as gr
import yaml
from tools.log import logger
from tools.subprocess_utils import run_script_with_log, second_elem_of
from common.log import logger
from common.subprocess_utils import run_script_with_log, second_elem_of
try:
import google.colab