From fb45423f13ae15b5dcc2959d4f0087fcbb795ebd Mon Sep 17 00:00:00 2001 From: Daiki Arai Date: Thu, 28 Dec 2023 11:05:23 +0900 Subject: [PATCH 01/10] rm unrequired param & add worker slider --- webui_train.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/webui_train.py b/webui_train.py index bf084dd..1f52f67 100644 --- a/webui_train.py +++ b/webui_train.py @@ -124,10 +124,10 @@ def bert_gen(model_name): return "Step 4: BERT特徴ファイルの生成が完了しました" -def style_gen(model_name): +def style_gen(model_name, num_processes): dataset_path, _, _, _, config_path = get_path(model_name) result = subprocess_wrapper( - [python, "style_gen.py", "--config", config_path, "--model", dataset_path] + [python, "style_gen.py", "--config", config_path, "--num_processes", str(num_processes)] ) if result.stderr: return f"{result.stderr}" @@ -232,6 +232,13 @@ if __name__ == "__main__": bert_gen_btn = gr.Button(value="実行", variant="primary") with gr.Column(): gr.Markdown(value="### Step 5: スタイル特徴ファイルの生成") + num_processes = gr.Slider( + label="スレッドサイズ", + value=2, + minimum=1, + maximum=16, + step=1, + ) style_gen_btn = gr.Button(value="実行", variant="primary") with gr.Row(): with gr.Column(): @@ -246,7 +253,7 @@ if __name__ == "__main__": resample_btn.click(resample, inputs=[model_name], outputs=[info]) preprocess_text_btn.click(preprocess_text, inputs=[model_name], outputs=[info]) bert_gen_btn.click(bert_gen, inputs=[model_name], outputs=[info]) - style_gen_btn.click(style_gen, inputs=[model_name], outputs=[info]) + style_gen_btn.click(style_gen, inputs=[model_name, num_processes], outputs=[info]) train_btn.click(train, inputs=[model_name], outputs=[info]) app.launch(share=False, inbrowser=True) From 76463d0b85f0533b06093405b135d7be9237994e Mon Sep 17 00:00:00 2001 From: litagin02 Date: Thu, 28 Dec 2023 13:50:10 +0900 Subject: [PATCH 02/10] Change train better WebUI (wip) --- config.py | 2 +- webui_train.py | 55 +++++++++++++++++++++++++++++++++----------------- 2 files changed, 37 insertions(+), 20 deletions(-) diff --git a/config.py b/config.py index 286f028..c270abf 100644 --- a/config.py +++ b/config.py @@ -97,7 +97,7 @@ class Style_gen_config: def __init__( self, config_path: str, - num_processes: int = 2, + num_processes: int = 4, device: str = "cuda", ): self.config_path = config_path diff --git a/webui_train.py b/webui_train.py index 1f52f67..e276cff 100644 --- a/webui_train.py +++ b/webui_train.py @@ -125,9 +125,16 @@ def bert_gen(model_name): def style_gen(model_name, num_processes): - dataset_path, _, _, _, config_path = get_path(model_name) + _, _, _, _, config_path = get_path(model_name) result = subprocess_wrapper( - [python, "style_gen.py", "--config", config_path, "--num_processes", str(num_processes)] + [ + python, + "style_gen.py", + "--config", + config_path, + "--num_processes", + str(num_processes), + ] ) if result.stderr: return f"{result.stderr}" @@ -187,8 +194,16 @@ english_teacher.wav|Mary|EN|How are you? I'm fine, thank you, and you? 日本語話者の単一話者データセットでも構いません。 """ +css = """ +/* マークダウン要素のスタイル */ +div.panel { + justify-content: space-between; +} + +""" + if __name__ == "__main__": - with gr.Blocks(theme="NoCrypt/miku") as app: + with gr.Blocks(theme="NoCrypt/miku", css=css) as app: gr.Markdown(initial_md) with gr.Accordion(label="データの前準備", open=False): gr.Markdown(prepare_md) @@ -196,9 +211,9 @@ if __name__ == "__main__": label="モデル名", ) info = gr.Textbox(label="状況") - with gr.Row(): - with gr.Column(): - gr.Markdown(value="### Step 1: 設定ファイルの生成") + with gr.Row(variant="panel"): + with gr.Column(variant="panel", min_width=160): + gr.Markdown(value="Step 1: 設定ファイルの生成") batch_size = gr.Slider( label="バッチサイズ", info="VRAM 12GBで4くらい", @@ -221,28 +236,28 @@ if __name__ == "__main__": value=True, ) generate_config_btn = gr.Button(value="実行", variant="primary") - with gr.Column(): - gr.Markdown(value="### Step 2: 音声ファイルの前処理") + with gr.Column(variant="panel", min_width=160): + gr.Markdown(value="Step 2: 音声ファイルの前処理") resample_btn = gr.Button(value="実行", variant="primary") - with gr.Column(): - gr.Markdown(value="### Step 3: 書き起こしファイルの前処理") + with gr.Column(variant="panel", min_width=160): + gr.Markdown(value="Step 3: 書き起こしファイルの前処理") preprocess_text_btn = gr.Button(value="実行", variant="primary") - with gr.Column(): - gr.Markdown(value="### Step 4: BERT特徴ファイルの生成") + with gr.Column(variant="panel", min_width=160): + gr.Markdown(value="Step 4: BERT特徴ファイルの生成") bert_gen_btn = gr.Button(value="実行", variant="primary") - with gr.Column(): - gr.Markdown(value="### Step 5: スタイル特徴ファイルの生成") + with gr.Column(variant="panel", min_width=160): + gr.Markdown(value="Step 5: スタイル特徴ファイルの生成") num_processes = gr.Slider( - label="スレッドサイズ", - value=2, + label="プロセス数", + value=4, minimum=1, maximum=16, step=1, ) style_gen_btn = gr.Button(value="実行", variant="primary") - with gr.Row(): + with gr.Row(variant="panel"): with gr.Column(): - gr.Markdown(value="### Final Step: 学習") + gr.Markdown(value="Final Step: 学習") train_btn = gr.Button(value="学習", variant="primary") generate_config_btn.click( @@ -253,7 +268,9 @@ if __name__ == "__main__": resample_btn.click(resample, inputs=[model_name], outputs=[info]) preprocess_text_btn.click(preprocess_text, inputs=[model_name], outputs=[info]) bert_gen_btn.click(bert_gen, inputs=[model_name], outputs=[info]) - style_gen_btn.click(style_gen, inputs=[model_name, num_processes], outputs=[info]) + style_gen_btn.click( + style_gen, inputs=[model_name, num_processes], outputs=[info] + ) train_btn.click(train, inputs=[model_name], outputs=[info]) app.launch(share=False, inbrowser=True) From 250cbcdcf2f85ef5679cefd14a2eddcab10b9550 Mon Sep 17 00:00:00 2001 From: litagin02 Date: Thu, 28 Dec 2023 14:22:02 +0900 Subject: [PATCH 03/10] Delete hf spaces demo env --- app.py | 25 +++---------------------- 1 file changed, 3 insertions(+), 22 deletions(-) diff --git a/app.py b/app.py index 5f6da4d..09ced91 100644 --- a/app.py +++ b/app.py @@ -14,9 +14,6 @@ from config import config from infer import get_net_g, infer from tools.log import logger -is_hf_spaces = os.getenv("SYSTEM") == "spaces" -limit = 100 - class Model: def __init__(self, model_path, config_path, style_vec_path, device): @@ -222,9 +219,6 @@ def tts_fn( emotion, emotion_weight, ): - if is_hf_spaces and len(text) > limit: - raise Exception(f"文字数が{limit}文字を超えています") - assert model_holder.current_model is not None start_time = datetime.datetime.now() @@ -253,7 +247,7 @@ def tts_fn( initial_text = "こんにちは、初めまして。あなたの名前はなんていうの?" -example_local = [ +examples = [ [initial_text, "JP"], [ """あなたがそんなこと言うなんて、私はとっても嬉しい。 @@ -307,18 +301,6 @@ example_local = [ ["语音合成是人工制造人类语音。用于此目的的计算机系统称为语音合成器,可以通过软件或硬件产品实现。", "ZH"], ] -example_hf_spaces = [ - [initial_text, "JP"], - ["えっと、私、あなたのことが好きです!もしよければ付き合ってくれませんか?", "JP"], - ["吾輩は猫である。名前はまだ無い。", "JP"], - ["どこで生れたかとんと見当がつかぬ。なんでも薄暗いじめじめした所でニャーニャー泣いていた事だけは記憶している。", "JP"], - ["やったー!テストで満点取れたよ!私とっても嬉しいな!", "JP"], - ["どうして私の意見を無視するの?許せない!ムカつく!あんたなんか死ねばいいのに。", "JP"], - ["あはははっ!この漫画めっちゃ笑える、見てよこれ、ふふふ、あはは。", "JP"], - ["あなたがいなくなって、私は一人になっちゃって、泣いちゃいそうなほど悲しい。", "JP"], - ["深層学習の応用により、感情やアクセントを含む声質の微妙な変化も再現されている。", "JP"], -] - initial_md = """ # Style-Bert-VITS2 音声合成 @@ -389,13 +371,12 @@ if __name__ == "__main__": model_holder = ModelHolder(model_dir, device) languages = ["JP", "EN", "ZH"] - examples = example_hf_spaces if is_hf_spaces else example_local model_names = model_holder.model_names if len(model_names) == 0: logger.error(f"モデルが見つかりませんでした。{model_dir}にモデルを置いてください。") sys.exit(1) - initial_id = 1 if is_hf_spaces else 0 + initial_id = 0 initial_pth_files = model_holder.model_files_dict[model_names[initial_id]] with gr.Blocks(theme="NoCrypt/miku") as app: @@ -416,7 +397,7 @@ if __name__ == "__main__": choices=initial_pth_files, value=initial_pth_files[0], ) - refresh_button = gr.Button("更新", scale=1, visible=not is_hf_spaces) + refresh_button = gr.Button("更新", scale=1, visible=True) load_button = gr.Button("ロード", scale=1, variant="primary") text_input = gr.TextArea(label="テキスト", value=initial_text) From 8689555cf45892f1a412c7a41ac6a29d24e54e92 Mon Sep 17 00:00:00 2001 From: litagin02 Date: Thu, 28 Dec 2023 15:05:18 +0900 Subject: [PATCH 04/10] Update README --- README.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 3fba8d8..b143266 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ Windows環境で最近のNVIDIA製グラボがあることを前提にしてい 1. [このzipファイル](https://github.com/litagin02/Style-Bert-VITS2/releases/download/1.0/Style-Bert-VITS2.zip)をダウンロードして展開し、中にある`Install-Style-Bert-VITS2.bat`をダブルクリックします。 2. 待つと自動で必要な環境がインストールされます。 -3. その後、自動的に音声合成するためのWebUIが起動したらインストール成功です。デフォルトのモデルがダウンロードされるので、そのまま遊ぶことができます。 +3. その後、自動的に音声合成するためのWebUIが起動したらインストール成功です。デフォルトのモデルがダウンロードされるているので、そのまま遊ぶことができます。 またアップデートをしたい場合は、`Update-Style-Bert-VITS2.bat`をダブルクリックしてください。 @@ -40,15 +40,15 @@ python -m venv venv venv\Scripts\activate pip3 install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu121 pip install -r requirements.txt -python initialize.py +python initialize.py # 必要なモデルとデフォルトTTSモデルをダウンロード ``` 最後を忘れずに。 ### 音声合成 -`App.bat`をダブルクリックするとWebUIが起動します。 +`App.bat`をダブルクリックするとWebUIが起動します。インストール時にデフォルトのモデルがダウンロードされているので、学習していなくてもそれを使うことができます。 -合成に必要なモデルファイルたちは以下のように配置します。 +音声合成に必要なモデルファイルたちの構造は以下の通りです(手動で配置する必要はありません)。 ``` model_assets ├── your_model @@ -71,6 +71,7 @@ model_assets - `Style.bat`をダブルクリックするとWebUIが起動します。 - この手順は、音声ファイルたちからスタイルを作るのに必要な手順です。 - 学習とは独立しているので、学習中でもできるし、学習が終わっても何度もやりなおせます。 +- スタイルについての詳細は[clustering.ipynb](clustering.ipynb)を参照してください。 ### データセット作り @@ -96,6 +97,7 @@ model_assets - [ ] 本家のver 2.1, 2.2, 2.3モデルの推論対応?(ver 2.1以外は明らかにめんどいのでたぶんやらない) - [ ] `server_fastapi.py`の対応、とくにAPIで使えるようになると嬉しい人が増えるのかもしれない - [ ] モデルのマージで声音と感情表現を混ぜる機能の実装 +- [ ] 英語等多言語対応? ## 実験したいこと @@ -109,7 +111,7 @@ In addition to the original reference (written below), I used the following repo - [Bert-VITS2](https://github.com/fishaudio/Bert-VITS2) - [EasyBertVits2](https://github.com/Zuntan03/EasyBertVits2) -The pretrained model is essentially taken from [the original base model of Bert-VITS2 v2.1](https://huggingface.co/Garydesu/bert-vits2_base_model-2.1), so all the credits go to the original author ([Fish Audio](https://github.com/fishaudio)): +[The pretrained model](https://huggingface.co/litagin/Style-Bert-VITS2-1.0-base) is essentially taken from [the original base model of Bert-VITS2 v2.1](https://huggingface.co/Garydesu/bert-vits2_base_model-2.1), so all the credits go to the original author ([Fish Audio](https://github.com/fishaudio)): Below is the original README.md. From b0fbcc4667f3009a556155615266f0c09a796b9c Mon Sep 17 00:00:00 2001 From: litagin02 Date: Thu, 28 Dec 2023 23:21:27 +0900 Subject: [PATCH 05/10] Refactor and add one-click training --- resample.py | 2 + tools/subprocess_utils.py | 33 +++++ webui_train.py | 292 +++++++++++++++++++++++++------------- 3 files changed, 230 insertions(+), 97 deletions(-) create mode 100644 tools/subprocess_utils.py diff --git a/resample.py b/resample.py index c8326ca..dbe5952 100644 --- a/resample.py +++ b/resample.py @@ -65,6 +65,8 @@ if __name__ == "__main__": twople = (spk_dir, filename, args) tasks.append(twople) + if len(tasks) == 0: + raise ValueError(f"No wav files found in {args.in_dir}") for _ in tqdm( pool.imap_unordered(process, tasks), file=sys.stdout, total=len(tasks) ): diff --git a/tools/subprocess_utils.py b/tools/subprocess_utils.py new file mode 100644 index 0000000..dea2e0d --- /dev/null +++ b/tools/subprocess_utils.py @@ -0,0 +1,33 @@ +import subprocess +import sys + +from .log import logger + +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=sys.stdout, + 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 diff --git a/webui_train.py b/webui_train.py index e276cff..164f823 100644 --- a/webui_train.py +++ b/webui_train.py @@ -1,22 +1,14 @@ import json import os import shutil -import subprocess import sys +from multiprocessing import Pool, cpu_count import gradio as gr import yaml -python = sys.executable - - -def subprocess_wrapper(cmd): - return subprocess.run( - cmd, - stdout=sys.stdout, - stderr=subprocess.PIPE, - text=True, - ) +from tools.log import logger +from tools.subprocess_utils import run_script_with_log, second_elem_of def get_path(model_name): @@ -30,6 +22,7 @@ def get_path(model_name): def initialize(model_name, batch_size, epochs, bf16_run): + logger.info("Step 1: start initialization...") dataset_path, _, train_path, val_path, config_path = get_path(model_name) if os.path.isfile(config_path): config = json.load(open(config_path, "r", encoding="utf-8")) @@ -47,9 +40,11 @@ def initialize(model_name, batch_size, epochs, bf16_run): try: shutil.copytree(src="pretrained", dst=model_path) except FileExistsError: - return f"Error: モデルフォルダ {model_path} が既に存在します。問題なければ削除してください。" + logger.error(f"Step 1: {model_path} already exists.") + return False, f"Step1, Error: モデルフォルダ {model_path} が既に存在します。問題なければ削除してください。" except FileNotFoundError: - return "Error: pretrainedフォルダが見つかりません。" + logger.error("Step 1: `pretrained` folder not found.") + return False, "Step 1, Error: pretrainedフォルダが見つかりません。" with open(config_path, "w", encoding="utf-8") as f: json.dump(config, f, indent=2) @@ -62,16 +57,17 @@ def initialize(model_name, batch_size, epochs, bf16_run): yml_data["dataset_path"] = dataset_path with open("config.yml", "w", encoding="utf-8") as f: yaml.dump(yml_data, f, allow_unicode=True) - return "Step 1: 初期設定が完了しました" + logger.success("Step 1: initialization finished.") + return True, "Step 1, Success: 初期設定が完了しました" def resample(model_name): + logger.info("Step 2: start resampling...") dataset_path, _, _, _, _ = get_path(model_name) in_dir = os.path.join(dataset_path, "raw") out_dir = os.path.join(dataset_path, "wavs") - result = subprocess_wrapper( + success, message = run_script_with_log( [ - python, "resample.py", "--in_dir", in_dir, @@ -81,14 +77,24 @@ def resample(model_name): "44100", ] ) - if result.stderr: - return f"{result.stderr}" - return "Step 2: 音声ファイルの前処理が完了しました" + if not success: + logger.error(f"Step 2: resampling failed.") + return False, f"Step 2, Error: 音声ファイルの前処理に失敗しました:\n{message}" + elif message: + logger.warning(f"Step 2: resampling finished with stderr.") + return True, f"Step 2, Success: 音声ファイルの前処理が完了しました:\n{message}" + logger.success("Step 2: resampling finished.") + return True, "Step 2, Success: 音声ファイルの前処理が完了しました" def preprocess_text(model_name): + logger.info("Step 3: start preprocessing text...") dataset_path, lbl_path, train_path, val_path, config_path = get_path(model_name) - lines = open(lbl_path, "r", encoding="utf-8").readlines() + try: + lines = open(lbl_path, "r", encoding="utf-8").readlines() + except FileNotFoundError: + logger.error(f"Step 3: {lbl_path} not found.") + return False, f"Step 3, Error: 書き起こしファイル {lbl_path} が見つかりません。" with open(lbl_path, "w", encoding="utf-8") as f: for line in lines: path, spk, language, text = line.strip().split("|") @@ -96,9 +102,8 @@ def preprocess_text(model_name): "\\", "/" ) f.writelines(f"{path}|{spk}|{language}|{text}\n") - result = subprocess_wrapper( + success, message = run_script_with_log( [ - python, "preprocess_text.py", "--config-path", config_path, @@ -110,25 +115,33 @@ def preprocess_text(model_name): val_path, ] ) - - if result.stderr: - return f"{result.stderr}" - return "Step 3: 書き起こしファイルの前処理が完了しました" + if not success: + logger.error(f"Step 3: preprocessing text failed.") + return False, f"Step 3, Error: 書き起こしファイルの前処理に失敗しました:\n{message}" + elif message: + logger.warning(f"Step 3: preprocessing text finished with stderr.") + return True, f"Step 3, Success: 書き起こしファイルの前処理が完了しました:\n{message}" + logger.success("Step 3: preprocessing text finished.") + return True, "Step 3, Success: 書き起こしファイルの前処理が完了しました" def bert_gen(model_name): _, _, _, _, config_path = get_path(model_name) - result = subprocess_wrapper([python, "bert_gen.py", "--config", config_path]) - if result.stderr: - return f"{result.stderr}" - return "Step 4: BERT特徴ファイルの生成が完了しました" + success, message = run_script_with_log(["bert_gen.py", "--config", config_path]) + if not success: + logger.error(f"Step 4: bert_gen failed.") + return False, f"Step 4, Error: BERT特徴ファイルの生成に失敗しました:\n{message}" + elif message: + logger.warning(f"Step 4: bert_gen finished with stderr.") + return True, f"Step 4, Success: BERT特徴ファイルの生成が完了しました:\n{message}" + logger.success("Step 4: bert_gen finished.") + return True, "Step 4, Success: BERT特徴ファイルの生成が完了しました" def style_gen(model_name, num_processes): _, _, _, _, config_path = get_path(model_name) - result = subprocess_wrapper( + success, message = run_script_with_log( [ - python, "style_gen.py", "--config", config_path, @@ -136,19 +149,49 @@ def style_gen(model_name, num_processes): str(num_processes), ] ) - if result.stderr: - return f"{result.stderr}" - return "Step 5: スタイル特徴ファイルの生成が完了しました" + if not success: + logger.error(f"Step 5: style_gen failed.") + return False, f"Step 5, Error: スタイル特徴ファイルの生成に失敗しました:\n{message}" + elif message: + logger.warning(f"Step 5: style_gen finished with stderr.") + return True, f"Step 5, Success: スタイル特徴ファイルの生成が完了しました:\n{message}" + logger.success("Step 5: style_gen finished.") + return True, "Step 5, Success: スタイル特徴ファイルの生成が完了しました" + + +def preprocess_all(model_name, batch_size, epochs, bf16_run, num_processes): + success, message = initialize(model_name, batch_size, epochs, bf16_run) + if not success: + return success, message + success, message = resample(model_name) + if not success: + return success, message + success, message = preprocess_text(model_name) + if not success: + return success, message + success, message = bert_gen(model_name) + if not success: + return success, message + success, message = style_gen(model_name, num_processes) + if not success: + return success, message + logger.success("Success: all preprocess finished.") + return True, "Success: 全ての前処理が完了しました。ターミナルを確認しておかしいところがないか確認するのをおすすめします。" def train(model_name): dataset_path, _, _, _, config_path = get_path(model_name) - result = subprocess_wrapper( - [python, "train_ms.py", "--config", config_path, "--model", dataset_path] + success, message = run_script_with_log( + ["train_ms.py", "--config", config_path, "--model", dataset_path] ) - if result.stderr: - return f"{result.stderr}" - return "Final Step: 学習が完了しました!" + if not success: + logger.error(f"Final Step: train failed.") + return False, f"Final Step, Error: 学習に失敗しました:\n{message}" + elif message: + logger.warning(f"Final Step: train finished with stderr.") + return True, f"Final Step, Success: 学習が完了しました:\n{message}" + logger.success("Final Step: train finished.") + return True, "Final Step, Success: 学習が完了しました" initial_md = """ @@ -195,11 +238,9 @@ english_teacher.wav|Mary|EN|How are you? I'm fine, thank you, and you? """ css = """ -/* マークダウン要素のスタイル */ div.panel { justify-content: space-between; } - """ if __name__ == "__main__": @@ -211,66 +252,123 @@ if __name__ == "__main__": label="モデル名", ) info = gr.Textbox(label="状況") + gr.Markdown("### 自動前処理") with gr.Row(variant="panel"): - with gr.Column(variant="panel", min_width=160): - gr.Markdown(value="Step 1: 設定ファイルの生成") - batch_size = gr.Slider( - label="バッチサイズ", - info="VRAM 12GBで4くらい", - value=4, - minimum=1, - maximum=64, - step=1, - ) - epochs = gr.Slider( - label="エポック数", - info="100もあれば十分そう", - value=100, - minimum=1, - maximum=1000, - step=1, - ) - bf16_run = gr.Checkbox( - label="bfloat16を使う", - info="新しめのグラボだと学習が早くなるかも、古いグラボだと動かないかも", - value=True, - ) - generate_config_btn = gr.Button(value="実行", variant="primary") - with gr.Column(variant="panel", min_width=160): - gr.Markdown(value="Step 2: 音声ファイルの前処理") - resample_btn = gr.Button(value="実行", variant="primary") - with gr.Column(variant="panel", min_width=160): - gr.Markdown(value="Step 3: 書き起こしファイルの前処理") - preprocess_text_btn = gr.Button(value="実行", variant="primary") - with gr.Column(variant="panel", min_width=160): - gr.Markdown(value="Step 4: BERT特徴ファイルの生成") - bert_gen_btn = gr.Button(value="実行", variant="primary") - with gr.Column(variant="panel", min_width=160): - gr.Markdown(value="Step 5: スタイル特徴ファイルの生成") - num_processes = gr.Slider( - label="プロセス数", - value=4, - minimum=1, - maximum=16, - step=1, - ) - style_gen_btn = gr.Button(value="実行", variant="primary") + batch_size = gr.Slider( + label="バッチサイズ", + info="VRAM 12GBで4くらい", + value=4, + minimum=1, + maximum=64, + step=1, + ) + epochs = gr.Slider( + label="エポック数", + info="100もあれば十分そう", + value=100, + minimum=1, + maximum=1000, + step=1, + ) + bf16_run = gr.Checkbox( + label="bfloat16を使う", + info="bfloat16を使うかどうか。新しめのグラボだと学習が早くなるかも、古いグラボだと動かないかも。", + value=True, + ) + num_processes = gr.Slider( + label="プロセス数", + info="前処理時の並列処理プロセス数、大きすぎるとフリーズするかも", + value=cpu_count() // 2, + minimum=1, + maximum=cpu_count(), + step=1, + ) + preprocess_button = gr.Button(value="実行", variant="primary") + with gr.Accordion(open=False, label="手動前処理"): + with gr.Row(variant="panel"): + with gr.Column(variant="panel", min_width=160): + gr.Markdown(value="#### Step 1: 設定ファイルの生成") + batch_size_manual = gr.Slider( + label="バッチサイズ", + value=4, + minimum=1, + maximum=64, + step=1, + ) + epochs_manual = gr.Slider( + label="エポック数", + value=100, + minimum=1, + maximum=1000, + step=1, + ) + bf16_run_manual = gr.Checkbox( + label="bfloat16を使う", + value=True, + ) + generate_config_btn = gr.Button(value="実行", variant="primary") + with gr.Column(variant="panel", min_width=160): + gr.Markdown(value="#### Step 2: 音声ファイルの前処理") + num_processes_resample = gr.Slider( + label="プロセス数", + value=cpu_count() // 2, + minimum=1, + maximum=cpu_count(), + step=1, + ) + resample_btn = gr.Button(value="実行", variant="primary") + with gr.Column(variant="panel", min_width=160): + gr.Markdown(value="#### Step 3: 書き起こしファイルの前処理") + preprocess_text_btn = gr.Button(value="実行", variant="primary") + with gr.Column(variant="panel", min_width=160): + gr.Markdown(value="#### Step 4: BERT特徴ファイルの生成") + num_processes_bert = gr.Slider( + label="プロセス数", + value=cpu_count() // 2, + minimum=1, + maximum=cpu_count(), + step=1, + ) + bert_gen_btn = gr.Button(value="実行", variant="primary") + with gr.Column(variant="panel", min_width=160): + gr.Markdown(value="#### Step 5: スタイル特徴ファイルの生成") + num_processes_style = gr.Slider( + label="プロセス数", + value=cpu_count() // 2, + minimum=1, + maximum=cpu_count(), + step=1, + ) + style_gen_btn = gr.Button(value="実行", variant="primary") with gr.Row(variant="panel"): with gr.Column(): - gr.Markdown(value="Final Step: 学習") - train_btn = gr.Button(value="学習", variant="primary") + gr.Markdown("## 学習") + train_btn = gr.Button(value="学習を開始する", variant="primary") - generate_config_btn.click( - initialize, - inputs=[model_name, batch_size, epochs, bf16_run], + preprocess_button.click( + second_elem_of(preprocess_all), + inputs=[model_name, batch_size, epochs, bf16_run, num_processes], outputs=[info], ) - resample_btn.click(resample, inputs=[model_name], outputs=[info]) - preprocess_text_btn.click(preprocess_text, inputs=[model_name], outputs=[info]) - bert_gen_btn.click(bert_gen, inputs=[model_name], outputs=[info]) - style_gen_btn.click( - style_gen, inputs=[model_name, num_processes], outputs=[info] + generate_config_btn.click( + second_elem_of(initialize), + inputs=[model_name, batch_size_manual, epochs_manual, bf16_run_manual], + outputs=[info], ) - train_btn.click(train, inputs=[model_name], outputs=[info]) + resample_btn.click( + second_elem_of(resample), inputs=[model_name], outputs=[info] + ) + preprocess_text_btn.click( + second_elem_of(preprocess_text), inputs=[model_name], outputs=[info] + ) + bert_gen_btn.click( + second_elem_of(bert_gen), inputs=[model_name], outputs=[info] + ) + style_gen_btn.click( + second_elem_of(style_gen), + inputs=[model_name, num_processes_style], + outputs=[info], + ) + train_btn.click(second_elem_of(train), inputs=[model_name], outputs=[info]) - app.launch(share=False, inbrowser=True) + app.launch(inbrowser=True) From 9d0617a8c0cc976c16358c73c7f9c3aac8dd7231 Mon Sep 17 00:00:00 2001 From: litagin02 <139731664+litagin02@users.noreply.github.com> Date: Fri, 29 Dec 2023 12:26:56 +0900 Subject: [PATCH 06/10] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index b143266..267dbc4 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,7 @@ Bert-VITS2 with more controllable voice styles. https://github.com/litagin02/Style-Bert-VITS2/assets/139731664/b907c1b8-43aa-46e6-b03f-f6362f5a5a1e +**注意**: 上記動画のライセンス表記は誤っていました、正しくはCC BY-SA 4.0で商用利用に制限はありません。近日訂正版動画に差し替えます。 Online demo: https://huggingface.co/spaces/litagin/Style-Bert-VITS2-JVNV From c731a9c7f4a198f3d1745f5e1a7d29a990eae8c0 Mon Sep 17 00:00:00 2001 From: litagin02 <139731664+litagin02@users.noreply.github.com> Date: Fri, 29 Dec 2023 12:27:19 +0900 Subject: [PATCH 07/10] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 267dbc4..e76e08c 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,7 @@ Bert-VITS2 with more controllable voice styles. https://github.com/litagin02/Style-Bert-VITS2/assets/139731664/b907c1b8-43aa-46e6-b03f-f6362f5a5a1e + **注意**: 上記動画のライセンス表記は誤っていました、正しくはCC BY-SA 4.0で商用利用に制限はありません。近日訂正版動画に差し替えます。 Online demo: https://huggingface.co/spaces/litagin/Style-Bert-VITS2-JVNV From cc2d5513ea8046c3f05e19b3dff3da8df0372a2f Mon Sep 17 00:00:00 2001 From: litagin02 <139731664+litagin02@users.noreply.github.com> Date: Fri, 29 Dec 2023 15:13:41 +0900 Subject: [PATCH 08/10] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index e76e08c..9de9c02 100644 --- a/README.md +++ b/README.md @@ -95,6 +95,7 @@ model_assets - その他軽微なbugfixやリファクタリング ## TODO +- [ ] LinuxやWSL等、Windowsの通常環境以外でのサポート? - [ ] 複数話者学習での音声合成対応(学習は現在でも可能) - [ ] 本家のver 2.1, 2.2, 2.3モデルの推論対応?(ver 2.1以外は明らかにめんどいのでたぶんやらない) - [ ] `server_fastapi.py`の対応、とくにAPIで使えるようになると嬉しい人が増えるのかもしれない From fc2bde673a214ded2bceeab838701a195bb2ed22 Mon Sep 17 00:00:00 2001 From: litagin02 Date: Fri, 29 Dec 2023 18:43:14 +0900 Subject: [PATCH 09/10] Improve WebUI and add loudness normalization --- README.md | 10 +- requirements.txt | 1 + resample.py | 22 +++- slice.py | 24 ++--- train_ms.py | 5 +- webui_dataset.py | 65 ++++++------ webui_train.py | 266 ++++++++++++++++++++++++++++++----------------- 7 files changed, 245 insertions(+), 148 deletions(-) diff --git a/README.md b/README.md index b143266..0bfcfe1 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,7 @@ python initialize.py # 必要なモデルとデフォルトTTSモデルをダ ### 音声合成 -`App.bat`をダブルクリックするとWebUIが起動します。インストール時にデフォルトのモデルがダウンロードされているので、学習していなくてもそれを使うことができます。 +`App.bat`をダブルクリックか、`python app.py`するとWebUIが起動します。インストール時にデフォルトのモデルがダウンロードされているので、学習していなくてもそれを使うことができます。 音声合成に必要なモデルファイルたちの構造は以下の通りです(手動で配置する必要はありません)。 ``` @@ -64,18 +64,20 @@ model_assets ### 学習 -`Train.bat`をダブルクリックするとWebUIが起動します。 +`Train.bat`をダブルクリックか`python webui_train.py`するとWebUIが起動します。 ### スタイルの生成 -- `Style.bat`をダブルクリックするとWebUIが起動します。 +- `Style.bat`をダブルクリックか`python webui_style_vectors.py`するとWebUIが起動します。 - この手順は、音声ファイルたちからスタイルを作るのに必要な手順です。 - 学習とは独立しているので、学習中でもできるし、学習が終わっても何度もやりなおせます。 - スタイルについての詳細は[clustering.ipynb](clustering.ipynb)を参照してください。 ### データセット作り -- `Dataset.bat`をダブルクリックすると、音声ファイルからデータセットを作るためのWebUIが起動します。音声ファイルのみからでもこれを使って学習できます。 +- `Dataset.bat`をダブルクリックか`python webui_dataset.py`すると、音声ファイルからデータセットを作るためのWebUIが起動します。音声ファイルのみからでもこれを使って学習できます。 + +注意: データセットの手動修正やノイズ除去や、より高品質なデータセットを作りたい場合は、[Aivis](https://github.com/tsukumijima/Aivis)や、そのデータセット部分のWindows対応版 [Aivis Dataset](https://github.com/litagin02/Aivis-Dataset) を使うのをおすすめします。 ## Bert-VITS2 v2.1との関係 diff --git a/requirements.txt b/requirements.txt index 8fc8d7d..9776e40 100644 --- a/requirements.txt +++ b/requirements.txt @@ -16,6 +16,7 @@ numba numpy psutil pyannote.audio>=3.1.0 +pyloudnorm pyopenjtalk-prebuilt pypinyin PyYAML diff --git a/resample.py b/resample.py index dbe5952..e80da9c 100644 --- a/resample.py +++ b/resample.py @@ -4,10 +4,20 @@ import sys from multiprocessing import Pool, cpu_count import librosa +import pyloudnorm as pyln import soundfile from tqdm import tqdm from config import config +from tools.log import logger + + +def normalize_audio(data, sr): + meter = pyln.Meter(sr) # create BS.1770 meter + loudness = meter.integrated_loudness(data) + # logger.info(f"loudness: {loudness}") + data = pyln.normalize.loudness(data, loudness, -23.0) + return data def process(item): @@ -15,6 +25,8 @@ def process(item): wav_path = os.path.join(args.in_dir, spkdir, wav_name) if os.path.exists(wav_path) and wav_path.lower().endswith(".wav"): wav, sr = librosa.load(wav_path, sr=args.sr) + if args.normalize: + wav = normalize_audio(wav, sr) soundfile.write(os.path.join(args.out_dir, spkdir, wav_name), wav, sr) @@ -44,13 +56,18 @@ if __name__ == "__main__": default=4, help="cpu_processes", ) + parser.add_argument( + "--normalize", + action="store_true", + default=True, + help="loudness normalize audio", + ) args, _ = parser.parse_known_args() # autodl 无卡模式会识别出46个cpu if args.num_processes == 0: processes = cpu_count() - 2 if cpu_count() > 4 else 1 else: processes = args.num_processes - pool = Pool(processes=processes) tasks = [] @@ -66,7 +83,10 @@ if __name__ == "__main__": tasks.append(twople) if len(tasks) == 0: + logger.error(f"No wav files found in {args.in_dir}") raise ValueError(f"No wav files found in {args.in_dir}") + + pool = Pool(processes=processes) for _ in tqdm( pool.imap_unordered(process, tasks), file=sys.stdout, total=len(tasks) ): diff --git a/slice.py b/slice.py index 2f33c30..c4faf0a 100644 --- a/slice.py +++ b/slice.py @@ -3,8 +3,8 @@ import os import shutil import sys +import soundfile as sf import torch -from pydub import AudioSegment from tqdm import tqdm vad_model, utils = torch.hub.load( @@ -52,16 +52,9 @@ def split_wav( audio_file, min_silence_dur_ms=min_silence_dur_ms, min_sec=min_sec ) - # WAVファイルを読み込む - audio = AudioSegment.from_wav(audio_file) + data, sr = sf.read(audio_file) - # リサンプリング(44100Hz) - audio = audio.set_frame_rate(44100) - - # ステレオをモノラルに変換 - audio = audio.set_channels(1) - - total_ms = len(audio) + total_ms = len(data) / sr * 1000 file_name = os.path.basename(audio_file).split(".")[0] os.makedirs(target_dir, exist_ok=True) @@ -74,8 +67,15 @@ def split_wav( end_ms = min(ts["end"] / 16 + margin, total_ms) if end_ms - start_ms > upper_bound_ms: continue - segment = audio[start_ms:end_ms] - segment.export(os.path.join(target_dir, f"{file_name}-{i}.wav"), format="wav") + + start_sample = int(start_ms / 1000 * sr) + end_sample = int(end_ms / 1000 * sr) + segment = data[start_sample:end_sample] + + if normalize: + segment = normalize_audio(segment, sr) + + sf.write(os.path.join(target_dir, f"{file_name}-{i}.wav"), segment, sr) total_time_ms += end_ms - start_ms return total_time_ms / 1000 diff --git a/train_ms.py b/train_ms.py index 00d3e36..8272d5e 100644 --- a/train_ms.py +++ b/train_ms.py @@ -658,8 +658,9 @@ def train_and_evaluate( ) global_step += 1 - gc.collect() - torch.cuda.empty_cache() + # 本家ではこれをスピードアップのために消すと書かれていたので、一応消してみる + # gc.collect() + # torch.cuda.empty_cache() if rank == 0: logger.info(f"====> Epoch: {epoch}, step: {global_step}") diff --git a/webui_dataset.py b/webui_dataset.py index 5fb841f..436944d 100644 --- a/webui_dataset.py +++ b/webui_dataset.py @@ -1,43 +1,35 @@ import os -import subprocess -import sys import gradio as gr -python = sys.executable +from tools.log import logger +from tools.subprocess_utils import run_script_with_log, second_elem_of -def subprocess_wrapper(cmd): - return subprocess.run( - cmd, - stdout=sys.stdout, - stderr=subprocess.PIPE, - text=True, - ) - - -def do_slice(model_name): +def do_slice(model_name, normalize): + logger.info("Start slicing...") input_dir = "inputs" output_dir = os.path.join("Data", model_name, "raw") - result = subprocess_wrapper( - [ - python, - "slice.py", - "--input_dir", - input_dir, - "--output_dir", - output_dir, - ] - ) - return "ターミナルを見て結果を確認してください。" + cmd = [ + "slice.py", + "--input_dir", + input_dir, + "--output_dir", + output_dir, + ] + if normalize: + cmd.append("--normalize") + success, message = run_script_with_log(cmd) + if not success: + return f"Error: {message}" + return "音声のスライスが完了しました。" def do_transcribe(model_name): input_dir = os.path.join("Data", model_name, "raw") output_file = os.path.join("Data", model_name, "esd.list") - result = subprocess_wrapper( + result = run_script_with_log( [ - python, "transcribe.py", "--input_dir", input_dir, @@ -47,13 +39,13 @@ def do_transcribe(model_name): model_name, ] ) - if result.stderr: - return f"{result.stderr}" return "音声の文字起こしが完了しました。" initial_md = """ -# 学習用データセット作成ツール +# 簡易学習用データセット作成ツール + +**注意**:より精密で高品質なデータセットを作成したい・書き起こしをいろいろ修正したい場合は、[Aivis Dataset](https://github.com/litagin02/Aivis-Dataset)をおすすめします。書き起こし部分もかなり工夫されています。このツールはあくまでスライスして書き起こすという簡易的なことしかしていません。 Style-Bert-VITS2の学習用データセットを作成するためのツールです。与えられた音声からちょうどいい長さの発話区間を切り取りスライスし、それぞれの音声に対して文字起こしを行います。 @@ -69,23 +61,24 @@ Style-Bert-VITS2の学習用データセットを作成するためのツール 細かいパラメータ調整とかがしたい人は、`slice.py`と`transcribe.py`を眺めて直接実行してください。 また、出来上がった音声ファイルたちは`Data/{モデル名}/raw`に、書き起こしファイルは`Data/{モデル名}/esd.list`に保存されます。 -書き起こしの結果は、**そこまで正確に誤字や誤りを修正しなくても、それなりの質になる**ので、あまり修正は必要ないかもしれません(私は手動修正したことないです)。 - -**ffmpeg のインストールが別途必要のよう**です、「Couldn't find ffmpeg」とか怒られたら、「Windows ffmpeg インストール」等でググって別途インストールしてください。 +書き起こしの結果をどれだけ修正すればいいかはデータセットに依存しそうです。 """ with gr.Blocks(theme="NoCrypt/miku") as app: gr.Markdown(initial_md) model_name = gr.Textbox(label="モデル名を入力してください(話者名としても使われます)。") - with gr.Row(): - slice_button = gr.Button("音声のスライス") - result1 = gr.Textbox(label="結果") + with gr.Accordion("音声のスライス"): + with gr.Row(): + with gr.Column(): + normalize = gr.Checkbox(label="スライスされた音声の音量を正規化する", value=True) + slice_button = gr.Button("スライスを実行") + result1 = gr.Textbox(label="結果") with gr.Row(): transcribe_button = gr.Button("音声の文字起こし") result2 = gr.Textbox(label="結果") slice_button.click( do_slice, - inputs=[model_name], + inputs=[model_name, normalize], outputs=[result1], ) transcribe_button.click( diff --git a/webui_train.py b/webui_train.py index 164f823..41486f3 100644 --- a/webui_train.py +++ b/webui_train.py @@ -2,7 +2,7 @@ import json import os import shutil import sys -from multiprocessing import Pool, cpu_count +from multiprocessing import cpu_count import gradio as gr import yaml @@ -21,7 +21,7 @@ def get_path(model_name): return dataset_path, lbl_path, train_path, val_path, config_path -def initialize(model_name, batch_size, epochs, bf16_run): +def initialize(model_name, batch_size, epochs, save_every_steps, bf16_run): logger.info("Step 1: start initialization...") dataset_path, _, train_path, val_path, config_path = get_path(model_name) if os.path.isfile(config_path): @@ -35,6 +35,7 @@ def initialize(model_name, batch_size, epochs, bf16_run): config["train"]["batch_size"] = batch_size config["train"]["epochs"] = epochs config["train"]["bf16_run"] = bf16_run + config["train"]["eval_interval"] = save_every_steps model_path = os.path.join(dataset_path, "models") try: @@ -61,22 +62,25 @@ def initialize(model_name, batch_size, epochs, bf16_run): return True, "Step 1, Success: 初期設定が完了しました" -def resample(model_name): +def resample(model_name, normalize, num_processes): logger.info("Step 2: start resampling...") dataset_path, _, _, _, _ = get_path(model_name) in_dir = os.path.join(dataset_path, "raw") out_dir = os.path.join(dataset_path, "wavs") - success, message = run_script_with_log( - [ - "resample.py", - "--in_dir", - in_dir, - "--out_dir", - out_dir, - "--sr", - "44100", - ] - ) + cmd = [ + "resample.py", + "--in_dir", + in_dir, + "--out_dir", + out_dir, + "--num_processes", + str(num_processes), + "--sr", + "44100", + ] + if normalize: + cmd.append("--normalize") + success, message = run_script_with_log(cmd) if not success: logger.error(f"Step 2: resampling failed.") return False, f"Step 2, Error: 音声ファイルの前処理に失敗しました:\n{message}" @@ -126,8 +130,17 @@ def preprocess_text(model_name): def bert_gen(model_name): + logger.info("Step 4: start bert_gen...") _, _, _, _, config_path = get_path(model_name) - success, message = run_script_with_log(["bert_gen.py", "--config", config_path]) + success, message = run_script_with_log( + [ + "bert_gen.py", + "--config", + config_path, + # "--num_processes", # bert_genは重いのでプロセス数いじらない + # str(num_processes), + ] + ) if not success: logger.error(f"Step 4: bert_gen failed.") return False, f"Step 4, Error: BERT特徴ファイルの生成に失敗しました:\n{message}" @@ -139,6 +152,7 @@ def bert_gen(model_name): def style_gen(model_name, num_processes): + logger.info("Step 5: start style_gen...") _, _, _, _, config_path = get_path(model_name) success, message = run_script_with_log( [ @@ -159,23 +173,29 @@ def style_gen(model_name, num_processes): return True, "Step 5, Success: スタイル特徴ファイルの生成が完了しました" -def preprocess_all(model_name, batch_size, epochs, bf16_run, num_processes): - success, message = initialize(model_name, batch_size, epochs, bf16_run) +def preprocess_all( + model_name, batch_size, epochs, save_every_steps, bf16_run, num_processes, normalize +): + if model_name == "": + return False, "Error: モデル名を入力してください" + success, message = initialize( + model_name, batch_size, epochs, save_every_steps, bf16_run + ) if not success: - return success, message - success, message = resample(model_name) + return False, message + success, message = resample(model_name, normalize, num_processes) if not success: - return success, message + return False, message success, message = preprocess_text(model_name) if not success: - return success, message - success, message = bert_gen(model_name) + return False, message + success, message = bert_gen(model_name) # bert_genは重いのでプロセス数いじらない if not success: - return success, message + return False, message success, message = style_gen(model_name, num_processes) if not success: - return success, message - logger.success("Success: all preprocess finished.") + return False, message + logger.success("Success: All preprocess finished!") return True, "Success: 全ての前処理が完了しました。ターミナルを確認しておかしいところがないか確認するのをおすすめします。" @@ -199,16 +219,20 @@ initial_md = """ ## 使い方 -- データを準備して、各ステップを順に実行してください。進捗状況等はターミナルに表示されます。 +- データを準備して、モデル名を入力して、必要なら設定を調整してから、「自動前処理を実行」ボタンを押してください。進捗状況等はターミナルに表示されます。 -- 途中から学習を再開する場合は、モデル名を入力してFinal Stepだけ実行すればよいです。 +- 各ステップごとに実行する場合は「手動前処理」を使ってください(基本的には自動でいいはず)。 + +- 前処理が終わったら、「学習を開始する」ボタンを押すと学習が開始されます。 + +- 途中から学習を再開する場合は、モデル名を入力してから「学習を開始する」を押せばよいです。 注意: 音声合成で使うには、スタイルベクトルファイル`style_vectors.npy`を作る必要があります。これは、`Style.bat`を実行してそこで作成してください。 動作は軽いはずなので、学習中でも実行でき、何度でも繰り返して試せます。 """ prepare_md = """ -まず音声データ(wavファイルで1ファイルが2-15秒程度の、長すぎず短すぎない発話のものをいくつか)と、書き起こしテキストを用意してください。 +まず音声データ(wavファイルで1ファイルが2-12秒程度の、長すぎず短すぎない発話のものをいくつか)と、書き起こしテキストを用意してください。 それを次のように配置します。 ``` @@ -237,56 +261,64 @@ english_teacher.wav|Mary|EN|How are you? I'm fine, thank you, and you? 日本語話者の単一話者データセットでも構いません。 """ -css = """ -div.panel { - justify-content: space-between; -} -""" - if __name__ == "__main__": - with gr.Blocks(theme="NoCrypt/miku", css=css) as app: + with gr.Blocks(theme="NoCrypt/miku") as app: gr.Markdown(initial_md) with gr.Accordion(label="データの前準備", open=False): gr.Markdown(prepare_md) model_name = gr.Textbox( label="モデル名", ) - info = gr.Textbox(label="状況") gr.Markdown("### 自動前処理") with gr.Row(variant="panel"): - batch_size = gr.Slider( - label="バッチサイズ", - info="VRAM 12GBで4くらい", - value=4, - minimum=1, - maximum=64, - step=1, - ) - epochs = gr.Slider( - label="エポック数", - info="100もあれば十分そう", - value=100, - minimum=1, - maximum=1000, - step=1, - ) - bf16_run = gr.Checkbox( - label="bfloat16を使う", - info="bfloat16を使うかどうか。新しめのグラボだと学習が早くなるかも、古いグラボだと動かないかも。", - value=True, - ) - num_processes = gr.Slider( - label="プロセス数", - info="前処理時の並列処理プロセス数、大きすぎるとフリーズするかも", - value=cpu_count() // 2, - minimum=1, - maximum=cpu_count(), - step=1, - ) - preprocess_button = gr.Button(value="実行", variant="primary") + with gr.Column(): + batch_size = gr.Slider( + label="バッチサイズ", + info="VRAM 12GBで4くらい", + value=4, + minimum=1, + maximum=64, + step=1, + ) + epochs = gr.Slider( + label="エポック数", + info="100もあれば十分そう", + value=100, + minimum=1, + maximum=1000, + step=1, + ) + save_every_steps = gr.Slider( + label="何ステップごとに結果を保存するか", + info="エポック数とは違うことに注意", + value=1000, + minimum=100, + maximum=10000, + step=100, + ) + bf16_run = gr.Checkbox( + label="bfloat16を使う", + info="bfloat16を使うかどうか。新しめのグラボだと学習が早くなるかも、古いグラボだと動かないかも。", + value=True, + ) + num_processes = gr.Slider( + label="プロセス数", + info="前処理時の並列処理プロセス数、大きすぎるとフリーズするかも", + value=cpu_count() // 2, + minimum=1, + maximum=cpu_count(), + step=1, + ) + normalize = gr.Checkbox( + label="音声の音量を正規化する", + value=True, + ) + with gr.Column(): + preprocess_button = gr.Button(value="自動前処理を実行", variant="primary") + info_all = gr.Textbox(label="状況") with gr.Accordion(open=False, label="手動前処理"): with gr.Row(variant="panel"): - with gr.Column(variant="panel", min_width=160): + with gr.Column(): gr.Markdown(value="#### Step 1: 設定ファイルの生成") batch_size_manual = gr.Slider( label="バッチサイズ", @@ -302,12 +334,22 @@ if __name__ == "__main__": maximum=1000, step=1, ) + save_every_steps_manual = gr.Slider( + label="何ステップごとに結果を保存するか", + value=1000, + minimum=100, + maximum=10000, + step=100, + ) bf16_run_manual = gr.Checkbox( label="bfloat16を使う", value=True, ) + with gr.Column(): generate_config_btn = gr.Button(value="実行", variant="primary") - with gr.Column(variant="panel", min_width=160): + info_init = gr.Textbox(label="状況") + with gr.Row(variant="panel"): + with gr.Column(): gr.Markdown(value="#### Step 2: 音声ファイルの前処理") num_processes_resample = gr.Slider( label="プロセス数", @@ -316,21 +358,35 @@ if __name__ == "__main__": maximum=cpu_count(), step=1, ) - resample_btn = gr.Button(value="実行", variant="primary") - with gr.Column(variant="panel", min_width=160): - gr.Markdown(value="#### Step 3: 書き起こしファイルの前処理") - preprocess_text_btn = gr.Button(value="実行", variant="primary") - with gr.Column(variant="panel", min_width=160): - gr.Markdown(value="#### Step 4: BERT特徴ファイルの生成") - num_processes_bert = gr.Slider( - label="プロセス数", - value=cpu_count() // 2, - minimum=1, - maximum=cpu_count(), - step=1, + normalize_resample = gr.Checkbox( + label="音声の音量を正規化する", + value=True, ) + with gr.Column(): + resample_btn = gr.Button(value="実行", variant="primary") + info_resample = gr.Textbox(label="状況") + with gr.Row(variant="panel"): + with gr.Column(): + gr.Markdown(value="#### Step 3: 書き起こしファイルの前処理") + with gr.Column(): + preprocess_text_btn = gr.Button(value="実行", variant="primary") + info_preprocess_text = gr.Textbox(label="状況") + with gr.Row(variant="panel"): + with gr.Column(): + gr.Markdown(value="#### Step 4: BERT特徴ファイルの生成") + # num_processes_bert = gr.Slider( + # label="プロセス数", + # value=cpu_count() // 2, + # minimum=1, + # maximum=cpu_count(), + # step=1, + # ) + # bert_genは重いようで、初期の4がよいみたい + with gr.Column(): bert_gen_btn = gr.Button(value="実行", variant="primary") - with gr.Column(variant="panel", min_width=160): + info_bert = gr.Textbox(label="状況") + with gr.Row(variant="panel"): + with gr.Column(): gr.Markdown(value="#### Step 5: スタイル特徴ファイルの生成") num_processes_style = gr.Slider( label="プロセス数", @@ -339,36 +395,60 @@ if __name__ == "__main__": maximum=cpu_count(), step=1, ) + with gr.Column(): style_gen_btn = gr.Button(value="実行", variant="primary") + info_style = gr.Textbox(label="状況") + gr.Markdown("## 学習") with gr.Row(variant="panel"): - with gr.Column(): - gr.Markdown("## 学習") - train_btn = gr.Button(value="学習を開始する", variant="primary") + train_btn = gr.Button(value="学習を開始する", variant="primary") + info_train = gr.Textbox(label="状況") preprocess_button.click( second_elem_of(preprocess_all), - inputs=[model_name, batch_size, epochs, bf16_run, num_processes], - outputs=[info], + inputs=[ + model_name, + batch_size, + epochs, + save_every_steps, + bf16_run, + num_processes, + normalize, + ], + outputs=[info_all], ) generate_config_btn.click( second_elem_of(initialize), - inputs=[model_name, batch_size_manual, epochs_manual, bf16_run_manual], - outputs=[info], + inputs=[ + model_name, + batch_size_manual, + epochs_manual, + save_every_steps_manual, + bf16_run_manual, + ], + outputs=[info_init], ) resample_btn.click( - second_elem_of(resample), inputs=[model_name], outputs=[info] + second_elem_of(resample), + inputs=[model_name, normalize_resample, num_processes_resample], + outputs=[info_resample], ) preprocess_text_btn.click( - second_elem_of(preprocess_text), inputs=[model_name], outputs=[info] + second_elem_of(preprocess_text), + inputs=[model_name], + outputs=[info_preprocess_text], ) bert_gen_btn.click( - second_elem_of(bert_gen), inputs=[model_name], outputs=[info] + second_elem_of(bert_gen), + inputs=[model_name], + outputs=[info_bert], ) style_gen_btn.click( second_elem_of(style_gen), inputs=[model_name, num_processes_style], - outputs=[info], + outputs=[info_style], + ) + train_btn.click( + second_elem_of(train), inputs=[model_name], outputs=[info_train] ) - train_btn.click(second_elem_of(train), inputs=[model_name], outputs=[info]) app.launch(inbrowser=True) From 863b201b6206e031015989ab5f9879d9a31d2977 Mon Sep 17 00:00:00 2001 From: litagin02 Date: Fri, 29 Dec 2023 18:53:31 +0900 Subject: [PATCH 10/10] ver 1.1 --- .gitignore | 1 + README.md | 2 ++ docs/CHANGELOG.md | 8 ++++++++ 3 files changed, 11 insertions(+) create mode 100644 docs/CHANGELOG.md diff --git a/.gitignore b/.gitignore index 2170dfe..445cda3 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,4 @@ venv/ /pretrained/*.pth /scripts/test/ +*.zip diff --git a/README.md b/README.md index 2d14e87..4766e8d 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,8 @@ Online demo: https://huggingface.co/spaces/litagin/Style-Bert-VITS2-JVNV This repository is based on [Bert-VITS2](https://github.com/fishaudio/Bert-VITS2) v2.1, so many thanks to the original author! +- [更新履歴](docs/CHANGELOG.md) + **概要** - [Bert-VITS2](https://github.com/fishaudio/Bert-VITS2)のv2.1を元に、正確に感情や発話スタイルを強弱混みで指定して音声を生成することができるようにしたものです。 diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md new file mode 100644 index 0000000..079bf60 --- /dev/null +++ b/docs/CHANGELOG.md @@ -0,0 +1,8 @@ +# Changelog + +## v1.1 +- TrainとDatasetのWebUIの改良・調整(一括事前処理ボタン等) +- 前処理のリサンプリング時に音量を正規化するオプションを追加(デフォルトでオン) + +## v1.0 +- 初版