Add: Preparation for ONNX inference support ②

This commit is contained in:
tsukumi
2024-09-17 09:02:14 +09:00
parent eede796c4e
commit b9fdeb4492
5 changed files with 439 additions and 96 deletions

View File

@@ -12,14 +12,16 @@ from style_bert_vits2.models.models_jp_extra import (
SynthesizerTrn as SynthesizerTrnJPExtra, SynthesizerTrn as SynthesizerTrnJPExtra,
) )
from style_bert_vits2.nlp import ( from style_bert_vits2.nlp import (
clean_text, clean_text_with_given_phone_tone,
cleaned_text_to_sequence, cleaned_text_to_sequence,
extract_bert_feature, extract_bert_feature,
) )
from style_bert_vits2.nlp.symbols import SYMBOLS from style_bert_vits2.nlp.symbols import SYMBOLS
def get_net_g(model_path: str, version: str, device: str, hps: HyperParameters): def get_net_g(
model_path: str, version: str, device: str, hps: HyperParameters
) -> Union[SynthesizerTrn, SynthesizerTrnJPExtra]:
if version.endswith("JP-Extra"): if version.endswith("JP-Extra"):
logger.info("Using JP-Extra model") logger.info("Using JP-Extra model")
net_g = SynthesizerTrnJPExtra( net_g = SynthesizerTrnJPExtra(
@@ -104,59 +106,19 @@ def get_text(
assist_text_weight: float = 0.7, assist_text_weight: float = 0.7,
given_phone: Optional[list[str]] = None, given_phone: Optional[list[str]] = None,
given_tone: Optional[list[int]] = None, given_tone: Optional[list[int]] = None,
): ) -> tuple[
torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor
]:
use_jp_extra = hps.version.endswith("JP-Extra") use_jp_extra = hps.version.endswith("JP-Extra")
# 推論時のみ呼び出されるので、raise_yomi_error は False に設定 norm_text, phone, tone, word2ph = clean_text_with_given_phone_tone(
norm_text, phone, tone, word2ph = clean_text(
text, text,
language_str, language_str,
given_phone=given_phone,
given_tone=given_tone,
use_jp_extra=use_jp_extra, use_jp_extra=use_jp_extra,
# 推論時のみ呼び出されるので、raise_yomi_error は False に設定
raise_yomi_error=False, raise_yomi_error=False,
) )
# phone と tone の両方が与えられた場合はそれを使う
if given_phone is not None and given_tone is not None:
# 指定された phone と指定された tone 両方の長さが一致していなければならない
if len(given_phone) != len(given_tone):
raise InvalidPhoneError(
f"Length of given_phone ({len(given_phone)}) != length of given_tone ({len(given_tone)})"
)
# 与えられた音素数と pyopenjtalk で生成した読みの音素数が一致しない
if len(given_phone) != sum(word2ph):
# 日本語の場合、len(given_phone) と sum(word2ph) が一致するように word2ph を適切に調整する
# 他の言語は word2ph の調整方法が思いつかないのでエラー
if language_str == Languages.JP:
from style_bert_vits2.nlp.japanese.g2p import adjust_word2ph
# use_jp_extra でない場合は given_phone 内の「N」を「n」に変換
if not use_jp_extra:
given_phone = [p if p != "N" else "n" for p in given_phone]
# clean_text() から取得した word2ph を調整結果で上書き
word2ph = adjust_word2ph(word2ph, phone, given_phone)
# 上記処理により word2ph の合計が given_phone の長さと一致するはず
# それでも一致しない場合、大半は読み上げテキストと given_phone が著しく乖離していて調整し切れなかったことを意味する
if len(given_phone) != sum(word2ph):
raise InvalidPhoneError(
f"Length of given_phone ({len(given_phone)}) != sum of word2ph ({sum(word2ph)})"
)
else:
raise InvalidPhoneError(
f"Length of given_phone ({len(given_phone)}) != sum of word2ph ({sum(word2ph)})"
)
phone = given_phone
# 生成あるいは指定された phone と指定された tone 両方の長さが一致していなければならない
if len(phone) != len(given_tone):
raise InvalidToneError(
f"Length of phone ({len(phone)}) != length of given_tone ({len(given_tone)})"
)
tone = given_tone
# tone だけが与えられた場合は clean_text() で生成した phone と合わせて使う
elif given_tone is not None:
# 生成した phone と指定された tone 両方の長さが一致していなければならない
if len(phone) != len(given_tone):
raise InvalidToneError(
f"Length of phone ({len(phone)}) != length of given_tone ({len(given_tone)})"
)
tone = given_tone
phone, tone, language = cleaned_text_to_sequence(phone, tone, language_str) phone, tone, language = cleaned_text_to_sequence(phone, tone, language_str)
if hps.data.add_blank: if hps.data.add_blank:
@@ -220,7 +182,7 @@ def infer(
assist_text_weight: float = 0.7, assist_text_weight: float = 0.7,
given_phone: Optional[list[str]] = None, given_phone: Optional[list[str]] = None,
given_tone: Optional[list[int]] = None, given_tone: Optional[list[int]] = None,
): ) -> NDArray[Any]:
is_jp_extra = hps.version.endswith("JP-Extra") is_jp_extra = hps.version.endswith("JP-Extra")
bert, ja_bert, en_bert, phones, tones, lang_ids = get_text( bert, ja_bert, en_bert, phones, tones, lang_ids = get_text(
text, text,
@@ -246,6 +208,7 @@ def infer(
bert = bert[:, :-2] bert = bert[:, :-2]
ja_bert = ja_bert[:, :-2] ja_bert = ja_bert[:, :-2]
en_bert = en_bert[:, :-2] en_bert = en_bert[:, :-2]
with torch.no_grad(): with torch.no_grad():
x_tst = phones.to(device).unsqueeze(0) x_tst = phones.to(device).unsqueeze(0)
tones = tones.to(device).unsqueeze(0) tones = tones.to(device).unsqueeze(0)
@@ -257,6 +220,7 @@ def infer(
style_vec_tensor = torch.from_numpy(style_vec).to(device).unsqueeze(0) style_vec_tensor = torch.from_numpy(style_vec).to(device).unsqueeze(0)
del phones del phones
sid_tensor = torch.LongTensor([sid]).to(device) sid_tensor = torch.LongTensor([sid]).to(device)
if is_jp_extra: if is_jp_extra:
output = cast(SynthesizerTrnJPExtra, net_g).infer( output = cast(SynthesizerTrnJPExtra, net_g).infer(
x_tst, x_tst,
@@ -287,7 +251,9 @@ def infer(
noise_scale_w=noise_scale_w, noise_scale_w=noise_scale_w,
length_scale=length_scale, length_scale=length_scale,
) )
audio = output[0][0, 0].data.cpu().float().numpy() audio = output[0][0, 0].data.cpu().float().numpy()
del ( del (
x_tst, x_tst,
tones, tones,
@@ -301,12 +267,5 @@ def infer(
) # , emo ) # , emo
if torch.cuda.is_available(): if torch.cuda.is_available():
torch.cuda.empty_cache() torch.cuda.empty_cache()
return audio return audio
class InvalidPhoneError(ValueError):
pass
class InvalidToneError(ValueError):
pass

View File

@@ -0,0 +1,178 @@
from typing import Any, Optional, Sequence
import numpy as np
import onnxruntime
from numpy.typing import NDArray
from style_bert_vits2.constants import Languages
from style_bert_vits2.models import commons
from style_bert_vits2.models.hyper_parameters import HyperParameters
from style_bert_vits2.nlp import (
clean_text_with_given_phone_tone,
cleaned_text_to_sequence,
extract_bert_feature_onnx,
)
def get_text_onnx(
text: str,
language_str: Languages,
hps: HyperParameters,
onnx_providers: list[str],
onnx_provider_options: Optional[Sequence[dict[str, Any]]],
assist_text: Optional[str] = None,
assist_text_weight: float = 0.7,
given_phone: Optional[list[str]] = None,
given_tone: Optional[list[int]] = None,
) -> tuple[
NDArray[Any], NDArray[Any], NDArray[Any], NDArray[Any], NDArray[Any], NDArray[Any]
]:
use_jp_extra = hps.version.endswith("JP-Extra")
norm_text, phone, tone, word2ph = clean_text_with_given_phone_tone(
text,
language_str,
given_phone=given_phone,
given_tone=given_tone,
use_jp_extra=use_jp_extra,
# 推論時のみ呼び出されるので、raise_yomi_error は False に設定
raise_yomi_error=False,
)
phone, tone, language = cleaned_text_to_sequence(phone, tone, language_str)
if hps.data.add_blank:
phone = commons.intersperse(phone, 0)
tone = commons.intersperse(tone, 0)
language = commons.intersperse(language, 0)
for i in range(len(word2ph)):
word2ph[i] = word2ph[i] * 2
word2ph[0] += 1
bert_ori = extract_bert_feature_onnx(
norm_text,
word2ph,
language_str,
onnx_providers,
onnx_provider_options,
assist_text,
assist_text_weight,
)
del word2ph
assert bert_ori.shape[-1] == len(phone), phone
if language_str == Languages.ZH:
bert = bert_ori
ja_bert = np.zeros((1024, len(phone)))
en_bert = np.zeros((1024, len(phone)))
elif language_str == Languages.JP:
bert = np.zeros((1024, len(phone)))
ja_bert = bert_ori
en_bert = np.zeros((1024, len(phone)))
elif language_str == Languages.EN:
bert = np.zeros((1024, len(phone)))
ja_bert = np.zeros((1024, len(phone)))
en_bert = bert_ori
else:
raise ValueError("language_str should be ZH, JP or EN")
assert bert.shape[-1] == len(
phone
), f"Bert seq len {bert.shape[-1]} != {len(phone)}"
phone = np.array(phone)
tone = np.array(tone)
language = np.array(language)
return bert, ja_bert, en_bert, phone, tone, language
def infer_onnx(
text: str,
style_vec: NDArray[Any],
sdp_ratio: float,
noise_scale: float,
noise_scale_w: float,
length_scale: float,
sid: int, # In the original Bert-VITS2, its speaker_name: str, but here it's id
language: Languages,
hps: HyperParameters,
onnx_session: onnxruntime.InferenceSession,
onnx_providers: list[str],
onnx_provider_options: Optional[Sequence[dict[str, Any]]],
skip_start: bool = False,
skip_end: bool = False,
assist_text: Optional[str] = None,
assist_text_weight: float = 0.7,
given_phone: Optional[list[str]] = None,
given_tone: Optional[list[int]] = None,
) -> NDArray[Any]:
is_jp_extra = hps.version.endswith("JP-Extra")
bert, ja_bert, en_bert, phones, tones, lang_ids = get_text_onnx(
text,
language,
hps,
onnx_providers=onnx_providers,
onnx_provider_options=onnx_provider_options,
assist_text=assist_text,
assist_text_weight=assist_text_weight,
given_phone=given_phone,
given_tone=given_tone,
)
if skip_start:
phones = phones[3:]
tones = tones[3:]
lang_ids = lang_ids[3:]
bert = bert[:, 3:]
ja_bert = ja_bert[:, 3:]
en_bert = en_bert[:, 3:]
if skip_end:
phones = phones[:-2]
tones = tones[:-2]
lang_ids = lang_ids[:-2]
bert = bert[:, :-2]
ja_bert = ja_bert[:, :-2]
en_bert = en_bert[:, :-2]
x_tst = np.expand_dims(phones, axis=0)
tones = np.expand_dims(tones, axis=0)
lang_ids = np.expand_dims(lang_ids, axis=0)
bert = np.expand_dims(bert, axis=0)
ja_bert = np.expand_dims(ja_bert, axis=0)
en_bert = np.expand_dims(en_bert, axis=0)
x_tst_lengths = np.array([phones.shape[0]], dtype=np.int64)
style_vec_tensor = np.expand_dims(style_vec, axis=0)
del phones
sid_tensor = np.array([sid], dtype=np.int64)
input_names = [input.name for input in onnx_session.get_inputs()]
output_name = onnx_session.get_outputs()[0].name
if is_jp_extra:
output = onnx_session.run(
[output_name],
{
input_names[0]: x_tst,
input_names[1]: x_tst_lengths,
input_names[2]: sid_tensor,
input_names[3]: tones,
input_names[4]: lang_ids,
input_names[5]: ja_bert,
input_names[6]: style_vec_tensor,
input_names[7]: length_scale,
input_names[8]: sdp_ratio,
},
)
else:
raise NotImplementedError("Not implemented yet")
audio = output[0][0, 0]
del (
x_tst,
tones,
lang_ids,
bert,
x_tst_lengths,
sid_tensor,
ja_bert,
en_bert,
style_vec,
) # , emo
return audio

View File

@@ -1,4 +1,8 @@
from typing import TYPE_CHECKING, Optional from __future__ import annotations
from typing import TYPE_CHECKING, Any, Optional, Sequence
from numpy.typing import NDArray
from style_bert_vits2.constants import Languages from style_bert_vits2.constants import Languages
from style_bert_vits2.nlp.symbols import ( from style_bert_vits2.nlp.symbols import (
@@ -24,9 +28,9 @@ def extract_bert_feature(
device: str, device: str,
assist_text: Optional[str] = None, assist_text: Optional[str] = None,
assist_text_weight: float = 0.7, assist_text_weight: float = 0.7,
) -> "torch.Tensor": ) -> torch.Tensor:
""" """
テキストから BERT の特徴量を抽出する テキストから BERT の特徴量を抽出する (PyTorch 推論)
Args: Args:
text (str): テキスト text (str): テキスト
@@ -52,6 +56,46 @@ def extract_bert_feature(
return extract_bert_feature(text, word2ph, device, assist_text, assist_text_weight) return extract_bert_feature(text, word2ph, device, assist_text, assist_text_weight)
def extract_bert_feature_onnx(
text: str,
word2ph: list[int],
language: Languages,
onnx_providers: list[str],
onnx_provider_options: Optional[Sequence[dict[str, Any]]],
assist_text: Optional[str] = None,
assist_text_weight: float = 0.7,
) -> NDArray[Any]:
"""
テキストから BERT の特徴量を抽出する (ONNX 推論)
Args:
text (str): テキスト
word2ph (list[int]): 元のテキストの各文字に音素が何個割り当てられるかを表すリスト
language (Languages): テキストの言語
onnx_providers (list[str]): ONNX 推論で利用する ExecutionProvider (CPUExecutionProvider, CUDAExecutionProvider など)
onnx_provider_options (Optional[dict[str, Any]]): ONNX 推論で利用する ExecutionProvider のオプション
assist_text (Optional[str], optional): 補助テキスト (デフォルト: None)
assist_text_weight (float, optional): 補助テキストの重み (デフォルト: 0.7)
Returns:
NDArray[Any]: BERT の特徴量
"""
if language == Languages.JP:
from style_bert_vits2.nlp.japanese.bert_feature import extract_bert_feature_onnx
else:
raise ValueError(f"Language {language} not supported")
return extract_bert_feature_onnx(
text,
word2ph,
onnx_providers,
onnx_provider_options,
assist_text,
assist_text_weight,
)
def clean_text( def clean_text(
text: str, text: str,
language: Languages, language: Languages,
@@ -96,6 +140,87 @@ def clean_text(
return norm_text, phones, tones, word2ph return norm_text, phones, tones, word2ph
def clean_text_with_given_phone_tone(
text: str,
language: Languages,
given_phone: Optional[list[str]] = None,
given_tone: Optional[list[int]] = None,
use_jp_extra: bool = True,
raise_yomi_error: bool = False,
) -> tuple[str, list[str], list[int], list[int]]:
"""
テキストをクリーニングし、音素に変換する
変換時、given_phone や given_tone が与えられた場合はそれを調整して使う
Args:
text (str): クリーニングするテキスト
language (Languages): テキストの言語
given_phone (Optional[list[int]], optional): 読み上げテキストの読みを表す音素列。指定する場合は given_tone も別途指定が必要. Defaults to None.
given_tone (Optional[list[int]], optional): アクセントのトーンのリスト. Defaults to None.
use_jp_extra (bool, optional): テキストが日本語の場合に JP-Extra モデルを利用するかどうか。Defaults to True.
raise_yomi_error (bool, optional): False の場合、読めない文字が消えたような扱いとして処理される。Defaults to False.
Returns:
tuple[str, list[str], list[int], list[int]]: クリーニングされたテキストと、音素・アクセント・元のテキストの各文字に音素が何個割り当てられるかのリスト
"""
# 与えられたテキストをクリーニング
norm_text, phone, tone, word2ph = clean_text(
text,
language,
use_jp_extra=use_jp_extra,
raise_yomi_error=raise_yomi_error,
)
# phone と tone の両方が与えられた場合はそれを使う
if given_phone is not None and given_tone is not None:
# 指定された phone と指定された tone 両方の長さが一致していなければならない
if len(given_phone) != len(given_tone):
raise InvalidPhoneError(
f"Length of given_phone ({len(given_phone)}) != length of given_tone ({len(given_tone)})"
)
# 与えられた音素数と pyopenjtalk で生成した読みの音素数が一致しない
if len(given_phone) != sum(word2ph):
# 日本語の場合、len(given_phone) と sum(word2ph) が一致するように word2ph を適切に調整する
# 他の言語は word2ph の調整方法が思いつかないのでエラー
if language == Languages.JP:
from style_bert_vits2.nlp.japanese.g2p import adjust_word2ph
# use_jp_extra でない場合は given_phone 内の「N」を「n」に変換
if not use_jp_extra:
given_phone = [p if p != "N" else "n" for p in given_phone]
# clean_text() から取得した word2ph を調整結果で上書き
word2ph = adjust_word2ph(word2ph, phone, given_phone)
# 上記処理により word2ph の合計が given_phone の長さと一致するはず
# それでも一致しない場合、大半は読み上げテキストと given_phone が著しく乖離していて調整し切れなかったことを意味する
if len(given_phone) != sum(word2ph):
raise InvalidPhoneError(
f"Length of given_phone ({len(given_phone)}) != sum of word2ph ({sum(word2ph)})"
)
else:
raise InvalidPhoneError(
f"Length of given_phone ({len(given_phone)}) != sum of word2ph ({sum(word2ph)})"
)
phone = given_phone
# 生成あるいは指定された phone と指定された tone 両方の長さが一致していなければならない
if len(phone) != len(given_tone):
raise InvalidToneError(
f"Length of phone ({len(phone)}) != length of given_tone ({len(given_tone)})"
)
tone = given_tone
# tone だけが与えられた場合は clean_text() で生成した phone と合わせて使う
elif given_tone is not None:
# 生成した phone と指定された tone 両方の長さが一致していなければならない
if len(phone) != len(given_tone):
raise InvalidToneError(
f"Length of phone ({len(phone)}) != length of given_tone ({len(given_tone)})"
)
tone = given_tone
return norm_text, phone, tone, word2ph
def cleaned_text_to_sequence( def cleaned_text_to_sequence(
cleaned_phones: list[str], tones: list[int], language: Languages cleaned_phones: list[str], tones: list[int], language: Languages
) -> tuple[list[int], list[int], list[int]]: ) -> tuple[list[int], list[int], list[int]]:
@@ -118,3 +243,11 @@ def cleaned_text_to_sequence(
lang_ids = [lang_id for i in phones] lang_ids = [lang_id for i in phones]
return phones, tones, lang_ids return phones, tones, lang_ids
class InvalidPhoneError(ValueError):
pass
class InvalidToneError(ValueError):
pass

View File

@@ -1,12 +1,19 @@
from typing import Optional from __future__ import annotations
import torch from typing import TYPE_CHECKING, Any, Optional, Sequence
import numpy as np
from numpy.typing import NDArray
from style_bert_vits2.constants import Languages from style_bert_vits2.constants import Languages
from style_bert_vits2.nlp import bert_models from style_bert_vits2.nlp import bert_models
from style_bert_vits2.nlp.japanese.g2p import text_to_sep_kata from style_bert_vits2.nlp.japanese.g2p import text_to_sep_kata
if TYPE_CHECKING:
import torch
def extract_bert_feature( def extract_bert_feature(
text: str, text: str,
word2ph: list[int], word2ph: list[int],
@@ -15,7 +22,7 @@ def extract_bert_feature(
assist_text_weight: float = 0.7, assist_text_weight: float = 0.7,
) -> torch.Tensor: ) -> torch.Tensor:
""" """
日本語のテキストから BERT の特徴量を抽出する 日本語のテキストから BERT の特徴量を抽出する (PyTorch 推論)
Args: Args:
text (str): 日本語のテキスト text (str): 日本語のテキスト
@@ -28,6 +35,8 @@ def extract_bert_feature(
torch.Tensor: BERT の特徴量 torch.Tensor: BERT の特徴量
""" """
import torch
# 各単語が何文字かを作る `word2ph` を使う必要があるので、読めない文字は必ず無視する # 各単語が何文字かを作る `word2ph` を使う必要があるので、読めない文字は必ず無視する
# でないと `word2ph` の結果とテキストの文字数結果が整合性が取れない # でないと `word2ph` の結果とテキストの文字数結果が整合性が取れない
text = "".join(text_to_sep_kata(text, raise_yomi_error=False)[0]) text = "".join(text_to_sep_kata(text, raise_yomi_error=False)[0])
@@ -72,3 +81,64 @@ def extract_bert_feature(
phone_level_feature = torch.cat(phone_level_feature, dim=0) phone_level_feature = torch.cat(phone_level_feature, dim=0)
return phone_level_feature.T return phone_level_feature.T
def extract_bert_feature_onnx(
text: str,
word2ph: list[int],
onnx_providers: list[str],
onnx_provider_options: Optional[Sequence[dict[str, Any]]],
assist_text: Optional[str] = None,
assist_text_weight: float = 0.7,
) -> NDArray[Any]:
"""
日本語のテキストから BERT の特徴量を抽出する (ONNX 推論)
Args:
text (str): 日本語のテキスト
word2ph (list[int]): 元のテキストの各文字に音素が何個割り当てられるかを表すリスト
onnx_providers (list[str]): ONNX 推論で利用する ExecutionProvider (CPUExecutionProvider, CUDAExecutionProvider など)
onnx_provider_options (Optional[dict[str, Any]]): ONNX 推論で利用する ExecutionProvider のオプション
assist_text (Optional[str], optional): 補助テキスト (デフォルト: None)
assist_text_weight (float, optional): 補助テキストの重み (デフォルト: 0.7)
Returns:
NDArray[Any]: BERT の特徴量
"""
# 各単語が何文字かを作る `word2ph` を使う必要があるので、読めない文字は必ず無視する
# でないと `word2ph` の結果とテキストの文字数結果が整合性が取れない
text = "".join(text_to_sep_kata(text, raise_yomi_error=False)[0])
if assist_text:
assist_text = "".join(text_to_sep_kata(assist_text, raise_yomi_error=False)[0])
tokenizer = Tokenizer.from_file("tokenizer.json")
token_ids = [1]
attention_mask = [1]
for word in text:
encoded = tokenizer.encode(word)
token_ids.extend(encoded.ids[1:-1])
attention_mask.extend(encoded.attention_mask[1:-1])
token_ids.append(2)
attention_mask.append(1)
bert_output_name = bert_session.get_outputs()[0].name
res = bert_session.run(
[bert_output_name],
{
"input_ids": np.array(token_ids).reshape(1, -1),
"attention_mask": np.array(attention_mask).reshape(1, -1),
},
)[0]
assert len(word2ph) == len(text) + 2, text
word2phone = word2ph
phone_level_feature = []
for i in range(len(word2phone)):
repeat_feature = np.tile(res[i], (word2phone[i], 1))
phone_level_feature.append(repeat_feature)
phone_level_feature = np.concatenate(phone_level_feature, axis=0)
return phone_level_feature.T

View File

@@ -1,5 +1,7 @@
from __future__ import annotations
from pathlib import Path from pathlib import Path
from typing import Any, Optional, Sequence, Union from typing import TYPE_CHECKING, Any, Optional, Sequence, Union
import numpy as np import numpy as np
import onnxruntime import onnxruntime
@@ -20,14 +22,16 @@ from style_bert_vits2.constants import (
) )
from style_bert_vits2.logging import logger from style_bert_vits2.logging import logger
from style_bert_vits2.models.hyper_parameters import HyperParameters from style_bert_vits2.models.hyper_parameters import HyperParameters
from style_bert_vits2.models.infer import get_net_g, infer
from style_bert_vits2.models.models import SynthesizerTrn
from style_bert_vits2.models.models_jp_extra import (
SynthesizerTrn as SynthesizerTrnJPExtra,
)
from style_bert_vits2.voice import adjust_voice from style_bert_vits2.voice import adjust_voice
if TYPE_CHECKING:
from style_bert_vits2.models.models import SynthesizerTrn
from style_bert_vits2.models.models_jp_extra import (
SynthesizerTrn as SynthesizerTrnJPExtra,
)
class TTSModel: class TTSModel:
""" """
Style-Bert-VITS2 の音声合成モデルを操作するクラス。 Style-Bert-VITS2 の音声合成モデルを操作するクラス。
@@ -109,10 +113,8 @@ class TTSModel:
# __net_g は PyTorch 推論時のみ遅延初期化される # __net_g は PyTorch 推論時のみ遅延初期化される
self.__net_g: Union[SynthesizerTrn, SynthesizerTrnJPExtra, None] = None self.__net_g: Union[SynthesizerTrn, SynthesizerTrnJPExtra, None] = None
# __inference_session* は ONNX 推論時のみ遅延初期化される # __onnx_session は ONNX 推論時のみ遅延初期化される
self.__onnx_session: Optional[onnxruntime.InferenceSession] = None self.__onnx_session: Optional[onnxruntime.InferenceSession] = None
self.__onnx_input_names: Optional[list[str]] = None
self.__onnx_output_names: Optional[list[str]] = None
def load(self) -> None: def load(self) -> None:
""" """
@@ -121,6 +123,8 @@ class TTSModel:
# PyTorch 推論時 # PyTorch 推論時
if not self.is_onnx_model: if not self.is_onnx_model:
from style_bert_vits2.models.infer import get_net_g
self.__net_g = get_net_g( self.__net_g = get_net_g(
model_path=str(self.model_path), model_path=str(self.model_path),
version=self.hyper_parameters.version, version=self.hyper_parameters.version,
@@ -135,14 +139,8 @@ class TTSModel:
providers=self.onnx_providers, providers=self.onnx_providers,
provider_options=self.onnx_provider_options, provider_options=self.onnx_provider_options,
) )
self.__onnx_input_names = [
input.name for input in self.__onnx_session.get_inputs()
]
self.__onnx_output_names = [
output.name for output in self.__onnx_session.get_outputs()
]
def __get_style_vector(self, style_id: int, weight: float = 1.0) -> NDArray[Any]: def get_style_vector(self, style_id: int, weight: float = 1.0) -> NDArray[Any]:
""" """
スタイルベクトルを取得する。 スタイルベクトルを取得する。
@@ -158,7 +156,7 @@ class TTSModel:
style_vec = mean + (style_vec - mean) * weight style_vec = mean + (style_vec - mean) * weight
return style_vec return style_vec
def __get_style_vector_from_audio( def get_style_vector_from_audio(
self, audio_path: str, weight: float = 1.0 self, audio_path: str, weight: float = 1.0
) -> NDArray[Any]: ) -> NDArray[Any]:
""" """
@@ -199,7 +197,7 @@ class TTSModel:
xvec = mean + (xvec - mean) * weight xvec = mean + (xvec - mean) * weight
return xvec return xvec
def __convert_to_16_bit_wav(self, data: NDArray[Any]) -> NDArray[Any]: def convert_to_16_bit_wav(self, data: NDArray[Any]) -> NDArray[Any]:
""" """
音声データを 16-bit int 形式に変換する。 音声データを 16-bit int 形式に変換する。
gradio.processing_utils.convert_to_16_bit_wav() を移植したもの。 gradio.processing_utils.convert_to_16_bit_wav() を移植したもの。
@@ -299,9 +297,9 @@ class TTSModel:
# スタイルベクトルを取得 # スタイルベクトルを取得
if reference_audio_path is None: if reference_audio_path is None:
style_id = self.style2id[style] style_id = self.style2id[style]
style_vector = self.__get_style_vector(style_id, style_weight) style_vector = self.get_style_vector(style_id, style_weight)
else: else:
style_vector = self.__get_style_vector_from_audio( style_vector = self.get_style_vector_from_audio(
reference_audio_path, style_weight reference_audio_path, style_weight
) )
@@ -309,6 +307,8 @@ class TTSModel:
if not self.is_onnx_model: if not self.is_onnx_model:
import torch import torch
from style_bert_vits2.models.infer import infer
# モデルがロードされていない場合はロードする # モデルがロードされていない場合はロードする
if self.__net_g is None: if self.__net_g is None:
self.load() self.load()
@@ -365,13 +365,12 @@ class TTSModel:
# ONNX 推論時 # ONNX 推論時
else: else:
from style_bert_vits2.models.infer_onnx import infer_onnx
# モデルがロードされていない場合はロードする # モデルがロードされていない場合はロードする
if self.__onnx_session is None: if self.__onnx_session is None:
self.load() self.load()
assert self.__onnx_session is not None assert self.__onnx_session is not None
assert self.__onnx_input_names is not None
assert self.__onnx_output_names is not None
# 通常のテキストから音声を生成 # 通常のテキストから音声を生成
if not line_split: if not line_split:
@@ -384,7 +383,9 @@ class TTSModel:
sid=speaker_id, sid=speaker_id,
language=language, language=language,
hps=self.hyper_parameters, hps=self.hyper_parameters,
device=self.device, onnx_session=self.__onnx_session,
onnx_providers=self.onnx_providers,
onnx_provider_options=self.onnx_provider_options,
assist_text=assist_text, assist_text=assist_text,
assist_text_weight=assist_text_weight, assist_text_weight=assist_text_weight,
style_vec=style_vector, style_vec=style_vector,
@@ -408,7 +409,9 @@ class TTSModel:
sid=speaker_id, sid=speaker_id,
language=language, language=language,
hps=self.hyper_parameters, hps=self.hyper_parameters,
device=self.device, onnx_session=self.__onnx_session,
onnx_providers=self.onnx_providers,
onnx_provider_options=self.onnx_provider_options,
assist_text=assist_text, assist_text=assist_text,
assist_text_weight=assist_text_weight, assist_text_weight=assist_text_weight,
style_vec=style_vector, style_vec=style_vector,
@@ -427,7 +430,7 @@ class TTSModel:
pitch_scale=pitch_scale, pitch_scale=pitch_scale,
intonation_scale=intonation_scale, intonation_scale=intonation_scale,
) )
audio = self.__convert_to_16_bit_wav(audio) audio = self.convert_to_16_bit_wav(audio)
return (self.hyper_parameters.data.sampling_rate, audio) return (self.hyper_parameters.data.sampling_rate, audio)
@@ -560,9 +563,9 @@ class TTSModelHolder:
speakers = list(self.current_model.spk2id.keys()) speakers = list(self.current_model.spk2id.keys())
styles = list(self.current_model.style2id.keys()) styles = list(self.current_model.style2id.keys())
return ( return (
gr.Dropdown(choices=styles, value=styles[0]), # type: ignore gr.Dropdown(choices=styles, value=styles[0]),
gr.Button(interactive=True, value="音声合成"), gr.Button(interactive=True, value="音声合成"),
gr.Dropdown(choices=speakers, value=speakers[0]), # type: ignore gr.Dropdown(choices=speakers, value=speakers[0]),
) )
self.current_model = TTSModel( self.current_model = TTSModel(
model_path=model_path, model_path=model_path,
@@ -573,16 +576,16 @@ class TTSModelHolder:
speakers = list(self.current_model.spk2id.keys()) speakers = list(self.current_model.spk2id.keys())
styles = list(self.current_model.style2id.keys()) styles = list(self.current_model.style2id.keys())
return ( return (
gr.Dropdown(choices=styles, value=styles[0]), # type: ignore gr.Dropdown(choices=styles, value=styles[0]),
gr.Button(interactive=True, value="音声合成"), gr.Button(interactive=True, value="音声合成"),
gr.Dropdown(choices=speakers, value=speakers[0]), # type: ignore gr.Dropdown(choices=speakers, value=speakers[0]),
) )
def update_model_files_for_gradio(self, model_name: str): def update_model_files_for_gradio(self, model_name: str):
import gradio as gr import gradio as gr
model_files = [str(f) for f in self.model_files_dict[model_name]] model_files = [str(f) for f in self.model_files_dict[model_name]]
return gr.Dropdown(choices=model_files, value=model_files[0]) # type: ignore return gr.Dropdown(choices=model_files, value=model_files[0])
def update_model_names_for_gradio( def update_model_names_for_gradio(
self, self,
@@ -595,7 +598,7 @@ class TTSModelHolder:
str(f) for f in self.model_files_dict[initial_model_name] str(f) for f in self.model_files_dict[initial_model_name]
] ]
return ( return (
gr.Dropdown(choices=self.model_names, value=initial_model_name), # type: ignore gr.Dropdown(choices=self.model_names, value=initial_model_name),
gr.Dropdown(choices=initial_model_files, value=initial_model_files[0]), # type: ignore gr.Dropdown(choices=initial_model_files, value=initial_model_files[0]),
gr.Button(interactive=False), # For tts_button gr.Button(interactive=False), # For tts_button
) )