diff --git a/app.py b/app.py index 7057f45..997bd04 100644 --- a/app.py +++ b/app.py @@ -1,30 +1,51 @@ -import pyopenjtalk -import gradio as gr -from webui import ( - create_dataset_app, - create_train_app, - create_merge_app, - create_style_vectors_app, -) +import argparse from pathlib import Path -pyopenjtalk.unset_user_dict() +import gradio as gr +import torch +import yaml -setting_json = Path("webui/setting.json") +from common.constants import GRADIO_THEME, LATEST_VERSION +from common.tts_model import ModelHolder +from webui import ( + create_dataset_app, + create_inference_app, + create_merge_app, + create_style_vectors_app, + create_train_app, +) -with gr.Blocks() as app: +# 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") +parser.add_argument("--no_autolaunch", action="store_true") +parser.add_argument("--share", action="store_true") + +args = parser.parse_args() +device = args.device +if device == "cuda" and not torch.cuda.is_available(): + device = "cpu" + +model_holder = ModelHolder(Path(assets_root), device) + +with gr.Blocks(theme=GRADIO_THEME) as app: + gr.Markdown(f"# Style-Bert-VITS2 WebUI (version {LATEST_VERSION})") with gr.Tabs(): - with gr.Tab("Hello"): - gr.Markdown("## Hello, Gradio!") - gr.Textbox("input", label="Input Text") - with gr.Tab("Dataset"): + with gr.Tab("音声合成"): + create_inference_app(model_holder=model_holder) + with gr.Tab("データセット作成"): create_dataset_app() - with gr.Tab("Train"): + with gr.Tab("学習"): create_train_app() - with gr.Tab("Merge"): - create_merge_app() - with gr.Tab("Create Style Vectors"): + with gr.Tab("スタイル作成"): create_style_vectors_app() + with gr.Tab("マージ"): + create_merge_app(model_holder=model_holder) -app.launch(inbrowser=True) +app.launch(inbrowser=not args.no_autolaunch, share=args.share) diff --git a/common/constants.py b/common/constants.py index fe62019..f3ccb69 100644 --- a/common/constants.py +++ b/common/constants.py @@ -4,7 +4,7 @@ import enum # See https://huggingface.co/spaces/gradio/theme-gallery for more themes GRADIO_THEME: str = "NoCrypt/miku" -LATEST_VERSION: str = "2.3.1" +LATEST_VERSION: str = "2.4" USER_DICT_DIR = "dict_data" diff --git a/webui/dataset.py b/webui/dataset.py index 5ed656c..3169fd2 100644 --- a/webui/dataset.py +++ b/webui/dataset.py @@ -8,12 +8,6 @@ from common.constants import GRADIO_THEME from common.log import logger from common.subprocess_utils import run_script_with_log -# 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"] - def do_slice( model_name: str, @@ -69,13 +63,11 @@ def do_transcribe( ] ) if not success: - return f"Error: {message}" + return f"Error: {message}. しかし何故かエラーが起きても正常に終了している場合がほとんどなので、書き起こし結果を確認して問題なければ学習に使えます。" return "音声の文字起こしが完了しました。" -initial_md = """ -# 簡易学習用データセット作成ツール - +how_to_md = """ Style-Bert-VITS2の学習用データセットを作成するためのツールです。以下の2つからなります。 - 与えられた音声からちょうどいい長さの発話区間を切り取りスライス @@ -107,10 +99,10 @@ Style-Bert-VITS2の学習用データセットを作成するためのツール """ -def create_dataset_app(): - - with gr.Blocks(theme=GRADIO_THEME) as app: - gr.Markdown(initial_md) +def create_dataset_app() -> gr.Blocks: + with gr.Blocks() as app: + with gr.Accordion("使い方", open=False): + gr.Markdown(how_to_md) model_name = gr.Textbox( label="モデル名を入力してください(話者名としても使われます)。" ) @@ -118,8 +110,8 @@ def create_dataset_app(): with gr.Row(): with gr.Column(): input_dir = gr.Textbox( - label="入力フォルダ名(デフォルトはinputs)", - placeholder="inputs", + label="元音声の入っているフォルダパス", + value="inputs", info="下記フォルダにwavファイルを入れておいてください", ) min_sec = gr.Slider( @@ -201,20 +193,4 @@ def create_dataset_app(): outputs=[result2], ) - parser = argparse.ArgumentParser() - parser.add_argument( - "--server-name", - type=str, - default=None, - help="Server name for Gradio app", - ) - parser.add_argument( - "--no-autolaunch", - action="store_true", - default=False, - help="Do not launch app automatically", - ) - args = parser.parse_args() - - # app.launch(inbrowser=not args.no_autolaunch, server_name=args.server_name) return app diff --git a/webui/inference.py b/webui/inference.py index 663d5a6..94124b9 100644 --- a/webui/inference.py +++ b/webui/inference.py @@ -1,14 +1,9 @@ import argparse import datetime import json -import os -import sys -from pathlib import Path from typing import Optional import gradio as gr -import torch -import yaml from common.constants import ( DEFAULT_ASSIST_TEXT_WEIGHT, @@ -21,7 +16,6 @@ from common.constants import ( DEFAULT_STYLE, DEFAULT_STYLE_WEIGHT, GRADIO_THEME, - LATEST_VERSION, Languages, ) from common.log import logger @@ -29,119 +23,9 @@ from common.tts_model import ModelHolder from infer import InvalidToneError from text.japanese import g2kata_tone, kata_tone2phone_tone, text_normalize -# 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"] - languages = [l.value for l in Languages] -def tts_fn( - model_name, - model_path, - text, - language, - reference_audio_path, - sdp_ratio, - noise_scale, - noise_scale_w, - length_scale, - line_split, - split_interval, - assist_text, - assist_text_weight, - use_assist_text, - style, - style_weight, - kata_tone_json_str, - use_tone, - speaker, - pitch_scale, - intonation_scale, -): - model_holder.load_model_gr(model_name, model_path) - - wrong_tone_message = "" - kata_tone: Optional[list[tuple[str, int]]] = None - if use_tone and kata_tone_json_str != "": - if language != "JP": - logger.warning("Only Japanese is supported for tone generation.") - wrong_tone_message = "アクセント指定は現在日本語のみ対応しています。" - if line_split: - logger.warning("Tone generation is not supported for line split.") - wrong_tone_message = ( - "アクセント指定は改行で分けて生成を使わない場合のみ対応しています。" - ) - try: - kata_tone = [] - json_data = json.loads(kata_tone_json_str) - # tupleを使うように変換 - for kana, tone in json_data: - assert isinstance(kana, str) and tone in (0, 1), f"{kana}, {tone}" - kata_tone.append((kana, tone)) - except Exception as e: - logger.warning(f"Error occurred when parsing kana_tone_json: {e}") - wrong_tone_message = f"アクセント指定が不正です: {e}" - kata_tone = None - - # toneは実際に音声合成に代入される際のみnot Noneになる - tone: Optional[list[int]] = None - if kata_tone is not None: - phone_tone = kata_tone2phone_tone(kata_tone) - tone = [t for _, t in phone_tone] - - speaker_id = model_holder.current_model.spk2id[speaker] - - start_time = datetime.datetime.now() - - assert model_holder.current_model is not None - - try: - sr, audio = model_holder.current_model.infer( - text=text, - language=language, - reference_audio_path=reference_audio_path, - sdp_ratio=sdp_ratio, - noise=noise_scale, - noisew=noise_scale_w, - length=length_scale, - line_split=line_split, - split_interval=split_interval, - assist_text=assist_text, - assist_text_weight=assist_text_weight, - use_assist_text=use_assist_text, - style=style, - style_weight=style_weight, - given_tone=tone, - sid=speaker_id, - pitch_scale=pitch_scale, - intonation_scale=intonation_scale, - ) - except InvalidToneError as e: - logger.error(f"Tone error: {e}") - return f"Error: アクセント指定が不正です:\n{e}", None, kata_tone_json_str - except ValueError as e: - logger.error(f"Value error: {e}") - return f"Error: {e}", None, kata_tone_json_str - - end_time = datetime.datetime.now() - duration = (end_time - start_time).total_seconds() - - if tone is None and language == "JP": - # アクセント指定に使えるようにアクセント情報を返す - norm_text = text_normalize(text) - kata_tone = g2kata_tone(norm_text) - kata_tone_json_str = json.dumps(kata_tone, ensure_ascii=False) - elif tone is None: - kata_tone_json_str = "" - message = f"Success, time: {duration} seconds." - if wrong_tone_message != "": - message = wrong_tone_message + "\n" + message - return message, (sr, audio), kata_tone_json_str - - initial_text = "こんにちは、初めまして。あなたの名前はなんていうの?" examples = [ @@ -202,9 +86,7 @@ examples = [ ] initial_md = f""" -# Style-Bert-VITS2 ver {LATEST_VERSION} 音声合成 - -- Ver 2.3で追加されたエディターのほうが実際に読み上げさせるには使いやすいかもしれません。`Editor.bat`か`python server_editor.py`で起動できます。 +- Ver 2.3で追加されたエディターのほうが実際に読み上げさせるには使いやすいかもしれません。`Editor.bat`か`python server_editor.py --inbrowser`で起動できます。 - 初期からある[jvnvのモデル](https://huggingface.co/litagin/style_bert_vits2_jvnv)は、[JVNVコーパス(言語音声と非言語音声を持つ日本語感情音声コーパス)](https://sites.google.com/site/shinnosuketakamichi/research-topics/jvnv_corpus)で学習されたモデルです。ライセンスは[CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/deed.ja)です。 """ @@ -254,43 +136,119 @@ def gr_util(item): return (gr.update(visible=False), gr.update(visible=True)) -def create_inference_app(): - parser = argparse.ArgumentParser() - parser.add_argument("--cpu", action="store_true", help="Use CPU instead of GPU") - parser.add_argument( - "--dir", "-d", type=str, help="Model directory", default=assets_root - ) - parser.add_argument( - "--share", action="store_true", help="Share this app publicly", default=False - ) - parser.add_argument( - "--server-name", - type=str, - default=None, - help="Server name for Gradio app", - ) - parser.add_argument( - "--no-autolaunch", - action="store_true", - default=False, - help="Do not launch app automatically", - ) - args = parser.parse_args() - model_dir = Path(args.dir) +def create_inference_app(model_holder: ModelHolder) -> gr.Blocks: + def tts_fn( + model_name, + model_path, + text, + language, + reference_audio_path, + sdp_ratio, + noise_scale, + noise_scale_w, + length_scale, + line_split, + split_interval, + assist_text, + assist_text_weight, + use_assist_text, + style, + style_weight, + kata_tone_json_str, + use_tone, + speaker, + pitch_scale, + intonation_scale, + ): + model_holder.load_model(model_name, model_path) + assert model_holder.current_model is not None - if args.cpu: - device = "cpu" - else: - device = "cuda" if torch.cuda.is_available() else "cpu" + wrong_tone_message = "" + kata_tone: Optional[list[tuple[str, int]]] = None + if use_tone and kata_tone_json_str != "": + if language != "JP": + logger.warning("Only Japanese is supported for tone generation.") + wrong_tone_message = "アクセント指定は現在日本語のみ対応しています。" + if line_split: + logger.warning("Tone generation is not supported for line split.") + wrong_tone_message = ( + "アクセント指定は改行で分けて生成を使わない場合のみ対応しています。" + ) + try: + kata_tone = [] + json_data = json.loads(kata_tone_json_str) + # tupleを使うように変換 + for kana, tone in json_data: + assert isinstance(kana, str) and tone in (0, 1), f"{kana}, {tone}" + kata_tone.append((kana, tone)) + except Exception as e: + logger.warning(f"Error occurred when parsing kana_tone_json: {e}") + wrong_tone_message = f"アクセント指定が不正です: {e}" + kata_tone = None - model_holder = ModelHolder(model_dir, device) + # toneは実際に音声合成に代入される際のみnot Noneになる + tone: Optional[list[int]] = None + if kata_tone is not None: + phone_tone = kata_tone2phone_tone(kata_tone) + tone = [t for _, t in phone_tone] + + speaker_id = model_holder.current_model.spk2id[speaker] + + start_time = datetime.datetime.now() + + try: + sr, audio = model_holder.current_model.infer( + text=text, + language=language, + reference_audio_path=reference_audio_path, + sdp_ratio=sdp_ratio, + noise=noise_scale, + noisew=noise_scale_w, + length=length_scale, + line_split=line_split, + split_interval=split_interval, + assist_text=assist_text, + assist_text_weight=assist_text_weight, + use_assist_text=use_assist_text, + style=style, + style_weight=style_weight, + given_tone=tone, + sid=speaker_id, + pitch_scale=pitch_scale, + intonation_scale=intonation_scale, + ) + except InvalidToneError as e: + logger.error(f"Tone error: {e}") + return f"Error: アクセント指定が不正です:\n{e}", None, kata_tone_json_str + except ValueError as e: + logger.error(f"Value error: {e}") + return f"Error: {e}", None, kata_tone_json_str + + end_time = datetime.datetime.now() + duration = (end_time - start_time).total_seconds() + + if tone is None and language == "JP": + # アクセント指定に使えるようにアクセント情報を返す + norm_text = text_normalize(text) + kata_tone = g2kata_tone(norm_text) + kata_tone_json_str = json.dumps(kata_tone, ensure_ascii=False) + elif tone is None: + kata_tone_json_str = "" + message = f"Success, time: {duration} seconds." + if wrong_tone_message != "": + message = wrong_tone_message + "\n" + message + return message, (sr, audio), kata_tone_json_str model_names = model_holder.model_names if len(model_names) == 0: logger.error( - f"モデルが見つかりませんでした。{model_dir}にモデルを置いてください。" + f"モデルが見つかりませんでした。{model_holder.root_dir}にモデルを置いてください。" ) - sys.exit(1) + with gr.Blocks() as app: + gr.Markdown( + f"Error: モデルが見つかりませんでした。{model_holder.root_dir}にモデルを置いてください。" + ) + return app initial_id = 0 initial_pth_files = model_holder.model_files_dict[model_names[initial_id]] @@ -497,8 +455,4 @@ def create_inference_app(): outputs=[style, ref_audio_path], ) - # app.launch( - # inbrowser=not args.no_autolaunch, share=args.share, server_name=args.server_name - # ) - return app diff --git a/webui/merge.py b/webui/merge.py index c002e33..c9386ef 100644 --- a/webui/merge.py +++ b/webui/merge.py @@ -1,7 +1,6 @@ import argparse import json import os -import sys from pathlib import Path import gradio as gr @@ -28,8 +27,6 @@ with open(os.path.join("configs", "paths.yml"), "r", encoding="utf-8") as f: # dataset_root = path_config["dataset_root"] assets_root = path_config["assets_root"] -model_holder = ModelHolder(Path(assets_root), device) - def merge_style(model_name_a, model_name_b, weight, output_name, style_triple_list): """ @@ -331,13 +328,17 @@ Happy, Surprise, HappySurprise """ -def create_merge_app(): +def create_merge_app(model_holder: ModelHolder) -> gr.Blocks: model_names = model_holder.model_names if len(model_names) == 0: logger.error( f"モデルが見つかりませんでした。{assets_root}にモデルを置いてください。" ) - sys.exit(1) + with gr.Blocks() as app: + gr.Markdown( + f"Error: モデルが見つかりませんでした。{assets_root}にモデルを置いてください。" + ) + return app initial_id = 0 initial_model_files = model_holder.model_files_dict[model_names[initial_id]] @@ -499,23 +500,4 @@ def create_merge_app(): outputs=[audio_output], ) - parser = argparse.ArgumentParser() - parser.add_argument( - "--server-name", - type=str, - default=None, - help="Server name for Gradio app", - ) - parser.add_argument( - "--no-autolaunch", - action="store_true", - default=False, - help="Do not launch app automatically", - ) - parser.add_argument("--share", action="store_true", default=False) - args = parser.parse_args() - - # app.launch( - # inbrowser=not args.no_autolaunch, server_name=args.server_name, share=args.share - # ) return app diff --git a/webui/style_vectors.py b/webui/style_vectors.py index 27b95bb..8effa37 100644 --- a/webui/style_vectors.py +++ b/webui/style_vectors.py @@ -277,9 +277,7 @@ def save_style_vectors_from_files( return f"成功!\n{style_vector_path}に保存し{config_path}を更新しました。" -initial_md = f""" -# Style Bert-VITS2 スタイルベクトルの作成 - +how_to_md = f""" Style-Bert-VITS2でこまかくスタイルを指定して音声合成するには、モデルごとにスタイルベクトルのファイル`style_vectors.npy`を手動で作成する必要があります。 ただし、学習の過程で自動的に平均スタイル「{DEFAULT_STYLE}」のみは作成されるので、それをそのまま使うこともできます(その場合はこのWebUIは使いません)。 @@ -326,7 +324,8 @@ https://ja.wikipedia.org/wiki/DBSCAN def create_style_vectors_app(): with gr.Blocks(theme=GRADIO_THEME) as app: - gr.Markdown(initial_md) + with gr.Accordion("使い方", open=False): + gr.Markdown(how_to_md) with gr.Row(): model_name = gr.Textbox(placeholder="your_model_name", label="モデル名") reduction_method = gr.Radio( @@ -461,23 +460,4 @@ def create_style_vectors_app(): outputs=[info2], ) - parser = argparse.ArgumentParser() - parser.add_argument( - "--server-name", - type=str, - default=None, - help="Server name for Gradio app", - ) - parser.add_argument( - "--no-autolaunch", - action="store_true", - default=False, - help="Do not launch app automatically", - ) - parser.add_argument("--share", action="store_true", default=False) - args = parser.parse_args() - - # app.launch( - # inbrowser=not args.no_autolaunch, server_name=args.server_name, share=args.share - # ) return app diff --git a/webui/train.py b/webui/train.py index 1c9a214..dee018e 100644 --- a/webui/train.py +++ b/webui/train.py @@ -14,7 +14,6 @@ from pathlib import Path import gradio as gr import yaml -from common.constants import GRADIO_THEME, LATEST_VERSION from common.log import logger from common.stdout_wrapper import SAFE_STDOUT from common.subprocess_utils import run_script_with_log, second_elem_of @@ -398,9 +397,7 @@ def run_tensorboard(model_name): yield gr.Button("Tensorboardを開く") -initial_md = f""" -# Style-Bert-VITS2 ver {LATEST_VERSION} 学習用WebUI - +how_to_md = f""" ## 使い方 - データを準備して、モデル名を入力して、必要なら設定を調整してから、「自動前処理を実行」ボタンを押してください。進捗状況等はターミナルに表示されます。 @@ -452,10 +449,11 @@ english_teacher.wav|Mary|EN|How are you? I'm fine, thank you, and you? def create_train_app(): - with gr.Blocks(theme=GRADIO_THEME).queue() as app: - gr.Markdown(initial_md) - with gr.Accordion(label="データの前準備", open=False): - gr.Markdown(prepare_md) + with gr.Blocks().queue() as app: + with gr.Accordion("使い方", open=False): + gr.Markdown(how_to_md) + with gr.Accordion(label="データの前準備", open=False): + gr.Markdown(prepare_md) model_name = gr.Textbox(label="モデル名") gr.Markdown("### 自動前処理") with gr.Row(variant="panel"): @@ -794,20 +792,5 @@ def create_train_app(): outputs=[use_jp_extra_train], ) - parser = argparse.ArgumentParser() - parser.add_argument( - "--server-name", - type=str, - default=None, - help="Server name for Gradio app", - ) - parser.add_argument( - "--no-autolaunch", - action="store_true", - default=False, - help="Do not launch app automatically", - ) - args = parser.parse_args() - # app.launch(inbrowser=not args.no_autolaunch, server_name=args.server_name) return app