diff --git a/.dockerignore b/.dockerignore index da10b929b..bf1b3a037 100644 --- a/.dockerignore +++ b/.dockerignore @@ -6,12 +6,13 @@ !/style_bert_vits2/ !/bert/deberta-v2-large-japanese-char-wwm/ -!/common/ !/configs/ !/dict_data/default.csv !/model_assets/ +!/static/ !/config.py !/default_config.yml +!/initialize.py !/requirements.txt !/server_editor.py diff --git a/.gitignore b/.gitignore index cc5fed449..6ca8d26c6 100644 --- a/.gitignore +++ b/.gitignore @@ -39,3 +39,5 @@ safetensors.ipynb # pyopenjtalk's dictionary *.dic + +playground.ipynb diff --git a/Dockerfile.deploy b/Dockerfile.deploy index dd351d107..48c22d0b8 100644 --- a/Dockerfile.deploy +++ b/Dockerfile.deploy @@ -20,4 +20,4 @@ COPY --chown=user . $HOME/app RUN pip install --no-cache-dir -r $HOME/app/requirements.txt # 必要に応じて制限を変更してください -CMD ["python", "server_editor.py", "--line_length", "50", "--line_count", "3"] +CMD ["python", "server_editor.py", "--line_length", "50", "--line_count", "3", "--skip_static_files"] diff --git a/README.md b/README.md index 59059808b..b326a581c 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # Style-Bert-VITS2 +**利用の際は必ず[利用規約](/docs/TERMS_OF_USE.md)をお読みください。** + Bert-VITS2 with more controllable voice styles. https://github.com/litagin02/Style-Bert-VITS2/assets/139731664/e853f9a2-db4a-4202-a1dd-56ded3c562a0 @@ -7,13 +9,13 @@ https://github.com/litagin02/Style-Bert-VITS2/assets/139731664/e853f9a2-db4a-420 You can install via `pip install style-bert-vits2` (inference only), see [library.ipynb](/library.ipynb) for example usage. - **解説チュートリアル動画** [YouTube](https://youtu.be/aTUSzgDl1iY) [ニコニコ動画](https://www.nicovideo.jp/watch/sm43391524) -- [English README](docs/README_en.md) - [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](http://colab.research.google.com/github/litagin02/Style-Bert-VITS2/blob/master/colab.ipynb) +- [FAQ](/docs/FAQ.md) - [🤗 オンラインデモはこちらから](https://huggingface.co/spaces/litagin/Style-Bert-VITS2-Editor-Demo) - [Zennの解説記事](https://zenn.dev/litagin/articles/034819a5256ff4) - [**リリースページ**](https://github.com/litagin02/Style-Bert-VITS2/releases/)、[更新履歴](/docs/CHANGELOG.md) - + - 2024-06-01: Ver 2.5.0 (**[利用規約](/docs/TERMS_OF_USE.md)の追加**、フォルダ分けからのスタイル生成、小春音アミ・あみたろモデルの追加、インストールの高速化等) - 2024-03-16: ver 2.4.1 (**batファイルによるインストール方法の変更**) - 2024-03-15: ver 2.4.0 (大規模リファクタリングや種々の改良、ライブラリ化) - 2024-02-26: ver 2.3 (辞書機能とエディター機能) @@ -32,13 +34,15 @@ This repository is based on [Bert-VITS2](https://github.com/fishaudio/Bert-VITS2 - 入力されたテキストの内容をもとに感情豊かな音声を生成する[Bert-VITS2](https://github.com/fishaudio/Bert-VITS2)のv2.1とJapanese-Extraを元に、感情や発話スタイルを強弱込みで自由に制御できるようにしたものです。 - GitやPythonがない人でも(Windowsユーザーなら)簡単にインストールでき、学習もできます (多くを[EasyBertVits2](https://github.com/Zuntan03/EasyBertVits2/)からお借りしました)。またGoogle Colabでの学習もサポートしています: [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](http://colab.research.google.com/github/litagin02/Style-Bert-VITS2/blob/master/colab.ipynb) - 音声合成のみに使う場合は、グラボがなくてもCPUで動作します。 +- 音声合成のみに使う場合、Pythonライブラリとして`pip install style-bert-vits2`でインストールできます。例は[library.ipynb](/library.ipynb)を参照してください。 - 他との連携に使えるAPIサーバーも同梱しています ([@darai0512](https://github.com/darai0512) 様によるPRです、ありがとうございます)。 - 元々「楽しそうな文章は楽しそうに、悲しそうな文章は悲しそうに」読むのがBert-VITS2の強みですので、スタイル指定がデフォルトでも感情豊かな音声を生成することができます。 ## 使い方 -CLIでの使い方は[こちら](/docs/CLI.md)を参照してください。 +- CLIでの使い方は[こちら](/docs/CLI.md)を参照してください。 +- [よくある質問](/docs/FAQ.md)も参照してください。 ### 動作環境 @@ -52,7 +56,7 @@ Pythonライブラリとしてのpipでのインストールや使用例は[libr Windowsを前提としています。 -1. [このzipファイル](https://github.com/litagin02/Style-Bert-VITS2/releases/download/2.4.1/sbv2.zip)を**パスに日本語や空白が含まれない場所に**ダウンロードして展開します。 +1. [このzipファイル](https://github.com/litagin02/Style-Bert-VITS2/releases/download/2.5.0/sbv2.zip)を**パスに日本語や空白が含まれない場所に**ダウンロードして展開します。 - グラボがある方は、`Install-Style-Bert-VITS2.bat`をダブルクリックします。 - グラボがない方は、`Install-Style-Bert-VITS2-CPU.bat`をダブルクリックします。CPU版では学習はできませんが、音声合成とマージは可能です。 2. 待つと自動で必要な環境がインストールされます。 @@ -64,13 +68,17 @@ Windowsを前提としています。 #### GitやPython使える人 +Pythonの仮想環境・パッケージ管理ツールである[uv](https://github.com/astral-sh/uv)がpipより高速なので、それを使ってインストールすることをお勧めします。 +(使いたくない場合は通常のpipでも大丈夫です。) + ```bash +powershell -c "irm https://astral.sh/uv/install.ps1 | iex" git clone https://github.com/litagin02/Style-Bert-VITS2.git cd Style-Bert-VITS2 -python -m venv venv +uv venv venv +uv pip install torch torchaudio --index-url https://download.pytorch.org/whl/cu118 +uv pip install -r requirements.txt venv\Scripts\activate -pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118 -pip install -r requirements.txt python initialize.py # 必要なモデルとデフォルトTTSモデルをダウンロード ``` 最後を忘れずに。 @@ -116,20 +124,16 @@ model_assets - `App.bat`をダブルクリックか`python app.py`したところの「データセット作成」タブから、音声ファイルを適切な長さにスライスし、その後に文字の書き起こしを自動で行えます。 - 指示に従った後、下の「学習」タブでそのまま学習を行うことができます。 -注意: データセットの手動修正やノイズ除去等、細かい修正を行いたい場合は[Aivis](https://github.com/tsukumijima/Aivis)や、そのデータセット部分のWindows対応版 [Aivis Dataset](https://github.com/litagin02/Aivis-Dataset) を使うといいかもしれません。ですがファイル数が多い場合などは、このツールで簡易的に切り出してデータセットを作るだけでも十分という気もしています。 - -データセットがどのようなものがいいかは各自試行錯誤中してください。 - #### 学習WebUI - `App.bat`をダブルクリックか`python app.py`して開くWebUIの「学習」タブから指示に従ってください。 ### スタイルの生成 -- デフォルトスタイル「Neutral」以外のスタイルを使いたい人向けです。 +- デフォルトでは、デフォルトスタイル「Neutral」の他、学習フォルダのフォルダ分けに応じたスタイルが生成されます。 +- それ以外の方法で手動でスタイルを作成したい人向けです。 - `App.bat`をダブルクリックか`python app.py`して開くWebUIの「スタイル作成」タブから、音声ファイルを使ってスタイルを生成できます。 - 学習とは独立しているので、学習中でもできるし、学習が終わっても何度もやりなおせます(前処理は終わらせている必要があります)。 -- スタイルについての仕様の詳細は[clustering.ipynb](clustering.ipynb)を参照してください。 ### API Server diff --git a/app.py b/app.py index f556a2377..46684efad 100644 --- a/app.py +++ b/app.py @@ -3,13 +3,14 @@ import gradio as gr import torch -import yaml +from config import get_path_config from gradio_tabs.dataset import create_dataset_app from gradio_tabs.inference import create_inference_app from gradio_tabs.merge import create_merge_app from gradio_tabs.style_vectors import create_style_vectors_app from gradio_tabs.train import create_train_app +from initialize import download_default_models from style_bert_vits2.constants import GRADIO_THEME, VERSION from style_bert_vits2.nlp.japanese import pyopenjtalk_worker from style_bert_vits2.nlp.japanese.user_dict import update_dict @@ -22,11 +23,6 @@ # dict_data/ 以下の辞書データを pyopenjtalk に適用 update_dict() -# 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") @@ -34,13 +30,18 @@ parser.add_argument("--port", type=int, default=None) parser.add_argument("--no_autolaunch", action="store_true") parser.add_argument("--share", action="store_true") +parser.add_argument("--skip_default_models", action="store_true") args = parser.parse_args() device = args.device if device == "cuda" and not torch.cuda.is_available(): device = "cpu" -model_holder = TTSModelHolder(Path(assets_root), device) +if not args.skip_default_models: + download_default_models() + +path_config = get_path_config() +model_holder = TTSModelHolder(Path(path_config.assets_root), device) with gr.Blocks(theme=GRADIO_THEME) as app: gr.Markdown(f"# Style-Bert-VITS2 WebUI (version {VERSION})") @@ -56,7 +57,6 @@ with gr.Tab("マージ"): create_merge_app(model_holder=model_holder) - app.launch( server_name=args.host, server_port=args.port, diff --git a/bert_gen.py b/bert_gen.py index 1dcab9eb2..c4995cb16 100644 --- a/bert_gen.py +++ b/bert_gen.py @@ -5,21 +5,18 @@ import torch.multiprocessing as mp from tqdm import tqdm -from config import config +from config import get_config from style_bert_vits2.constants import Languages from style_bert_vits2.logging import logger from style_bert_vits2.models import commons from style_bert_vits2.models.hyper_parameters import HyperParameters -from style_bert_vits2.nlp import ( - bert_models, - cleaned_text_to_sequence, - extract_bert_feature, -) +from style_bert_vits2.nlp import cleaned_text_to_sequence, extract_bert_feature from style_bert_vits2.nlp.japanese import pyopenjtalk_worker from style_bert_vits2.nlp.japanese.user_dict import update_dict from style_bert_vits2.utils.stdout_wrapper import SAFE_STDOUT +config = get_config() # このプロセスからはワーカーを起動して辞書を使いたいので、ここで初期化 pyopenjtalk_worker.initialize_worker() @@ -61,7 +58,7 @@ def process_line(x: tuple[str, bool]): bert = torch.load(bert_path) assert bert.shape[-1] == len(phone) except Exception: - bert = extract_bert_feature(text, word2ph, language_str, device) + bert = extract_bert_feature(text, word2ph, Languages(language_str), device) assert bert.shape[-1] == len(phone) torch.save(bert, bert_path) @@ -77,10 +74,10 @@ def process_line(x: tuple[str, bool]): config_path = args.config hps = HyperParameters.load_from_json(config_path) lines: list[str] = [] - with open(hps.data.training_files, "r", encoding="utf-8") as f: + with open(hps.data.training_files, encoding="utf-8") as f: lines.extend(f.readlines()) - with open(hps.data.validation_files, "r", encoding="utf-8") as f: + with open(hps.data.validation_files, encoding="utf-8") as f: lines.extend(f.readlines()) add_blank = [hps.data.add_blank] * len(lines) diff --git a/clustering.ipynb b/clustering.ipynb deleted file mode 100644 index 12e728ef6..000000000 --- a/clustering.ipynb +++ /dev/null @@ -1,316 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# スタイルベクトルをもっと詳しく作りたい人向け\n", - "\n", - "JVNVコーパスの例を使いながらクラスタリングをいろいろいじったり、また正解ラベルからベクトルを作りたい人向けです。\n", - "もっといろいろ足したりいろんなスタイルベクトルを作って遊べると思います。\n", - "ある程度慣れている人向けです。\n", - "\n", - "JVNVコーパスのように**スタイルが既にファイル名等で分かれている場合は、最後の方のセルを使えばそれを利用してスタイルを作ることができます。**\n", - "\n", - "例では[JVNVコーパス](https://sites.google.com/site/shinnosuketakamichi/research-topics/jvnv_corpus)のjvnv-M1を使います。\n", - "\n", - "## そもそもスタイルベクトルとは\n", - "[この話者識別モデル](https://huggingface.co/pyannote/wespeaker-voxceleb-resnet34-LM)を使って生成された、1つの音声ファイルにつき256次元のベクトルです。話者識別用のものですが、感情や声音の特徴も含まれているので、スタイルベクトルとして使えます。\n", - "\n", - "このStyle-Bert-VITS2では、この256次元のベクトルをエンコーダに注入して学習しているので、推論時にそのベクトルを入れてあげる必要があります。ある感情を強く表していると思われるベクトルを入れると、その感情を強く表現した音声が生成される、という仕組みです。\n", - "\n", - "もともとが話者識別用なので、「この感情はこのベクトル」のような普遍的なスタイルベクトルは使えません。なのでこのように、いちいちデータセットごとにベクトルを作る必要があります。\n", - "\n", - "## モデルを使うために必要なもの\n", - "- `model_assets/{model_name}/model_name.safetensors`: 学習の結果出力されるモデルファイル。これは自動的にこの場所に置かれ、スタイルベクトルとは全く独立。\n", - "- `model_assets/{model_name}/style_vectors.npy`: スタイルベクトルのnumpyファイル。ベクトルをいくつかいれる。\n", - "- `model_assets/{model_name}/config.json`: モデルの設定ファイル(学習前準備で自動的に生成されるはず)。これの以下の項目を設定。\n", - "```json\n", - "{\n", - " \"data\": {\n", - " \"num_styles\": 4, // スタイルベクトルの数\n", - " \"style2id\": { // スタイルベクトルの名前とidの対応\n", - " \"Neutral\": 0,\n", - " \"Angry\": 1,\n", - " \"Happy\": 2,\n", - " \"Sad\": 3\n", - " }\n", - " }\n", - "}\n", - "```\n", - "ここでidは0から始まる整数で、スタイルベクトルのnumpyファイルの何番目のベクトルかを指定します。最初のNeutralは含めたほうがよさそうで、WebUIや下では全スタイルベクトルの平均を入れています。" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [], - "source": [ - "import os\n", - "import numpy as np\n", - "\n", - "wav_dir = \"Data/jvnv-M1/wavs\"\n", - "\n", - "embs = []\n", - "names= []\n", - "for file in os.listdir(wav_dir):\n", - " if file.endswith(\".npy\"):\n", - " xvec = np.load(os.path.join(wav_dir, file))\n", - " embs.append(np.expand_dims(xvec, axis=0))\n", - " names.append(file)\n", - "\n", - "x = np.concatenate(embs, axis=0)" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "# TSNEで可視化\n", - "from sklearn.manifold import TSNE\n", - "import matplotlib.pyplot as plt\n", - "\n", - "\n", - "# 特徴量の関係か、コサイン距離が良さげ\n", - "tsne = TSNE(n_components=2, random_state=42, metric=\"cosine\")\n", - "\n", - "x_tsne = tsne.fit_transform(x)\n", - "\n", - "plt.figure(figsize=(7, 7))\n", - "plt.scatter(x_tsne[:, 0], x_tsne[:, 1])\n", - "\n", - "plt.show()" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [], - "source": [ - "from sklearn.cluster import KMeans, AgglomerativeClustering\n", - "\n", - "method = \"k\"\n", - "n_clusters = 5\n", - "\n", - "if method == \"k\":\n", - " model = KMeans(n_clusters=n_clusters, random_state=42, n_init=10)\n", - "elif method == \"a\":\n", - " model = AgglomerativeClustering(n_clusters=n_clusters)\n", - "\n", - "y_predict = model.fit_predict(x)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "上は直接生のベクトルでクラスタリングしてますが、次のようにt-SNEで図のように2次元に削減したものをクラスタリングしたほうが、識別がきれいに分かれる、ことが多いような気がします、が詳しくは分かりません:" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [], - "source": [ - "y_predict = model.fit_predict(x_tsne)" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [], - "source": [ - "centroids = []\n", - "for i in range(n_clusters):\n", - " centroids.append(x[y_predict == i].mean(axis=0))" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "# TSNEで可視化\n", - "import matplotlib.pyplot as plt\n", - "\n", - "plt.figure(figsize=(7, 7))\n", - "plt.scatter(x_tsne[:, 0], x_tsne[:, 1], c=y_predict)\n", - "\n", - "plt.show()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from scipy.spatial.distance import cdist\n", - "from IPython.display import Audio, display\n", - "\n", - "# 各クラスターのセントロイドに最も近い点に対応するファイル名を取得\n", - "closest_files = []\n", - "for center_idx in range(len(centroids)):\n", - " closest_idx = np.argmin(\n", - " cdist(centroids[center_idx : center_idx + 1], x, metric=\"cosine\")\n", - " )\n", - " closest_files.append(names[closest_idx])\n", - "\n", - "# 対応する音声ファイルをJupyterノートブック上で再生\n", - "for file_name in closest_files:\n", - " wav_path = os.path.join(wav_dir, file_name.replace(\".npy\", \"\"))\n", - " if os.path.exists(wav_path):\n", - " print(wav_path)\n", - " display(Audio(wav_path))" - ] - }, - { - "cell_type": "code", - "execution_count": 37, - "metadata": {}, - "outputs": [], - "source": [ - "# meanとcentroidを保存\n", - "mean = x.mean(axis=0)\n", - "save_vectors = np.vstack([mean, centroids])\n", - "os.makedirs(\"model_assets/jvnv-M1\", exist_ok=True)\n", - "np.save(\"model_assets/jvnv-M1/style_vectors.npy\", save_vectors)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 正解ラベルがファイル名から分かる場合の作り方\n", - "JVNVコーパス等でファイル名によってスタイルラベルが分かる場合、以下のようにしてスタイルベクトルを作ることができます(デフォルトのJVNVモデルのスタイルはこれで作成しています)。" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": {}, - "outputs": [], - "source": [ - "# JVNVコーパスの場合は正解はファイル名の最初の方から分かる。それを使って正解ラベルを作成\n", - "label_dict = {\"ang\": 0, \"dis\": 1, \"fea\": 2, \"hap\": 3, \"sad\": 4, \"sur\": 5}\n", - "\n", - "y_true = [label_dict[name[3:6]] for name in names]" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": {}, - "outputs": [], - "source": [ - "# 正解のセントロイドを計算\n", - "y_true = np.array(y_true)\n", - "true_centroids = []\n", - "for i in range(6):\n", - " true_centroids.append(np.mean(x[y_true == i], axis=0))\n", - "\n", - "true_centroids = np.array(true_centroids)\n", - "\n", - "# すべてのベクトルの平均を計算\n", - "mean = np.mean(x, axis=0)\n", - "\n", - "# 保存\n", - "os.makedirs(\"model_assets/jvnv-M1\", exist_ok=True)\n", - "np.save(\"model_assets/jvnv-M1/style_vectors.npy\", np.vstack([mean, true_centroids]))" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "# TSNEで正解ラベルを可視化\n", - "import matplotlib.pyplot as plt\n", - "\n", - "cmap = plt.get_cmap(\"tab10\")\n", - "\n", - "true_label_dict = {0: \"ang\", 1: \"dis\", 2: \"fea\", 3: \"hap\", 4: \"sad\", 5: \"sur\"}\n", - "\n", - "plt.figure(figsize=(7, 7))\n", - "for i in true_label_dict:\n", - " plt.scatter(\n", - " x_tsne[y_true == i, 0],\n", - " x_tsne[y_true == i, 1],\n", - " color=cmap(i),\n", - " label=f\"{true_label_dict[i]}\",\n", - " )\n", - "plt.legend()\n", - "plt.show()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "手動でconfig.jsonのstyle2idとnum_stylesを設定するのを忘れずに。" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "venv", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.10.11" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/colab.ipynb b/colab.ipynb index 4f21124c7..0234e8614 100644 --- a/colab.ipynb +++ b/colab.ipynb @@ -1,384 +1,455 @@ { - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Style-Bert-VITS2 (ver 2.4.1) のGoogle Colabでの学習\n", - "\n", - "Google Colab上でStyle-Bert-VITS2の学習を行うことができます。\n", - "\n", - "このnotebookでは、通常使用ではあなたのGoogle Driveにフォルダ`Style-Bert-VITS2`を作り、その内部での作業を行います。他のフォルダには触れません。\n", - "Google Driveを使わない場合は、初期設定のところで適切なパスを指定してください。\n", - "\n", - "## 流れ\n", - "\n", - "### 学習を最初からやりたいとき\n", - "上から順に実行していけばいいです。音声合成に必要なファイルはGoogle Driveの`Style-Bert-VITS2/model_assets/`に保存されます。また、途中経過も`Style-Bert-VITS2/Data/`に保存されるので、学習を中断したり、途中から再開することもできます。\n", - "\n", - "### 学習を途中から再開したいとき\n", - "0と1を行い、3の前処理は飛ばして、4から始めてください。スタイル分け5は、学習が終わったら必要なら行ってください。\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 0. 環境構築\n", - "\n", - "Style-Bert-VITS2の環境をcolab上に構築します。グラボモードが有効になっていることを確認し、以下のセルを順に実行してください。\n", - "\n", - "**最近のcolabのアップデートにより、エラーダイアログ「WARNING: The following packages were previously imported in this runtime: [pydevd_plugins]」が出るが、「キャンセル」を選択して続行してください。**" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# このセルを実行して環境構築してください。\n", - "# エラーダイアログ「WARNING: The following packages were previously imported in this runtime: [pydevd_plugins]」が出るが「キャンセル」を選択して続行してください。\n", - "\n", - "!git clone https://github.com/litagin02/Style-Bert-VITS2.git\n", - "%cd Style-Bert-VITS2/\n", - "!pip install -r requirements.txt\n", - "!python initialize.py --skip_jvnv" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Google driveを使う方はこちらを実行してください。\n", - "\n", - "from google.colab import drive\n", - "drive.mount(\"/content/drive\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 1. 初期設定\n", - "\n", - "学習とその結果を保存するディレクトリ名を指定します。\n", - "Google driveの場合はそのまま実行、カスタマイズしたい方は変更して実行してください。" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [], - "source": [ - "# 学習に必要なファイルや途中経過が保存されるディレクトリ\n", - "dataset_root = \"/content/drive/MyDrive/Style-Bert-VITS2/Data\"\n", - "\n", - "# 学習結果(音声合成に必要なファイルたち)が保存されるディレクトリ\n", - "assets_root = \"/content/drive/MyDrive/Style-Bert-VITS2/model_assets\"\n", - "\n", - "import yaml\n", - "\n", - "\n", - "with open(\"configs/paths.yml\", \"w\", encoding=\"utf-8\") as f:\n", - " yaml.dump({\"dataset_root\": dataset_root, \"assets_root\": assets_root}, f)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 2. 学習に使うデータ準備\n", - "\n", - "すでに音声ファイル(1ファイル2-12秒程度)とその書き起こしデータがある場合は2.2を、ない場合は2.1を実行してください。" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### 2.1 音声ファイルからのデータセットの作成(ある人はスキップ可)\n", - "\n", - "音声ファイル(1ファイル2-12秒程度)とその書き起こしのデータセットを持っていない方は、(日本語の)音声ファイルのみから以下の手順でデータセットを作成することができます。Google drive上の`Style-Bert-VITS2/inputs/`フォルダに音声ファイル(wavファイル形式、1ファイルでも複数ファイルでも可)を置いて、下を実行すると、データセットが作られ、自動的に正しい場所へ配置されます。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# 元となる音声ファイル(wav形式)を入れるディレクトリ\n", - "input_dir = \"/content/drive/MyDrive/Style-Bert-VITS2/inputs\"\n", - "# モデル名(話者名)を入力\n", - "model_name = \"your_model_name\"\n", - "\n", - "# こういうふうに書き起こして欲しいという例文(句読点の入れ方・笑い方や固有名詞等)\n", - "initial_prompt = \"こんにちは。元気、ですかー?ふふっ、私は……ちゃんと元気だよ!\"\n", - "\n", - "!python slice.py -i {input_dir} --model_name {model_name}\n", - "!python transcribe.py --model_name {model_name} --initial_prompt {initial_prompt} --use_hf_whisper" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "成功したらそのまま3へ進んでください" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### 2.2 音声ファイルと書き起こしデータがすでにある場合\n", - "\n", - "指示に従って適切にデータセットを配置してください。\n", - "\n", - "次のセルを実行して、学習データをいれるフォルダ(1で設定した`dataset_root`)を作成します。" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": { - "id": "esCNJl704h52" - }, - "outputs": [], - "source": [ - "import os\n", - "\n", - "os.makedirs(dataset_root, exist_ok=True)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "次に、学習に必要なデータを、Google driveに作成された`Style-Bert-VITS2/Data`フォルダに配置します。\n", - "\n", - "まず音声データ(wavファイルで1ファイルが2-12秒程度の、長すぎず短すぎない発話のものをいくつか)と、書き起こしテキストを用意してください。wavファイル名やモデルの名前は空白を含まない半角で、wavファイルの拡張子は小文字`.wav`である必要があります。\n", - "\n", - "書き起こしテキストは、次の形式で記述してください。\n", - "```\n", - "****.wav|{話者名}|{言語ID、ZHかJPかEN}|{書き起こしテキスト}\n", - "```\n", - "\n", - "例:\n", - "```\n", - "wav_number1.wav|hanako|JP|こんにちは、聞こえて、いますか?\n", - "wav_next.wav|taro|JP|はい、聞こえています……。\n", - "english_teacher.wav|Mary|EN|How are you? I'm fine, thank you, and you?\n", - "...\n", - "```\n", - "日本語話者の単一話者データセットで構いません。\n", - "\n", - "### データセットの配置\n", - "\n", - "次にモデルの名前を適当に決めてください(空白を含まない半角英数字がよいです)。\n", - "そして、書き起こしファイルを`esd.list`という名前で保存し、またwavファイルも`raw`というフォルダを作成し、あなたのGoogle Driveの中の(上で自動的に作られるはずの)`Data`フォルダのなかに、次のように配置します。\n", - "```\n", - "├── Data\n", - "│ ├── {モデルの名前}\n", - "│ │ ├── esd.list\n", - "│ │ ├── raw\n", - "│ │ │ ├── ****.wav\n", - "│ │ │ ├── ****.wav\n", - "│ │ │ ├── ...\n", - "```" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "5r85-W20ECcr" - }, - "source": [ - "## 3. 学習の前処理\n", - "\n", - "次に学習の前処理を行います。必要なパラメータをここで指定します。次のセルに設定等を入力して実行してください。「~~かどうか」は`True`もしくは`False`を指定してください。" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": { - "id": "CXR7kjuF5GlE" - }, - "outputs": [], - "source": [ - "# 上でつけたフォルダの名前`Data/{model_name}/`\n", - "model_name = \"your_model_name\"\n", - "\n", - "# JP-Extra (日本語特化版)を使うかどうか。日本語の能力が向上する代わりに英語と中国語は使えなくなります。\n", - "use_jp_extra = True\n", - "\n", - "# 学習のバッチサイズ。VRAMのはみ出具合に応じて調整してください。\n", - "batch_size = 4\n", - "\n", - "# 学習のエポック数(データセットを合計何周するか)。\n", - "# 100で多すぎるほどかもしれませんが、もっと多くやると質が上がるのかもしれません。\n", - "epochs = 100\n", - "\n", - "# 保存頻度。何ステップごとにモデルを保存するか。分からなければデフォルトのままで。\n", - "save_every_steps = 1000\n", - "\n", - "# 音声ファイルの音量を正規化するかどうか\n", - "normalize = False\n", - "\n", - "# 音声ファイルの開始・終了にある無音区間を削除するかどうか\n", - "trim = False\n", - "\n", - "# 読みのエラーが出た場合にどうするか。\n", - "# \"raise\"ならテキスト前処理が終わったら中断、\"skip\"なら読めない行は学習に使わない、\"use\"なら無理やり使う\n", - "yomi_error = \"skip\"" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "上のセルが実行されたら、次のセルを実行して学習の前処理を行います。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "id": "xMVaOIPLabV5", - "outputId": "15fac868-9132-45d9-9f5f-365b6aeb67b0" - }, - "outputs": [], - "source": [ - "from gradio_tabs.train import preprocess_all\n", - "\n", - "preprocess_all(\n", - " model_name=model_name,\n", - " batch_size=batch_size,\n", - " epochs=epochs,\n", - " save_every_steps=save_every_steps,\n", - " num_processes=2,\n", - " normalize=normalize,\n", - " trim=trim,\n", - " freeze_EN_bert=False,\n", - " freeze_JP_bert=False,\n", - " freeze_ZH_bert=False,\n", - " freeze_style=False,\n", - " freeze_decoder=False, # ここをTrueにするともしかしたら違う結果になるかもしれません。\n", - " use_jp_extra=use_jp_extra,\n", - " val_per_lang=0,\n", - " log_interval=200,\n", - " yomi_error=yomi_error\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 4. 学習\n", - "\n", - "前処理が正常に終わったら、学習を行います。次のセルを実行すると学習が始まります。\n", - "\n", - "学習の結果は、上で指定した`save_every_steps`の間隔で、Google Driveの中の`Style-Bert-VITS2/Data/{モデルの名前}/model_assets/`フォルダに保存されます。\n", - "\n", - "このフォルダをダウンロードし、ローカルのStyle-Bert-VITS2の`model_assets`フォルダに上書きすれば、学習結果を使うことができます。" - ] + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "id": "F7aJhsgLAWvO" + }, + "source": [ + "# Style-Bert-VITS2 (ver 2.5.0) のGoogle Colabでの学習\n", + "\n", + "Google Colab上でStyle-Bert-VITS2の学習を行うことができます。\n", + "\n", + "このnotebookでは、通常使用ではあなたのGoogle Driveにフォルダ`Style-Bert-VITS2`を作り、その内部での作業を行います。他のフォルダには触れません。\n", + "Google Driveを使わない場合は、初期設定のところで適切なパスを指定してください。\n", + "\n", + "## 流れ\n", + "\n", + "### 学習を最初からやりたいとき\n", + "上から順に実行していけばいいです。音声合成に必要なファイルはGoogle Driveの`Style-Bert-VITS2/model_assets/`に保存されます。また、途中経過も`Style-Bert-VITS2/Data/`に保存されるので、学習を中断したり、途中から再開することもできます。\n", + "\n", + "### 学習を途中から再開したいとき\n", + "0と1を行い、3の前処理は飛ばして、4から始めてください。スタイル分け5は、学習が終わったら必要なら行ってください。\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "L-gAIubBAWvQ" + }, + "source": [ + "## 0. 環境構築\n", + "\n", + "Style-Bert-VITS2の環境をcolab上に構築します。ランタイムがT4等のGPUバックエンドになっていることを確認し、実行してください。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "id": "laieKrbEb6Ij", - "outputId": "72238c88-f294-4ed9-84f6-84c1c17999ca" - }, - "outputs": [], - "source": [ - "# 上でつけたモデル名を入力。学習を途中からする場合はきちんとモデルが保存されているフォルダ名を入力。\n", - "model_name = \"your_model_name\"\n", - "\n", - "\n", - "import yaml\n", - "from gradio_tabs.train import get_path\n", - "\n", - "dataset_path, _, _, _, config_path = get_path(model_name)\n", - "\n", - "with open(\"default_config.yml\", \"r\", encoding=\"utf-8\") as f:\n", - " yml_data = yaml.safe_load(f)\n", - "yml_data[\"model_name\"] = model_name\n", - "with open(\"config.yml\", \"w\", encoding=\"utf-8\") as f:\n", - " yaml.dump(yml_data, f, allow_unicode=True)" - ] + "id": "0GNj8JyDAlm2", + "outputId": "d8be4a1a-e52d-46f8-8675-3f1a24bc9a51" + }, + "outputs": [], + "source": [ + "import os\n", + "\n", + "os.environ[\"PATH\"] += \":/root/.cargo/bin\"\n", + "\n", + "!curl -LsSf https://astral.sh/uv/install.sh | sh\n", + "!git clone https://github.com/litagin02/Style-Bert-VITS2.git\n", + "%cd Style-Bert-VITS2/\n", + "!uv pip install --system -q -r requirements-colab.txt\n", + "!python initialize.py --skip_default_models" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# 日本語特化版を「使う」場合\n", - "!python train_ms_jp_extra.py --config {config_path} --model {dataset_path} --assets_root {assets_root}" - ] + "id": "o5z1nzkvAWvR", + "outputId": "cd87f053-18e0-4dbb-f904-d5230d1fa7ef" + }, + "outputs": [], + "source": [ + "# Google driveを使う方はこちらを実行してください。\n", + "\n", + "from google.colab import drive\n", + "\n", + "drive.mount(\"/content/drive\")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "WU9apXzcAWvR" + }, + "source": [ + "## 1. 初期設定\n", + "\n", + "学習とその結果を保存するディレクトリ名を指定します。\n", + "Google driveの場合はそのまま実行、カスタマイズしたい方は変更して実行してください。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "gO3OwZV1AWvR" + }, + "outputs": [], + "source": [ + "# 学習に必要なファイルや途中経過が保存されるディレクトリ\n", + "dataset_root = \"/content/drive/MyDrive/Style-Bert-VITS2/Data\"\n", + "\n", + "# 学習結果(音声合成に必要なファイルたち)が保存されるディレクトリ\n", + "assets_root = \"/content/drive/MyDrive/Style-Bert-VITS2/model_assets\"\n", + "\n", + "import yaml\n", + "\n", + "\n", + "with open(\"configs/paths.yml\", \"w\", encoding=\"utf-8\") as f:\n", + " yaml.dump({\"dataset_root\": dataset_root, \"assets_root\": assets_root}, f)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "dA_yLeezAWvS" + }, + "source": [ + "## 2. 学習に使うデータ準備\n", + "\n", + "すでに音声ファイル(1ファイル2-12秒程度)とその書き起こしデータがある場合は2.2を、ない場合は2.1を実行してください。" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "8s9gOnTCAWvS" + }, + "source": [ + "### 2.1 音声ファイルからのデータセットの作成(ある人はスキップ可)\n", + "\n", + "音声ファイル(1ファイル2-12秒程度)とその書き起こしのデータセットを持っていない方は、(日本語の)音声ファイルのみから以下の手順でデータセットを作成することができます。Google drive上の`Style-Bert-VITS2/inputs/`フォルダに音声ファイル(wavやmp3等の通常の音声ファイル形式、1ファイルでも複数ファイルでも可)を置いて、下を実行すると、データセットが作られ、自動的に正しい場所へ配置されます。\n", + "\n", + "**2024-06-02のVer 2.5以降**、`inputs/`フォルダにサブフォルダを2個以上作ってそこへ音声ファイルをスタイルに応じて振り分けて置くと、学習の際にサブディレクトリに応じたスタイルが自動的に作成されます。デフォルトスタイルのみでよい場合や手動でスタイルを後で作成する場合は`inputs/`直下へ入れれば大丈夫です。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# 日本語特化版を「使わない」場合\n", - "!python train_ms.py --config {config_path} --model {dataset_path} --assets_root {assets_root}" - ] + "id": "_fXCTPuiAWvS", + "outputId": "47abd55b-efe5-48e2-f6fa-8e2016efe0ec" + }, + "outputs": [], + "source": [ + "# 元となる音声ファイル(wav形式)を入れるディレクトリ\n", + "input_dir = \"/content/drive/MyDrive/Style-Bert-VITS2/inputs\"\n", + "# モデル名(話者名)を入力\n", + "model_name = \"your_model_name\"\n", + "\n", + "# こういうふうに書き起こして欲しいという例文(句読点の入れ方・笑い方や固有名詞等)\n", + "initial_prompt = \"こんにちは。元気、ですかー?ふふっ、私は……ちゃんと元気だよ!\"\n", + "\n", + "!python slice.py -i {input_dir} --model_name {model_name}\n", + "!python transcribe.py --model_name {model_name} --initial_prompt {initial_prompt} --use_hf_whisper" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "j7vEWewoAWvS" + }, + "source": [ + "成功したらそのまま3へ進んでください" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "Z3AC-3zpAWvS" + }, + "source": [ + "### 2.2 音声ファイルと書き起こしデータがすでにある場合\n", + "\n", + "指示に従って適切にデータセットを配置してください。\n", + "\n", + "次のセルを実行して、学習データをいれるフォルダ(1で設定した`dataset_root`)を作成します。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "esCNJl704h52" + }, + "outputs": [], + "source": [ + "import os\n", + "\n", + "os.makedirs(dataset_root, exist_ok=True)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "aaDgJCjCAWvT" + }, + "source": [ + "まず音声データと、書き起こしテキストを用意してください。\n", + "\n", + "それを次のように配置します。\n", + "```\n", + "├── Data/\n", + "│ ├── {モデルの名前}\n", + "│ │ ├── esd.list\n", + "│ │ ├── raw/\n", + "│ │ │ ├── foo.wav\n", + "│ │ │ ├── bar.mp3\n", + "│ │ │ ├── style1/\n", + "│ │ │ │ ├── baz.wav\n", + "│ │ │ │ ├── qux.wav\n", + "│ │ │ ├── style2/\n", + "│ │ │ │ ├── corge.wav\n", + "│ │ │ │ ├── grault.wav\n", + "...\n", + "```\n", + "\n", + "### 配置の仕方\n", + "- 上のように配置すると、`style1/`と`style2/`フォルダの内部(直下以外も含む)に入っている音声ファイルたちから、自動的にデフォルトスタイルに加えて`style1`と`style2`というスタイルが作成されます\n", + "- 特にスタイルを作る必要がない場合や、スタイル分類機能等でスタイルを作る場合は、`raw/`フォルダ直下に全てを配置してください。このように`raw/`のサブディレクトリの個数が0または1の場合は、スタイルはデフォルトスタイルのみが作成されます。\n", + "- 音声ファイルのフォーマットはwav形式以外にもmp3等の多くの音声ファイルに対応しています\n", + "\n", + "### 書き起こしファイル`esd.list`\n", + "\n", + "`Data/{モデルの名前}/esd.list` ファイルには、以下のフォーマットで各音声ファイルの情報を記述してください。\n", + "\n", + "\n", + "```\n", + "path/to/audio.wav(wavファイル以外でもこう書く)|{話者名}|{言語ID、ZHかJPかEN}|{書き起こしテキスト}\n", + "```\n", + "\n", + "- ここで、最初の`path/to/audio.wav`は、`raw/`からの相対パスです。つまり、`raw/foo.wav`の場合は`foo.wav`、`raw/style1/bar.wav`の場合は`style1/bar.wav`となります。\n", + "- 拡張子がwavでない場合でも、`esd.list`には`wav`と書いてください、つまり、`raw/bar.mp3`の場合でも`bar.wav`と書いてください。\n", + "\n", + "\n", + "例:\n", + "```\n", + "foo.wav|hanako|JP|こんにちは、元気ですか?\n", + "bar.wav|taro|JP|はい、聞こえています……。何か用ですか?\n", + "style1/baz.wav|hanako|JP|今日はいい天気ですね。\n", + "style1/qux.wav|taro|JP|はい、そうですね。\n", + "...\n", + "english_teacher.wav|Mary|EN|How are you? I'm fine, thank you, and you?\n", + "...\n", + "```\n", + "もちろん日本語話者の単一話者データセットでも構いません。" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "5r85-W20ECcr" + }, + "source": [ + "## 3. 学習の前処理\n", + "\n", + "次に学習の前処理を行います。必要なパラメータをここで指定します。次のセルに設定等を入力して実行してください。「~~かどうか」は`True`もしくは`False`を指定してください。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "CXR7kjuF5GlE" + }, + "outputs": [], + "source": [ + "# 上でつけたフォルダの名前`Data/{model_name}/`\n", + "model_name = \"your_model_name\"\n", + "\n", + "# JP-Extra (日本語特化版)を使うかどうか。日本語の能力が向上する代わりに英語と中国語は使えなくなります。\n", + "use_jp_extra = True\n", + "\n", + "# 学習のバッチサイズ。VRAMのはみ出具合に応じて調整してください。\n", + "batch_size = 4\n", + "\n", + "# 学習のエポック数(データセットを合計何周するか)。\n", + "# 100で多すぎるほどかもしれませんが、もっと多くやると質が上がるのかもしれません。\n", + "epochs = 100\n", + "\n", + "# 保存頻度。何ステップごとにモデルを保存するか。分からなければデフォルトのままで。\n", + "save_every_steps = 1000\n", + "\n", + "# 音声ファイルの音量を正規化するかどうか\n", + "normalize = False\n", + "\n", + "# 音声ファイルの開始・終了にある無音区間を削除するかどうか\n", + "trim = False\n", + "\n", + "# 読みのエラーが出た場合にどうするか。\n", + "# \"raise\"ならテキスト前処理が終わったら中断、\"skip\"なら読めない行は学習に使わない、\"use\"なら無理やり使う\n", + "yomi_error = \"skip\"" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "BFZdLTtpAWvT" + }, + "source": [ + "上のセルが実行されたら、次のセルを実行して学習の前処理を行います。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "id": "c7g0hrdeP1Tl", - "outputId": "94f9a6f6-027f-4554-ce0c-60ac56251c22" - }, - "outputs": [], - "source": [ - "# 学習結果を試す・マージ・スタイル分けはこちらから\n", - "!python app.py --share" - ] - } - ], - "metadata": { - "accelerator": "GPU", + "id": "xMVaOIPLabV5", + "outputId": "36b1c2b2-6df0-4d00-d86a-519a0fc0af63" + }, + "outputs": [], + "source": [ + "from gradio_tabs.train import preprocess_all\n", + "from style_bert_vits2.nlp.japanese import pyopenjtalk_worker\n", + "\n", + "\n", + "pyopenjtalk_worker.initialize_worker()\n", + "\n", + "preprocess_all(\n", + " model_name=model_name,\n", + " batch_size=batch_size,\n", + " epochs=epochs,\n", + " save_every_steps=save_every_steps,\n", + " num_processes=2,\n", + " normalize=normalize,\n", + " trim=trim,\n", + " freeze_EN_bert=False,\n", + " freeze_JP_bert=False,\n", + " freeze_ZH_bert=False,\n", + " freeze_style=False,\n", + " freeze_decoder=False,\n", + " use_jp_extra=use_jp_extra,\n", + " val_per_lang=0,\n", + " log_interval=200,\n", + " yomi_error=yomi_error,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "sVhwI5C-AWvT" + }, + "source": [ + "## 4. 学習\n", + "\n", + "前処理が正常に終わったら、学習を行います。次のセルを実行すると学習が始まります。\n", + "\n", + "学習の結果は、上で指定した`save_every_steps`の間隔で、Google Driveの中の`Style-Bert-VITS2/Data/{モデルの名前}/model_assets/`フォルダに保存されます。\n", + "\n", + "このフォルダをダウンロードし、ローカルのStyle-Bert-VITS2の`model_assets`フォルダに上書きすれば、学習結果を使うことができます。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "laieKrbEb6Ij" + }, + "outputs": [], + "source": [ + "# 上でつけたモデル名を入力。学習を途中からする場合はきちんとモデルが保存されているフォルダ名を入力。\n", + "model_name = \"your_model_name\"\n", + "\n", + "\n", + "import yaml\n", + "from gradio_tabs.train import get_path\n", + "\n", + "paths = get_path(model_name)\n", + "dataset_path = str(paths.dataset_path)\n", + "config_path = str(paths.config_path)\n", + "\n", + "with open(\"default_config.yml\", \"r\", encoding=\"utf-8\") as f:\n", + " yml_data = yaml.safe_load(f)\n", + "yml_data[\"model_name\"] = model_name\n", + "with open(\"config.yml\", \"w\", encoding=\"utf-8\") as f:\n", + " yaml.dump(yml_data, f, allow_unicode=True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { "colab": { - "gpuType": "T4", - "provenance": [] + "background_save": true, + "base_uri": "https://localhost:8080/" }, - "kernelspec": { - "display_name": "Python 3", - "name": "python3" + "id": "JqGeHNabAWvT", + "outputId": "c51b422c-728b-420b-fa92-b787fa058adf" + }, + "outputs": [], + "source": [ + "# 日本語特化版を「使う」場合\n", + "!python train_ms_jp_extra.py --config {config_path} --model {dataset_path} --assets_root {assets_root}" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "rVbjh-WPAWvU" + }, + "outputs": [], + "source": [ + "# 日本語特化版を「使わない」場合\n", + "!python train_ms.py --config {config_path} --model {dataset_path} --assets_root {assets_root}" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.10.11" - } + "id": "c7g0hrdeP1Tl", + "outputId": "4bb9d21e-50df-4ba5-a547-daa78a4b63dc" + }, + "outputs": [], + "source": [ + "# 学習結果を試す・マージ・スタイル分けはこちらから\n", + "!python app.py --share --skip_default_models" + ] + } + ], + "metadata": { + "accelerator": "GPU", + "colab": { + "gpuType": "T4", + "provenance": [] + }, + "kernelspec": { + "display_name": "Python 3", + "name": "python3" }, - "nbformat": 4, - "nbformat_minor": 0 + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.11" + } + }, + "nbformat": 4, + "nbformat_minor": 0 } diff --git a/config.py b/config.py index 2229e6156..4ce32f8d3 100644 --- a/config.py +++ b/config.py @@ -2,9 +2,9 @@ @Desc: 全局配置文件读取 """ -import os import shutil -from typing import Dict, List +from pathlib import Path +from typing import Any import torch import yaml @@ -12,6 +12,12 @@ from style_bert_vits2.logging import logger +class PathConfig: + def __init__(self, dataset_root: str, assets_root: str): + self.dataset_root = Path(dataset_root) + self.assets_root = Path(assets_root) + + # If not cuda available, set possible devices to cpu cuda_available = torch.cuda.is_available() @@ -20,17 +26,17 @@ class Resample_config: """重采样配置""" def __init__(self, in_dir: str, out_dir: str, sampling_rate: int = 44100): - self.sampling_rate: int = sampling_rate # 目标采样率 - self.in_dir: str = in_dir # 待处理音频目录路径 - self.out_dir: str = out_dir # 重采样输出路径 + self.sampling_rate = sampling_rate # 目标采样率 + self.in_dir = Path(in_dir) # 待处理音频目录路径 + self.out_dir = Path(out_dir) # 重采样输出路径 @classmethod - def from_dict(cls, dataset_path: str, data: Dict[str, any]): + def from_dict(cls, dataset_path: Path, data: dict[str, Any]): """从字典中生成实例""" # 不检查路径是否有效,此逻辑在resample.py中处理 - data["in_dir"] = os.path.join(dataset_path, data["in_dir"]) - data["out_dir"] = os.path.join(dataset_path, data["out_dir"]) + data["in_dir"] = dataset_path / data["in_dir"] + data["out_dir"] = dataset_path / data["out_dir"] return cls(**data) @@ -49,39 +55,32 @@ def __init__( max_val_total: int = 10000, clean: bool = True, ): - self.transcription_path: str = ( - transcription_path # 原始文本文件路径,文本格式应为{wav_path}|{speaker_name}|{language}|{text}。 - ) - self.cleaned_path: str = ( - cleaned_path # 数据清洗后文本路径,可以不填。不填则将在原始文本目录生成 - ) - self.train_path: str = ( - train_path # 训练集路径,可以不填。不填则将在原始文本目录生成 - ) - self.val_path: str = ( - val_path # 验证集路径,可以不填。不填则将在原始文本目录生成 - ) - self.config_path: str = config_path # 配置文件路径 - self.val_per_lang: int = val_per_lang # 每个speaker的验证集条数 - self.max_val_total: int = ( - max_val_total # 验证集最大条数,多于的会被截断并放到训练集中 - ) - self.clean: bool = clean # 是否进行数据清洗 + self.transcription_path = Path(transcription_path) + self.train_path = Path(train_path) + if cleaned_path == "" or cleaned_path is None: + self.cleaned_path = self.transcription_path.with_name( + self.transcription_path.name + ".cleaned" + ) + else: + self.cleaned_path = Path(cleaned_path) + self.val_path = Path(val_path) + self.config_path = Path(config_path) + self.val_per_lang = val_per_lang + self.max_val_total = max_val_total + self.clean = clean @classmethod - def from_dict(cls, dataset_path: str, data: Dict[str, any]): + def from_dict(cls, dataset_path: Path, data: dict[str, Any]): """从字典中生成实例""" - data["transcription_path"] = os.path.join( - dataset_path, data["transcription_path"] - ) + data["transcription_path"] = dataset_path / data["transcription_path"] if data["cleaned_path"] == "" or data["cleaned_path"] is None: - data["cleaned_path"] = None + data["cleaned_path"] = "" else: - data["cleaned_path"] = os.path.join(dataset_path, data["cleaned_path"]) - data["train_path"] = os.path.join(dataset_path, data["train_path"]) - data["val_path"] = os.path.join(dataset_path, data["val_path"]) - data["config_path"] = os.path.join(dataset_path, data["config_path"]) + data["cleaned_path"] = dataset_path / data["cleaned_path"] + data["train_path"] = dataset_path / data["train_path"] + data["val_path"] = dataset_path / data["val_path"] + data["config_path"] = dataset_path / data["config_path"] return cls(**data) @@ -96,7 +95,7 @@ def __init__( device: str = "cuda", use_multi_device: bool = False, ): - self.config_path = config_path + self.config_path = Path(config_path) self.num_processes = num_processes if not cuda_available: device = "cpu" @@ -104,8 +103,8 @@ def __init__( self.use_multi_device = use_multi_device @classmethod - def from_dict(cls, dataset_path: str, data: Dict[str, any]): - data["config_path"] = os.path.join(dataset_path, data["config_path"]) + def from_dict(cls, dataset_path: Path, data: dict[str, Any]): + data["config_path"] = dataset_path / data["config_path"] return cls(**data) @@ -119,15 +118,15 @@ def __init__( num_processes: int = 4, device: str = "cuda", ): - self.config_path = config_path + self.config_path = Path(config_path) self.num_processes = num_processes if not cuda_available: device = "cpu" self.device = device @classmethod - def from_dict(cls, dataset_path: str, data: Dict[str, any]): - data["config_path"] = os.path.join(dataset_path, data["config_path"]) + def from_dict(cls, dataset_path: Path, data: dict[str, Any]): + data["config_path"] = dataset_path / data["config_path"] return cls(**data) @@ -138,7 +137,7 @@ class Train_ms_config: def __init__( self, config_path: str, - env: Dict[str, any], + env: dict[str, Any], # base: Dict[str, any], model_dir: str, num_workers: int, @@ -147,16 +146,18 @@ def __init__( ): self.env = env # 需要加载的环境变量 # self.base = base # 底模配置 - self.model_dir = model_dir # 训练模型存储目录,该路径为相对于dataset_path的路径,而非项目根目录 - self.config_path = config_path # 配置文件路径 + self.model_dir = Path( + model_dir + ) # 训练模型存储目录,该路径为相对于dataset_path的路径,而非项目根目录 + self.config_path = Path(config_path) # 配置文件路径 self.num_workers = num_workers # worker数量 self.spec_cache = spec_cache # 是否启用spec缓存 self.keep_ckpts = keep_ckpts # ckpt数量 @classmethod - def from_dict(cls, dataset_path: str, data: Dict[str, any]): + def from_dict(cls, dataset_path: Path, data: dict[str, Any]): # data["model"] = os.path.join(dataset_path, data["model"]) - data["config_path"] = os.path.join(dataset_path, data["config_path"]) + data["config_path"] = dataset_path / data["config_path"] return cls(**data) @@ -176,20 +177,18 @@ def __init__( ): if not cuda_available: device = "cpu" - self.device: str = device - self.model: str = model # 端口号 - self.config_path: str = config_path # 是否公开部署,对外网开放 - self.port: int = port # 是否开启debug模式 - self.share: bool = share # 模型路径 - self.debug: bool = debug # 配置文件路径 - self.language_identification_library: str = ( - language_identification_library # 语种识别库 - ) + self.device = device + self.model = Path(model) + self.config_path = Path(config_path) + self.port: int = port + self.share: bool = share + self.debug: bool = debug + self.language_identification_library: str = language_identification_library @classmethod - def from_dict(cls, dataset_path: str, data: Dict[str, any]): - data["config_path"] = os.path.join(dataset_path, data["config_path"]) - data["model"] = os.path.join(dataset_path, data["model"]) + def from_dict(cls, dataset_path: Path, data: dict[str, Any]): + data["config_path"] = dataset_path / data["config_path"] + data["model"] = dataset_path / data["model"] return cls(**data) @@ -200,7 +199,7 @@ def __init__( device: str = "cuda", limit: int = 100, language: str = "JP", - origins: List[str] = None, + origins: list[str] = ["*"], ): self.port: int = port if not cuda_available: @@ -208,10 +207,10 @@ def __init__( self.device: str = device self.language: str = language self.limit: int = limit - self.origins: List[str] = origins + self.origins: list[str] = origins @classmethod - def from_dict(cls, data: Dict[str, any]): + def from_dict(cls, data: dict[str, Any]): return cls(**data) @@ -223,32 +222,33 @@ def __init__(self, app_key: str, secret_key: str): self.secret_key = secret_key @classmethod - def from_dict(cls, data: Dict[str, any]): + def from_dict(cls, data: dict[str, Any]): return cls(**data) class Config: - def __init__(self, config_path: str, path_config: dict[str, str]): - if not os.path.isfile(config_path) and os.path.isfile("default_config.yml"): + def __init__(self, config_path: str, path_config: PathConfig): + if not Path(config_path).exists(): shutil.copy(src="default_config.yml", dst=config_path) logger.info( f"A configuration file {config_path} has been generated based on the default configuration file default_config.yml." ) logger.info( - "If you have no special needs, please do not modify default_config.yml." + "Please do not modify default_config.yml. Instead, modify config.yml." ) # sys.exit(0) - with open(config_path, "r", encoding="utf-8") as file: - yaml_config: Dict[str, any] = yaml.safe_load(file.read()) + with open(config_path, encoding="utf-8") as file: + yaml_config: dict[str, Any] = yaml.safe_load(file.read()) model_name: str = yaml_config["model_name"] self.model_name: str = model_name if "dataset_path" in yaml_config: - dataset_path = yaml_config["dataset_path"] + dataset_path = Path(yaml_config["dataset_path"]) else: - dataset_path = os.path.join(path_config["dataset_root"], model_name) - self.dataset_path: str = dataset_path - self.assets_root: str = path_config["assets_root"] - self.out_dir = os.path.join(self.assets_root, model_name) + dataset_path = path_config.dataset_root / model_name + self.dataset_path = dataset_path + self.dataset_root = path_config.dataset_root + self.assets_root = path_config.assets_root + self.out_dir = self.assets_root / model_name self.resample_config: Resample_config = Resample_config.from_dict( dataset_path, yaml_config["resample"] ) @@ -277,16 +277,31 @@ def __init__(self, config_path: str, path_config: dict[str, str]): # ) -with open(os.path.join("configs", "paths.yml"), "r", encoding="utf-8") as f: - path_config: dict[str, str] = yaml.safe_load(f.read()) - # Should contain the following keys: - # - dataset_root: the root directory of the dataset, default to "Data" - # - assets_root: the root directory of the assets, default to "model_assets" +# Load and initialize the configuration + + +def get_path_config() -> PathConfig: + path_config_path = Path("configs/paths.yml") + if not path_config_path.exists(): + shutil.copy(src="configs/default_paths.yml", dst=path_config_path) + logger.info( + f"A configuration file {path_config_path} has been generated based on the default configuration file default_paths.yml." + ) + logger.info( + "Please do not modify configs/default_paths.yml. Instead, modify configs/paths.yml." + ) + with open(path_config_path, encoding="utf-8") as file: + path_config_dict: dict[str, str] = yaml.safe_load(file.read()) + return PathConfig(**path_config_dict) + +def get_config() -> Config: + path_config = get_path_config() + try: + config = Config("config.yml", path_config) + except (TypeError, KeyError): + logger.warning("Old config.yml found. Replace it with default_config.yml.") + shutil.copy(src="default_config.yml", dst="config.yml") + config = Config("config.yml", path_config) -try: - config = Config("config.yml", path_config) -except (TypeError, KeyError): - logger.warning("Old config.yml found. Replace it with default_config.yml.") - shutil.copy(src="default_config.yml", dst="config.yml") - config = Config("config.yml", path_config) + return config diff --git a/configs/config.json b/configs/config.json index 2f3988b4f..75629e9ea 100644 --- a/configs/config.json +++ b/configs/config.json @@ -69,5 +69,5 @@ "use_spectral_norm": false, "gin_channels": 256 }, - "version": "2.4.1" + "version": "2.5.0" } diff --git a/configs/config_jp_extra.json b/configs/config_jp_extra.json index d7548094b..d7e135ad0 100644 --- a/configs/config_jp_extra.json +++ b/configs/config_jp_extra.json @@ -76,5 +76,5 @@ "initial_channel": 64 } }, - "version": "2.4.1-JP-Extra" + "version": "2.5.0-JP-Extra" } diff --git a/data_utils.py b/data_utils.py index 73d4303c8..96eab3866 100644 --- a/data_utils.py +++ b/data_utils.py @@ -7,7 +7,7 @@ import torch.utils.data from tqdm import tqdm -from config import config +from config import get_config from mel_processing import mel_spectrogram_torch, spectrogram_torch from style_bert_vits2.logging import logger from style_bert_vits2.models import commons @@ -16,6 +16,7 @@ from style_bert_vits2.nlp import cleaned_text_to_sequence +config = get_config() """Multi speaker version""" @@ -70,16 +71,16 @@ def _filter(self): self.audiopaths_sid_text, file=sys.stdout ): audiopath = f"{_id}" - if self.min_text_len <= len(phones) and len(phones) <= self.max_text_len: - phones = phones.split(" ") - tone = [int(i) for i in tone.split(" ")] - word2ph = [int(i) for i in word2ph.split(" ")] - audiopaths_sid_text_new.append( - [audiopath, spk, language, text, phones, tone, word2ph] - ) - lengths.append(os.path.getsize(audiopath) // (2 * self.hop_length)) - else: - skipped += 1 + # if self.min_text_len <= len(phones) and len(phones) <= self.max_text_len: + phones = phones.split(" ") + tone = [int(i) for i in tone.split(" ")] + word2ph = [int(i) for i in word2ph.split(" ")] + audiopaths_sid_text_new.append( + [audiopath, spk, language, text, phones, tone, word2ph] + ) + lengths.append(os.path.getsize(audiopath) // (2 * self.hop_length)) + # else: + # skipped += 1 logger.info( "skipped: " + str(skipped) @@ -120,9 +121,7 @@ def get_audio(self, filename): audio, sampling_rate = load_wav_to_torch(filename) if sampling_rate != self.sampling_rate: raise ValueError( - "{} {} SR doesn't match target {} SR".format( - filename, sampling_rate, self.sampling_rate - ) + f"{filename} {sampling_rate} SR doesn't match target {self.sampling_rate} SR" ) audio_norm = audio / self.max_wav_value audio_norm = audio_norm.unsqueeze(0) diff --git a/default_style.py b/default_style.py index 49881eb1c..b17b6c03b 100644 --- a/default_style.py +++ b/default_style.py @@ -1,5 +1,4 @@ import json -import os from pathlib import Path from typing import Union @@ -9,26 +8,91 @@ from style_bert_vits2.logging import logger -def set_style_config(json_path: Path, output_path: Path): - with open(json_path, "r", encoding="utf-8") as f: +def save_neutral_vector( + wav_dir: Union[Path, str], + output_dir: Union[Path, str], + config_path: Union[Path, str], + config_output_path: Union[Path, str], +): + wav_dir = Path(wav_dir) + output_dir = Path(output_dir) + embs = [] + for file in wav_dir.rglob("*.npy"): + xvec = np.load(file) + embs.append(np.expand_dims(xvec, axis=0)) + + x = np.concatenate(embs, axis=0) # (N, 256) + mean = np.mean(x, axis=0) # (256,) + only_mean = np.stack([mean]) # (1, 256) + np.save(output_dir / "style_vectors.npy", only_mean) + logger.info(f"Saved mean style vector to {output_dir}") + + with open(config_path, encoding="utf-8") as f: json_dict = json.load(f) json_dict["data"]["num_styles"] = 1 json_dict["data"]["style2id"] = {DEFAULT_STYLE: 0} - with open(output_path, "w", encoding="utf-8") as f: + with open(config_output_path, "w", encoding="utf-8") as f: json.dump(json_dict, f, indent=2, ensure_ascii=False) - logger.info(f"Save style config (only {DEFAULT_STYLE}) to {output_path}") + logger.info(f"Saved style config to {config_output_path}") -def save_neutral_vector(wav_dir: Union[Path, str], output_path: Union[Path, str]): +def save_styles_by_dirs( + wav_dir: Union[Path, str], + output_dir: Union[Path, str], + config_path: Union[Path, str], + config_output_path: Union[Path, str], +): wav_dir = Path(wav_dir) - output_path = Path(output_path) + output_dir = Path(output_dir) + output_dir.mkdir(parents=True, exist_ok=True) + config_path = Path(config_path) + config_output_path = Path(config_output_path) + + subdirs = [d for d in wav_dir.iterdir() if d.is_dir()] + subdirs.sort() + if len(subdirs) in (0, 1): + logger.info( + f"At least 2 subdirectories are required for generating style vectors with respect to them, found {len(subdirs)}." + ) + logger.info("Generating only neutral style vector instead.") + save_neutral_vector(wav_dir, output_dir, config_path, config_output_path) + return + + # First get mean of all for Neutral embs = [] for file in wav_dir.rglob("*.npy"): xvec = np.load(file) embs.append(np.expand_dims(xvec, axis=0)) - x = np.concatenate(embs, axis=0) # (N, 256) mean = np.mean(x, axis=0) # (256,) - only_mean = np.stack([mean]) # (1, 256) - np.save(output_path, only_mean) - logger.info(f"Saved mean style vector to {output_path}") + style_vectors = [mean] + + names = [DEFAULT_STYLE] + for style_dir in subdirs: + npy_files = list(style_dir.rglob("*.npy")) + if not npy_files: + continue + embs = [] + for file in npy_files: + xvec = np.load(file) + embs.append(np.expand_dims(xvec, axis=0)) + + x = np.concatenate(embs, axis=0) # (N, 256) + mean = np.mean(x, axis=0) # (256,) + style_vectors.append(mean) + names.append(style_dir.name) + + # Stack them to make (num_styles, 256) + style_vectors_npy = np.stack(style_vectors, axis=0) + np.save(output_dir / "style_vectors.npy", style_vectors_npy) + logger.info(f"Saved style vectors to {output_dir / 'style_vectors.npy'}") + + # Save style2id config to json + style2id = {name: i for i, name in enumerate(names)} + with open(config_path, encoding="utf-8") as f: + json_dict = json.load(f) + json_dict["data"]["num_styles"] = len(names) + json_dict["data"]["style2id"] = style2id + with open(config_output_path, "w", encoding="utf-8") as f: + json.dump(json_dict, f, indent=2, ensure_ascii=False) + logger.info(f"Saved style config to {config_output_path}") diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index fe1ed527a..3252386b2 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,5 +1,43 @@ # Changelog +## v2.5.0 (2024-06-02) + +このバージョンから[利用規約](/docs/TERMS_OF_USE.md)が追加されました。ご利用の際は必ずお読みください。 + +### 新機能等 + +- デフォルトモデルに [あみたろの声素材工房](https://amitaro.net/) のあみたろ様が公開しているコーパスとライブ配信音声を利用して学習した**小春音アミ**と**あみたろ**モデルを追加(あみたろ様には事前に連絡して許諾を得ています) + - アプデの場合は新たに`App.bat`や`Editor.bat`を起動した際に自動でダウンロードされます +- 学習時に音声データをスタイルごとにフォルダ分けしておくことで、そのフォルダごとのスタイルを学習時に自動的に作成するように + - `inputs`からスライスして使う場合は`inputs`直下に作りたいスタイルだけサブフォルダを作りそこに音声ファイルを配置 + - `Data/モデル名/raw`から使う場合も`raw`直下に同様に配置 + - サブフォルダの個数が0または1の場合は、今まで通りのNeutralスタイルのみが作成されます +- batファイルでのインストールの大幅な高速化(Pythonのライブラリインストールに[uv](https://github.com/astral-sh/uv)を使用) +- [よくある質問](/docs/FAQ.md)を追加 +- 英語の音声合成の速度向上([gordon0414](https://github.com/gordon0414)さんによる[PR](https://github.com/litagin02/Style-Bert-VITS2/pull/124)です、ありがとうございます!) +- エディターの各種機能改善(多くが[kamexy](https://github.com/kamexy)様による[エディターリポジトリ](https://github.com/litagin02/Style-Bert-VITS2-Editor)へのプルリク群です、ありがとうございます!) + - 選択した行の下に新規の行を作成できるように + - Mac使用時に日本語変換のエンターで音声合成が走るバグの修正 + - ペースト時に改行を含まない場合は通常のペーストの振る舞いになるように修正 + + +### その他の改善 + +- 上のスタイル自動作成機能を既存モデルでも使えるような機能追加。具体的には、スタイル作成タブにて、フォルダ分けされた音声ファイルのディレクトリを任意に指定し、そのフォルダ分けを使って既存のモデルのスタイルの作成が可能に +- 音声書き起こしに[kotoba-whisper](https://huggingface.co/kotoba-tech/kotoba-whisper-v1.1)を追加 +- 音声書き起こし時にHugging FaceのWhisperモデルを使う際に、書き起こしを順次保存するように改善 +- (**ライブラリとしてのみ**)依存関係の軽量化、音声合成時に読み上げテキストの読みを表す音素列を指定する機能を追加 + 様々な改善 ([tsukumijimaさん](https://github.com/tsukumijima)による[プルリク](https://github.com/litagin02/Style-Bert-VITS2/pull/118)です、ありがとうございます!) + +### 内部変更 + +- これまでpath管理に`configs/paths.yml`を使っていたが、`configs/default_paths.yml`にリネームし、`configs/paths.yml`はgitの管理対象外に変更 + +### バグ修正 + +- Gradioのアップデートにより、モデル選択時やスタイルのDBSCAN作成時等に`TypeError: Type is not JSON serializable: WindowsPath`のようなエラーが出る問題を修正 +- TensorboardをWebUIから立ち上げた際にエラーが出る問題の修正 ([#129](https://github.com/litagin02/Style-Bert-VITS2/issues/129)) + + ## v2.4.1 (2024-03-16) **batファイルでのインストール・アップデート方法の変更**(それ以外の変更はありません) diff --git a/docs/CLI.md b/docs/CLI.md index 97d296aa0..726c42d16 100644 --- a/docs/CLI.md +++ b/docs/CLI.md @@ -7,17 +7,17 @@ git clone https://github.com/litagin02/Style-Bert-VITS2.git cd Style-Bert-VITS2 python -m venv venv venv\Scripts\activate -pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118 +pip install torch torchaudio --index-url https://download.pytorch.org/whl/cu118 pip install -r requirements.txt ``` Then download the necessary models and the default TTS model, and set the global paths. ```bash -python initialize.py [--skip_jvnv] [--dataset_root ] [--assets_root ] +python initialize.py [--skip_default_models] [--dataset_root ] [--assets_root ] ``` Optional: -- `--skip_jvnv`: Skip downloading the default JVNV voice models (use this if you only have to train your own models). +- `--skip_default_models`: Skip downloading the default voice models (use this if you only have to train your own models). - `--dataset_root`: Default: `Data`. Root directory of the training dataset. The training dataset of `{model_name}` should be placed in `{dataset_root}/{model_name}`. - `--assets_root`: Default: `model_assets`. Root directory of the model assets (for inference). In training, the model assets will be saved to `{assets_root}/{model_name}`, and in inference, we load all the models from `{assets_root}`. @@ -26,7 +26,7 @@ Optional: ### 1.1. Slice audio files -The following audio formats are supported: ".wav", ".flac", ".mp3", ".ogg", ".opus". +The following audio formats are supported: ".wav", ".flac", ".mp3", ".ogg", ".opus", ".m4a". ```bash python slice.py --model_name [-i ] [-m ] [-M ] [--time_suffix] ``` @@ -101,4 +101,4 @@ python train_ms_jp_extra.py [--repo_id /] [--skip_default_s Optional: - `--repo_id`: Hugging Face repository ID to upload the trained model to. You should have logged in using `huggingface-cli login` before running this command. -- `--skip_default_style`: Skip making the default style vector. Use this if you want to resume training (since the default style vector is already made). +- `--skip_default_style`: Skip making the default style vector. Use this if you want to resume training (since the default style vector has been already made). diff --git a/docs/FAQ.md b/docs/FAQ.md new file mode 100644 index 000000000..024184be0 --- /dev/null +++ b/docs/FAQ.md @@ -0,0 +1,39 @@ +# よくある質問 + +## 学習に時間がかかりすぎる + +デフォルトの100エポックは音声データ量によっては過剰な場合があります。デフォルトでは1000ステップごとにモデルが保存されるはずなので、途中で学習を中断してみて途中のもので試してみてもいいでしょう。 + +またバッチサイズが多き過ぎてメモリがVRAMから溢れると非常に遅くなることがあります。VRAM使用量がギリギリだったり物理メモリに溢れている場合はバッチサイズを小さくしてみてください。 + +## どのくらいの音声データが必要なの? + +分かりません。試行錯誤してください。 + +参考として、数分程度でも学習はできるらしく、またRVCでよく言われているのは多くても45分くらいで十分説があります。ただ多ければ多いほど精度が上がる可能性もありますが、分かりません。 + +## どのくらいのステップ・エポックがいいの? + +分かりません。試行錯誤してください。 + +参考として、最初の2k-3kで声音はかなり似始めて、5k-10k-15kステップほどで感情含めてよい感じになりやすく、そこからどんどん回して20kなり30kなり50kなり100kなりでどんどん微妙に変わっていきます。が微妙に変わるので、どこがいいとかは分かりません。 + +## APIサーバーで長い文章が合成できない + +デフォルトで`server_fastapi.py`の入力文字上限は100文字に設定されています。 +`config.yml`の`server.limit`の100を好きな数字に変更してください。 +上限をなくしたい方は`server.limit`を-1に設定してください。 + +## 学習を中断・再開するには + +- 学習を中断するには、学習の進捗が表示されている画面(bat使用ならコマンドプロンプト)を好きなタイミングで閉じてください。 +- 学習を再開するには、WebUIでモデル名を再開したいモデルと同じ名前に設定して、前処理等はせずに一番下の「学習を開始する」ボタンを押してください(「スタイルファイルの生成をスキップする」にチェックを入れるのをおすすめします)。 + +## 途中でバッチサイズやエポック数を変更したい + +`Data/{モデル名}/config.json`を手動で変更してから、学習を再開してください。 + +## その他 + +ググったり調べたりChatGPTに聞くか、それでも分からない場合・または手順通りやってもエラーが出る等明らかに不具合やバグと思われる場合は、GitHubの[Issue](https://github.com/litagin02/Style-Bert-VITS2/issues)に投稿してください。 + diff --git a/docs/README_en.md b/docs/README_en.md deleted file mode 100644 index 0b4104764..000000000 --- a/docs/README_en.md +++ /dev/null @@ -1,127 +0,0 @@ -# This English README is for 1.x versions. WIP for 2.x versions. - -# Style-Bert-VITS2 - -Bert-VITS2 with more controllable voice styles. - -https://github.com/litagin02/Style-Bert-VITS2/assets/139731664/b907c1b8-43aa-46e6-b03f-f6362f5a5a1e - -[Zenn Commentary Article (translated)](Style-Bert-VITS2_en.md) ([original](https://zenn.dev/litagin/articles/034819a5256ff4)) - -[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](http://colab.research.google.com/github/litagin02/Style-Bert-VITS2/blob/master/colab.ipynb) - -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! - -- [Update History](docs/CHANGELOG.md) - -**Overview** - -- Based on Bert-VITS2 v2.1, which generates emotionally rich voices from entered text, this version allows free control of emotions and speaking styles, including intensity. -- Easy to install and train for people without Git or Python (for Windows users), much is borrowed from [EasyBertVits2](https://github.com/Zuntan03/EasyBertVits2/). Training on Google Colab is also supported: [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](http://colab.research.google.com/github/litagin02/Style-Bert-VITS2/blob/master/colab.ipynb) -- If used only for voice synthesis, it can operate on CPU without a graphics card. -- Also includes an API server for integration with others (PR by [@darai0512](https://github.com/darai0512), thank you). -- Originally, Bert-VITS2's strength was to read "happy text happily, sad text sadly", so even without using the added style specification in this fork, you can generate emotionally rich voices. - - -## How to Use - - - -### Operating Environment - -We have confirmed the operation in Windows Command Prompt, WSL2, and Linux (Ubuntu Desktop) for each UI and API Server (please be creative with path specifications in WSL). - -### Installation - -#### For Those Unfamiliar with Git or Python - -Assuming Windows: - -1. Download and unzip [this zip file](https://github.com/litagin02/Style-Bert-VITS2/releases/download/1.3/Style-Bert-VITS2.zip). - - If you have a graphics card, double-click `Install-Style-Bert-VITS2.bat`. - - If you don't have a graphics card, double-click `Install-Style-Bert-VITS2-CPU.bat`. -2. Wait for the necessary environment to install automatically. -3. After that, if the WebUI for voice synthesis launches automatically, the installation is successful. The default model will be downloaded, so you can play with it immediately. - -For updates, please double-click `Update-Style-Bert-VITS2.bat`. - -#### For Those Familiar with Git and Python - -```bash -git clone https://github.com/litagin02/Style-Bert-VITS2.git -cd Style-Bert-VITS2 -python -m venv venv -venv\Scripts\activate -pip3 install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118 -pip install -r requirements.txt -python initialize.py # Download necessary models and default TTS model -``` -Don't forget the last step. - -### Voice Synthesis -Double-click `App.bat` or run `python app.py` to launch the WebUI. The default model is downloaded during installation, so you can use it even if you haven't trained it. - -The structure of the model files required for voice synthesis is as follows (you don't need to place them manually): - -``` -model_assets -├── your_model -│ ├── config.json -│ ├── your_model_file1.safetensors -│ ├── your_model_file2.safetensors -│ ├── ... -│ └── style_vectors.npy -└── another_model - ├── ... -``` - -For inference, `config.json`, `*.safetensors`, and `style_vectors.npy` are necessary. If you want to share a model, please share these three files. - -Among them, `style_vectors.npy` is a file necessary to control the style. By default, the average style "Neutral" is generated during training. -If you want to use multiple styles for more detailed control, please refer to "Generating Styles" below (even with only the average style, if the training data is emotionally rich, sufficiently emotionally rich voices can be generated). - -### Training - -Double-click Train.bat or run `python webui_train.py` to launch the WebUI. - -### Generating Styles -For those who want to use styles other than the default "Neutral". - -- Double-click `Style.bat` or run `python webui_style_vectors.py` to launch the WebUI. -- It is independent of training, so you can do it even during training, and you can redo it any number of times after training is complete (preprocessing must be finished). -- For more details on the specifications of the style, please refer to [clustering.ipynb](../clustering.ipynb). - -### Dataset Creation - -- Double-click `Dataset.bat` or run `python webui_dataset.py` to launch the WebUI for creating datasets from audio files. You can use this tool to learn from audio files only. - -Note: If you want to manually correct the dataset, remove noise, etc., you may find [Aivis](https://github.com/tsukumijima/Aivis) or its Windows-compatible dataset part [Aivis Dataset](https://github.com/litagin02/Aivis-Dataset) useful. However, if there are many files, etc., it may be sufficient to simply cut out and create a dataset with this tool. - -Please experiment to see what kind of dataset is best. - -### API Server -Run `python server_fastapi.py` in the constructed environment to launch the API server. -Please check the API specification after launching at `/docs`. - -By default, CORS settings are allowed for all domains. -As much as possible, change the value of server.origins in `config.yml` and limit it to trusted domains (if you delete the key, you can disable the CORS settings). - -### Merging -You can create a new model by mixing two models in terms of "voice", "emotional expression", and "tempo". -Double-click `Merge.bat` or run `python webui_merge.py` to launch the WebUI. - -## Relation to Bert-VITS2 v2.1 -Basically, it's just a slight modification of the Bert-VITS2 v2.1 model structure. The [pre-trained model](https://huggingface.co/litagin/Style-Bert-VITS2-1.0-base) is also essentially the same as Bert-VITS2 v2.1 (unnecessary weights have been removed and converted to safetensors). - -The differences are as follows: - -- Like [EasyBertVits2](https://github.com/Zuntan03/EasyBertVits2), it is easy to use even for people who do not know Python or Git. -- Changed the model for emotional embedding (from 1024-dimensional [wav2vec2-large-robust-12-ft-emotion-msp-dim](https://huggingface.co/audeering/wav2vec2-large-robust-12-ft-emotion-msp-dim) to 256-dimensional [wespeaker-voxceleb-resnet34-LM](https://huggingface.co/pyannote/wespeaker-voxceleb-resnet34-LM), which is more for speaker identification than emotional embedding) -- Removed vector quantization from embeddings and replaced it with just a fully connected layer. -- By creating a style vector file `style_vectors.npy`, you can generate voices using that style and continuously specify the strength of the effect. -- Various WebUIs created -- Support for bf16 training -- Support for safetensors format, defaulting to using safetensors -- Other minor bug fixes and refactoring \ No newline at end of file diff --git a/docs/TERMS_OF_USE.md b/docs/TERMS_OF_USE.md new file mode 100644 index 000000000..e34144f68 --- /dev/null +++ b/docs/TERMS_OF_USE.md @@ -0,0 +1,51 @@ +# 利用規約 + +- 2024-06-01: 初版 + +Style-Bert-VITS2を用いる際は、以下の利用規約を遵守してください。 + +## 禁止事項 + +以下の目的での利用は禁止されています。 + +- 法律に違反する目的 +- 政治的な目的(本家Bert-VITS2で禁止されています) +- 他者を傷つける目的 +- なりすまし・ディープフェイク作成目的 + +## 遵守事項 + +- Style-Bert-VITS2を利用する際は、使用するモデルの利用規約・ライセンス必ず確認し、存在する場合はそれに従わなければなりません。 +- またソースコードを利用する際は、[リポジトリのライセンス](https://github.com/litagin02/Style-Bert-VITS2#license)に従わなければなりません。 + +以下はデフォルトで付随しているモデルのライセンスです。 + +## JVNVコーパス (jvnv-F1-jp, jvnv-F2-jp, jvnv-M1-jp, jvnv-M2-jp) + +- [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)ですので、これを継承します。 + +## 小春音アミ (koharune-ami) / あみたろ (amitaro) + +[あみたろの声素材工房様の規約](https://amitaro.net/voice/voice_rule/) と [あみたろのライブ配信音声・利用規約](https://amitaro.net/voice/livevoice/#index_id6) を全て守らなければなりません。特に、以下の事項を遵守してください(規約を守れば商用非商用問わず利用できます)。 + +### 禁止事項 + +- 年齢制限のある作品・用途への使用 +- 新興宗教・政治・マルチ購などに深く関係する作品・用途 +- 特定の団体や個人や国家を誹謗中傷する作品・用途 +- 生成された音声を、あみたろ本人の声として扱うこと +- 生成された音声を、あみたろ以外の人の声として扱うこと + +### クレジット表記 + +生成音声を公開する際は(媒体は問わない)、必ず分かりやすい場所に `あみたろの声素材工房 (https://amitaro.net/)` の声を元にした音声モデルを使用していることが分かるようなクレジット表記を記載してください。 + +クレジット表記例: +- `Style-BertVITS2モデル: 小春音アミ、あみたろの声素材工房 (https://amitaro.net/)` +- `Style-BertVITS2モデル: あみたろ、あみたろの声素材工房 (https://amitaro.net/)` + +### モデルマージ + +モデルマージに関しては、[あみたろの声素材工房のよくある質問への回答](https://amitaro.net/voice/faq/#index_id17)を遵守してください: +- 本モデルを別モデルとマージできるのは、その別モデル作成の際に学習に使われた声の権利者が許諾している場合に限る +- あみたろの声の特徴が残っている場合(マージの割合が25%以上の場合)は、その利用は[あみたろの声素材工房様の規約](https://amitaro.net/voice/voice_rule/)の範囲内に限定され、そのモデルに関してもこの規約が適応される \ No newline at end of file diff --git a/gen_yaml.py b/gen_yaml.py index ac27103ea..18e3115fd 100644 --- a/gen_yaml.py +++ b/gen_yaml.py @@ -22,7 +22,7 @@ def gen_yaml(model_name, dataset_path): if not os.path.exists("config.yml"): shutil.copy(src="default_config.yml", dst="config.yml") - with open("config.yml", "r", encoding="utf-8") as f: + with open("config.yml", encoding="utf-8") as f: yml_data = yaml.safe_load(f) yml_data["model_name"] = model_name yml_data["dataset_path"] = dataset_path diff --git a/gradio_tabs/dataset.py b/gradio_tabs/dataset.py index 21b1063b5..a905cafb5 100644 --- a/gradio_tabs/dataset.py +++ b/gradio_tabs/dataset.py @@ -43,10 +43,10 @@ def do_transcribe( compute_type, language, initial_prompt, - device, use_hf_whisper, batch_size, num_beams, + hf_repo_id, ): if model_name == "": return "Error: モデル名を入力してください。" @@ -59,8 +59,6 @@ def do_transcribe( whisper_model, "--compute_type", compute_type, - "--device", - device, "--language", language, "--initial_prompt", @@ -71,9 +69,12 @@ def do_transcribe( if use_hf_whisper: cmd.append("--use_hf_whisper") cmd.extend(["--batch_size", str(batch_size)]) - success, message = run_script_with_log(cmd) + if hf_repo_id != "openai/whisper": + cmd.extend(["--hf_repo_id", hf_repo_id]) + success, message = run_script_with_log(cmd, ignore_warning=True) if not success: return f"Error: {message}. エラーメッセージが空の場合、何も問題がない可能性があるので、書き起こしファイルをチェックして問題なければ無視してください。" + return "音声の文字起こしが完了しました。" how_to_md = """ @@ -82,46 +83,51 @@ def do_transcribe( - 与えられた音声からちょうどいい長さの発話区間を切り取りスライス - 音声に対して文字起こし -このうち両方を使ってもよいし、スライスする必要がない場合は後者のみを使ってもよいです。 +このうち両方を使ってもよいし、スライスする必要がない場合は後者のみを使ってもよいです。**コーパス音源などすでに適度な長さの音声ファイルがある場合はスライスは不要**です。 ## 必要なもの -学習したい音声が入ったwavファイルいくつか。 +学習したい音声が入った音声ファイルいくつか(形式はwav以外でもmp3等通常の音声ファイル形式なら可能)。 合計時間がある程度はあったほうがいいかも、10分とかでも大丈夫だったとの報告あり。単一ファイルでも良いし複数ファイルでもよい。 ## スライス使い方 -1. `inputs`フォルダにwavファイルをすべて入れる +1. `inputs`フォルダに音声ファイルをすべて入れる(スタイル分けをしたい場合は、サブフォルダにスタイルごとに音声を分けて入れる) 2. `モデル名`を入力して、設定を必要なら調整して`音声のスライス`ボタンを押す 3. 出来上がった音声ファイルたちは`Data/{モデル名}/raw`に保存される ## 書き起こし使い方 -1. 書き起こしたい音声ファイルのあるフォルダを指定(デフォルトは`Data/{モデル名}/raw`なのでスライス後に行う場合は省略してよい) +1. `Data/{モデル名}/raw`に音声ファイルが入っていることを確認(直下でなくてもよい) 2. 設定を必要なら調整してボタンを押す 3. 書き起こしファイルは`Data/{モデル名}/esd.list`に保存される ## 注意 -- 長すぎる秒数(12-15秒くらいより長い?)のwavファイルは学習に用いられないようです。また短すぎてもあまりよくない可能性もあります。 +- ~~長すぎる秒数(12-15秒くらいより長い?)のwavファイルは学習に用いられないようです。また短すぎてもあまりよくない可能性もあります。~~ この制限はVer 2.5では学習時に「カスタムバッチサンプラーを使わない」を選択すればなくなりました。が、長すぎる音声があるとVRAM消費量が増えたり安定しなかったりするので、適度な長さにスライスすることをおすすめします。 - 書き起こしの結果をどれだけ修正すればいいかはデータセットに依存しそうです。 -- 手動で書き起こしをいろいろ修正したり結果を細かく確認したい場合は、[Aivis Dataset](https://github.com/litagin02/Aivis-Dataset)もおすすめします。書き起こし部分もかなり工夫されています。ですがファイル数が多い場合などは、このツールで簡易的に切り出してデータセットを作るだけでも十分という気もしています。 """ def create_dataset_app() -> gr.Blocks: with gr.Blocks() as app: + gr.Markdown( + "**既に1ファイル2-12秒程度の音声ファイル集とその書き起こしデータがある場合は、このタブは使用せずに学習できます。**" + ) with gr.Accordion("使い方", open=False): gr.Markdown(how_to_md) model_name = gr.Textbox( label="モデル名を入力してください(話者名としても使われます)。" ) with gr.Accordion("音声のスライス"): + gr.Markdown( + "**すでに適度な長さの音声ファイルからなるデータがある場合は、その音声をData/{モデル名}/rawに入れれば、このステップは不要です。**" + ) with gr.Row(): with gr.Column(): input_dir = gr.Textbox( label="元音声の入っているフォルダパス", value="inputs", - info="下記フォルダにwavファイルを入れておいてください", + info="下記フォルダにwavやmp3等のファイルを入れておいてください", ) min_sec = gr.Slider( minimum=0, @@ -167,6 +173,12 @@ def create_dataset_app() -> gr.Blocks: ) use_hf_whisper = gr.Checkbox( label="HuggingFaceのWhisperを使う(速度が速いがVRAMを多く使う)", + value=True, + ) + hf_repo_id = gr.Dropdown( + ["openai/whisper", "kotoba-tech/kotoba-whisper-v1.1"], + label="HuggingFaceのWhisperモデル", + value="openai/whisper", ) compute_type = gr.Dropdown( [ @@ -181,6 +193,7 @@ def create_dataset_app() -> gr.Blocks: ], label="計算精度", value="bfloat16", + visible=False, ) batch_size = gr.Slider( minimum=1, @@ -189,9 +202,7 @@ def create_dataset_app() -> gr.Blocks: step=1, label="バッチサイズ", info="大きくすると速度が速くなるがVRAMを多く使う", - visible=False, ) - device = gr.Radio(["cuda", "cpu"], label="デバイス", value="cuda") language = gr.Dropdown(["ja", "en", "zh"], value="ja", label="言語") initial_prompt = gr.Textbox( label="初期プロンプト", @@ -228,21 +239,21 @@ def create_dataset_app() -> gr.Blocks: compute_type, language, initial_prompt, - device, use_hf_whisper, batch_size, num_beams, + hf_repo_id, ], outputs=[result2], ) use_hf_whisper.change( lambda x: ( gr.update(visible=x), - gr.update(visible=not x), + gr.update(visible=x), gr.update(visible=not x), ), inputs=[use_hf_whisper], - outputs=[batch_size, compute_type, device], + outputs=[hf_repo_id, batch_size, compute_type], ) return app diff --git a/gradio_tabs/inference.py b/gradio_tabs/inference.py index 1049db473..548f0e7e6 100644 --- a/gradio_tabs/inference.py +++ b/gradio_tabs/inference.py @@ -96,9 +96,63 @@ ] initial_md = """ -- Ver 2.3で追加されたエディターのほうが実際に読み上げさせるには使いやすいかもしれません。`Editor.bat`か`python server_editor.py --inbrowser`で起動できます。 +- Ver 2.5で追加されたデフォルトの [`koharune-ami`(小春音アミ)モデル](https://huggingface.co/litagin/sbv2_koharune_ami) と[`amitaro`(あみたろ)モデル](https://huggingface.co/litagin/sbv2_amitaro) は、[あみたろの声素材工房](https://amitaro.net/)で公開されているコーパス音源・ライブ配信音声を利用して事前に許可を得て学習したモデルです。下記の**利用規約を必ず読んで**からご利用ください。 -- 初期からある[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)です。 +- Ver 2.3で追加された**エディター版**のほうが実際に読み上げさせるには使いやすいかもしれません。`Editor.bat`か`python server_editor.py --inbrowser`で起動できます。 +""" + +terms_of_use_md = """ +## 利用規約 + +最新の利用規約は [こちら](https://github.com/litagin02/Style-Bert-VITS2/blob/master/docs/TERMS_OF_USE.md) を参照してください。常に最新のものが適用されます。 + +Style-Bert-VITS2を用いる際は、以下の利用規約を遵守してください。 + +## 禁止事項 + +以下の目的での利用は禁止されています。 + +- 法律に違反する目的 +- 政治的な目的(本家Bert-VITS2で禁止されています) +- 他者を傷つける目的 +- なりすまし・ディープフェイク作成目的 + +## 遵守事項 + +- Style-Bert-VITS2を利用する際は、使用するモデルの利用規約・ライセンス必ず確認し、存在する場合はそれに従わなければなりません。 +- またソースコードを利用する際は、[リポジトリのライセンス](https://github.com/litagin02/Style-Bert-VITS2#license)に従わなければなりません。 + +以下はデフォルトで付随しているモデルのライセンスです。 + +### JVNVコーパス (jvnv-F1-jp, jvnv-F2-jp, jvnv-M1-jp, jvnv-M2-jp) + +- [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)ですので、これを継承します。 + +### 小春音アミ (koharune-ami) / あみたろ (amitaro) + +[あみたろの声素材工房様の規約](https://amitaro.net/voice/voice_rule/) と [あみたろのライブ配信音声・利用規約](https://amitaro.net/voice/livevoice/#index_id6) を全て守らなければなりません。特に、以下の事項を遵守してください(規約を守れば商用非商用問わず利用できます): + +#### 禁止事項 + +- 年齢制限のある作品・用途への使用 +- 新興宗教・政治・マルチ購などに深く関係する作品・用途 +- 特定の団体や個人や国家を誹謗中傷する作品・用途 +- 生成された音声を、あみたろ本人の声として扱うこと +- 生成された音声を、あみたろ以外の人の声として扱うこと + +#### クレジット表記 + +生成音声を公開する際は(媒体は問わない)、必ず分かりやすい場所に `あみたろの声素材工房 (https://amitaro.net/)` の声を元にした音声モデルを使用していることが分かるようなクレジット表記を記載してください。 + +クレジット表記例: +- `Style-BertVITS2モデル: 小春音アミ、あみたろの声素材工房 (https://amitaro.net/)` +- `Style-BertVITS2モデル: あみたろ、あみたろの声素材工房 (https://amitaro.net/)` + +#### モデルマージ + +モデルマージに関しては、[あみたろの声素材工房のよくある質問への回答](https://amitaro.net/voice/faq/#index_id17)を遵守してください: +- 本モデルを別モデルとマージできるのは、その別モデル作成の際に学習に使われた声の権利者が許諾している場合に限る +- あみたろの声の特徴が残っている場合(マージの割合が25%以上の場合)は、その利用は[あみたろの声素材工房様の規約](https://amitaro.net/voice/voice_rule/)の範囲内に限定され、そのモデルに関してもこの規約が適応される """ how_to_md = """ @@ -266,6 +320,7 @@ def tts_fn( with gr.Blocks(theme=GRADIO_THEME) as app: gr.Markdown(initial_md) + gr.Markdown(terms_of_use_md) with gr.Accordion(label="使い方", open=False): gr.Markdown(how_to_md) with gr.Row(): @@ -394,10 +449,10 @@ def tts_fn( ) style_weight = gr.Slider( minimum=0, - maximum=50, + maximum=20, value=DEFAULT_STYLE_WEIGHT, step=0.1, - label="スタイルの強さ", + label="スタイルの強さ(声が崩壊したら小さくしてください)", ) ref_audio_path = gr.Audio( label="参照音声", type="filepath", visible=False diff --git a/gradio_tabs/merge.py b/gradio_tabs/merge.py index 661f23369..750f310c6 100644 --- a/gradio_tabs/merge.py +++ b/gradio_tabs/merge.py @@ -1,14 +1,13 @@ import json -import os from pathlib import Path import gradio as gr import numpy as np import torch -import yaml from safetensors import safe_open from safetensors.torch import save_file +from config import get_path_config from style_bert_vits2.constants import DEFAULT_STYLE, GRADIO_THEME from style_bert_vits2.logging import logger from style_bert_vits2.tts_model import TTSModel, TTSModelHolder @@ -20,15 +19,17 @@ tempo_keys = ["sdp", "dp"] device = "cuda" if torch.cuda.is_available() else "cpu" +path_config = get_path_config() +assets_root = path_config.assets_root -# 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 merge_style(model_name_a, model_name_b, weight, output_name, style_triple_list): +def merge_style( + model_name_a: str, + model_name_b: str, + weight: float, + output_name: str, + style_triple_list: list[tuple[str, str, str]], +) -> tuple[Path, list[str]]: """ style_triple_list: list[(model_aでのスタイル名, model_bでのスタイル名, 出力するスタイル名)] """ @@ -41,18 +42,14 @@ def merge_style(model_name_a, model_name_b, weight, output_name, style_triple_li raise ValueError(f"No element with {DEFAULT_STYLE} output style name found.") style_vectors_a = np.load( - os.path.join(assets_root, model_name_a, "style_vectors.npy") + assets_root / model_name_a / "style_vectors.npy" ) # (style_num_a, 256) style_vectors_b = np.load( - os.path.join(assets_root, model_name_b, "style_vectors.npy") + assets_root / model_name_b / "style_vectors.npy" ) # (style_num_b, 256) - with open( - os.path.join(assets_root, model_name_a, "config.json"), "r", encoding="utf-8" - ) as f: + with open(assets_root / model_name_a / "config.json", encoding="utf-8") as f: config_a = json.load(f) - with open( - os.path.join(assets_root, model_name_b, "config.json"), "r", encoding="utf-8" - ) as f: + with open(assets_root / model_name_b / "config.json", encoding="utf-8") as f: config_b = json.load(f) style2id_a = config_a["data"]["style2id"] style2id_b = config_b["data"]["style2id"] @@ -73,22 +70,20 @@ def merge_style(model_name_a, model_name_b, weight, output_name, style_triple_li new_style2id[style_out] = len(new_style_vecs) - 1 new_style_vecs = np.array(new_style_vecs) - output_style_path = os.path.join(assets_root, output_name, "style_vectors.npy") + output_style_path = assets_root / output_name / "style_vectors.npy" np.save(output_style_path, new_style_vecs) new_config = config_a.copy() new_config["data"]["num_styles"] = len(new_style2id) new_config["data"]["style2id"] = new_style2id new_config["model_name"] = output_name - with open( - os.path.join(assets_root, output_name, "config.json"), "w", encoding="utf-8" - ) as f: + with open(assets_root / output_name / "config.json", "w", encoding="utf-8") as f: json.dump(new_config, f, indent=2, ensure_ascii=False) # recipe.jsonを読み込んで、style_triple_listを追記 - info_path = os.path.join(assets_root, output_name, "recipe.json") - if os.path.exists(info_path): - with open(info_path, "r", encoding="utf-8") as f: + info_path = assets_root / output_name / "recipe.json" + if info_path.exists(): + with open(info_path, encoding="utf-8") as f: info = json.load(f) else: info = {} @@ -99,11 +94,13 @@ def merge_style(model_name_a, model_name_b, weight, output_name, style_triple_li return output_style_path, list(new_style2id.keys()) -def lerp_tensors(t, v0, v1): +def lerp_tensors(t: float, v0: torch.Tensor, v1: torch.Tensor): return v0 * (1 - t) + v1 * t -def slerp_tensors(t, v0, v1, dot_thres=0.998): +def slerp_tensors( + t: float, v0: torch.Tensor, v1: torch.Tensor, dot_thres: float = 0.998 +): device = v0.device v0c = v0.cpu().numpy() v1c = v1.cpu().numpy() @@ -123,30 +120,30 @@ def slerp_tensors(t, v0, v1, dot_thres=0.998): def merge_models( - model_path_a, - model_path_b, - voice_weight, - voice_pitch_weight, - speech_style_weight, - tempo_weight, - output_name, - use_slerp_instead_of_lerp, + model_path_a: str, + model_path_b: str, + voice_weight: float, + voice_pitch_weight: float, + speech_style_weight: float, + tempo_weight: float, + output_name: str, + use_slerp_instead_of_lerp: bool, ): """model Aを起点に、model Bの各要素を重み付けしてマージする。 safetensors形式を前提とする。""" - model_a_weight = {} + model_a_weight: dict[str, torch.Tensor] = {} with safe_open(model_path_a, framework="pt", device="cpu") as f: for k in f.keys(): model_a_weight[k] = f.get_tensor(k) - model_b_weight = {} + model_b_weight: dict[str, torch.Tensor] = {} with safe_open(model_path_b, framework="pt", device="cpu") as f: for k in f.keys(): model_b_weight[k] = f.get_tensor(k) merged_model_weight = model_a_weight.copy() - for key in model_a_weight.keys(): + for key in model_a_weight: if any([key.startswith(prefix) for prefix in voice_keys]): weight = voice_weight elif any([key.startswith(prefix) for prefix in voice_pitch_keys]): @@ -161,10 +158,8 @@ def merge_models( slerp_tensors if use_slerp_instead_of_lerp else lerp_tensors )(weight, model_a_weight[key], model_b_weight[key]) - merged_model_path = os.path.join( - assets_root, output_name, f"{output_name}.safetensors" - ) - os.makedirs(os.path.dirname(merged_model_path), exist_ok=True) + merged_model_path = assets_root / output_name / f"{output_name}.safetensors" + merged_model_path.parent.mkdir(parents=True, exist_ok=True) save_file(merged_model_weight, merged_model_path) info = { @@ -175,24 +170,22 @@ def merge_models( "speech_style_weight": speech_style_weight, "tempo_weight": tempo_weight, } - with open( - os.path.join(assets_root, output_name, "recipe.json"), "w", encoding="utf-8" - ) as f: + with open(assets_root / output_name / "recipe.json", "w", encoding="utf-8") as f: json.dump(info, f, indent=2, ensure_ascii=False) return merged_model_path def merge_models_gr( - model_name_a, - model_path_a, - model_name_b, - model_path_b, - output_name, - voice_weight, - voice_pitch_weight, - speech_style_weight, - tempo_weight, - use_slerp_instead_of_lerp, + model_name_a: str, + model_path_a: str, + model_name_b: str, + model_path_b: str, + output_name: str, + voice_weight: float, + voice_pitch_weight: float, + speech_style_weight: float, + tempo_weight: float, + use_slerp_instead_of_lerp: bool, ): if output_name == "": return "Error: 新しいモデル名を入力してください。" @@ -210,10 +203,10 @@ def merge_models_gr( def merge_style_gr( - model_name_a, - model_name_b, - weight, - output_name, + model_name_a: str, + model_name_b: str, + weight: float, + output_name: str, style_triple_list_str: str, ): if output_name == "": @@ -245,12 +238,14 @@ def merge_style_gr( ) -def simple_tts(model_name, text, style=DEFAULT_STYLE, style_weight=1.0): - model_path = os.path.join(assets_root, model_name, f"{model_name}.safetensors") - config_path = os.path.join(assets_root, model_name, "config.json") - style_vec_path = os.path.join(assets_root, model_name, "style_vectors.npy") +def simple_tts( + model_name: str, text: str, style: str = DEFAULT_STYLE, style_weight: float = 1.0 +): + model_path = assets_root / model_name / f"{model_name}.safetensors" + config_path = assets_root / model_name / "config.json" + style_vec_path = assets_root / model_name / "style_vectors.npy" - model = TTSModel(Path(model_path), Path(config_path), Path(style_vec_path), device) + model = TTSModel(model_path, config_path, style_vec_path, device) return model.infer(text, style=style, style_weight=style_weight) @@ -259,14 +254,14 @@ def update_two_model_names_dropdown(model_holder: TTSModelHolder): return new_names, new_files, new_names, new_files -def load_styles_gr(model_name_a, model_name_b): - config_path_a = os.path.join(assets_root, model_name_a, "config.json") - with open(config_path_a, "r", encoding="utf-8") as f: +def load_styles_gr(model_name_a: str, model_name_b: str): + config_path_a = assets_root / model_name_a / "config.json" + with open(config_path_a, encoding="utf-8") as f: config_a = json.load(f) styles_a = list(config_a["data"]["style2id"].keys()) - config_path_b = os.path.join(assets_root, model_name_b, "config.json") - with open(config_path_b, "r", encoding="utf-8") as f: + config_path_b = assets_root / model_name_b / "config.json" + with open(config_path_b, encoding="utf-8") as f: config_b = json.load(f) styles_b = list(config_b["data"]["style2id"].keys()) @@ -336,7 +331,10 @@ def create_merge_app(model_holder: TTSModelHolder) -> gr.Blocks: ) return app initial_id = 0 - initial_model_files = model_holder.model_files_dict[model_names[initial_id]] + # initial_model_files = model_holder.model_files_dict[model_names[initial_id]] + initial_model_files = [ + str(f) for f in model_holder.model_files_dict[model_names[initial_id]] + ] with gr.Blocks(theme=GRADIO_THEME) as app: gr.Markdown( diff --git a/gradio_tabs/style_vectors.py b/gradio_tabs/style_vectors.py index 1800c8562..4618efbf6 100644 --- a/gradio_tabs/style_vectors.py +++ b/gradio_tabs/style_vectors.py @@ -1,27 +1,24 @@ import json -import os import shutil from pathlib import Path import gradio as gr import matplotlib.pyplot as plt import numpy as np -import yaml from scipy.spatial.distance import pdist, squareform from sklearn.cluster import DBSCAN, AgglomerativeClustering, KMeans from sklearn.manifold import TSNE from umap import UMAP -from config import config +from config import get_path_config +from default_style import save_styles_by_dirs from style_bert_vits2.constants import DEFAULT_STYLE, GRADIO_THEME from style_bert_vits2.logging import logger -# 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(path_config["dataset_root"]) - # assets_root = path_config["assets_root"] +path_config = get_path_config() +dataset_root = path_config.dataset_root +assets_root = path_config.assets_root MAX_CLUSTER_NUM = 10 MAX_AUDIO_NUM = 10 @@ -39,11 +36,7 @@ def load(model_name: str, reduction_method: str): global wav_files, x, x_reduced, mean - # wavs_dir = os.path.join(dataset_root, model_name, "wavs") wavs_dir = dataset_root / model_name / "wavs" - # style_vector_files = [ - # os.path.join(wavs_dir, f) for f in os.listdir(wavs_dir) if f.endswith(".npy") - # ] style_vector_files = [f for f in wavs_dir.rglob("*.npy") if f.is_file()] # foo.wav.npy -> foo.wav wav_files = [f.with_suffix("") for f in style_vector_files] @@ -143,7 +136,7 @@ def do_dbscan_gradio(eps=2.5, min_samples=15): ) plt.legend() - n_clusters = max(y_pred) + 1 + n_clusters = int(max(y_pred) + 1) if n_clusters > MAX_CLUSTER_NUM: # raise ValueError(f"The number of clusters is too large: {n_clusters}") @@ -196,21 +189,21 @@ def do_clustering_gradio(n_clusters=4, method="KMeans"): ] * MAX_AUDIO_NUM -def save_style_vectors_from_clustering(model_name, style_names_str: str): +def save_style_vectors_from_clustering(model_name: str, style_names_str: str): """centerとcentroidsを保存する""" - result_dir = os.path.join(config.assets_root, model_name) - os.makedirs(result_dir, exist_ok=True) + result_dir = assets_root / model_name + result_dir.mkdir(parents=True, exist_ok=True) style_vectors = np.stack([mean] + centroids) - style_vector_path = os.path.join(result_dir, "style_vectors.npy") - if os.path.exists(style_vector_path): + style_vector_path = result_dir / "style_vectors.npy" + if style_vector_path.exists(): logger.info(f"Backup {style_vector_path} to {style_vector_path}.bak") shutil.copy(style_vector_path, f"{style_vector_path}.bak") np.save(style_vector_path, style_vectors) logger.success(f"Saved style vectors to {style_vector_path}") # config.jsonの更新 - config_path = os.path.join(result_dir, "config.json") - if not os.path.exists(config_path): + config_path = result_dir / "config.json" + if not config_path.exists(): return f"{config_path}が存在しません。" style_names = [name.strip() for name in style_names_str.split(",")] style_name_list = [DEFAULT_STYLE] + style_names @@ -221,7 +214,7 @@ def save_style_vectors_from_clustering(model_name, style_names_str: str): logger.info(f"Backup {config_path} to {config_path}.bak") shutil.copy(config_path, f"{config_path}.bak") - with open(config_path, "r", encoding="utf-8") as f: + with open(config_path, encoding="utf-8") as f: json_dict = json.load(f) json_dict["data"]["num_styles"] = len(style_name_list) style_dict = {name: i for i, name in enumerate(style_name_list)} @@ -233,7 +226,7 @@ def save_style_vectors_from_clustering(model_name, style_names_str: str): def save_style_vectors_from_files( - model_name, audio_files_str: str, style_names_str: str + model_name: str, audio_files_str: str, style_names_str: str ): """音声ファイルからスタイルベクトルを作成して保存する""" global mean @@ -241,8 +234,8 @@ def save_style_vectors_from_files( return "Error: スタイルベクトルを読み込んでください。" mean = np.mean(x, axis=0) - result_dir = os.path.join(config.assets_root, model_name) - os.makedirs(result_dir, exist_ok=True) + result_dir = assets_root / model_name + result_dir.mkdir(parents=True, exist_ok=True) audio_files = [name.strip() for name in audio_files_str.split(",")] style_names = [name.strip() for name in style_names_str.split(",")] if len(audio_files) != len(style_names): @@ -252,28 +245,28 @@ def save_style_vectors_from_files( return "スタイル名が重複しています。" style_vectors = [mean] - wavs_dir = os.path.join(dataset_root, model_name, "wavs") + wavs_dir = dataset_root / model_name / "wavs" for audio_file in audio_files: - path = os.path.join(wavs_dir, audio_file) - if not os.path.exists(path): + path = wavs_dir / audio_file + if not path.exists(): return f"{path}が存在しません。" style_vectors.append(np.load(f"{path}.npy")) style_vectors = np.stack(style_vectors) assert len(style_name_list) == len(style_vectors) - style_vector_path = os.path.join(result_dir, "style_vectors.npy") - if os.path.exists(style_vector_path): + style_vector_path = result_dir / "style_vectors.npy" + if style_vector_path.exists(): logger.info(f"Backup {style_vector_path} to {style_vector_path}.bak") shutil.copy(style_vector_path, f"{style_vector_path}.bak") np.save(style_vector_path, style_vectors) # config.jsonの更新 - config_path = os.path.join(result_dir, "config.json") - if not os.path.exists(config_path): + config_path = result_dir / "config.json" + if not config_path.exists(): return f"{config_path}が存在しません。" logger.info(f"Backup {config_path} to {config_path}.bak") shutil.copy(config_path, f"{config_path}.bak") - with open(config_path, "r", encoding="utf-8") as f: + with open(config_path, encoding="utf-8") as f: json_dict = json.load(f) json_dict["data"]["num_styles"] = len(style_name_list) style_dict = {name: i for i, name in enumerate(style_name_list)} @@ -284,20 +277,102 @@ def save_style_vectors_from_files( return f"成功!\n{style_vector_path}に保存し{config_path}を更新しました。" -how_to_md = f""" -Style-Bert-VITS2でこまかくスタイルを指定して音声合成するには、モデルごとにスタイルベクトルのファイル`style_vectors.npy`を手動で作成する必要があります。 +def save_style_vectors_by_dirs(model_name: str, audio_dir_str: str): + if model_name == "": + return "モデル名を入力してください。" + if audio_dir_str == "": + return "音声ファイルが入っているディレクトリを入力してください。" + + from concurrent.futures import ThreadPoolExecutor + from multiprocessing import cpu_count + + from tqdm import tqdm + + from style_bert_vits2.utils.stdout_wrapper import SAFE_STDOUT + from style_gen import save_style_vector + + # First generate style vectors for each audio file + + audio_dir = Path(audio_dir_str) + audio_suffixes = [".wav", ".flac", ".mp3", ".ogg", ".opus", ".m4a"] + audio_files = [f for f in audio_dir.rglob("*") if f.suffix in audio_suffixes] + + def process(file: Path): + # f: `test.wav` -> search `test.wav.npy` + if (file.with_name(file.name + ".npy")).exists(): + return file, None + try: + save_style_vector(str(file)) + except Exception as e: + return file, e + return file, None + + with ThreadPoolExecutor(max_workers=cpu_count() // 2) as executor: + _ = list( + tqdm( + executor.map( + process, + audio_files, + ), + total=len(audio_files), + file=SAFE_STDOUT, + desc="Generating style vectors", + ) + ) + + result_dir = assets_root / model_name + config_path = result_dir / "config.json" + if not config_path.exists(): + return f"{config_path}が存在しません。" + logger.info(f"Backup {config_path} to {config_path}.bak") + shutil.copy(config_path, f"{config_path}.bak") + + style_vector_path = result_dir / "style_vectors.npy" + if style_vector_path.exists(): + logger.info(f"Backup {style_vector_path} to {style_vector_path}.bak") + shutil.copy(style_vector_path, f"{style_vector_path}.bak") + save_styles_by_dirs(audio_dir, result_dir) + return f"成功!\n{result_dir}にスタイルベクトルを保存しました。" -ただし、学習の過程で自動的に平均スタイル「{DEFAULT_STYLE}」のみは作成されるので、それをそのまま使うこともできます(その場合はこのWebUIは使いません)。 -このプロセスは学習とは全く関係がないので、何回でも独立して繰り返して試せます。また学習中にもたぶん軽いので動くはずです。 +how_to_md = f""" +Style-Bert-VITS2でこまかくスタイルを指定して音声合成するには、モデルごとにスタイルベクトルのファイル`style_vectors.npy`を作成する必要があります。 + +ただし、学習の過程では自動的に、平均スタイル「{DEFAULT_STYLE}」と、(**Ver 2.5.0以降からは**)音声をサブフォルダに分けていた場合はそのサブフォルダごとのスタイルが保存されています。 ## 方法 +- 方法0: 音声を作りたいスタイルごとのサブフォルダに分け、そのフォルダごとにスタイルベクトルを作成 - 方法1: 音声ファイルを自動でスタイル別に分け、その各スタイルの平均を取って保存 - 方法2: スタイルを代表する音声ファイルを手動で選んで、その音声のスタイルベクトルを保存 - 方法3: 自分でもっと頑張ってこだわって作る(JVNVコーパスなど、もともとスタイルラベル等が利用可能な場合はこれがよいかも) """ +method0 = """ +音声をスタイルごとにサブフォルダを作り、その中に音声ファイルを入れてください。 + +**注意** + +- Ver 2.5.0以降では、`inputs/`フォルダや`raw/`フォルダにサブディレクトリに分けて音声ファイルを入れるだけで、スタイルベクトルが自動で作成されるので、この手順は不要です。 +- それ未満のバージョンで学習したモデルに新しくスタイルベクトルをつけたい場合や、学習に使ったのとは別の音声でスタイルベクトルを作成したい場合に使います。 +- 学習との整合性のため、もし**現在学習中や、今後学習する予定がある場合は**、音声ファイルは、`Data/{モデル名}/wavs`フォルダではなく**新しい別のディレクトリに保存してください**。 + +例: + +```bash +audio_dir +├── style1 +│ ├── audio1.wav +│ ├── audio2.wav +│ └── ... +├── style2 +│ ├── audio1.wav +│ ├── audio2.wav +│ └── ... +└── ... +``` +""" + method1 = f""" 学習の時に取り出したスタイルベクトルを読み込んで、可視化を見ながらスタイルを分けていきます。 @@ -333,138 +408,163 @@ def create_style_vectors_app(): with gr.Blocks(theme=GRADIO_THEME) as app: 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( - choices=["UMAP", "t-SNE"], - label="次元削減方法", - info="v 1.3以前はt-SNEでしたがUMAPのほうがよい可能性もあります。", - value="UMAP", + model_name = gr.Textbox(placeholder="your_model_name", label="モデル名") + with gr.Tab("方法0: サブフォルダごとにスタイルベクトルを作成"): + gr.Markdown(method0) + audio_dir = gr.Textbox( + placeholder="path/to/audio_dir", + label="音声が入っているフォルダ", + info="音声ファイルをスタイルごとにサブフォルダに分けて保存してください。", ) - load_button = gr.Button("スタイルベクトルを読み込む", variant="primary") - output = gr.Plot(label="音声スタイルの可視化") - load_button.click(load, inputs=[model_name, reduction_method], outputs=[output]) - with gr.Tab("方法1: スタイル分けを自動で行う"): - with gr.Tab("スタイル分け1"): - n_clusters = gr.Slider( - minimum=2, - maximum=10, - step=1, - value=4, - label="作るスタイルの数(平均スタイルを除く)", - info="上の図を見ながらスタイルの数を試行錯誤してください。", - ) - c_method = gr.Radio( - choices=[ - "Agglomerative after reduction", - "KMeans after reduction", - "Agglomerative", - "KMeans", - ], - label="アルゴリズム", - info="分類する(クラスタリング)アルゴリズムを選択します。いろいろ試してみてください。", - value="Agglomerative after reduction", - ) - c_button = gr.Button("スタイル分けを実行") - with gr.Tab("スタイル分け2: DBSCAN"): - gr.Markdown(dbscan_md) - eps = gr.Slider( - minimum=0.1, - maximum=10, - step=0.01, - value=0.3, - label="eps", - ) - min_samples = gr.Slider( - minimum=1, - maximum=50, - step=1, - value=15, - label="min_samples", - ) - with gr.Row(): - dbscan_button = gr.Button("スタイル分けを実行") - num_styles_result = gr.Textbox(label="スタイル数") - gr.Markdown("スタイル分けの結果") - gr.Markdown( - "注意: もともと256次元なものをを2次元に落としているので、正確なベクトルの位置関係ではありません。" + method0_btn = gr.Button("スタイルベクトルを作成", variant="primary") + method0_info = gr.Textbox(label="結果") + method0_btn.click( + save_style_vectors_by_dirs, + inputs=[model_name, audio_dir], + outputs=[method0_info], ) + with gr.Tab("その他の方法"): with gr.Row(): - gr_plot = gr.Plot() - with gr.Column(): - with gr.Row(): - cluster_index = gr.Slider( - minimum=1, - maximum=MAX_CLUSTER_NUM, - step=1, - value=1, - label="スタイル番号", - info="選択したスタイルの代表音声を表示します。", - ) - num_files = gr.Slider( - minimum=1, - maximum=MAX_AUDIO_NUM, - step=1, - value=5, - label="代表音声の数をいくつ表示するか", - ) - get_audios_button = gr.Button("代表音声を取得") - with gr.Row(): - audio_list = [] - for i in range(MAX_AUDIO_NUM): - audio_list.append(gr.Audio(visible=False, show_label=True)) - c_button.click( - do_clustering_gradio, - inputs=[n_clusters, c_method], - outputs=[gr_plot, cluster_index] + audio_list, - ) - dbscan_button.click( - do_dbscan_gradio, - inputs=[eps, min_samples], - outputs=[gr_plot, cluster_index, num_styles_result] + audio_list, - ) - get_audios_button.click( - representative_wav_files_gradio, - inputs=[cluster_index, num_files], - outputs=audio_list, + reduction_method = gr.Radio( + choices=["UMAP", "t-SNE"], + label="次元削減方法", + info="v 1.3以前はt-SNEでしたがUMAPのほうがよい可能性もあります。", + value="UMAP", ) - gr.Markdown("結果が良さそうなら、これを保存します。") - style_names = gr.Textbox( - "Angry, Sad, Happy", - label="スタイルの名前", - info=f"スタイルの名前を`,`で区切って入力してください(日本語可)。例: `Angry, Sad, Happy`や`怒り, 悲しみ, 喜び`など。平均音声は{DEFAULT_STYLE}として自動的に保存されます。", - ) - with gr.Row(): - save_button1 = gr.Button("スタイルベクトルを保存", variant="primary") - info2 = gr.Textbox(label="保存結果") - - save_button1.click( - save_style_vectors_from_clustering, - inputs=[model_name, style_names], - outputs=[info2], - ) - with gr.Tab("方法2: 手動でスタイルを選ぶ"): - gr.Markdown( - "下のテキスト欄に、各スタイルの代表音声のファイル名を`,`区切りで、その横に対応するスタイル名を`,`区切りで入力してください。" - ) - gr.Markdown("例: `angry.wav, sad.wav, happy.wav`と`Angry, Sad, Happy`") - gr.Markdown( - f"注意: {DEFAULT_STYLE}スタイルは自動的に保存されます、手動では{DEFAULT_STYLE}という名前のスタイルは指定しないでください。" + load_button = gr.Button("スタイルベクトルを読み込む", variant="primary") + output = gr.Plot(label="音声スタイルの可視化") + load_button.click( + load, inputs=[model_name, reduction_method], outputs=[output] ) - with gr.Row(): - audio_files_text = gr.Textbox( - label="音声ファイル名", placeholder="angry.wav, sad.wav, happy.wav" + with gr.Tab("方法1: スタイル分けを自動で行う"): + with gr.Tab("スタイル分け1"): + n_clusters = gr.Slider( + minimum=2, + maximum=10, + step=1, + value=4, + label="作るスタイルの数(平均スタイルを除く)", + info="上の図を見ながらスタイルの数を試行錯誤してください。", + ) + c_method = gr.Radio( + choices=[ + "Agglomerative after reduction", + "KMeans after reduction", + "Agglomerative", + "KMeans", + ], + label="アルゴリズム", + info="分類する(クラスタリング)アルゴリズムを選択します。いろいろ試してみてください。", + value="Agglomerative after reduction", + ) + c_button = gr.Button("スタイル分けを実行") + with gr.Tab("スタイル分け2: DBSCAN"): + gr.Markdown(dbscan_md) + eps = gr.Slider( + minimum=0.1, + maximum=10, + step=0.01, + value=0.3, + label="eps", + ) + min_samples = gr.Slider( + minimum=1, + maximum=50, + step=1, + value=15, + label="min_samples", + ) + with gr.Row(): + dbscan_button = gr.Button("スタイル分けを実行") + num_styles_result = gr.Textbox(label="スタイル数") + gr.Markdown("スタイル分けの結果") + gr.Markdown( + "注意: もともと256次元なものをを2次元に落としているので、正確なベクトルの位置関係ではありません。" ) - style_names_text = gr.Textbox( - label="スタイル名", placeholder="Angry, Sad, Happy" + with gr.Row(): + gr_plot = gr.Plot() + with gr.Column(): + with gr.Row(): + cluster_index = gr.Slider( + minimum=1, + maximum=MAX_CLUSTER_NUM, + step=1, + value=1, + label="スタイル番号", + info="選択したスタイルの代表音声を表示します。", + ) + num_files = gr.Slider( + minimum=1, + maximum=MAX_AUDIO_NUM, + step=1, + value=5, + label="代表音声の数をいくつ表示するか", + ) + get_audios_button = gr.Button("代表音声を取得") + with gr.Row(): + audio_list = [] + for i in range(MAX_AUDIO_NUM): + audio_list.append( + gr.Audio(visible=False, show_label=True) + ) + c_button.click( + do_clustering_gradio, + inputs=[n_clusters, c_method], + outputs=[gr_plot, cluster_index] + audio_list, + ) + dbscan_button.click( + do_dbscan_gradio, + inputs=[eps, min_samples], + outputs=[gr_plot, cluster_index, num_styles_result] + + audio_list, + ) + get_audios_button.click( + representative_wav_files_gradio, + inputs=[cluster_index, num_files], + outputs=audio_list, + ) + gr.Markdown("結果が良さそうなら、これを保存します。") + style_names = gr.Textbox( + "Angry, Sad, Happy", + label="スタイルの名前", + info=f"スタイルの名前を`,`で区切って入力してください(日本語可)。例: `Angry, Sad, Happy`や`怒り, 悲しみ, 喜び`など。平均音声は{DEFAULT_STYLE}として自動的に保存されます。", ) - with gr.Row(): - save_button2 = gr.Button("スタイルベクトルを保存", variant="primary") - info2 = gr.Textbox(label="保存結果") - save_button2.click( - save_style_vectors_from_files, - inputs=[model_name, audio_files_text, style_names_text], + with gr.Row(): + save_button1 = gr.Button( + "スタイルベクトルを保存", variant="primary" + ) + info2 = gr.Textbox(label="保存結果") + + save_button1.click( + save_style_vectors_from_clustering, + inputs=[model_name, style_names], outputs=[info2], ) + with gr.Tab("方法2: 手動でスタイルを選ぶ"): + gr.Markdown( + "下のテキスト欄に、各スタイルの代表音声のファイル名を`,`区切りで、その横に対応するスタイル名を`,`区切りで入力してください。" + ) + gr.Markdown("例: `angry.wav, sad.wav, happy.wav`と`Angry, Sad, Happy`") + gr.Markdown( + f"注意: {DEFAULT_STYLE}スタイルは自動的に保存されます、手動では{DEFAULT_STYLE}という名前のスタイルは指定しないでください。" + ) + with gr.Row(): + audio_files_text = gr.Textbox( + label="音声ファイル名", + placeholder="angry.wav, sad.wav, happy.wav", + ) + style_names_text = gr.Textbox( + label="スタイル名", placeholder="Angry, Sad, Happy" + ) + with gr.Row(): + save_button2 = gr.Button( + "スタイルベクトルを保存", variant="primary" + ) + info2 = gr.Textbox(label="保存結果") + save_button2.click( + save_style_vectors_from_files, + inputs=[model_name, audio_files_text, style_names_text], + outputs=[info2], + ) return app diff --git a/gradio_tabs/train.py b/gradio_tabs/train.py index 03efdde57..ebee2a6fa 100644 --- a/gradio_tabs/train.py +++ b/gradio_tabs/train.py @@ -1,11 +1,11 @@ import json -import os import shutil import socket import subprocess import sys import time import webbrowser +from dataclasses import dataclass from datetime import datetime from multiprocessing import cpu_count from pathlib import Path @@ -13,6 +13,7 @@ import gradio as gr import yaml +from config import get_path_config from style_bert_vits2.logging import logger from style_bert_vits2.utils.stdout_wrapper import SAFE_STDOUT from style_bert_vits2.utils.subprocess import run_script_with_log, second_elem_of @@ -21,20 +22,27 @@ logger_handler = None tensorboard_executed = False -# 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(path_config["dataset_root"]) +path_config = get_path_config() +dataset_root = path_config.dataset_root -def get_path(model_name: str) -> tuple[Path, Path, Path, Path, Path]: +@dataclass +class PathsForPreprocess: + dataset_path: Path + esd_path: Path + train_path: Path + val_path: Path + config_path: Path + + +def get_path(model_name: str) -> PathsForPreprocess: assert model_name != "", "モデル名は空にできません" dataset_path = dataset_root / model_name - lbl_path = dataset_path / "esd.list" + esd_path = dataset_path / "esd.list" train_path = dataset_path / "train.list" val_path = dataset_path / "val.list" config_path = dataset_path / "config.json" - return dataset_path, lbl_path, train_path, val_path, config_path + return PathsForPreprocess(dataset_path, esd_path, train_path, val_path, config_path) def initialize( @@ -51,14 +59,14 @@ def initialize( log_interval: int, ): global logger_handler - dataset_path, _, train_path, val_path, config_path = get_path(model_name) + paths = get_path(model_name) # 前処理のログをファイルに保存する timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") file_name = f"preprocess_{timestamp}.log" if logger_handler is not None: logger.remove(logger_handler) - logger_handler = logger.add(os.path.join(dataset_path, file_name)) + logger_handler = logger.add(paths.dataset_path / file_name) logger.info( f"Step 1: start initialization...\nmodel_name: {model_name}, batch_size: {batch_size}, epochs: {epochs}, save_every_steps: {save_every_steps}, freeze_ZH_bert: {freeze_ZH_bert}, freeze_JP_bert: {freeze_JP_bert}, freeze_EN_bert: {freeze_EN_bert}, freeze_style: {freeze_style}, freeze_decoder: {freeze_decoder}, use_jp_extra: {use_jp_extra}" @@ -68,11 +76,11 @@ def initialize( "configs/config.json" if not use_jp_extra else "configs/config_jp_extra.json" ) - with open(default_config_path, "r", encoding="utf-8") as f: + with open(default_config_path, encoding="utf-8") as f: config = json.load(f) config["model_name"] = model_name - config["data"]["training_files"] = str(train_path) - config["data"]["validation_files"] = str(val_path) + config["data"]["training_files"] = str(paths.train_path) + config["data"]["validation_files"] = str(paths.val_path) config["train"]["batch_size"] = batch_size config["train"]["epochs"] = epochs config["train"]["eval_interval"] = save_every_steps @@ -89,14 +97,14 @@ def initialize( # 今はデフォルトであるが、以前は非JP-Extra版になくバグの原因になるので念のため config["data"]["use_jp_extra"] = use_jp_extra - model_path = dataset_path / "models" + model_path = paths.dataset_path / "models" if model_path.exists(): logger.warning( f"Step 1: {model_path} already exists, so copy it to backup to {model_path}_backup" ) shutil.copytree( src=model_path, - dst=dataset_path / "models_backup", + dst=paths.dataset_path / "models_backup", dirs_exist_ok=True, ) shutil.rmtree(model_path) @@ -110,14 +118,14 @@ def initialize( logger.error(f"Step 1: {pretrained_dir} folder not found.") return False, f"Step 1, Error: {pretrained_dir}フォルダが見つかりません。" - with open(config_path, "w", encoding="utf-8") as f: + with open(paths.config_path, "w", encoding="utf-8") as f: json.dump(config, f, indent=2, ensure_ascii=False) if not Path("config.yml").exists(): shutil.copy(src="default_config.yml", dst="config.yml") - with open("config.yml", "r", encoding="utf-8") as f: + with open("config.yml", encoding="utf-8") as f: yml_data = yaml.safe_load(f) yml_data["model_name"] = model_name - yml_data["dataset_path"] = str(dataset_path) + yml_data["dataset_path"] = str(paths.dataset_path) with open("config.yml", "w", encoding="utf-8") as f: yaml.dump(yml_data, f, allow_unicode=True) logger.success("Step 1: initialization finished.") @@ -126,7 +134,7 @@ def initialize( def resample(model_name: str, normalize: bool, trim: bool, num_processes: int): logger.info("Step 2: start resampling...") - dataset_path, _, _, _, _ = get_path(model_name) + dataset_path = get_path(model_name).dataset_path input_dir = dataset_path / "raw" output_dir = dataset_path / "wavs" cmd = [ @@ -159,21 +167,24 @@ def preprocess_text( model_name: str, use_jp_extra: bool, val_per_lang: int, yomi_error: str ): logger.info("Step 3: start preprocessing text...") - _, lbl_path, train_path, val_path, config_path = get_path(model_name) - if not lbl_path.exists(): - logger.error(f"Step 3: {lbl_path} not found.") - return False, f"Step 3, Error: 書き起こしファイル {lbl_path} が見つかりません。" + paths = get_path(model_name) + if not paths.esd_path.exists(): + logger.error(f"Step 3: {paths.esd_path} not found.") + return ( + False, + f"Step 3, Error: 書き起こしファイル {paths.esd_path} が見つかりません。", + ) cmd = [ "preprocess_text.py", "--config-path", - str(config_path), + str(paths.config_path), "--transcription-path", - str(lbl_path), + str(paths.esd_path), "--train-path", - str(train_path), + str(paths.train_path), "--val-path", - str(val_path), + str(paths.val_path), "--val-per-lang", str(val_per_lang), "--yomi_error", @@ -201,7 +212,7 @@ def preprocess_text( def bert_gen(model_name: str): logger.info("Step 4: start bert_gen...") - _, _, _, _, config_path = get_path(model_name) + config_path = get_path(model_name).config_path success, message = run_script_with_log( ["bert_gen.py", "--config", str(config_path)] ) @@ -220,7 +231,7 @@ def bert_gen(model_name: str): def style_gen(model_name: str, num_processes: int): logger.info("Step 5: start style_gen...") - _, _, _, _, config_path = get_path(model_name) + config_path = get_path(model_name).config_path success, message = run_script_with_log( [ "style_gen.py", @@ -318,22 +329,31 @@ def train( skip_style: bool = False, use_jp_extra: bool = True, speedup: bool = False, + not_use_custom_batch_sampler: bool = False, ): - dataset_path, _, _, _, config_path = get_path(model_name) + paths = get_path(model_name) # 学習再開の場合を考えて念のためconfig.ymlの名前等を更新 - with open("config.yml", "r", encoding="utf-8") as f: + with open("config.yml", encoding="utf-8") as f: yml_data = yaml.safe_load(f) yml_data["model_name"] = model_name - yml_data["dataset_path"] = str(dataset_path) + yml_data["dataset_path"] = str(paths.dataset_path) with open("config.yml", "w", encoding="utf-8") as f: yaml.dump(yml_data, f, allow_unicode=True) train_py = "train_ms.py" if not use_jp_extra else "train_ms_jp_extra.py" - cmd = [train_py, "--config", str(config_path), "--model", str(dataset_path)] + cmd = [ + train_py, + "--config", + str(paths.config_path), + "--model", + str(paths.dataset_path), + ] if skip_style: cmd.append("--skip_default_style") if speedup: cmd.append("--speedup") + if not_use_custom_batch_sampler: + cmd.append("--not_use_custom_batch_sampler") success, message = run_script_with_log(cmd, ignore_warning=True) if not success: logger.error("Train failed.") @@ -385,6 +405,15 @@ def run_tensorboard(model_name: str): yield gr.Button("Tensorboardを開く") +change_log_md = """ +**Ver 2.5以降の変更点** + +- `raw/`フォルダの中で音声をサブディレクトリに分けて配置することで、自動的にスタイルが作成されるようになりました。詳細は下の「使い方/データの前準備」を参照してください。 +- これまでは1ファイルあたり14秒程度を超えた音声ファイルは学習には用いられていませんでしたが、Ver 2.5以降では「カスタムバッチサンプラーを使わない」にチェックを入れることでその制限が無しに学習できるようになりました(デフォルトはオフ)。ただし: + - 音声ファイルが長い場合の学習効率は悪いかもしれず、挙動も確認していません + - チェックを入れると要求VRAMがかなり増えるようので、学習に失敗したりVRAM不足になる場合は、バッチサイズを小さくするか、チェックを外してください +""" + how_to_md = """ ## 使い方 @@ -396,9 +425,6 @@ def run_tensorboard(model_name: str): - 途中から学習を再開する場合は、モデル名を入力してから「学習を開始する」を押せばよいです。 -注意: 標準スタイル以外のスタイルを音声合成で使うには、スタイルベクトルファイル`style_vectors.npy`を作る必要があります。これは、`Style.bat`を実行してそこで作成してください。 -動作は軽いはずなので、学習中でも実行でき、何度でも繰り返して試せます。 - ## JP-Extra版について 元とするモデル構造として [Bert-VITS2 Japanese-Extra](https://github.com/fishaudio/Bert-VITS2/releases/tag/JP-Exta) を使うことができます。 @@ -406,40 +432,60 @@ def run_tensorboard(model_name: str): """ prepare_md = """ -まず音声データ(wavファイルで1ファイルが2-12秒程度の、長すぎず短すぎない発話のものをいくつか)と、書き起こしテキストを用意してください。 +まず音声データと、書き起こしテキストを用意してください。 それを次のように配置します。 ``` -├── Data +├── Data/ │ ├── {モデルの名前} │ │ ├── esd.list -│ │ ├── raw -│ │ │ ├── ****.wav -│ │ │ ├── ****.wav -│ │ │ ├── ... +│ │ ├── raw/ +│ │ │ ├── foo.wav +│ │ │ ├── bar.mp3 +│ │ │ ├── style1/ +│ │ │ │ ├── baz.wav +│ │ │ │ ├── qux.wav +│ │ │ ├── style2/ +│ │ │ │ ├── corge.wav +│ │ │ │ ├── grault.wav +... ``` -wavファイル名やモデルの名前は空白を含まない半角で、wavファイルの拡張子は小文字`.wav`である必要があります。 -`raw` フォルダにはすべてのwavファイルを入れ、`esd.list` ファイルには、以下のフォーマットで各wavファイルの情報を記述してください。 +### 配置の仕方 +- 上のように配置すると、`style1/`と`style2/`フォルダの内部(直下以外も含む)に入っている音声ファイルたちから、自動的にデフォルトスタイルに加えて`style1`と`style2`というスタイルが作成されます +- 特にスタイルを作る必要がない場合や、スタイル分類機能等でスタイルを作る場合は、`raw/`フォルダ直下に全てを配置してください。このように`raw/`のサブディレクトリの個数が0または1の場合は、スタイルはデフォルトスタイルのみが作成されます。 +- 音声ファイルのフォーマットはwav形式以外にもmp3等の多くの音声ファイルに対応しています + +### 書き起こしファイル`esd.list` + +`Data/{モデルの名前}/esd.list` ファイルには、以下のフォーマットで各音声ファイルの情報を記述してください。 + + ``` -****.wav|{話者名}|{言語ID、ZHかJPかEN}|{書き起こしテキスト} +path/to/audio.wav(wavファイル以外でもこう書く)|{話者名}|{言語ID、ZHかJPかEN}|{書き起こしテキスト} ``` +- ここで、最初の`path/to/audio.wav`は、`raw/`からの相対パスです。つまり、`raw/foo.wav`の場合は`foo.wav`、`raw/style1/bar.wav`の場合は`style1/bar.wav`となります。 +- 拡張子がwavでない場合でも、`esd.list`には`wav`と書いてください、つまり、`raw/bar.mp3`の場合でも`bar.wav`と書いてください。 + + 例: ``` -wav_number1.wav|hanako|JP|こんにちは、聞こえて、いますか? -wav_next.wav|taro|JP|はい、聞こえています……。 +foo.wav|hanako|JP|こんにちは、元気ですか? +bar.wav|taro|JP|はい、聞こえています……。何か用ですか? +style1/baz.wav|hanako|JP|今日はいい天気ですね。 +style1/qux.wav|taro|JP|はい、そうですね。 +... english_teacher.wav|Mary|EN|How are you? I'm fine, thank you, and you? ... ``` -日本語話者の単一話者データセットでも構いません。 - -- 音声ファイルはrawフォルダの直下でなくてもサブフォルダに入れても構いません。その場合は、`esd.list`の最初には`raw`からの相対パスを記述してください。 +もちろん日本語話者の単一話者データセットでも構いません。 """ def create_train_app(): with gr.Blocks().queue() as app: + gr.Markdown(change_log_md) with gr.Accordion("使い方", open=False): gr.Markdown(how_to_md) with gr.Accordion(label="データの前準備", open=False): @@ -491,7 +537,7 @@ def create_train_app(): ("読めないファイルは使わず続行", "skip"), ("読めないファイルも無理やり読んで学習に使う", "use"), ], - value="raise", + value="skip", ) with gr.Accordion("詳細設定", open=False): num_processes = gr.Slider( @@ -677,6 +723,11 @@ def create_train_app(): label="JP-Extra版を使う", value=True, ) + not_use_custom_batch_sampler = gr.Checkbox( + label="カスタムバッチサンプラーを使わない", + info="VRAMに余裕がある場合にチェックすると、長い音声ファイルも学習に使われるようになります", + value=False, + ) speedup = gr.Checkbox( label="ログ等をスキップして学習を高速化する", value=False, @@ -764,7 +815,13 @@ def create_train_app(): # Train train_btn.click( second_elem_of(train), - inputs=[model_name, skip_style, use_jp_extra_train, speedup], + inputs=[ + model_name, + skip_style, + use_jp_extra_train, + speedup, + not_use_custom_batch_sampler, + ], outputs=[info_train], ) tensorboard_btn.click( diff --git a/initialize.py b/initialize.py index 664d57055..267465e38 100644 --- a/initialize.py +++ b/initialize.py @@ -10,7 +10,7 @@ def download_bert_models(): - with open("bert/bert_models.json", "r", encoding="utf-8") as fp: + with open("bert/bert_models.json", encoding="utf-8") as fp: models = json.load(fp) for k, v in models.items(): local_path = Path("bert").joinpath(k) @@ -50,7 +50,7 @@ def download_jp_extra_pretrained_models(): ) -def download_jvnv_models(): +def download_default_models(): files = [ "jvnv-F1-jp/config.json", "jvnv-F1-jp/jvnv-F1-jp_e160_s14000.safetensors", @@ -72,13 +72,33 @@ def download_jvnv_models(): "litagin/style_bert_vits2_jvnv", file, local_dir="model_assets", - local_dir_use_symlinks=False, ) + additional_files = { + "litagin/sbv2_koharune_ami": [ + "koharune-ami/config.json", + "koharune-ami/style_vectors.npy", + "koharune-ami/koharune-ami.safetensors", + ], + "litagin/sbv2_amitaro": [ + "amitaro/config.json", + "amitaro/style_vectors.npy", + "amitaro/amitaro.safetensors", + ], + } + for repo_id, files in additional_files.items(): + for file in files: + if not Path(f"model_assets/{file}").exists(): + logger.info(f"Downloading {file}") + hf_hub_download( + repo_id, + file, + local_dir="model_assets", + ) def main(): parser = argparse.ArgumentParser() - parser.add_argument("--skip_jvnv", action="store_true") + parser.add_argument("--skip_default_models", action="store_true") parser.add_argument("--only_infer", action="store_true") parser.add_argument( "--dataset_root", @@ -96,8 +116,8 @@ def main(): download_bert_models() - if not args.skip_jvnv: - download_jvnv_models() + if not args.skip_default_models: + download_default_models() if not args.only_infer: download_slm_model() download_pretrained_models() @@ -113,7 +133,7 @@ def main(): return # Change default paths if necessary - with open(paths_yml, "r", encoding="utf-8") as f: + with open(paths_yml, encoding="utf-8") as f: yml_data = yaml.safe_load(f) if args.assets_root is not None: yml_data["assets_root"] = args.assets_root diff --git a/library.ipynb b/library.ipynb index 753059a79..8f158eaa7 100644 --- a/library.ipynb +++ b/library.ipynb @@ -1,138 +1,135 @@ { - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Style-Bert-VITS2ライブラリの使用例\n", - "\n", - "`pip install style-bert-vits2`を使った、jupyter notebookでの使用例です。Google colab等でも動きます。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# PyTorch環境の構築(ない場合)\n", - "# 参照: https://pytorch.org/get-started/locally/\n", - "\n", - "!pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "LLrngKcQEAyP" - }, - "outputs": [], - "source": [ - "# style-bert-vits2のインストール\n", - "\n", - "!pip install style-bert-vits2" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "9xRtfUg5EZkx" - }, - "outputs": [], - "source": [ - "# BERTモデルをロード(ローカルに手動でダウンロードする必要はありません)\n", - "\n", - "from style_bert_vits2.nlp import bert_models\n", - "from style_bert_vits2.constants import Languages\n", - "\n", - "\n", - "bert_models.load_model(Languages.JP, \"ku-nlp/deberta-v2-large-japanese-char-wwm\")\n", - "bert_models.load_tokenizer(Languages.JP, \"ku-nlp/deberta-v2-large-japanese-char-wwm\")\n", - "# bert_models.load_model(Languages.EN, \"microsoft/deberta-v3-large\")\n", - "# bert_models.load_tokenizer(Languages.EN, \"microsoft/deberta-v3-large\")\n", - "# bert_models.load_model(Languages.ZH, \"hfl/chinese-roberta-wwm-ext-large\")\n", - "# bert_models.load_tokenizer(Languages.ZH, \"hfl/chinese-roberta-wwm-ext-large\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "q2V9d3HyFAr_" - }, - "outputs": [], - "source": [ - "# Hugging Faceから試しにデフォルトモデルをダウンロードしてみて、それを音声合成に使ってみる\n", - "# model_assetsディレクトリにダウンロードされます\n", - "\n", - "from pathlib import Path\n", - "from huggingface_hub import hf_hub_download\n", - "\n", - "\n", - "model_file = \"jvnv-F1-jp/jvnv-F1-jp_e160_s14000.safetensors\"\n", - "config_file = \"jvnv-F1-jp/config.json\"\n", - "style_file = \"jvnv-F1-jp/style_vectors.npy\"\n", - "\n", - "for file in [model_file, config_file, style_file]:\n", - " print(file)\n", - " hf_hub_download(\n", - " \"litagin/style_bert_vits2_jvnv\",\n", - " file,\n", - " local_dir=\"model_assets\"\n", - " )" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "hJa31MEUFhe4" - }, - "outputs": [], - "source": [ - "# 上でダウンロードしたモデルファイルを指定して音声合成のテスト\n", - "\n", - "from style_bert_vits2.tts_model import TTSModel\n", - "\n", - "assets_root = Path(\"model_assets\")\n", - "\n", - "model = TTSModel(\n", - " model_path=assets_root / model_file,\n", - " config_path=assets_root / config_file,\n", - " style_vec_path=assets_root / style_file,\n", - " device=\"cpu\"\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "Gal0tqrtGXZx" - }, - "outputs": [], - "source": [ - "from IPython.display import Audio, display\n", - "\n", - "sr, audio = model.infer(text=\"こんにちは\")\n", - "display(Audio(audio, rate=sr))" - ] - } - ], - "metadata": { - "colab": { - "provenance": [] - }, - "kernelspec": { - "display_name": "Python 3", - "name": "python3" - }, - "language_info": { - "name": "python" - } + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Style-Bert-VITS2ライブラリの使用例\n", + "\n", + "`pip install style-bert-vits2`を使った、jupyter notebookでの使用例です。Google colab等でも動きます。" + ] }, - "nbformat": 4, - "nbformat_minor": 0 + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# PyTorch環境の構築(ない場合)\n", + "# 参照: https://pytorch.org/get-started/locally/\n", + "\n", + "!pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "LLrngKcQEAyP" + }, + "outputs": [], + "source": [ + "# style-bert-vits2のインストール\n", + "\n", + "!pip install style-bert-vits2" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "9xRtfUg5EZkx" + }, + "outputs": [], + "source": [ + "# BERTモデルをロード(ローカルに手動でダウンロードする必要はありません)\n", + "\n", + "from style_bert_vits2.nlp import bert_models\n", + "from style_bert_vits2.constants import Languages\n", + "\n", + "\n", + "bert_models.load_model(Languages.JP, \"ku-nlp/deberta-v2-large-japanese-char-wwm\")\n", + "bert_models.load_tokenizer(Languages.JP, \"ku-nlp/deberta-v2-large-japanese-char-wwm\")\n", + "# bert_models.load_model(Languages.EN, \"microsoft/deberta-v3-large\")\n", + "# bert_models.load_tokenizer(Languages.EN, \"microsoft/deberta-v3-large\")\n", + "# bert_models.load_model(Languages.ZH, \"hfl/chinese-roberta-wwm-ext-large\")\n", + "# bert_models.load_tokenizer(Languages.ZH, \"hfl/chinese-roberta-wwm-ext-large\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "q2V9d3HyFAr_" + }, + "outputs": [], + "source": [ + "# Hugging Faceから試しにデフォルトモデルをダウンロードしてみて、それを音声合成に使ってみる\n", + "# model_assetsディレクトリにダウンロードされます\n", + "\n", + "from pathlib import Path\n", + "from huggingface_hub import hf_hub_download\n", + "\n", + "\n", + "model_file = \"jvnv-F1-jp/jvnv-F1-jp_e160_s14000.safetensors\"\n", + "config_file = \"jvnv-F1-jp/config.json\"\n", + "style_file = \"jvnv-F1-jp/style_vectors.npy\"\n", + "\n", + "for file in [model_file, config_file, style_file]:\n", + " print(file)\n", + " hf_hub_download(\"litagin/style_bert_vits2_jvnv\", file, local_dir=\"model_assets\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "hJa31MEUFhe4" + }, + "outputs": [], + "source": [ + "# 上でダウンロードしたモデルファイルを指定して音声合成のテスト\n", + "\n", + "from style_bert_vits2.tts_model import TTSModel\n", + "\n", + "assets_root = Path(\"model_assets\")\n", + "\n", + "model = TTSModel(\n", + " model_path=assets_root / model_file,\n", + " config_path=assets_root / config_file,\n", + " style_vec_path=assets_root / style_file,\n", + " device=\"cpu\",\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "Gal0tqrtGXZx" + }, + "outputs": [], + "source": [ + "from IPython.display import Audio, display\n", + "\n", + "sr, audio = model.infer(text=\"こんにちは\")\n", + "display(Audio(audio, rate=sr))" + ] + } + ], + "metadata": { + "colab": { + "provenance": [] + }, + "kernelspec": { + "display_name": "Python 3", + "name": "python3" + }, + "language_info": { + "name": "python", + "version": "3.10.11" + } + }, + "nbformat": 4, + "nbformat_minor": 0 } diff --git a/preprocess_text.py b/preprocess_text.py index a65a1d6be..4dd7e33e0 100644 --- a/preprocess_text.py +++ b/preprocess_text.py @@ -2,12 +2,12 @@ import json from collections import defaultdict from pathlib import Path -from random import shuffle +from random import sample, shuffle from typing import Optional from tqdm import tqdm -from config import Preprocess_text_config, config +from config import get_config from style_bert_vits2.logging import logger from style_bert_vits2.nlp import clean_text from style_bert_vits2.nlp.japanese import pyopenjtalk_worker @@ -22,7 +22,7 @@ update_dict() -preprocess_text_config: Preprocess_text_config = config.preprocess_text_config +preprocess_text_config = get_config().preprocess_text_config # Count lines for tqdm @@ -145,7 +145,7 @@ def preprocess( spk_utt_map[spk].append(line) # 新しい話者が出てきたら話者IDを割り当て、current_sidを1増やす - if spk not in spk_id_map.keys(): + if spk not in spk_id_map: spk_id_map[spk] = current_sid current_sid += 1 if count_same > 0 or count_not_found > 0: @@ -156,16 +156,26 @@ def preprocess( train_list: list[str] = [] val_list: list[str] = [] - # 各話者ごとにシャッフルして、val_per_lang個をval_listに、残りをtrain_listに追加 + # 各話者ごとに発話リストを処理 for spk, utts in spk_utt_map.items(): - shuffle(utts) - val_list += utts[:val_per_lang] - train_list += utts[val_per_lang:] - - shuffle(val_list) + if val_per_lang == 0: + train_list.extend(utts) + continue + # ランダムにval_per_lang個のインデックスを選択 + val_indices = set(sample(range(len(utts)), val_per_lang)) + # 元の順序を保ちながらリストを分割 + for index, utt in enumerate(utts): + if index in val_indices: + val_list.append(utt) + else: + train_list.append(utt) + + # バリデーションリストのサイズ調整 if len(val_list) > max_val_total: - train_list += val_list[max_val_total:] + extra_val = val_list[max_val_total:] val_list = val_list[:max_val_total] + # 余剰のバリデーション発話をトレーニングリストに追加(元の順序を保持) + train_list.extend(extra_val) with train_path.open("w", encoding="utf-8") as f: for line in train_list: diff --git a/pyproject.toml b/pyproject.toml index 77306ad06..45fbbcb7e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ build-backend = "hatchling.build" [project] name = "style-bert-vits2" dynamic = ["version"] -description = 'Style-Bert-VITS2: Bert-VITS2 with more controllable voice styles.' +description = "Style-Bert-VITS2: Bert-VITS2 with more controllable voice styles." readme = "README.md" requires-python = ">=3.9" license = "AGPL-3.0" @@ -22,21 +22,21 @@ classifiers = [ "Programming Language :: Python :: Implementation :: CPython", ] dependencies = [ - 'cmudict', - 'cn2an', - 'g2p_en', - 'jieba', - 'loguru', - 'num2words', - 'numba', - 'numpy', - 'pydantic>=2.0', - 'pyopenjtalk-dict', - 'pypinyin', - 'pyworld-prebuilt', - 'safetensors', - 'torch>=2.1', - 'transformers', + "cmudict", + "cn2an", + "g2p_en", + "jieba", + "loguru", + "num2words", + "numba", + "numpy", + "pydantic>=2.0", + "pyopenjtalk-dict", + "pypinyin", + "pyworld-prebuilt", + "safetensors", + "torch>=2.1", + "transformers", ] [project.urls] @@ -59,42 +59,26 @@ only-include = [ "pyproject.toml", "README.md", ] -exclude = [ - ".git", - ".gitignore", - ".gitattributes", -] +exclude = [".git", ".gitignore", ".gitattributes"] [tool.hatch.build.targets.wheel] packages = ["style_bert_vits2"] [tool.hatch.envs.test] -dependencies = [ - "coverage[toml]>=6.5", - "pytest", -] +dependencies = ["coverage[toml]>=6.5", "pytest"] [tool.hatch.envs.test.scripts] # Usage: `hatch run test:test` test = "pytest {args:tests}" # Usage: `hatch run test:coverage` test-cov = "coverage run -m pytest {args:tests}" # Usage: `hatch run test:cov-report` -cov-report = [ - "- coverage combine", - "coverage report", -] +cov-report = ["- coverage combine", "coverage report"] # Usage: `hatch run test:cov` -cov = [ - "test-cov", - "cov-report", -] +cov = ["test-cov", "cov-report"] [tool.hatch.envs.style] detached = true -dependencies = [ - "black", - "isort", -] +dependencies = ["black[jupyter]", "isort"] [tool.hatch.envs.style.scripts] check = [ "black --check --diff .", @@ -113,17 +97,17 @@ python = ["3.9", "3.10", "3.11"] source_pkgs = ["style_bert_vits2", "tests"] branch = true parallel = true -omit = [ - "style_bert_vits2/constants.py", -] +omit = ["style_bert_vits2/constants.py"] [tool.coverage.paths] style_bert_vits2 = ["style_bert_vits2", "*/style-bert-vits2/style_bert_vits2"] tests = ["tests", "*/style-bert-vits2/tests"] [tool.coverage.report] -exclude_lines = [ - "no cov", - "if __name__ == .__main__.:", - "if TYPE_CHECKING:", -] +exclude_lines = ["no cov", "if __name__ == .__main__.:", "if TYPE_CHECKING:"] + +[tool.ruff] +extend-select = ["I"] + +[tool.ruff.lint.isort] +lines-after-imports = 2 \ No newline at end of file diff --git a/requirements-colab.txt b/requirements-colab.txt new file mode 100644 index 000000000..160c9e59f --- /dev/null +++ b/requirements-colab.txt @@ -0,0 +1,18 @@ +cmudict +cn2an +g2p_en +gradio +jieba +librosa==0.9.2 +loguru +num2words +onnxruntime +pyannote.audio>=3.1.0 +pyloudnorm +pyopenjtalk-dict +pypinyin +pyworld-prebuilt +torch +torchaudio +transformers +umap-learn diff --git a/requirements-infer.txt b/requirements-infer.txt new file mode 100644 index 000000000..6dc1dd472 --- /dev/null +++ b/requirements-infer.txt @@ -0,0 +1,23 @@ +cmudict +cn2an +# faster-whisper==0.10.1 +g2p_en +GPUtil +gradio +jieba +# librosa==0.9.2 +loguru +num2words +# protobuf==4.25 +psutil +# punctuators +pyannote.audio>=3.1.0 +# pyloudnorm +pyopenjtalk-dict +pypinyin +pyworld-prebuilt +# stable_ts +# tensorboard +torch +transformers +umap-learn diff --git a/requirements.txt b/requirements.txt index d111e6910..3ae756766 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,26 +5,20 @@ g2p_en GPUtil gradio jieba -langid librosa==0.9.2 loguru -matplotlib num2words -numba -numpy +protobuf==4.25 psutil +punctuators pyannote.audio>=3.1.0 -pydantic>=2.0 pyloudnorm -# pyopenjtalk-prebuilt # Should be manually uninstalled pyopenjtalk-dict pypinyin pyworld-prebuilt -PyYAML -requests -safetensors -scipy +stable_ts tensorboard -torch>=2.1 +torch +torchaudio transformers umap-learn diff --git a/resample.py b/resample.py index 55f0f7f94..285105c46 100644 --- a/resample.py +++ b/resample.py @@ -10,7 +10,7 @@ from numpy.typing import NDArray from tqdm import tqdm -from config import config +from config import get_config from style_bert_vits2.logging import logger from style_bert_vits2.utils.stdout_wrapper import SAFE_STDOUT @@ -62,6 +62,7 @@ def resample( if trim: wav, _ = librosa.effects.trim(wav, top_db=30) relative_path = file.relative_to(input_dir) + # ここで拡張子が.wav以外でも.wavに置き換えられる output_path = output_dir / relative_path.with_suffix(".wav") output_path.parent.mkdir(parents=True, exist_ok=True) soundfile.write(output_path, wav, sr) @@ -70,6 +71,7 @@ def resample( if __name__ == "__main__": + config = get_config() parser = argparse.ArgumentParser() parser.add_argument( "--sr", diff --git a/scripts/Install-Style-Bert-VITS2-CPU.bat b/scripts/Install-Style-Bert-VITS2-CPU.bat index b62655ac5..cad168b01 100644 --- a/scripts/Install-Style-Bert-VITS2-CPU.bat +++ b/scripts/Install-Style-Bert-VITS2-CPU.bat @@ -89,6 +89,10 @@ if !errorlevel! neq 0 ( popd & exit /b !errorlevel! ) @REM Style-Bert-VITS2フォルダに移動 pushd Style-Bert-VITS2 +@REM 後で消す!!!!!!!!!! +@REM git checkout dev +@REM 後で消す!!!!!!!!!! + echo -------------------------------------------------- echo Activating the virtual environment... echo -------------------------------------------------- @@ -96,11 +100,25 @@ echo Executing: call ".\venv\Scripts\activate.bat" call ".\venv\Scripts\activate.bat" if !errorlevel! neq 0 ( popd & exit /b !errorlevel! ) +echo -------------------------------------------------- +echo Installing package manager uv... +echo -------------------------------------------------- +echo Executing: pip install uv +pip install uv +if !errorlevel! neq 0 ( pause & popd & exit /b !errorlevel! ) + +echo -------------------------------------------------- +echo Installing pip for compatibility... +echo -------------------------------------------------- +echo Executing: uv pip install pip +uv pip install pip +if !errorlevel! neq 0 ( pause & popd & exit /b !errorlevel! ) + echo -------------------------------------------------- echo Installing dependencies... echo -------------------------------------------------- -echo Executing: pip install -r requirements.txt -pip install -r requirements.txt +echo Executing: uv pip install -r requirements-infer.txt +uv pip install -r requirements-infer.txt if !errorlevel! neq 0 ( pause & popd & exit /b !errorlevel! ) echo ---------------------------------------- diff --git a/scripts/Install-Style-Bert-VITS2.bat b/scripts/Install-Style-Bert-VITS2.bat index 35ce45c0c..fb58d655e 100644 --- a/scripts/Install-Style-Bert-VITS2.bat +++ b/scripts/Install-Style-Bert-VITS2.bat @@ -89,6 +89,10 @@ if !errorlevel! neq 0 ( popd & exit /b !errorlevel! ) @REM Style-Bert-VITS2フォルダに移動 pushd Style-Bert-VITS2 +@REM 後で消す!!!!!!!!!! +@REM git checkout dev +@REM 後で消す!!!!!!!!!! + echo -------------------------------------------------- echo Activating the virtual environment... echo -------------------------------------------------- @@ -96,18 +100,32 @@ echo Executing: call ".\venv\Scripts\activate.bat" call ".\venv\Scripts\activate.bat" if !errorlevel! neq 0 ( popd & exit /b !errorlevel! ) +echo -------------------------------------------------- +echo Installing package manager uv... +echo -------------------------------------------------- +echo Executing: pip install uv +pip install uv +if !errorlevel! neq 0 ( pause & popd & exit /b !errorlevel! ) + +echo -------------------------------------------------- +echo Installing pip for compatibility... +echo -------------------------------------------------- +echo Executing: uv pip install pip +uv pip install pip +if !errorlevel! neq 0 ( pause & popd & exit /b !errorlevel! ) + echo -------------------------------------------------- echo Installing PyTorch... echo -------------------------------------------------- -echo Executing: pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118 -pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118 +echo Executing: uv pip install torch torchaudio --index-url https://download.pytorch.org/whl/cu118 +uv pip install torch torchaudio --index-url https://download.pytorch.org/whl/cu118 if !errorlevel! neq 0 ( pause & popd & exit /b !errorlevel! ) echo -------------------------------------------------- echo Installing other dependencies... echo -------------------------------------------------- -echo Executing: pip install -r requirements.txt -pip install -r requirements.txt +echo Executing: uv pip install -r requirements.txt +uv pip install -r requirements.txt if !errorlevel! neq 0 ( pause & popd & exit /b !errorlevel! ) echo ---------------------------------------- diff --git a/scripts/Setup-Python.bat b/scripts/Setup-Python.bat index 27ca69ec3..c5c99e7bc 100644 --- a/scripts/Setup-Python.bat +++ b/scripts/Setup-Python.bat @@ -66,7 +66,7 @@ if not exist "%PYTHON_DIR%"\ ( if !errorlevel! neq 0 ( pause & exit /b !errorlevel! ) echo -------------------------------------------------- - echo Installing pip and virtualenv... + echo Downloading get-pip.py... echo -------------------------------------------------- echo Executing: %CURL_CMD% -o "%PYTHON_DIR%\get-pip.py" https://bootstrap.pypa.io/get-pip.py %CURL_CMD% -o "%PYTHON_DIR%\get-pip.py" https://bootstrap.pypa.io/get-pip.py @@ -96,20 +96,6 @@ if not exist %VENV_DIR%\ ( if !errorlevel! neq 0 ( pause & exit /b !errorlevel! ) ) -echo -------------------------------------------------- -echo Activating virtual environment... -echo -------------------------------------------------- -echo Executing: call "%VENV_DIR%\Scripts\activate.bat" -call "%VENV_DIR%\Scripts\activate.bat" -if !errorlevel! neq 0 ( pause & exit /b !errorlevel! ) - -echo -------------------------------------------------- -echo Upgrading pip... -echo -------------------------------------------------- -echo Executing: python -m pip install --upgrade pip -python -m pip install --upgrade pip -if !errorlevel! neq 0 ( pause & exit /b !errorlevel! ) - echo -------------------------------------------------- echo Completed. echo -------------------------------------------------- diff --git a/scripts/Update-Style-Bert-VITS2.bat b/scripts/Update-Style-Bert-VITS2.bat index 5fd50e4e1..f43952bfd 100644 --- a/scripts/Update-Style-Bert-VITS2.bat +++ b/scripts/Update-Style-Bert-VITS2.bat @@ -44,11 +44,18 @@ echo Executing: call ".\venv\Scripts\activate.bat" call ".\venv\Scripts\activate.bat" if !errorlevel! neq 0 ( pause & popd & exit /b !errorlevel! ) +echo -------------------------------------------------- +echo Installing uv... +echo -------------------------------------------------- +echo Executing: pip install -U uv +pip install -U uv +if !errorlevel! neq 0 ( pause & popd & exit /b !errorlevel! ) + echo -------------------------------------------------- echo Updating dependencies... echo -------------------------------------------------- -echo Executing: pip install -U -r requirements.txt -pip install -U -r requirements.txt +echo Executing: uv pip install -U -r requirements.txt +uv pip install -U -r requirements.txt if !errorlevel! neq 0 ( pause & popd & exit /b !errorlevel! ) echo ---------------------------------------- diff --git a/server_editor.py b/server_editor.py index c6f856e2f..8caea47e5 100644 --- a/server_editor.py +++ b/server_editor.py @@ -22,7 +22,6 @@ import requests import torch import uvicorn -import yaml from fastapi import APIRouter, FastAPI, HTTPException, status from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import JSONResponse, Response @@ -30,6 +29,8 @@ from pydantic import BaseModel from scipy.io import wavfile +from config import get_path_config +from initialize import download_default_models from style_bert_vits2.constants import ( DEFAULT_ASSIST_TEXT_WEIGHT, DEFAULT_NOISE, @@ -127,7 +128,7 @@ def download_and_extract(url, extract_to: Path): def new_release_available(latest_release): if LAST_DOWNLOAD_FILE.exists(): - with open(LAST_DOWNLOAD_FILE, "r") as file: + with open(LAST_DOWNLOAD_FILE) as file: last_download_str = file.read().strip() # 'Z'を除去して日時オブジェクトに変換 last_download_str = last_download_str.replace("Z", "+00:00") @@ -174,35 +175,32 @@ class AudioResponse(Response): "http://127.0.0.1:8000", ] -# Get path settings -with open(Path("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"] - +path_config = get_path_config() parser = argparse.ArgumentParser() -parser.add_argument("--model_dir", type=str, default="model_assets/") +parser.add_argument("--model_dir", type=str, default=path_config.assets_root) parser.add_argument("--device", type=str, default="cuda") parser.add_argument("--port", type=int, default=8000) parser.add_argument("--inbrowser", action="store_true") parser.add_argument("--line_length", type=int, default=None) parser.add_argument("--line_count", type=int, default=None) -parser.add_argument( - "--dir", "-d", type=str, help="Model directory", default=assets_root -) - +parser.add_argument("--skip_default_models", action="store_true") +parser.add_argument("--skip_static_files", action="store_true") args = parser.parse_args() device = args.device if device == "cuda" and not torch.cuda.is_available(): device = "cpu" model_dir = Path(args.model_dir) port = int(args.port) +if not args.skip_default_models: + download_default_models() +skip_static_files = bool(args.skip_static_files) model_holder = TTSModelHolder(model_dir, device) if len(model_holder.model_names) == 0: logger.error(f"Models not found in {model_dir}.") sys.exit(1) + app = FastAPI() @@ -444,7 +442,8 @@ def delete_user_dict_word(uuid: str): app.include_router(router, prefix="/api") if __name__ == "__main__": - download_static_files("litagin02", "Style-Bert-VITS2-Editor", "out.zip") + if not skip_static_files: + download_static_files("litagin02", "Style-Bert-VITS2-Editor", "out.zip") app.mount("/", StaticFiles(directory=STATIC_DIR, html=True), name="static") if args.inbrowser: webbrowser.open(f"http://localhost:{port}") diff --git a/server_fastapi.py b/server_fastapi.py index 3c9d31e68..98ec79858 100644 --- a/server_fastapi.py +++ b/server_fastapi.py @@ -20,7 +20,7 @@ from fastapi.responses import FileResponse, Response from scipy.io import wavfile -from config import config +from config import get_config from style_bert_vits2.constants import ( DEFAULT_ASSIST_TEXT_WEIGHT, DEFAULT_LENGTH, @@ -40,6 +40,7 @@ from style_bert_vits2.tts_model import TTSModel, TTSModelHolder +config = get_config() ln = config.server_config.language @@ -113,6 +114,12 @@ def load_models(model_holder: TTSModelHolder): load_models(model_holder) limit = config.server_config.limit + if limit < 1: + limit = None + else: + logger.info( + f"The maximum length of the text is {limit}. If you want to change it, modify config.yml. Set limit to -1 to remove the limit." + ) app = FastAPI() allow_origins = config.server_config.origins if allow_origins: @@ -305,6 +312,9 @@ def get_audio( logger.info(f"server listen: http://127.0.0.1:{config.server_config.port}") logger.info(f"API docs: http://127.0.0.1:{config.server_config.port}/docs") + logger.info( + f"Input text length limit: {limit}. You can change it in server.limit in config.yml" + ) uvicorn.run( app, port=config.server_config.port, host="0.0.0.0", log_level="warning" ) diff --git a/slice.py b/slice.py index c4b2292fd..bfcb916cd 100644 --- a/slice.py +++ b/slice.py @@ -7,15 +7,15 @@ import soundfile as sf import torch -import yaml from tqdm import tqdm +from config import get_path_config from style_bert_vits2.logging import logger from style_bert_vits2.utils.stdout_wrapper import SAFE_STDOUT def is_audio_file(file: Path) -> bool: - supported_extensions = [".wav", ".flac", ".mp3", ".ogg", ".opus"] + supported_extensions = [".wav", ".flac", ".mp3", ".ogg", ".opus", ".m4a"] return file.suffix.lower() in supported_extensions @@ -150,13 +150,12 @@ def split_wav( ) args = parser.parse_args() - with open(Path("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"] + path_config = get_path_config() + dataset_root = path_config.dataset_root model_name = str(args.model_name) input_dir = Path(args.input_dir) - output_dir = Path(dataset_root) / model_name / "raw" + output_dir = dataset_root / model_name / "raw" min_sec: float = args.min_sec max_sec: float = args.max_sec min_silence_dur_ms: int = args.min_silence_dur_ms @@ -198,11 +197,12 @@ def process_queue( q.task_done() break try: + rel_path = file.relative_to(input_dir) time_sec, count = split_wav( vad_model=vad_model, utils=utils, audio_file=file, - target_dir=output_dir, + target_dir=output_dir / rel_path.parent, min_sec=min_sec, max_sec=max_sec, min_silence_dur_ms=min_silence_dur_ms, diff --git a/speech_mos.py b/speech_mos.py index 453b7d313..c7a6a25a7 100644 --- a/speech_mos.py +++ b/speech_mos.py @@ -10,7 +10,7 @@ import torch from tqdm import tqdm -from config import config +from config import get_path_config from style_bert_vits2.logging import logger from style_bert_vits2.tts_model import TTSModel @@ -35,6 +35,8 @@ "この分野の最新の研究成果を使うと、より自然で表現豊かな音声の生成が可能である。深層学習の応用により、感情やアクセントを含む声質の微妙な変化も再現することが出来る。", ] +path_config = get_path_config() + predictor = torch.hub.load( "tarepan/SpeechMOS:v1.2.0", "utmos22_strong", trust_repo=True ) @@ -48,17 +50,16 @@ model_name: str = args.model_name device: str = args.device -model_path = Path(config.assets_root) / model_name - +model_path = path_config.assets_root / model_name # .safetensorsファイルを検索 safetensors_files = model_path.glob("*.safetensors") def get_model(model_file: Path): return TTSModel( - model_path=str(model_file), - config_path=str(model_file.parent / "config.json"), - style_vec_path=str(model_file.parent / "style_vectors.npy"), + model_path=model_file, + config_path=model_file.parent / "config.json", + style_vec_path=model_file.parent / "style_vectors.npy", device=device, ) diff --git a/style_bert_vits2/constants.py b/style_bert_vits2/constants.py index 58b6441a6..eba3f1dad 100644 --- a/style_bert_vits2/constants.py +++ b/style_bert_vits2/constants.py @@ -4,7 +4,7 @@ # Style-Bert-VITS2 のバージョン -VERSION = "2.4.1" +VERSION = "2.5.0" # Style-Bert-VITS2 のベースディレクトリ BASE_DIR = Path(__file__).parent.parent @@ -32,7 +32,7 @@ class Languages(StrEnum): # デフォルトの推論パラメータ DEFAULT_STYLE = "Neutral" -DEFAULT_STYLE_WEIGHT = 5.0 +DEFAULT_STYLE_WEIGHT = 1.0 DEFAULT_SDP_RATIO = 0.2 DEFAULT_NOISE = 0.6 DEFAULT_NOISEW = 0.8 diff --git a/style_bert_vits2/models/hyper_parameters.py b/style_bert_vits2/models/hyper_parameters.py index feb6bfbaf..827ce6dea 100644 --- a/style_bert_vits2/models/hyper_parameters.py +++ b/style_bert_vits2/models/hyper_parameters.py @@ -125,5 +125,5 @@ def load_from_json(json_path: Union[str, Path]) -> "HyperParameters": HyperParameters: ハイパーパラメータ """ - with open(json_path, "r", encoding="utf-8") as f: + with open(json_path, encoding="utf-8") as f: return HyperParameters.model_validate_json(f.read()) diff --git a/style_bert_vits2/models/models.py b/style_bert_vits2/models/models.py index 56fb27c62..eaff2fadf 100644 --- a/style_bert_vits2/models/models.py +++ b/style_bert_vits2/models/models.py @@ -786,7 +786,7 @@ def __init__(self, spec_channels: int, gin_channels: int = 0) -> None: for i in range(K) ] self.convs = nn.ModuleList(convs) - # self.wns = nn.ModuleList([weight_norm(num_features=ref_enc_filters[i]) for i in range(K)]) # noqa: E501 + # self.wns = nn.ModuleList([weight_norm(num_features=ref_enc_filters[i]) for i in range(K)]) out_channels = self.calculate_channels(spec_channels, 3, 2, 1, K) self.gru = nn.GRU( diff --git a/style_bert_vits2/models/models_jp_extra.py b/style_bert_vits2/models/models_jp_extra.py index 2850baf20..e8df7d9fa 100644 --- a/style_bert_vits2/models/models_jp_extra.py +++ b/style_bert_vits2/models/models_jp_extra.py @@ -844,7 +844,7 @@ def __init__(self, spec_channels: int, gin_channels: int = 0) -> None: for i in range(K) ] self.convs = nn.ModuleList(convs) - # self.wns = nn.ModuleList([weight_norm(num_features=ref_enc_filters[i]) for i in range(K)]) # noqa: E501 + # self.wns = nn.ModuleList([weight_norm(num_features=ref_enc_filters[i]) for i in range(K)]) out_channels = self.calculate_channels(spec_channels, 3, 2, 1, K) self.gru = nn.GRU( diff --git a/style_bert_vits2/models/utils/__init__.py b/style_bert_vits2/models/utils/__init__.py index 2e750c8b4..b91c74aee 100644 --- a/style_bert_vits2/models/utils/__init__.py +++ b/style_bert_vits2/models/utils/__init__.py @@ -186,7 +186,7 @@ def load_filepaths_and_text( list[list[str]]: ファイルパスとテキストのリスト """ - with open(filename, "r", encoding="utf-8") as f: + with open(filename, encoding="utf-8") as f: filepaths_and_text = [line.strip().split(split) for line in f] return filepaths_and_text @@ -245,9 +245,7 @@ def check_git_hash(model_dir_path: Union[str, Path]) -> None: source_dir = os.path.dirname(os.path.realpath(__file__)) if not os.path.exists(os.path.join(source_dir, ".git")): logger.warning( - "{} is not a git repository, therefore hash value comparison will be ignored.".format( - source_dir - ) + f"{source_dir} is not a git repository, therefore hash value comparison will be ignored." ) return @@ -255,13 +253,11 @@ def check_git_hash(model_dir_path: Union[str, Path]) -> None: path = os.path.join(model_dir_path, "githash") if os.path.exists(path): - with open(path, "r", encoding="utf-8") as f: + with open(path, encoding="utf-8") as f: saved_hash = f.read() if saved_hash != cur_hash: logger.warning( - "git hash values are different. {}(saved) != {}(current)".format( - saved_hash[:8], cur_hash[:8] - ) + f"git hash values are different. {saved_hash[:8]}(saved) != {cur_hash[:8]}(current)" ) else: with open(path, "w", encoding="utf-8") as f: diff --git a/style_bert_vits2/models/utils/safetensors.py b/style_bert_vits2/models/utils/safetensors.py index 52ab115b2..4b4ef3fbd 100644 --- a/style_bert_vits2/models/utils/safetensors.py +++ b/style_bert_vits2/models/utils/safetensors.py @@ -77,7 +77,7 @@ def save_safetensors( keys = [] for k in state_dict: if "enc_q" in k and for_infer: - continue # noqa: E701 + continue keys.append(k) new_dict = ( diff --git a/style_bert_vits2/nlp/chinese/g2p.py b/style_bert_vits2/nlp/chinese/g2p.py index f38e09fa8..1f0894f95 100644 --- a/style_bert_vits2/nlp/chinese/g2p.py +++ b/style_bert_vits2/nlp/chinese/g2p.py @@ -8,7 +8,7 @@ from style_bert_vits2.nlp.symbols import PUNCTUATIONS -with open(Path(__file__).parent / "opencpop-strict.txt", "r", encoding="utf-8") as f: +with open(Path(__file__).parent / "opencpop-strict.txt", encoding="utf-8") as f: __PINYIN_TO_SYMBOL_MAP = { line.split("\t")[0]: line.strip().split("\t")[1] for line in f.readlines() } @@ -73,7 +73,7 @@ def __g2p(segments: list[str]) -> tuple[list[str], list[int], list[int]]: "iou": "iu", "uen": "un", } - if v_without_tone in v_rep_map.keys(): + if v_without_tone in v_rep_map: pinyin = c + v_rep_map[v_without_tone] else: # 单音节 @@ -83,7 +83,7 @@ def __g2p(segments: list[str]) -> tuple[list[str], list[int], list[int]]: "in": "yin", "u": "wu", } - if pinyin in pinyin_rep_map.keys(): + if pinyin in pinyin_rep_map: pinyin = pinyin_rep_map[pinyin] else: single_rep_map = { @@ -92,10 +92,10 @@ def __g2p(segments: list[str]) -> tuple[list[str], list[int], list[int]]: "i": "y", "u": "w", } - if pinyin[0] in single_rep_map.keys(): + if pinyin[0] in single_rep_map: pinyin = single_rep_map[pinyin[0]] + pinyin[1:] - assert pinyin in __PINYIN_TO_SYMBOL_MAP.keys(), ( + assert pinyin in __PINYIN_TO_SYMBOL_MAP, ( pinyin, seg, raw_pinyin, diff --git a/style_bert_vits2/nlp/chinese/normalizer.py b/style_bert_vits2/nlp/chinese/normalizer.py index 8239bc710..c076408dc 100644 --- a/style_bert_vits2/nlp/chinese/normalizer.py +++ b/style_bert_vits2/nlp/chinese/normalizer.py @@ -51,7 +51,7 @@ def normalize_text(text: str) -> str: def replace_punctuation(text: str) -> str: text = text.replace("嗯", "恩").replace("呣", "母") - pattern = re.compile("|".join(re.escape(p) for p in __REPLACE_MAP.keys())) + pattern = re.compile("|".join(re.escape(p) for p in __REPLACE_MAP)) replaced_text = pattern.sub(lambda x: __REPLACE_MAP[x.group()], text) diff --git a/style_bert_vits2/nlp/chinese/tone_sandhi.py b/style_bert_vits2/nlp/chinese/tone_sandhi.py index 552cb0d36..4945ab952 100644 --- a/style_bert_vits2/nlp/chinese/tone_sandhi.py +++ b/style_bert_vits2/nlp/chinese/tone_sandhi.py @@ -471,26 +471,27 @@ def _neural_sandhi(self, word: str, pos: str, finals: list[str]) -> list[str]: ): finals[j] = finals[j][:-1] + "5" ge_idx = word.find("个") - if len(word) >= 1 and word[-1] in "吧呢啊呐噻嘛吖嗨呐哦哒额滴哩哟喽啰耶喔诶": - finals[-1] = finals[-1][:-1] + "5" - elif len(word) >= 1 and word[-1] in "的地得": - finals[-1] = finals[-1][:-1] + "5" - # e.g. 走了, 看着, 去过 - # elif len(word) == 1 and word in "了着过" and pos in {"ul", "uz", "ug"}: - # finals[-1] = finals[-1][:-1] + "5" - elif ( - len(word) > 1 - and word[-1] in "们子" - and pos in {"r", "n"} - and word not in self.must_not_neural_tone_words + if ( + len(word) >= 1 + and word[-1] in "吧呢啊呐噻嘛吖嗨呐哦哒额滴哩哟喽啰耶喔诶" + or len(word) >= 1 + and word[-1] in "的地得" + or ( + ( + len(word) > 1 + and word[-1] in "们子" + and pos in {"r", "n"} + and word not in self.must_not_neural_tone_words + ) + or len(word) > 1 + and word[-1] in "上下里" + and pos in {"s", "l", "f"} + ) + or len(word) > 1 + and word[-1] in "来去" + and word[-2] in "上下进出回过起开" ): finals[-1] = finals[-1][:-1] + "5" - # e.g. 桌上, 地下, 家里 - elif len(word) > 1 and word[-1] in "上下里" and pos in {"s", "l", "f"}: - finals[-1] = finals[-1][:-1] + "5" - # e.g. 上来, 下去 - elif len(word) > 1 and word[-1] in "来去" and word[-2] in "上下进出回过起开": - finals[-1] = finals[-1][:-1] + "5" # 个做量词 elif ( ge_idx >= 1 @@ -500,12 +501,11 @@ def _neural_sandhi(self, word: str, pos: str, finals: list[str]) -> list[str]: ) ) or word == "个": finals[ge_idx] = finals[ge_idx][:-1] + "5" - else: - if ( - word in self.must_neural_tone_words - or word[-2:] in self.must_neural_tone_words - ): - finals[-1] = finals[-1][:-1] + "5" + elif ( + word in self.must_neural_tone_words + or word[-2:] in self.must_neural_tone_words + ): + finals[-1] = finals[-1][:-1] + "5" word_list = self._split_word(word) finals_list = [finals[: len(word_list[0])], finals[len(word_list[0]) :]] @@ -549,10 +549,8 @@ def _yi_sandhi(self, word: str, finals: list[str]) -> list[str]: if finals[i + 1][-1] == "4": finals[i] = finals[i][:-1] + "2" # "一" before non-tone4 should be yi4, e.g. 一天 - else: - # "一" 后面如果是标点,还读一声 - if word[i + 1] not in self.punc: - finals[i] = finals[i][:-1] + "4" + elif word[i + 1] not in self.punc: + finals[i] = finals[i][:-1] + "4" return finals def _split_word(self, word: str) -> list[str]: diff --git a/style_bert_vits2/nlp/english/cmudict.py b/style_bert_vits2/nlp/english/cmudict.py index 7772e77b8..fd17405f5 100644 --- a/style_bert_vits2/nlp/english/cmudict.py +++ b/style_bert_vits2/nlp/english/cmudict.py @@ -20,7 +20,7 @@ def get_dict() -> dict[str, list[list[str]]]: def read_dict() -> dict[str, list[list[str]]]: g2p_dict = {} start_line = 49 - with open(CMU_DICT_PATH, "r", encoding="utf-8") as f: + with open(CMU_DICT_PATH, encoding="utf-8") as f: line = f.readline() line_index = 1 while line: diff --git a/style_bert_vits2/nlp/english/g2p.py b/style_bert_vits2/nlp/english/g2p.py index deeef3f64..4e3f9b329 100644 --- a/style_bert_vits2/nlp/english/g2p.py +++ b/style_bert_vits2/nlp/english/g2p.py @@ -1,23 +1,91 @@ import re + from g2p_en import G2p + from style_bert_vits2.constants import Languages from style_bert_vits2.nlp import bert_models from style_bert_vits2.nlp.english.cmudict import get_dict from style_bert_vits2.nlp.symbols import PUNCTUATIONS, SYMBOLS + # Initialize global variables once ARPA = { - "AH0", "S", "AH1", "EY2", "AE2", "EH0", "OW2", "UH0", "NG", "B", "G", "AY0", - "M", "AA0", "F", "AO0", "ER2", "UH1", "IY1", "AH2", "DH", "IY0", "EY1", - "IH0", "K", "N", "W", "IY2", "T", "AA1", "ER1", "EH2", "OY0", "UH2", "UW1", - "Z", "AW2", "AW1", "V", "UW2", "AA2", "ER", "AW0", "UW0", "R", "OW1", "EH1", - "ZH", "AE0", "IH2", "IH", "Y", "JH", "P", "AY1", "EY0", "OY2", "TH", "HH", - "D", "ER0", "CH", "AO1", "AE1", "AO2", "OY1", "AY2", "IH1", "OW0", "L", - "SH" + "AH0", + "S", + "AH1", + "EY2", + "AE2", + "EH0", + "OW2", + "UH0", + "NG", + "B", + "G", + "AY0", + "M", + "AA0", + "F", + "AO0", + "ER2", + "UH1", + "IY1", + "AH2", + "DH", + "IY0", + "EY1", + "IH0", + "K", + "N", + "W", + "IY2", + "T", + "AA1", + "ER1", + "EH2", + "OY0", + "UH2", + "UW1", + "Z", + "AW2", + "AW1", + "V", + "UW2", + "AA2", + "ER", + "AW0", + "UW0", + "R", + "OW1", + "EH1", + "ZH", + "AE0", + "IH2", + "IH", + "Y", + "JH", + "P", + "AY1", + "EY0", + "OY2", + "TH", + "HH", + "D", + "ER0", + "CH", + "AO1", + "AE1", + "AO2", + "OY1", + "AY2", + "IH1", + "OW0", + "L", + "SH", } _g2p = G2p() eng_dict = get_dict() + def g2p(text: str) -> tuple[list[str], list[int], list[int]]: phones = [] tones = [] @@ -51,7 +119,7 @@ def g2p(text: str) -> tuple[list[str], list[int], list[int]]: tns.append(0) temp_phones += [__post_replace_ph(i) for i in phns] temp_tones += tns - + phones += temp_phones tones += temp_tones phone_len.append(len(temp_phones)) @@ -72,9 +140,19 @@ def g2p(text: str) -> tuple[list[str], list[int], list[int]]: def __post_replace_ph(ph: str) -> str: REPLACE_MAP = { - ":": ",", ";": ",", ",": ",", "。": ".", "!": "!", "?": "?", - "\n": ".", "·": ",", "、": ",", "…": "...", "···": "...", - "・・・": "...", "v": "V" + ":": ",", + ";": ",", + ",": ",", + "。": ".", + "!": "!", + "?": "?", + "\n": ".", + "·": ",", + "、": ",", + "…": "...", + "···": "...", + "・・・": "...", + "v": "V", } if ph in REPLACE_MAP: ph = REPLACE_MAP[ph] @@ -120,21 +198,22 @@ def __text_to_words(text: str) -> list[list[str]]: for idx, t in enumerate(tokens): if t.startswith("▁"): words.append([t[1:]]) - else: - if t in PUNCTUATIONS: - if idx == len(tokens) - 1: - words.append([f"{t}"]) - else: - if not tokens[idx + 1].startswith("▁") and tokens[idx + 1] not in PUNCTUATIONS: - if idx == 0: - words.append([]) - words[-1].append(f"{t}") - else: - words.append([f"{t}"]) - else: + elif t in PUNCTUATIONS: + if idx == len(tokens) - 1: + words.append([f"{t}"]) + elif ( + not tokens[idx + 1].startswith("▁") + and tokens[idx + 1] not in PUNCTUATIONS + ): if idx == 0: words.append([]) words[-1].append(f"{t}") + else: + words.append([f"{t}"]) + else: + if idx == 0: + words.append([]) + words[-1].append(f"{t}") return words @@ -149,4 +228,3 @@ def __text_to_words(text: str) -> list[list[str]]: # for ph in group: # all_phones.add(ph) # print(all_phones) - diff --git a/style_bert_vits2/nlp/english/normalizer.py b/style_bert_vits2/nlp/english/normalizer.py index f6ddc90c2..88cec023a 100644 --- a/style_bert_vits2/nlp/english/normalizer.py +++ b/style_bert_vits2/nlp/english/normalizer.py @@ -58,7 +58,7 @@ def replace_punctuation(text: str) -> str: "「": "'", "」": "'", } - pattern = re.compile("|".join(re.escape(p) for p in REPLACE_MAP.keys())) + pattern = re.compile("|".join(re.escape(p) for p in REPLACE_MAP)) replaced_text = pattern.sub(lambda x: REPLACE_MAP[x.group()], text) # replaced_text = re.sub( # r"[^\u3040-\u309F\u30A0-\u30FF\u4E00-\u9FFF\u3400-\u4DBF\u3005" diff --git a/style_bert_vits2/nlp/japanese/g2p.py b/style_bert_vits2/nlp/japanese/g2p.py index 54270ba3a..dab3dbf98 100644 --- a/style_bert_vits2/nlp/japanese/g2p.py +++ b/style_bert_vits2/nlp/japanese/g2p.py @@ -719,5 +719,3 @@ class YomiError(Exception): 基本的に「学習の前処理のテキスト処理時」には発生させ、そうでない場合は、 ignore_yomi_error=True にしておいて、この例外を発生させないようにする。 """ - - pass diff --git a/style_bert_vits2/nlp/japanese/normalizer.py b/style_bert_vits2/nlp/japanese/normalizer.py index edb394e8e..7ecbfb6f0 100644 --- a/style_bert_vits2/nlp/japanese/normalizer.py +++ b/style_bert_vits2/nlp/japanese/normalizer.py @@ -60,7 +60,7 @@ "」": "'", } # 記号類の正規化パターン -__REPLACE_PATTERN = re.compile("|".join(re.escape(p) for p in __REPLACE_MAP.keys())) +__REPLACE_PATTERN = re.compile("|".join(re.escape(p) for p in __REPLACE_MAP)) # 句読点等の正規化パターン __PUNCTUATION_CLEANUP_PATTERN = re.compile( # ↓ ひらがな、カタカナ、漢字 diff --git a/style_bert_vits2/nlp/japanese/pyopenjtalk_worker/__init__.py b/style_bert_vits2/nlp/japanese/pyopenjtalk_worker/__init__.py index 3a146b671..d86646787 100644 --- a/style_bert_vits2/nlp/japanese/pyopenjtalk_worker/__init__.py +++ b/style_bert_vits2/nlp/japanese/pyopenjtalk_worker/__init__.py @@ -88,7 +88,7 @@ def initialize_worker(port: int = WORKER_PORT) -> None: client = None try: client = WorkerClient(port) - except (socket.timeout, socket.error): + except (OSError, socket.timeout): logger.debug("try starting pyopenjtalk worker server") import os import subprocess @@ -120,7 +120,7 @@ def initialize_worker(port: int = WORKER_PORT) -> None: try: client = WorkerClient(port) break - except socket.error: + except OSError: time.sleep(0.5) count += 1 # 20: max number of retries diff --git a/style_bert_vits2/nlp/japanese/user_dict/word_model.py b/style_bert_vits2/nlp/japanese/user_dict/word_model.py index c85a5b954..43420da71 100644 --- a/style_bert_vits2/nlp/japanese/user_dict/word_model.py +++ b/style_bert_vits2/nlp/japanese/user_dict/word_model.py @@ -114,7 +114,7 @@ class PartOfSpeechDetail(BaseModel): part_of_speech_detail_2: str = Field(title="品詞細分類2") part_of_speech_detail_3: str = Field(title="品詞細分類3") # context_idは辞書の左・右文脈IDのこと - # https://github.com/VOICEVOX/open_jtalk/blob/427cfd761b78efb6094bea3c5bb8c968f0d711ab/src/mecab-naist-jdic/_left-id.def # noqa + # https://github.com/VOICEVOX/open_jtalk/blob/427cfd761b78efb6094bea3c5bb8c968f0d711ab/src/mecab-naist-jdic/_left-id.def context_id: int = Field(title="文脈ID") cost_candidates: List[int] = Field(title="コストのパーセンタイル") accent_associative_rules: List[str] = Field(title="アクセント結合規則の一覧") diff --git a/style_bert_vits2/tts_model.py b/style_bert_vits2/tts_model.py index 9f668743d..6df8394ae 100644 --- a/style_bert_vits2/tts_model.py +++ b/style_bert_vits2/tts_model.py @@ -1,5 +1,5 @@ from pathlib import Path -from typing import TYPE_CHECKING, Any, Optional, Union +from typing import Any, Optional, Union import numpy as np import torch diff --git a/style_bert_vits2/utils/subprocess.py b/style_bert_vits2/utils/subprocess.py index f8e8e9454..8f159a20f 100644 --- a/style_bert_vits2/utils/subprocess.py +++ b/style_bert_vits2/utils/subprocess.py @@ -27,6 +27,7 @@ def run_script_with_log( stderr=subprocess.PIPE, text=True, encoding="utf-8", + check=False, ) if result.returncode != 0: logger.error(f"Error: {' '.join(cmd)}\n{result.stderr}") diff --git a/style_gen.py b/style_gen.py index a21ab2b33..1ced5f0f8 100644 --- a/style_gen.py +++ b/style_gen.py @@ -8,12 +8,14 @@ from pyannote.audio import Inference, Model from tqdm import tqdm -from config import config +from config import get_config from style_bert_vits2.logging import logger from style_bert_vits2.models.hyper_parameters import HyperParameters from style_bert_vits2.utils.stdout_wrapper import SAFE_STDOUT +config = get_config() + model = Model.from_pretrained("pyannote/wespeaker-voxceleb-resnet34-LM") inference = Inference(model, window="whole") device = torch.device(config.style_gen_config.device) @@ -23,8 +25,6 @@ class NaNValueError(ValueError): """カスタム例外クラス。NaN値が見つかった場合に使用されます。""" - pass - # 推論時にインポートするために短いが関数を書く def get_style_vector(wav_path: str) -> NDArray[Any]: @@ -72,7 +72,7 @@ def process_line(line: str): device = config.style_gen_config.device training_lines: list[str] = [] - with open(hps.data.training_files, "r", encoding="utf-8") as f: + with open(hps.data.training_files, encoding="utf-8") as f: training_lines.extend(f.readlines()) with ThreadPoolExecutor(max_workers=num_processes) as executor: training_results = list( @@ -93,7 +93,7 @@ def process_line(line: str): ) val_lines: list[str] = [] - with open(hps.data.validation_files, "r", encoding="utf-8") as f: + with open(hps.data.validation_files, encoding="utf-8") as f: val_lines.extend(f.readlines()) with ThreadPoolExecutor(max_workers=num_processes) as executor: diff --git a/train_ms.py b/train_ms.py index 067a6a571..1adee4e77 100644 --- a/train_ms.py +++ b/train_ms.py @@ -16,7 +16,7 @@ # logging.getLogger("numba").setLevel(logging.WARNING) import default_style -from config import config +from config import get_config from data_utils import ( DistributedBucketSampler, TextAudioSpeakerCollate, @@ -48,7 +48,7 @@ ) # Not available if torch version is lower than 2.0 torch.backends.cuda.enable_math_sdp(True) - +config = get_config() global_step = 0 api = HfApi() @@ -97,6 +97,11 @@ def run(): help="Huggingface model repo id to backup the model.", default=None, ) + parser.add_argument( + "--not_use_custom_batch_sampler", + help="Don't use custom batch sampler for training, which was used in the version < 2.5", + action="store_true", + ) args = parser.parse_args() # Set log file @@ -108,7 +113,7 @@ def run(): envs = config.train_ms_config.env for env_name, env_value in envs.items(): if env_name not in os.environ.keys(): - logger.info("Loading configuration from config {}".format(str(env_value))) + logger.info(f"Loading configuration from config {env_value!s}") os.environ[env_name] = str(env_value) logger.info( "Loading environment variables \nMASTER_ADDR: {},\nMASTER_PORT: {},\nWORLD_SIZE: {},\nRANK: {},\nLOCAL_RANK: {}".format( @@ -142,7 +147,7 @@ def run(): if os.path.realpath(args.config) != os.path.realpath( config.train_ms_config.config_path ): - with open(args.config, "r", encoding="utf-8") as f: + with open(args.config, encoding="utf-8") as f: data = f.read() os.makedirs(os.path.dirname(config.train_ms_config.config_path), exist_ok=True) with open(config.train_ms_config.config_path, "w", encoding="utf-8") as f: @@ -192,13 +197,11 @@ def run(): os.makedirs(config.out_dir, exist_ok=True) if not args.skip_default_style: - # Save default style to out_dir - default_style.set_style_config( - args.config, os.path.join(config.out_dir, "config.json") - ) - default_style.save_neutral_vector( + default_style.save_styles_by_dirs( os.path.join(args.model, "wavs"), - os.path.join(config.out_dir, "style_vectors.npy"), + config.out_dir, + config_path=args.config, + config_output_path=os.path.join(config.out_dir, "config.json"), ) torch.manual_seed(hps.train.seed) @@ -214,28 +217,45 @@ def run(): writer = SummaryWriter(log_dir=model_dir) writer_eval = SummaryWriter(log_dir=os.path.join(model_dir, "eval")) train_dataset = TextAudioSpeakerLoader(hps.data.training_files, hps.data) - train_sampler = DistributedBucketSampler( - train_dataset, - hps.train.batch_size, - [32, 300, 400, 500, 600, 700, 800, 900, 1000], - num_replicas=n_gpus, - rank=rank, - shuffle=True, - ) collate_fn = TextAudioSpeakerCollate() - train_loader = DataLoader( - train_dataset, - # メモリ消費量を減らそうとnum_workersを1にしてみる - # num_workers=min(config.train_ms_config.num_workers, os.cpu_count() // 2), - num_workers=1, - shuffle=False, - pin_memory=True, - collate_fn=collate_fn, - batch_sampler=train_sampler, - persistent_workers=True, - # これもメモリ消費量を減らそうとしてコメントアウト - # prefetch_factor=4, - ) # DataLoader config could be adjusted. + if not args.not_use_custom_batch_sampler: + train_sampler = DistributedBucketSampler( + train_dataset, + hps.train.batch_size, + [32, 300, 400, 500, 600, 700, 800, 900, 1000], + num_replicas=n_gpus, + rank=rank, + shuffle=True, + ) + train_loader = DataLoader( + train_dataset, + # メモリ消費量を減らそうとnum_workersを1にしてみる + # num_workers=min(config.train_ms_config.num_workers, os.cpu_count() // 2), + num_workers=1, + shuffle=False, + pin_memory=True, + collate_fn=collate_fn, + batch_sampler=train_sampler, + # batch_size=hps.train.batch_size, + persistent_workers=True, + # これもメモリ消費量を減らそうとしてコメントアウト + # prefetch_factor=6, + ) + else: + train_loader = DataLoader( + train_dataset, + # メモリ消費量を減らそうとnum_workersを1にしてみる + # num_workers=min(config.train_ms_config.num_workers, os.cpu_count() // 2), + num_workers=1, + shuffle=True, + pin_memory=True, + collate_fn=collate_fn, + # batch_sampler=train_sampler, + batch_size=hps.train.batch_size, + persistent_workers=True, + # これもメモリ消費量を減らそうとしてコメントアウト + # prefetch_factor=6, + ) eval_dataset = None eval_loader = None if rank == 0 and not args.speedup: @@ -505,7 +525,7 @@ def lr_lambda(epoch): optim_g, hps.train.learning_rate, epoch, - os.path.join(model_dir, "G_{}.pth".format(global_step)), + os.path.join(model_dir, f"G_{global_step}.pth"), ) assert optim_d is not None utils.checkpoints.save_checkpoint( @@ -513,7 +533,7 @@ def lr_lambda(epoch): optim_d, hps.train.learning_rate, epoch, - os.path.join(model_dir, "D_{}.pth".format(global_step)), + os.path.join(model_dir, f"D_{global_step}.pth"), ) if net_dur_disc is not None: assert optim_dur_disc is not None @@ -522,7 +542,7 @@ def lr_lambda(epoch): optim_dur_disc, hps.train.learning_rate, epoch, - os.path.join(model_dir, "DUR_{}.pth".format(global_step)), + os.path.join(model_dir, f"DUR_{global_step}.pth"), ) utils.safetensors.save_safetensors( net_g, @@ -757,34 +777,32 @@ def train_and_evaluate( "loss/g/kl": loss_kl, } ) + scalar_dict.update({f"loss/g/{i}": v for i, v in enumerate(losses_gen)}) scalar_dict.update( - {"loss/g/{}".format(i): v for i, v in enumerate(losses_gen)} + {f"loss/d_r/{i}": v for i, v in enumerate(losses_disc_r)} ) scalar_dict.update( - {"loss/d_r/{}".format(i): v for i, v in enumerate(losses_disc_r)} + {f"loss/d_g/{i}": v for i, v in enumerate(losses_disc_g)} ) - scalar_dict.update( - {"loss/d_g/{}".format(i): v for i, v in enumerate(losses_disc_g)} - ) - - image_dict = { - "slice/mel_org": utils.plot_spectrogram_to_numpy( - y_mel[0].data.cpu().numpy() - ), - "slice/mel_gen": utils.plot_spectrogram_to_numpy( - y_hat_mel[0].data.cpu().numpy() - ), - "all/mel": utils.plot_spectrogram_to_numpy( - mel[0].data.cpu().numpy() - ), - "all/attn": utils.plot_alignment_to_numpy( - attn[0, 0].data.cpu().numpy() - ), - } + # 以降のログは計算が重い気がするし誰も見てない気がするのでコメントアウト + # image_dict = { + # "slice/mel_org": utils.plot_spectrogram_to_numpy( + # y_mel[0].data.cpu().numpy() + # ), + # "slice/mel_gen": utils.plot_spectrogram_to_numpy( + # y_hat_mel[0].data.cpu().numpy() + # ), + # "all/mel": utils.plot_spectrogram_to_numpy( + # mel[0].data.cpu().numpy() + # ), + # "all/attn": utils.plot_alignment_to_numpy( + # attn[0, 0].data.cpu().numpy() + # ), + # } utils.summarize( writer=writer, global_step=global_step, - images=image_dict, + # images=image_dict, scalars=scalar_dict, ) @@ -801,14 +819,14 @@ def train_and_evaluate( optim_g, hps.train.learning_rate, epoch, - os.path.join(hps.model_dir, "G_{}.pth".format(global_step)), + os.path.join(hps.model_dir, f"G_{global_step}.pth"), ) utils.checkpoints.save_checkpoint( net_d, optim_d, hps.train.learning_rate, epoch, - os.path.join(hps.model_dir, "D_{}.pth".format(global_step)), + os.path.join(hps.model_dir, f"D_{global_step}.pth"), ) if net_dur_disc is not None: utils.checkpoints.save_checkpoint( @@ -816,7 +834,7 @@ def train_and_evaluate( optim_dur_disc, hps.train.learning_rate, epoch, - os.path.join(hps.model_dir, "DUR_{}.pth".format(global_step)), + os.path.join(hps.model_dir, f"DUR_{global_step}.pth"), ) keep_ckpts = config.train_ms_config.keep_ckpts if keep_ckpts > 0: @@ -853,9 +871,7 @@ def train_and_evaluate( global_step += 1 if pbar is not None: pbar.set_description( - "Epoch {}({:.0f}%)/{}".format( - epoch, 100.0 * batch_idx / len(train_loader), hps.train.epochs - ) + f"Epoch {epoch}({100.0 * batch_idx / len(train_loader):.0f}%)/{hps.train.epochs}" ) pbar.update() # 本家ではこれをスピードアップのために消すと書かれていたので、一応消してみる @@ -870,6 +886,7 @@ def evaluate(hps, generator, eval_loader, writer_eval): generator.eval() image_dict = {} audio_dict = {} + print() logger.info("Evaluating ...") with torch.no_grad(): for batch_idx, ( @@ -913,32 +930,39 @@ def evaluate(hps, generator, eval_loader, writer_eval): sdp_ratio=0.0 if not use_sdp else 1.0, ) y_hat_lengths = mask.sum([1, 2]).long() * hps.data.hop_length - - mel = spec_to_mel_torch( - spec, - hps.data.filter_length, - hps.data.n_mel_channels, - hps.data.sampling_rate, - hps.data.mel_fmin, - hps.data.mel_fmax, - ) - y_hat_mel = mel_spectrogram_torch( - y_hat.squeeze(1).float(), - hps.data.filter_length, - hps.data.n_mel_channels, - hps.data.sampling_rate, - hps.data.hop_length, - hps.data.win_length, - hps.data.mel_fmin, - hps.data.mel_fmax, - ) - image_dict.update( - { - f"gen/mel_{batch_idx}": utils.plot_spectrogram_to_numpy( - y_hat_mel[0].cpu().numpy() - ) - } - ) + # 以降のログは計算が重い気がするし誰も見てない気がするのでコメントアウト + # mel = spec_to_mel_torch( + # spec, + # hps.data.filter_length, + # hps.data.n_mel_channels, + # hps.data.sampling_rate, + # hps.data.mel_fmin, + # hps.data.mel_fmax, + # ) + # y_hat_mel = mel_spectrogram_torch( + # y_hat.squeeze(1).float(), + # hps.data.filter_length, + # hps.data.n_mel_channels, + # hps.data.sampling_rate, + # hps.data.hop_length, + # hps.data.win_length, + # hps.data.mel_fmin, + # hps.data.mel_fmax, + # ) + # image_dict.update( + # { + # f"gen/mel_{batch_idx}": utils.plot_spectrogram_to_numpy( + # y_hat_mel[0].cpu().numpy() + # ) + # } + # ) + # image_dict.update( + # { + # f"gt/mel_{batch_idx}": utils.plot_spectrogram_to_numpy( + # mel[0].cpu().numpy() + # ) + # } + # ) audio_dict.update( { f"gen/audio_{batch_idx}_{use_sdp}": y_hat[ @@ -946,13 +970,6 @@ def evaluate(hps, generator, eval_loader, writer_eval): ] } ) - image_dict.update( - { - f"gt/mel_{batch_idx}": utils.plot_spectrogram_to_numpy( - mel[0].cpu().numpy() - ) - } - ) audio_dict.update({f"gt/audio_{batch_idx}": y[0, :, : y_lengths[0]]}) utils.summarize( diff --git a/train_ms_jp_extra.py b/train_ms_jp_extra.py index ad3d3bd92..e5c5bd198 100644 --- a/train_ms_jp_extra.py +++ b/train_ms_jp_extra.py @@ -16,7 +16,7 @@ # logging.getLogger("numba").setLevel(logging.WARNING) import default_style -from config import config +from config import get_config from data_utils import ( DistributedBucketSampler, TextAudioSpeakerCollate, @@ -48,6 +48,8 @@ torch.backends.cuda.enable_mem_efficient_sdp( True ) # Not available if torch version is lower than 2.0 + +config = get_config() global_step = 0 api = HfApi() @@ -96,6 +98,11 @@ def run(): help="Huggingface model repo id to backup the model.", default=None, ) + parser.add_argument( + "--not_use_custom_batch_sampler", + help="Don't use custom batch sampler for training, which was used in the version < 2.5", + action="store_true", + ) args = parser.parse_args() # Set log file @@ -107,7 +114,7 @@ def run(): envs = config.train_ms_config.env for env_name, env_value in envs.items(): if env_name not in os.environ.keys(): - logger.info("Loading configuration from config {}".format(str(env_value))) + logger.info(f"Loading configuration from config {env_value!s}") os.environ[env_name] = str(env_value) logger.info( "Loading environment variables \nMASTER_ADDR: {},\nMASTER_PORT: {},\nWORLD_SIZE: {},\nRANK: {},\nLOCAL_RANK: {}".format( @@ -141,7 +148,7 @@ def run(): if os.path.realpath(args.config) != os.path.realpath( config.train_ms_config.config_path ): - with open(args.config, "r", encoding="utf-8") as f: + with open(args.config, encoding="utf-8") as f: data = f.read() os.makedirs(os.path.dirname(config.train_ms_config.config_path), exist_ok=True) with open(config.train_ms_config.config_path, "w", encoding="utf-8") as f: @@ -191,13 +198,11 @@ def run(): os.makedirs(config.out_dir, exist_ok=True) if not args.skip_default_style: - # Save default style to out_dir - default_style.set_style_config( - args.config, os.path.join(config.out_dir, "config.json") - ) - default_style.save_neutral_vector( + default_style.save_styles_by_dirs( os.path.join(args.model, "wavs"), - os.path.join(config.out_dir, "style_vectors.npy"), + config.out_dir, + config_path=args.config, + config_output_path=os.path.join(config.out_dir, "config.json"), ) torch.manual_seed(hps.train.seed) @@ -213,28 +218,45 @@ def run(): writer = SummaryWriter(log_dir=model_dir) writer_eval = SummaryWriter(log_dir=os.path.join(model_dir, "eval")) train_dataset = TextAudioSpeakerLoader(hps.data.training_files, hps.data) - train_sampler = DistributedBucketSampler( - train_dataset, - hps.train.batch_size, - [32, 300, 400, 500, 600, 700, 800, 900, 1000], - num_replicas=n_gpus, - rank=rank, - shuffle=True, - ) collate_fn = TextAudioSpeakerCollate(use_jp_extra=True) - train_loader = DataLoader( - train_dataset, - # メモリ消費量を減らそうとnum_workersを1にしてみる - # num_workers=min(config.train_ms_config.num_workers, os.cpu_count() // 2), - num_workers=1, - shuffle=False, - pin_memory=True, - collate_fn=collate_fn, - batch_sampler=train_sampler, - persistent_workers=True, - # これもメモリ消費量を減らそうとしてコメントアウト - # prefetch_factor=6, - ) # DataLoader config could be adjusted. + if not args.not_use_custom_batch_sampler: + train_sampler = DistributedBucketSampler( + train_dataset, + hps.train.batch_size, + [32, 300, 400, 500, 600, 700, 800, 900, 1000], + num_replicas=n_gpus, + rank=rank, + shuffle=True, + ) + train_loader = DataLoader( + train_dataset, + # メモリ消費量を減らそうとnum_workersを1にしてみる + # num_workers=min(config.train_ms_config.num_workers, os.cpu_count() // 2), + num_workers=1, + shuffle=False, + pin_memory=True, + collate_fn=collate_fn, + batch_sampler=train_sampler, + # batch_size=hps.train.batch_size, + persistent_workers=True, + # これもメモリ消費量を減らそうとしてコメントアウト + # prefetch_factor=6, + ) + else: + train_loader = DataLoader( + train_dataset, + # メモリ消費量を減らそうとnum_workersを1にしてみる + # num_workers=min(config.train_ms_config.num_workers, os.cpu_count() // 2), + num_workers=1, + shuffle=True, + pin_memory=True, + collate_fn=collate_fn, + # batch_sampler=train_sampler, + batch_size=hps.train.batch_size, + persistent_workers=True, + # これもメモリ消費量を減らそうとしてコメントアウト + # prefetch_factor=6, + ) eval_dataset = None eval_loader = None if rank == 0 and not args.speedup: @@ -577,7 +599,7 @@ def lr_lambda(epoch): optim_g, hps.train.learning_rate, epoch, - os.path.join(model_dir, "G_{}.pth".format(global_step)), + os.path.join(model_dir, f"G_{global_step}.pth"), ) assert optim_d is not None utils.checkpoints.save_checkpoint( @@ -585,7 +607,7 @@ def lr_lambda(epoch): optim_d, hps.train.learning_rate, epoch, - os.path.join(model_dir, "D_{}.pth".format(global_step)), + os.path.join(model_dir, f"D_{global_step}.pth"), ) if net_dur_disc is not None: assert optim_dur_disc is not None @@ -594,7 +616,7 @@ def lr_lambda(epoch): optim_dur_disc, hps.train.learning_rate, epoch, - os.path.join(model_dir, "DUR_{}.pth".format(global_step)), + os.path.join(model_dir, f"DUR_{global_step}.pth"), ) if net_wd is not None: assert optim_wd is not None @@ -603,7 +625,7 @@ def lr_lambda(epoch): optim_wd, hps.train.learning_rate, epoch, - os.path.join(model_dir, "WD_{}.pth".format(global_step)), + os.path.join(model_dir, f"WD_{global_step}.pth"), ) utils.safetensors.save_safetensors( net_g, @@ -661,7 +683,7 @@ def train_and_evaluate( if writers is not None: writer, writer_eval = writers - train_loader.batch_sampler.set_epoch(epoch) + # train_loader.batch_sampler.set_epoch(epoch) global global_step net_g.train() @@ -867,14 +889,12 @@ def train_and_evaluate( "loss/g/kl": loss_kl, } ) + scalar_dict.update({f"loss/g/{i}": v for i, v in enumerate(losses_gen)}) scalar_dict.update( - {"loss/g/{}".format(i): v for i, v in enumerate(losses_gen)} + {f"loss/d_r/{i}": v for i, v in enumerate(losses_disc_r)} ) scalar_dict.update( - {"loss/d_r/{}".format(i): v for i, v in enumerate(losses_disc_r)} - ) - scalar_dict.update( - {"loss/d_g/{}".format(i): v for i, v in enumerate(losses_disc_g)} + {f"loss/d_g/{i}": v for i, v in enumerate(losses_disc_g)} ) if net_dur_disc is not None: @@ -882,23 +902,20 @@ def train_and_evaluate( scalar_dict.update( { - "loss/dur_disc_g/{}".format(i): v + f"loss/dur_disc_g/{i}": v for i, v in enumerate(losses_dur_disc_g) } ) scalar_dict.update( { - "loss/dur_disc_r/{}".format(i): v + f"loss/dur_disc_r/{i}": v for i, v in enumerate(losses_dur_disc_r) } ) scalar_dict.update({"loss/g/dur_gen": loss_dur_gen}) scalar_dict.update( - { - "loss/g/dur_gen_{}".format(i): v - for i, v in enumerate(losses_dur_gen) - } + {f"loss/g/dur_gen_{i}": v for i, v in enumerate(losses_dur_gen)} ) if net_wd is not None: @@ -910,24 +927,25 @@ def train_and_evaluate( "loss/g/lm_gen": loss_lm_gen, } ) - image_dict = { - "slice/mel_org": utils.plot_spectrogram_to_numpy( - y_mel[0].data.cpu().numpy() - ), - "slice/mel_gen": utils.plot_spectrogram_to_numpy( - y_hat_mel[0].data.cpu().numpy() - ), - "all/mel": utils.plot_spectrogram_to_numpy( - mel[0].data.cpu().numpy() - ), - "all/attn": utils.plot_alignment_to_numpy( - attn[0, 0].data.cpu().numpy() - ), - } + # 以降のログは計算が重い気がするし誰も見てない気がするのでコメントアウト + # image_dict = { + # "slice/mel_org": utils.plot_spectrogram_to_numpy( + # y_mel[0].data.cpu().numpy() + # ), + # "slice/mel_gen": utils.plot_spectrogram_to_numpy( + # y_hat_mel[0].data.cpu().numpy() + # ), + # "all/mel": utils.plot_spectrogram_to_numpy( + # mel[0].data.cpu().numpy() + # ), + # "all/attn": utils.plot_alignment_to_numpy( + # attn[0, 0].data.cpu().numpy() + # ), + # } utils.summarize( writer=writer, global_step=global_step, - images=image_dict, + # images=image_dict, scalars=scalar_dict, ) @@ -943,14 +961,14 @@ def train_and_evaluate( optim_g, hps.train.learning_rate, epoch, - os.path.join(hps.model_dir, "G_{}.pth".format(global_step)), + os.path.join(hps.model_dir, f"G_{global_step}.pth"), ) utils.checkpoints.save_checkpoint( net_d, optim_d, hps.train.learning_rate, epoch, - os.path.join(hps.model_dir, "D_{}.pth".format(global_step)), + os.path.join(hps.model_dir, f"D_{global_step}.pth"), ) if net_dur_disc is not None: utils.checkpoints.save_checkpoint( @@ -958,7 +976,7 @@ def train_and_evaluate( optim_dur_disc, hps.train.learning_rate, epoch, - os.path.join(hps.model_dir, "DUR_{}.pth".format(global_step)), + os.path.join(hps.model_dir, f"DUR_{global_step}.pth"), ) if net_wd is not None: utils.checkpoints.save_checkpoint( @@ -966,7 +984,7 @@ def train_and_evaluate( optim_wd, hps.train.learning_rate, epoch, - os.path.join(hps.model_dir, "WD_{}.pth".format(global_step)), + os.path.join(hps.model_dir, f"WD_{global_step}.pth"), ) keep_ckpts = config.train_ms_config.keep_ckpts if keep_ckpts > 0: @@ -1004,9 +1022,7 @@ def train_and_evaluate( global_step += 1 if pbar is not None: pbar.set_description( - "Epoch {}({:.0f}%)/{}".format( - epoch, 100.0 * batch_idx / len(train_loader), hps.train.epochs - ) + f"Epoch {epoch}({100.0 * batch_idx / len(train_loader):.0f}%)/{hps.train.epochs}" ) pbar.update() @@ -1020,6 +1036,7 @@ def evaluate(hps, generator, eval_loader, writer_eval): generator.eval() image_dict = {} audio_dict = {} + print() logger.info("Evaluating ...") with torch.no_grad(): for batch_idx, ( @@ -1057,32 +1074,39 @@ def evaluate(hps, generator, eval_loader, writer_eval): sdp_ratio=0.0 if not use_sdp else 1.0, ) y_hat_lengths = mask.sum([1, 2]).long() * hps.data.hop_length - - mel = spec_to_mel_torch( - spec, - hps.data.filter_length, - hps.data.n_mel_channels, - hps.data.sampling_rate, - hps.data.mel_fmin, - hps.data.mel_fmax, - ) - y_hat_mel = mel_spectrogram_torch( - y_hat.squeeze(1).float(), - hps.data.filter_length, - hps.data.n_mel_channels, - hps.data.sampling_rate, - hps.data.hop_length, - hps.data.win_length, - hps.data.mel_fmin, - hps.data.mel_fmax, - ) - image_dict.update( - { - f"gen/mel_{batch_idx}": utils.plot_spectrogram_to_numpy( - y_hat_mel[0].cpu().numpy() - ) - } - ) + # 以降のログは計算が重い気がするし誰も見てない気がするのでコメントアウト + # mel = spec_to_mel_torch( + # spec, + # hps.data.filter_length, + # hps.data.n_mel_channels, + # hps.data.sampling_rate, + # hps.data.mel_fmin, + # hps.data.mel_fmax, + # ) + # y_hat_mel = mel_spectrogram_torch( + # y_hat.squeeze(1).float(), + # hps.data.filter_length, + # hps.data.n_mel_channels, + # hps.data.sampling_rate, + # hps.data.hop_length, + # hps.data.win_length, + # hps.data.mel_fmin, + # hps.data.mel_fmax, + # ) + # image_dict.update( + # { + # f"gen/mel_{batch_idx}": utils.plot_spectrogram_to_numpy( + # y_hat_mel[0].cpu().numpy() + # ) + # } + # ) + # image_dict.update( + # { + # f"gt/mel_{batch_idx}": utils.plot_spectrogram_to_numpy( + # mel[0].cpu().numpy() + # ) + # } + # ) audio_dict.update( { f"gen/audio_{batch_idx}_{use_sdp}": y_hat[ @@ -1090,13 +1114,6 @@ def evaluate(hps, generator, eval_loader, writer_eval): ] } ) - image_dict.update( - { - f"gt/mel_{batch_idx}": utils.plot_spectrogram_to_numpy( - mel[0].cpu().numpy() - ) - } - ) audio_dict.update({f"gt/audio_{batch_idx}": y[0, :, : y_lengths[0]]}) utils.summarize( diff --git a/transcribe.py b/transcribe.py index 898fdc98e..275212613 100644 --- a/transcribe.py +++ b/transcribe.py @@ -1,13 +1,12 @@ import argparse -import os import sys from pathlib import Path from typing import Any, Optional -import yaml from torch.utils.data import Dataset from tqdm import tqdm +from config import get_path_config from style_bert_vits2.constants import Languages from style_bert_vits2.logging import logger from style_bert_vits2.utils.stdout_wrapper import SAFE_STDOUT @@ -49,6 +48,7 @@ def __getitem__(self, i: int) -> str: def transcribe_files_with_hf_whisper( audio_files: list[Path], model_id: str, + output_file: Path, initial_prompt: Optional[str] = None, language: str = "ja", batch_size: int = 16, @@ -69,13 +69,6 @@ def transcribe_files_with_hf_whisper( } logger.info(f"generate_kwargs: {generate_kwargs}") - if initial_prompt is not None: - prompt_ids: torch.Tensor = processor.get_prompt_ids( - initial_prompt, return_tensors="pt" - ) - prompt_ids = prompt_ids.to(device) - generate_kwargs["prompt_ids"] = prompt_ids - pipe = pipeline( model=model_id, max_new_tokens=128, @@ -83,17 +76,33 @@ def transcribe_files_with_hf_whisper( batch_size=batch_size, torch_dtype=torch.float16, device="cuda", - generate_kwargs=generate_kwargs, + trust_remote_code=True, + # generate_kwargs=generate_kwargs, ) + if initial_prompt is not None: + prompt_ids: torch.Tensor = pipe.tokenizer.get_prompt_ids( + initial_prompt, return_tensors="pt" + ).to(device) + generate_kwargs["prompt_ids"] = prompt_ids + dataset = StrListDataset([str(f) for f in audio_files]) results: list[str] = [] - for whisper_result in pipe(dataset): + for whisper_result, file in zip( + pipe(dataset, generate_kwargs=generate_kwargs), audio_files + ): text: str = whisper_result["text"] # なぜかテキストの最初に" {initial_prompt}"が入るので、文字の最初からこれを削除する # cf. https://github.com/huggingface/transformers/issues/27594 if text.startswith(f" {initial_prompt}"): text = text[len(f" {initial_prompt}") :] + # with open(output_file, "w", encoding="utf-8") as f: + # for wav_file, text in zip(wav_files, results): + # wav_rel_path = wav_file.relative_to(input_dir) + # f.write(f"{wav_rel_path}|{model_name}|{language_id}|{text}\n") + with open(output_file, "a", encoding="utf-8") as f: + wav_rel_path = file.relative_to(input_dir) + f.write(f"{wav_rel_path}|{model_name}|{language_id}|{text}\n") results.append(text) if pbar is not None: pbar.update(1) @@ -119,14 +128,14 @@ def transcribe_files_with_hf_whisper( parser.add_argument("--device", type=str, default="cuda") parser.add_argument("--compute_type", type=str, default="bfloat16") parser.add_argument("--use_hf_whisper", action="store_true") + parser.add_argument("--hf_repo_id", type=str, default="") parser.add_argument("--batch_size", type=int, default=16) parser.add_argument("--num_beams", type=int, default=1) parser.add_argument("--no_repeat_ngram_size", type=int, default=10) args = parser.parse_args() - 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(path_config["dataset_root"]) + path_config = get_path_config() + dataset_root = path_config.dataset_root model_name = str(args.model_name) @@ -144,7 +153,7 @@ def transcribe_files_with_hf_whisper( output_file.parent.mkdir(parents=True, exist_ok=True) wav_files = [f for f in input_dir.rglob("*.wav") if f.is_file()] - wav_files = sorted(wav_files, key=lambda x: x.name) + wav_files = sorted(wav_files, key=lambda x: str(x)) if output_file.exists(): logger.warning(f"{output_file} exists, backing up to {output_file}.bak") @@ -187,7 +196,10 @@ def transcribe_files_with_hf_whisper( with open(output_file, "a", encoding="utf-8") as f: f.write(f"{wav_rel_path}|{model_name}|{language_id}|{text}\n") else: - model_id = f"openai/whisper-{args.model}" + if args.hf_repo_id == "": + model_id = f"openai/whisper-{args.model}" + 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) results = transcribe_files_with_hf_whisper( @@ -200,10 +212,11 @@ def transcribe_files_with_hf_whisper( no_repeat_ngram_size=no_repeat_ngram_size, device=device, pbar=pbar, + output_file=output_file, ) - with open(output_file, "w", encoding="utf-8") as f: - for wav_file, text in zip(wav_files, results): - wav_rel_path = wav_file.relative_to(input_dir) - f.write(f"{wav_rel_path}|{model_name}|{language_id}|{text}\n") + # with open(output_file, "w", encoding="utf-8") as f: + # for wav_file, text in zip(wav_files, results): + # wav_rel_path = wav_file.relative_to(input_dir) + # f.write(f"{wav_rel_path}|{model_name}|{language_id}|{text}\n") sys.exit(0) diff --git a/vad_filter.py b/vad_filter.py new file mode 100644 index 000000000..1259051f5 --- /dev/null +++ b/vad_filter.py @@ -0,0 +1,92 @@ +import argparse +import os +import shutil +import sys +from pathlib import Path + +import pandas as pd +import torch +from tqdm import tqdm + +from style_bert_vits2.logging import logger + + +vad_model, utils = torch.hub.load( + repo_or_dir="litagin02/silero-vad", + model="silero_vad", + onnx=True, + trust_repo=True, +) + +(get_speech_timestamps, _, read_audio, *_) = utils + + +def get_speech_ratio(audio_file): + sampling_rate = 16000 + + wav = read_audio(audio_file, sampling_rate=sampling_rate) + speech_timestamps = get_speech_timestamps( + wav, vad_model, sampling_rate=sampling_rate + ) + + speech_dur_ms = 0 + + for ts in speech_timestamps: + start_ms = ts["start"] / 16 + end_ms = ts["end"] / 16 + speech_dur_ms += end_ms - start_ms + + total_dur_ms = len(wav) / sampling_rate * 1000 + return speech_dur_ms / total_dur_ms + + +def process(file: Path): + speech_ratio = get_speech_ratio(file) + return file, speech_ratio + + +def main(): + parser = argparse.ArgumentParser(description="Calculate speech ratio.") + parser.add_argument( + "-i", "--input", help="Directory containing audio files", required=True + ) + args = parser.parse_args() + + if os.path.exists(os.path.join(args.input, "low_speech_ratio")): + logger.info("Low speech ratio directory already exists, skipping...") + exit(0) + + data_dir = Path(args.input) + wav_files = list(data_dir.glob("*.wav")) + wav_files.sort() + + if len(wav_files) < 100: + logger.warning("Too few files, skipping...") + exit(0) + + logger.info(f"Start VAD filtering for {data_dir}...") + + results = [] + + for wav_file in tqdm(wav_files, file=sys.stdout): + speech_ratio = get_speech_ratio(wav_file) + results.append((wav_file, speech_ratio)) + + results_df = pd.DataFrame(results, columns=["file", "speech_ratio"]) + results_df.to_csv(os.path.join(data_dir, "speech_ratio.csv"), index=False) + + logger.info(f"Speech ratio stats:\n{results_df['speech_ratio'].describe()}") + threshold = 0.5 + + low_speech_ratio_dir = os.path.join(data_dir, "low_speech_ratio") + os.makedirs(low_speech_ratio_dir, exist_ok=True) + + low_speech_files = results_df[results_df["speech_ratio"] < threshold]["file"] + logger.info(f"Moving {len(low_speech_files)} files to {low_speech_ratio_dir}...") + for low_speech_file in low_speech_files: + shutil.move(low_speech_file, low_speech_ratio_dir) + logger.success("VAD filtering completed.") + + +if __name__ == "__main__": + main()