aka7774 commited on
Commit
0d64808
·
verified ·
1 Parent(s): 83262f6

Upload 3 files

Browse files
Files changed (3) hide show
  1. Dockerfile +53 -0
  2. app.py +300 -0
  3. requirements.txt +1 -0
Dockerfile ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # syntax=docker/dockerfile:1
2
+
3
+ # Python 3.10 をベースイメージとして使用
4
+ FROM python:3.10-slim
5
+
6
+ # 必要なシステムパッケージのインストール (git, curl など)
7
+ # tzdata は pyannote.audio の依存関係で必要になることがある
8
+ RUN apt-get update && apt-get install -y --no-install-recommends \
9
+ git \
10
+ curl \
11
+ build-essential \
12
+ tzdata \
13
+ && rm -rf /var/lib/apt/lists/*
14
+
15
+ # 作業ディレクトリを設定
16
+ WORKDIR /app
17
+
18
+ # uv (高速Pythonパッケージインストーラー) のインストール
19
+ RUN curl -LsSf https://astral.sh/uv/install.sh | sh
20
+ # uvコマンドをPATHに追加
21
+ ENV PATH="/root/.cargo/bin:${PATH}"
22
+
23
+ # Style-Bert-VITS2 リポジトリを dev ブランチでクローン
24
+ # --depth 1 で履歴を浅くクローンし、ビルド時間を短縮
25
+ RUN git clone --depth 1 --branch dev https://github.com/litagin02/Style-Bert-VITS2.git
26
+
27
+ # Style-Bert-VITS2 の依存関係を uv でインストール
28
+ # ここで requirements.txt を直接指定してインストール
29
+ # --system オプションは、uv が管理する環境ではなく、システムのPython環境にインストールすることを意味します
30
+ RUN uv pip install --system -r Style-Bert-VITS2/requirements.txt
31
+
32
+ # Style-Bert-VITS2 の初期化スクリプトを実行 (BERTモデルなどはダウンロードしない)
33
+ # この時点で Style-Bert-VITS2 のコードが利用可能になっている
34
+ RUN python Style-Bert-VITS2/initialize.py --skip_default_models
35
+
36
+ # Gradioアプリ用の requirements.txt をコピー
37
+ COPY requirements.txt .
38
+ # Gradioアプリ自体の依存関係をインストール
39
+ RUN uv pip install --system -r requirements.txt
40
+
41
+ # Gradioアプリのコードをコピー
42
+ COPY app.py .
43
+
44
+ # アプリケーションのポートを指定 (Gradioのデフォルトは7860)
45
+ EXPOSE 7860
46
+
47
+ # Gradioアプリを起動するための環境変数
48
+ # Hugging Face Spacesが外部からアクセスできるようにするため
49
+ ENV GRADIO_SERVER_NAME=0.0.0.0
50
+
51
+ # Gradioアプリを起動
52
+ # Hugging Face Spacesはこのコマンドを自動で実行します (app.pyが指定されている場合)
53
+ CMD ["python", "app.py"]
app.py ADDED
@@ -0,0 +1,300 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import subprocess
3
+ import os
4
+ import sys
5
+ import yaml
6
+ from pathlib import Path
7
+ import time
8
+ import threading
9
+ import tempfile
10
+ import shutil
11
+ import gc # ガベージコレクション用
12
+
13
+ # --- 定数 ---
14
+ # Dockerfile内でクローンされるパスに合わせる
15
+ SCRIPT_DIR = Path(__file__).parent
16
+ SBV2_REPO_PATH = SCRIPT_DIR / "Style-Bert-VITS2"
17
+ # ダウンロード用ファイルの一時置き場 (コンテナ内に作成)
18
+ OUTPUT_DIR = SCRIPT_DIR / "outputs"
19
+
20
+ # --- ヘルパー関数 ---
21
+ def add_sbv2_to_path():
22
+ """Style-Bert-VITS2リポジトリのパスを sys.path に追加"""
23
+ repo_path_str = str(SBV2_REPO_PATH.resolve())
24
+ if SBV2_REPO_PATH.exists() and repo_path_str not in sys.path:
25
+ sys.path.insert(0, repo_path_str)
26
+ print(f"Added {repo_path_str} to sys.path")
27
+ elif not SBV2_REPO_PATH.exists():
28
+ print(f"Warning: Style-Bert-VITS2 repository not found at {SBV2_REPO_PATH}")
29
+
30
+ def stream_process_output(process, log_list):
31
+ """サブプロセスの標準出力/エラーをリアルタイムでリストに追加"""
32
+ try:
33
+ if process.stdout:
34
+ for line in iter(process.stdout.readline, ''):
35
+ log_list.append(line.strip()) # 余分な改行を削除
36
+ if process.stderr:
37
+ for line in iter(process.stderr.readline, ''):
38
+ # WARNING以外のstderrはエラーとして強調表示しても良い
39
+ processed_line = f"stderr: {line.strip()}"
40
+ if "warning" not in line.lower():
41
+ processed_line = f"ERROR (stderr): {line.strip()}"
42
+ log_list.append(processed_line)
43
+ except Exception as e:
44
+ log_list.append(f"Error reading process stream: {e}")
45
+
46
+ # --- Gradio アプリのバックエンド関数 ---
47
+ def convert_safetensors_to_onnx_gradio(safetensors_file_obj, progress=gr.Progress(track_ τότε=True)):
48
+ """
49
+ アップロードされたSafetensorsモデルをONNXに変換し、結果をダウンロード可能にする。
50
+ """
51
+ log = ["Starting ONNX conversion..."]
52
+ # 初期状態ではダウンロードファイルは空
53
+ yield "\n".join(log), None
54
+
55
+ if safetensors_file_obj is None:
56
+ log.append("Error: No safetensors file uploaded. Please upload a .safetensors file.")
57
+ # エラーメッセージを表示し、ダウンロードはNoneのまま
58
+ yield "\n".join(log), None
59
+ return
60
+
61
+ # Style-Bert-VITS2 パスの確認
62
+ add_sbv2_to_path()
63
+ if not SBV2_REPO_PATH.exists():
64
+ log.append(f"Error: Style-Bert-VITS2 repository not found at {SBV2_REPO_PATH}. Check Space build logs.")
65
+ yield "\n".join(log), None
66
+ return
67
+
68
+ # 出力ディレクトリ作成 (存在しない場合)
69
+ OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
70
+
71
+ # 以前の出力ファイルを削除 (オプション、ディスクスペース節約のため)
72
+ # for item in OUTPUT_DIR.glob("*.onnx"):
73
+ # try:
74
+ # item.unlink()
75
+ # log.append(f"Cleaned up previous output: {item.name}")
76
+ # except OSError as e:
77
+ # log.append(f"Warning: Could not delete previous file {item.name}: {e}")
78
+
79
+ onnx_output_path_str = None # 最終的なONNXファイルパス (文字列)
80
+ current_log = log[:] # ログリストをコピー
81
+
82
+ try:
83
+ # 一時ディレクトリを作成して処理
84
+ with tempfile.TemporaryDirectory() as temp_dir:
85
+ temp_dir_path = Path(temp_dir)
86
+ # Gradio Fileコンポーネントは一時ファイルパスを .name で持つ
87
+ original_filename = Path(safetensors_file_obj.name).name
88
+ # ファイル名に不正な文字が含まれていないか基本的なチェック (オプション)
89
+ if "/" in original_filename or "\\" in original_filename or ".." in original_filename:
90
+ current_log.append(f"Error: Invalid characters found in filename: {original_filename}")
91
+ yield "\n".join(current_log), None
92
+ return
93
+
94
+ temp_safetensors_path_in_root = temp_dir_path / original_filename
95
+
96
+ current_log.append(f"Processing uploaded file: {original_filename}")
97
+ current_log.append(f"Copying to temporary location: {temp_safetensors_path_in_root}")
98
+ yield "\n".join(current_log), None
99
+ # アップロードされたファイルオブジェクトから一時ディレクトリにコピー
100
+ shutil.copy(safetensors_file_obj.name, temp_safetensors_path_in_root)
101
+
102
+
103
+ # --- SBV2が期待するディレクトリ構造を一時ディレクトリ内に作成 ---
104
+ # モデル名をファイル名から取得 (拡張子なし)
105
+ model_name = temp_safetensors_path_in_root.stem
106
+ # assets_root を一時ディレクトリ自体にする
107
+ assets_root = temp_dir_path
108
+ # assets_root の下に model_name のディレクトリを作成
109
+ model_dir_in_temp = assets_root / model_name
110
+ model_dir_in_temp.mkdir(exist_ok=True)
111
+ # safetensorsファイルを model_name ディレクトリに移動
112
+ temp_safetensors_path = model_dir_in_temp / original_filename
113
+ shutil.move(temp_safetensors_path_in_root, temp_safetensors_path)
114
+
115
+ # dataset_root も assets_root と同じにしておく (今回は使用しない)
116
+ dataset_root = assets_root
117
+
118
+ current_log.append(f"Using temporary assets_root: {assets_root}")
119
+ current_log.append(f"Created temporary model directory: {model_dir_in_temp}")
120
+ current_log.append(f"Using temporary model path: {temp_safetensors_path}")
121
+ yield "\n".join(current_log), None
122
+
123
+ # --- paths.yml を一時的に設定 ---
124
+ config_path = SBV2_REPO_PATH / "configs" / "paths.yml"
125
+ config_path.parent.mkdir(parents=True, exist_ok=True)
126
+ paths_config = {"dataset_root": str(dataset_root.resolve()), "assets_root": str(assets_root.resolve())}
127
+ with open(config_path, "w", encoding="utf-8") as f:
128
+ yaml.dump(paths_config, f)
129
+ current_log.append(f"Saved temporary paths config to {config_path}")
130
+ yield "\n".join(current_log), None
131
+
132
+ # --- ONNX変換スクリプト実行 ---
133
+ current_log.append(f"\nStarting ONNX conversion script for: {temp_safetensors_path.name}")
134
+ convert_script = SBV2_REPO_PATH / "convert_onnx.py"
135
+ if not convert_script.exists():
136
+ current_log.append(f"Error: convert_onnx.py not found at '{convert_script}'.")
137
+ yield "\n".join(current_log), None
138
+ return # tryブロックを抜ける
139
+
140
+ python_executable = sys.executable
141
+ command = [
142
+ python_executable,
143
+ str(convert_script.resolve()),
144
+ "--model",
145
+ str(temp_safetensors_path.resolve()) # 一時ディレクトリ内のモデルパス
146
+ ]
147
+ current_log.append(f"\nRunning command: {' '.join(command)}")
148
+ yield "\n".join(current_log), None
149
+
150
+ process_env = os.environ.copy()
151
+ # メモリリーク対策? (あまり効果ないかも)
152
+ # process_env["PYTORCH_CUDA_ALLOC_CONF"] = "max_split_size_mb:128"
153
+
154
+ process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
155
+ text=True, encoding='utf-8', errors='replace',
156
+ cwd=SBV2_REPO_PATH, # スクリプトの場所で実行
157
+ env=process_env)
158
+
159
+ # ログ出力用リスト (スレッドと共有)
160
+ process_output_lines = []
161
+ thread = threading.Thread(target=stream_process_output, args=(process, process_output_lines))
162
+ thread.start()
163
+
164
+ # 進捗表示のためのループ
165
+ while thread.is_alive():
166
+ # yieldでGradio UIを更新 (現在のログ + プロセスからのログ)
167
+ yield "\n".join(current_log + process_output_lines), None
168
+ time.sleep(0.2) # 更新頻度
169
+
170
+ # スレッド終了待ち
171
+ thread.join()
172
+ # プロセス終了待ち (タイムアウト設定)
173
+ try:
174
+ process.wait(timeout=900) # 15分タイムアウト
175
+ except subprocess.TimeoutExpired:
176
+ current_log.extend(process_output_lines) # ここまでのログを追加
177
+ current_log.append("\nError: Conversion process timed out after 15 minutes.")
178
+ process.kill() # タイムアウトしたらプロセスを強制終了
179
+ yield "\n".join(current_log), None
180
+ return # tryブロックを抜ける
181
+
182
+ # 最終的なプロセス出力を取得
183
+ final_stdout, final_stderr = process.communicate()
184
+ if final_stdout:
185
+ process_output_lines.extend(final_stdout.strip().split('\n'))
186
+ if final_stderr:
187
+ processed_stderr = []
188
+ for line in final_stderr.strip().split('\n'):
189
+ processed_line = f"stderr: {line.strip()}"
190
+ if "warning" not in line.lower():
191
+ processed_line = f"ERROR (stderr): {line.strip()}"
192
+ processed_stderr.append(processed_line)
193
+ if processed_stderr:
194
+ process_output_lines.append("--- stderr ---")
195
+ process_output_lines.extend(processed_stderr)
196
+ process_output_lines.append("--------------")
197
+
198
+ # 全てのプロセスログをメインログに追加
199
+ current_log.extend(process_output_lines)
200
+ current_log.append("\n-------------------------------")
201
+
202
+ # --- 結果の確認と出力ファイルのコピー ---
203
+ if process.returncode == 0:
204
+ current_log.append("ONNX conversion command finished successfully.")
205
+ # 期待されるONNXファイルパス (入力と同じディレクトリ内)
206
+ expected_onnx_path_in_temp = temp_safetensors_path.with_suffix(".onnx")
207
+
208
+ if expected_onnx_path_in_temp.exists():
209
+ current_log.append(f"Found converted ONNX file: {expected_onnx_path_in_temp.name}")
210
+ # 一時ディレクトリから永続的な出力ディレクトリにコピー
211
+ final_onnx_path = OUTPUT_DIR / expected_onnx_path_in_temp.name
212
+ shutil.copy(expected_onnx_path_in_temp, final_onnx_path)
213
+ current_log.append(f"Copied ONNX file for download to: {final_onnx_path}")
214
+ onnx_output_path_str = str(final_onnx_path) # ダウンロード用ファイルパスを設定
215
+ else:
216
+ current_log.append(f"Warning: Expected ONNX file not found at '{expected_onnx_path_in_temp}'. Please check the logs.")
217
+ else:
218
+ current_log.append(f"ONNX conversion command failed with return code {process.returncode}.")
219
+ current_log.append("Please check the logs above for errors (especially lines starting with 'ERROR').")
220
+
221
+ # 一時ディレクトリが自動で削除される前に yield する必要がある
222
+ yield "\n".join(current_log), onnx_output_path_str
223
+
224
+ except FileNotFoundError as e:
225
+ # コマンドが見つからない場合など
226
+ current_log.append(f"\nError: A required command or file was not found: {e.filename}. Check Dockerfile setup and PATH.")
227
+ current_log.append(f"{e}")
228
+ yield "\n".join(current_log), None
229
+ except Exception as e:
230
+ current_log.append(f"\nAn unexpected error occurred: {e}")
231
+ import traceback
232
+ current_log.append(traceback.format_exc())
233
+ # エラー発生時も最終ログとNoneを返す
234
+ yield "\n".join(current_log), None
235
+ finally:
236
+ # ガベージコレクションを試みる (メモリ解放目的)
237
+ gc.collect()
238
+ # 最終的な状態をUIに反映させるための最後のyield (重要)
239
+ # tryブロック内で既にyieldしている場合でも、finallyで再度yieldすることで
240
+ # UIが最終状態(エラーメッセージや成功メッセージ+ダウンロードリンク)に更新される
241
+ print("Conversion function finished.") # サーバーログ用
242
+ # ここで再度yieldするとUIの更新が確実になることがある
243
+ # yield "\n".join(current_log), onnx_output_path_str
244
+
245
+
246
+ # --- Gradio Interface ---
247
+ with gr.Blocks(theme=gr.themes.Soft()) as demo:
248
+ gr.Markdown("# Style-Bert-VITS2 Safetensors to ONNX Converter")
249
+ gr.Markdown(
250
+ "Upload your `.safetensors` model file, convert it to ONNX format, and download the result. "
251
+ "The environment setup (cloning repo, installing dependencies, running initialize.py) "
252
+ "is handled automatically when this Space starts."
253
+ )
254
+
255
+ with gr.Row():
256
+ with gr.Column(scale=1):
257
+ safetensors_upload = gr.File(
258
+ label="1. Upload Safetensors Model",
259
+ file_types=[".safetensors"],
260
+ # file_count="single" (default)
261
+ )
262
+ convert_button = gr.Button("2. Convert to ONNX", variant="primary")
263
+ gr.Markdown("---")
264
+ onnx_download = gr.File(
265
+ label="3. Download ONNX Model",
266
+ interactive=False, # 出力専用
267
+ )
268
+ gr.Markdown(
269
+ "**Note:** Conversion can take several minutes, especially on free hardware. "
270
+ "Please be patient. The log on the right will update during the process."
271
+ )
272
+
273
+ with gr.Column(scale=2):
274
+ output_log = gr.Textbox(
275
+ label="Conversion Log",
276
+ lines=25, # 少し高さを増やす
277
+ interactive=False,
278
+ autoscroll=True,
279
+ max_lines=1500 # ログが多くなる可能性を考慮
280
+ )
281
+
282
+ # ボタンクリック時のアクション設定
283
+ convert_button.click(
284
+ convert_safetensors_to_onnx_gradio,
285
+ inputs=[safetensors_upload],
286
+ outputs=[output_log, onnx_download] # ログとダウンロードファイルの2つを出力
287
+ )
288
+
289
+ # --- アプリの起動 ---
290
+ if __name__ == "__main__":
291
+ # Style-Bert-VITS2 へのパスを追加
292
+ add_sbv2_to_path()
293
+ # 出力ディレクトリ作成 (存在確認含む)
294
+ OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
295
+ print(f"Output directory: {OUTPUT_DIR.resolve()}")
296
+
297
+ # Gradioアプリを起動
298
+ # share=True にするとパブリックリンクが生成される(HF Spacesでは不要)
299
+ # queue() を使うと複数ユーザーのリクエストを処理しやすくなる
300
+ demo.queue().launch()
requirements.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ PyYAML