Ver 2.7.0 maybe

This commit is contained in:
litagin02
2025-08-24 11:27:27 +09:00
parent 4859d64e9f
commit c51c80fe1a
24 changed files with 173 additions and 33 deletions

View File

@@ -15,6 +15,7 @@ You can install via `pip install style-bert-vits2` (inference only), see [librar
- [Zennの解説記事](https://zenn.dev/litagin/articles/034819a5256ff4)
- [**リリースページ**](https://github.com/litagin02/Style-Bert-VITS2/releases/)、[更新履歴](/docs/CHANGELOG.md)
- 2025-08-24: Ver 2.7.0: 外部ライブラリ [Aivis Project](https://aivis-project.com/) 等との連携のため、ONNX変換のGUI追加、また音声認識モデルとして `litagin/anime-whisper` の追加等
- 2024-09-09: Ver 2.6.1: Google colabでうまく学習できない等のバグ修正のみ
- 2024-06-16: Ver 2.6.0 (モデルの差分マージ・加重マージ・ヌルモデルマージの追加、使い道については[この記事](https://zenn.dev/litagin/articles/1297b1dc7bdc79)参照)
- 2024-06-14: Ver 2.5.1 (利用規約をお願いへ変更したのみ)
@@ -155,6 +156,10 @@ API仕様は起動後に`/docs`にて確認ください。
2つのモデルを、「声質」「声の高さ」「感情表現」「テンポ」の4点で混ぜ合わせて、新しいモデルを作ったり、また「あるモデルに、別の2つのモデルの差分を足す」等の操作ができます。
`App.bat`をダブルクリックか`python app.py`して開くWebUIの「マージ」タブから、2つのモデルを選択してマージすることができます。または`Merge.bat`をダブルクリックでもその単独タブが開きます。
### ONNX変換
タブの「ONNX変換」から、学習済みsafetensorsファイルをONNX形式に変換することができます。これは外部ライブラリ等でONNX形式ファイルが必要な場合に使えます。例えば [Aivis Project](https://aivis-project.com/) では [AIVM Generator](https://aivm-generator.aivis-project.com/) を使って、safetensorsファイルとONNXファイルからAivis Speech用のモデルを作成できます。
### 自然性評価
学習結果のうちどのステップ数がいいかの「一つの」指標として、[SpeechMOS](https://github.com/tarepan/SpeechMOS) を使うスクリプトを用意しています:

8
app.py
View File

@@ -5,6 +5,7 @@ import gradio as gr
import torch
from config import get_path_config
from gradio_tabs.convert_onnx import create_onnx_app
from gradio_tabs.dataset import create_dataset_app
from gradio_tabs.inference import create_inference_app
from gradio_tabs.merge import create_merge_app
@@ -42,7 +43,10 @@ if device == "cuda" and not torch.cuda.is_available():
path_config = get_path_config()
model_holder = TTSModelHolder(
Path(path_config.assets_root), device, torch_device_to_onnx_providers(device)
Path(path_config.assets_root),
device,
torch_device_to_onnx_providers(device),
ignore_onnx=True,
)
with gr.Blocks(theme=GRADIO_THEME) as app:
@@ -58,6 +62,8 @@ with gr.Blocks(theme=GRADIO_THEME) as app:
create_style_vectors_app()
with gr.Tab("マージ"):
create_merge_app(model_holder=model_holder)
with gr.Tab("ONNX変換"):
create_onnx_app(model_holder=model_holder)
app.launch(
server_name=args.host,

View File

@@ -90,6 +90,7 @@ if __name__ == "__main__":
executor.map(process_line, zip(lines, add_blank)),
total=len(lines),
file=SAFE_STDOUT,
dynamic_ncols=True,
)
)

View File

@@ -425,6 +425,16 @@
"# 学習結果を試す・マージ・スタイル分けはこちらから\n",
"!python app.py --share"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# ONNX変換は、変換したいsafetensorsファイルを指定してこのセルを実行してください。\n",
"!python convert_onnx.py --model \"Data/your_model/your_model_e100_s10000.safetensors\""
]
}
],
"metadata": {

View File

@@ -68,7 +68,7 @@ class TextAudioSpeakerLoader(torch.utils.data.Dataset):
skipped = 0
logger.info("Init dataset...")
for _id, spk, language, text, phones, tone, word2ph in tqdm(
self.audiopaths_sid_text, file=sys.stdout
self.audiopaths_sid_text, file=sys.stdout, dynamic_ncols=True
):
audiopath = f"{_id}"
# if self.min_text_len <= len(phones) and len(phones) <= self.max_text_len:

View File

@@ -1,5 +1,11 @@
# Changelog
## v2.7.0 (2025-08-24)
- [AivisProject](https://aivis-project.com/) 等との連携のため、ONNX変換のGUI追加 (Gradioタブの一つとして)
- また音声認識モデルとして `litagin/anime-whisper` の追加
- その他軽微な修正等
## v2.6.1 (2024-09-09)
- Google colabで、torchのバージョン由来でエラーが発生する不具合の修正たぶん

101
gradio_tabs/convert_onnx.py Normal file
View File

@@ -0,0 +1,101 @@
from pathlib import Path
import gradio as gr
from style_bert_vits2.constants import GRADIO_THEME
from style_bert_vits2.logging import logger
from style_bert_vits2.tts_model import NullModelParam, TTSModelHolder
from style_bert_vits2.utils.subprocess import run_script_with_log
def call_convert_onnx(
model: str,
):
if model == "":
return "Error: モデル名を入力してください。"
logger.info("Start converting model to onnx...")
cmd = [
"convert_onnx.py",
"--model",
model,
]
success, message = run_script_with_log(cmd, ignore_warning=True)
if not success:
return f"Error: {message}"
return "ONNX変換が完了しました。"
initial_md = """
safetensors形式のモデルをONNX形式に変換します。
このONNXモデルは、[AIVM Generator](https://aivm-generator.aivis-project.com/) 等でさらにAIVM形式・AIVMX形式に変換して[AivisSpeech](https://aivis-project.com/)で利用できます。
変換には5分以上ほどの時間がかかります。進捗状況はターミナルのログを参照してください。
"""
def create_onnx_app(model_holder: TTSModelHolder) -> gr.Blocks:
def get_model_files(model_name: str):
return [str(f) for f in model_holder.model_files_dict[model_name]]
model_names = model_holder.model_names
if len(model_names) == 0:
logger.error(
f"モデルが見つかりませんでした。{model_holder.root_dir}にモデルを置いてください。"
)
with gr.Blocks() as app:
gr.Markdown(
f"Error: モデルが見つかりませんでした。{model_holder.root_dir}にモデルを置いてください。"
)
return app
initial_id = 0
initial_pth_files = get_model_files(model_names[initial_id])
with gr.Blocks(theme=GRADIO_THEME) as app:
gr.Markdown(initial_md)
with gr.Row():
with gr.Column():
model_name = gr.Dropdown(
label="モデル一覧",
choices=model_names,
value=model_names[initial_id],
)
model_path = gr.Dropdown(
label="モデルファイル",
choices=initial_pth_files,
value=initial_pth_files[0],
)
refresh_button = gr.Button("更新")
convert_button = gr.Button("ONNX形式に変換", variant="primary")
info = gr.Textbox(label="情報")
model_name.change(
model_holder.update_model_files_for_gradio,
inputs=[model_name],
outputs=[model_path],
)
def refresh_fn() -> tuple[gr.Dropdown, gr.Dropdown]:
names, files, _ = model_holder.update_model_names_for_gradio()
return names, files
refresh_button.click(
refresh_fn,
outputs=[model_name, model_path],
)
convert_button.click(
call_convert_onnx,
inputs=[model_path],
outputs=[info],
)
return app
if __name__ == "__main__":
from config import get_path_config
path_config = get_path_config()
assets_root = path_config.assets_root
model_holder = TTSModelHolder(assets_root, "cpu", "", ignore_onnx=True)
app = create_onnx_app(model_holder)
app.launch(inbrowser=True)

View File

@@ -51,6 +51,11 @@ def do_transcribe(
):
if model_name == "":
return "Error: モデル名を入力してください。"
if hf_repo_id == "litagin/anime-whisper":
logger.info(
"Since litagin/anime-whisper does not support initial prompt, it will be ignored."
)
initial_prompt = ""
cmd = [
"transcribe.py",
@@ -159,34 +164,30 @@ def create_dataset_app() -> gr.Blocks:
result1 = gr.Textbox(label="結果")
with gr.Row():
with gr.Column():
use_hf_whisper = gr.Checkbox(
label="HuggingFaceのWhisperを使う速度が速いがVRAMを多く使う",
value=False,
)
whisper_model = gr.Dropdown(
[
"tiny",
"base",
"small",
"medium",
"large",
"large-v2",
"large-v3",
],
label="Whisperモデル",
value="large-v3",
)
use_hf_whisper = gr.Checkbox(
label="HuggingFaceのWhisperを使う速度が速いがVRAMを多く使う",
value=False,
visible=True,
)
hf_repo_id = gr.Dropdown(
[
"openai/whisper-large-v3-turbo",
"openai/whisper-large-v3",
"openai/whisper-large-v2",
"kotoba-tech/kotoba-whisper-v1.1",
"kotoba-tech/kotoba-whisper-v2.1",
"litagin/anime-whisper",
],
label="HuggingFaceのWhisper repo_id",
value="openai/whisper-large-v3",
value="openai/whisper-large-v3-turbo",
visible=False,
)
compute_type = gr.Dropdown(
@@ -258,12 +259,13 @@ def create_dataset_app() -> gr.Blocks:
)
use_hf_whisper.change(
lambda x: (
gr.update(visible=not x),
gr.update(visible=x),
gr.update(visible=x),
gr.update(visible=not x),
),
inputs=[use_hf_whisper],
outputs=[hf_repo_id, batch_size, compute_type],
outputs=[whisper_model, hf_repo_id, batch_size, compute_type],
)
return app

View File

@@ -322,6 +322,7 @@ def save_style_vectors_by_dirs(model_name: str, audio_dir_str: str):
total=len(audio_files),
file=SAFE_STDOUT,
desc="Generating style vectors",
dynamic_ncols=True,
)
)

View File

@@ -98,7 +98,7 @@ def preprocess(
transcription_path.open("r", encoding="utf-8") as trans_file,
cleaned_path.open("w", encoding="utf-8") as out_file,
):
for line in tqdm(trans_file, file=SAFE_STDOUT, total=total_lines):
for line in tqdm(trans_file, file=SAFE_STDOUT, total=total_lines, dynamic_ncols=True):
try:
processed_line = process_line(
line,

View File

@@ -27,7 +27,7 @@ pypinyin
pyworld-prebuilt
# stable_ts
# tensorboard
torch
torchaudio
torch<2.4
torchaudio<2.4
transformers
umap-learn

View File

@@ -27,7 +27,7 @@ pypinyin
pyworld-prebuilt
stable_ts
tensorboard
torch
torchaudio
torch<2.4
torchaudio<2.4
transformers
umap-learn

View File

@@ -140,7 +140,7 @@ if __name__ == "__main__":
for file in original_files
]
for future in tqdm(
as_completed(futures), total=len(original_files), file=SAFE_STDOUT
as_completed(futures), total=len(original_files), file=SAFE_STDOUT, dynamic_ncols=True
):
pass

View File

@@ -202,7 +202,9 @@ if args.preload_onnx_bert:
)
onnx_bert_models.load_tokenizer(Languages.JP)
model_holder = TTSModelHolder(model_dir, device, torch_device_to_onnx_providers(device))
model_holder = TTSModelHolder(
model_dir, device, torch_device_to_onnx_providers(device), ignore_onnx=True
)
if len(model_holder.model_names) == 0:
logger.error(f"Models not found in {model_dir}.")
sys.exit(1)

View File

@@ -230,7 +230,7 @@ if __name__ == "__main__":
for t in threads:
t.start()
pbar = tqdm(total=len(audio_files), file=SAFE_STDOUT)
pbar = tqdm(total=len(audio_files), file=SAFE_STDOUT, dynamic_ncols=True)
for file in audio_files:
q.put(file)

View File

@@ -70,7 +70,7 @@ safetensors_files = list(safetensors_files)
logger.info(f"There are {len(safetensors_files)} models.")
for model_file in tqdm(safetensors_files):
for model_file in tqdm(safetensors_files, dynamic_ncols=True):
# `test_e10_s1000.safetensors`` -> 1000を取り出す
match = re.search(r"_s(\d+)\.safetensors$", model_file.name)
if match:

View File

@@ -11,10 +11,8 @@ import torch
from numpy.typing import NDArray
from style_bert_vits2.logging import logger
from style_bert_vits2.models.utils import (
checkpoints, # type: ignore # noqa: F401
safetensors, # type: ignore # noqa: F401
)
from style_bert_vits2.models.utils import checkpoints # type: ignore # noqa: F401
from style_bert_vits2.models.utils import safetensors # type: ignore # noqa: F401
if TYPE_CHECKING:

View File

@@ -121,7 +121,7 @@ def normalize_text(text: str) -> str:
# 結合文字の濁点・半濁点を削除
# 通常の「ば」等はそのままのこされる、「あ゛」は上で「あ゙」になりここで「あ」になる
res = res.replace("\u3099", "") # 結合文字の濁点を削除、る゙ → る
res = res.replace("\u309A", "") # 結合文字の半濁点を削除、な゚ → な
res = res.replace("\u309a", "") # 結合文字の半濁点を削除、な゚ → な
return res

View File

@@ -71,7 +71,7 @@ class WorkerServer:
def start_server(self, port: int, no_client_timeout: int = 30) -> None:
logger.info("start pyopenjtalk worker server")
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server_socket:
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server_socket.bind((socket.gethostname(), port))
server_socket.listen()
sockets = [server_socket]

View File

@@ -41,7 +41,7 @@ def run_script_with_log(
def second_elem_of(
original_function: Callable[..., tuple[Any, Any]]
original_function: Callable[..., tuple[Any, Any]],
) -> Callable[..., Any]:
"""
与えられた関数をラップし、その戻り値の 2 番目の要素のみを返す関数を生成する。

View File

@@ -80,6 +80,7 @@ if __name__ == "__main__":
executor.map(process_line, training_lines),
total=len(training_lines),
file=SAFE_STDOUT,
dynamic_ncols=True,
)
)
ok_training_lines = [line for line, error in training_results if error is None]
@@ -102,6 +103,7 @@ if __name__ == "__main__":
executor.map(process_line, val_lines),
total=len(val_lines),
file=SAFE_STDOUT,
dynamic_ncols=True,
)
)
ok_val_lines = [line for line, error in val_results if error is None]

View File

@@ -485,6 +485,7 @@ def run():
initial=global_step,
smoothing=0.05,
file=SAFE_STDOUT,
dynamic_ncols=True,
)
initial_step = global_step

View File

@@ -562,6 +562,7 @@ def run():
initial=global_step,
smoothing=0.05,
file=SAFE_STDOUT,
dynamic_ncols=True,
)
initial_step = global_step

View File

@@ -73,8 +73,8 @@ def transcribe_files_with_hf_whisper(
max_new_tokens=128,
chunk_length_s=30,
batch_size=batch_size,
torch_dtype=torch.float16,
device="cuda",
torch_dtype=torch.float16 if device == "cuda" else torch.float32,
device=device,
trust_remote_code=True,
# generate_kwargs=generate_kwargs,
)
@@ -154,6 +154,10 @@ if __name__ == "__main__":
wav_files = [f for f in input_dir.rglob("*.wav") if f.is_file()]
wav_files = sorted(wav_files, key=lambda x: str(x))
logger.info(f"Found {len(wav_files)} WAV files")
if len(wav_files) == 0:
logger.warning(f"No WAV files found in {input_dir}")
sys.exit(1)
if output_file.exists():
logger.warning(f"{output_file} exists, backing up to {output_file}.bak")
@@ -183,7 +187,7 @@ if __name__ == "__main__":
except ValueError as e:
logger.warning(f"Failed to load model, so use `auto` compute_type: {e}")
model = WhisperModel(args.model, device=device)
for wav_file in tqdm(wav_files, file=SAFE_STDOUT):
for wav_file in tqdm(wav_files, file=SAFE_STDOUT, dynamic_ncols=True):
text = transcribe_with_faster_whisper(
model=model,
audio_file=wav_file,
@@ -198,7 +202,7 @@ if __name__ == "__main__":
else:
model_id = args.hf_repo_id
logger.info(f"Loading HF Whisper model ({model_id})")
pbar = tqdm(total=len(wav_files), file=SAFE_STDOUT)
pbar = tqdm(total=len(wav_files), file=SAFE_STDOUT, dynamic_ncols=True)
results = transcribe_files_with_hf_whisper(
audio_files=wav_files,
model_id=model_id,