Upload 3 files
Browse files- Dockerfile +53 -0
- app.py +300 -0
- 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
|