Spaces:
Running on Zero
Running on Zero
Fix voice cloning: use prompt_wav+prompt_text (reference_wav_path produced gibberish); remove debug
Browse files
app.py
CHANGED
|
@@ -2,9 +2,12 @@
|
|
| 2 |
|
| 3 |
提供三種試用情境,並一律套用模型官方建議的最佳生成參數:
|
| 4 |
1. 指定語者:李宏毅 —— 用模型內附、已取得授權的語者向量控制音色
|
| 5 |
-
2. 聲音複製 ——
|
| 6 |
3. 長文逐句串流 —— 把長文切句,合成一句播一句,做出串流效果(每句含自動重試)
|
| 7 |
|
|
|
|
|
|
|
|
|
|
| 8 |
最佳參數讀自模型發佈中繼資料 release_metadata.json 的 recommended_generation_defaults
|
| 9 |
(以 500 句難句 + ASR CER 調出,最佳 trial:cfg=2.8 / steps=9),確保 demo 永遠跟著模型走。
|
| 10 |
"""
|
|
@@ -156,28 +159,36 @@ def tts_speaker(text):
|
|
| 156 |
|
| 157 |
|
| 158 |
@gpu
|
| 159 |
-
def tts_clone(text, reference_audio):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 160 |
text = (text or "").strip()
|
|
|
|
| 161 |
if not text:
|
| 162 |
raise gr.Error("請先輸入要合成的文字。")
|
| 163 |
if not reference_audio:
|
| 164 |
raise gr.Error("請先上傳或錄製一段參考音檔。")
|
|
|
|
|
|
|
| 165 |
audio = model.generate(
|
| 166 |
target_text=text,
|
| 167 |
-
|
|
|
|
| 168 |
**GEN_KWARGS,
|
| 169 |
)
|
| 170 |
return (SR, _to_numpy(audio))
|
| 171 |
|
| 172 |
|
| 173 |
@gpu
|
| 174 |
-
def tts_stream(text
|
| 175 |
-
"""長文逐句串流:把長文切成句子,逐句合成、合成一句就播一句
|
| 176 |
|
| 177 |
逐句串流直到接近單次 GPU 時間配額(約 100s)為止,盡量把整段長文串完,而非固定
|
| 178 |
句數;真的超過才截斷並提示分批。每句都用一般 generate(含 retry_badcase)產生
|
| 179 |
-
完整音檔再 yield;句與句之間可能有短暫間隔(ZeroGPU 約 0.44x 實時所致)。
|
| 180 |
-
李宏毅語者向量,上傳參考音檔則改為聲音複製。
|
| 181 |
"""
|
| 182 |
text = (text or "").strip()
|
| 183 |
if not text:
|
|
@@ -193,14 +204,9 @@ def tts_stream(text, reference_audio):
|
|
| 193 |
# 至少先產出第一句;之後一旦逼近 GPU 時間配額就停,避免配額被回收而報錯
|
| 194 |
if done and time.time() - start > _GPU_BUDGET_S:
|
| 195 |
break
|
| 196 |
-
|
| 197 |
-
|
| 198 |
-
|
| 199 |
-
)
|
| 200 |
-
else:
|
| 201 |
-
audio = model.generate(
|
| 202 |
-
target_text=sent, speaker_centroid=SPK_CENTROID, **GEN_KWARGS
|
| 203 |
-
)
|
| 204 |
yield (SR, _pcm16(_to_numpy(audio)))
|
| 205 |
done += 1
|
| 206 |
if done < total:
|
|
@@ -210,30 +216,6 @@ def tts_stream(text, reference_audio):
|
|
| 210 |
)
|
| 211 |
|
| 212 |
|
| 213 |
-
# --- TEMP diagnostic endpoint (remove after voice-clone debugging) ----------- #
|
| 214 |
-
@gpu
|
| 215 |
-
def tts_debug(target_text, audio_path, prompt_text, mode, cfg, use_null):
|
| 216 |
-
target_text = (target_text or "").strip()
|
| 217 |
-
if not target_text:
|
| 218 |
-
raise gr.Error("請輸入 target_text。")
|
| 219 |
-
base = {k: v for k, v in GEN_KWARGS.items() if k != "cfg_value"}
|
| 220 |
-
kw = dict(base, cfg_value=float(cfg), use_null_speaker=bool(use_null))
|
| 221 |
-
if mode == "prompt":
|
| 222 |
-
audio = model.generate(
|
| 223 |
-
target_text=target_text,
|
| 224 |
-
prompt_wav_path=audio_path or "",
|
| 225 |
-
prompt_text=(prompt_text or ""),
|
| 226 |
-
**kw,
|
| 227 |
-
)
|
| 228 |
-
else:
|
| 229 |
-
audio = model.generate(
|
| 230 |
-
target_text=target_text,
|
| 231 |
-
reference_wav_path=audio_path or "",
|
| 232 |
-
**kw,
|
| 233 |
-
)
|
| 234 |
-
return (SR, _to_numpy(audio))
|
| 235 |
-
|
| 236 |
-
|
| 237 |
# --------------------------------------------------------------------------- #
|
| 238 |
# 介面
|
| 239 |
# --------------------------------------------------------------------------- #
|
|
@@ -289,7 +271,9 @@ with gr.Blocks(title="BlueMagpie-TTS Demo", theme=gr.themes.Soft()) as demo:
|
|
| 289 |
|
| 290 |
with gr.Tab("聲音複製"):
|
| 291 |
gr.Markdown(
|
| 292 |
-
"上傳一段參考音檔,輸出會模仿該語者的音色。\n\n"
|
|
|
|
|
|
|
| 293 |
"⚠️ **請只使用你已取得授權的聲音**,請勿在未經本人同意下複製他人聲音。"
|
| 294 |
)
|
| 295 |
with gr.Row():
|
|
@@ -302,16 +286,19 @@ with gr.Blocks(title="BlueMagpie-TTS Demo", theme=gr.themes.Soft()) as demo:
|
|
| 302 |
label="參考音檔(上傳或錄製)", type="filepath",
|
| 303 |
sources=["upload", "microphone"],
|
| 304 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 305 |
c_btn = gr.Button("複製音色合成", variant="primary")
|
| 306 |
with gr.Column():
|
| 307 |
c_out = gr.Audio(label="合成結果", type="numpy")
|
| 308 |
-
c_btn.click(tts_clone, [c_text, c_ref], c_out)
|
| 309 |
|
| 310 |
with gr.Tab("長文逐句串流"):
|
| 311 |
gr.Markdown(
|
| 312 |
"**長文逐句串流**:把長文切成句子,合成一句就播一句,做出串流(邊聽邊等)效果。"
|
| 313 |
-
"第一句很快就能聽到,後面邊播邊合成。\n\n"
|
| 314 |
-
"預設用李宏毅語者向量;若上傳參考音檔則改為聲音複製(請只使用已取得授權的聲音)。\n"
|
| 315 |
"> ZeroGPU 約 0.44x 實時,句與句之間可能有短暫間隔;每句都套用最佳參數(含自動重試)。"
|
| 316 |
"單次串流以 ZeroGPU 時間配額為限(約可串數十句),超長會自動截斷並提示分批。"
|
| 317 |
)
|
|
@@ -321,26 +308,11 @@ with gr.Blocks(title="BlueMagpie-TTS Demo", theme=gr.themes.Soft()) as demo:
|
|
| 321 |
label="要合成的長文", lines=5,
|
| 322 |
placeholder="貼上一段多句的文字,會逐句合成、逐句播放…",
|
| 323 |
)
|
| 324 |
-
t_ref = gr.Audio(
|
| 325 |
-
label="參考音檔(選填;留空則用李宏毅音色)", type="filepath",
|
| 326 |
-
sources=["upload", "microphone"],
|
| 327 |
-
)
|
| 328 |
t_btn = gr.Button("逐句串流合成", variant="primary")
|
| 329 |
with gr.Column():
|
| 330 |
t_out = gr.Audio(label="逐句串流播放", streaming=True, autoplay=True)
|
| 331 |
gr.Examples(STREAM_EXAMPLES, inputs=t_text, label="長文範例")
|
| 332 |
-
t_btn.click(tts_stream,
|
| 333 |
-
|
| 334 |
-
with gr.Tab("debug"):
|
| 335 |
-
d_text = gr.Textbox(label="target_text", value="今天天氣真好,我們一起去公園散步看花。")
|
| 336 |
-
d_audio = gr.Audio(label="audio", type="filepath", sources=["upload", "microphone"])
|
| 337 |
-
d_prompt = gr.Textbox(label="prompt_text (for prompt mode)")
|
| 338 |
-
d_mode = gr.Radio(["reference", "prompt"], value="prompt", label="mode")
|
| 339 |
-
d_cfg = gr.Slider(1.0, 4.0, value=2.8, step=0.1, label="cfg")
|
| 340 |
-
d_null = gr.Checkbox(value=True, label="use_null_speaker")
|
| 341 |
-
d_btn = gr.Button("debug gen")
|
| 342 |
-
d_out = gr.Audio(label="out", type="numpy")
|
| 343 |
-
d_btn.click(tts_debug, [d_text, d_audio, d_prompt, d_mode, d_cfg, d_null], d_out)
|
| 344 |
|
| 345 |
gr.Markdown(
|
| 346 |
"---\n"
|
|
|
|
| 2 |
|
| 3 |
提供三種試用情境,並一律套用模型官方建議的最佳生成參數:
|
| 4 |
1. 指定語者:李宏毅 —— 用模型內附、已取得授權的語者向量控制音色
|
| 5 |
+
2. 聲音複製 —— 參考音檔 + 其逐字稿(prompt 模式)做 zero-shot 複製
|
| 6 |
3. 長文逐句串流 —— 把長文切句,合成一句播一句,做出串流效果(每句含自動重試)
|
| 7 |
|
| 8 |
+
註:transcript-free 的 reference_wav_path 路徑在此 checkpoint 會吐出亂掉內容(實測
|
| 9 |
+
CER≈3),故複製改用可靠的 prompt_wav_path + prompt_text(語音接續)路徑(實測 CER 0)。
|
| 10 |
+
|
| 11 |
最佳參數讀自模型發佈中繼資料 release_metadata.json 的 recommended_generation_defaults
|
| 12 |
(以 500 句難句 + ASR CER 調出,最佳 trial:cfg=2.8 / steps=9),確保 demo 永遠跟著模型走。
|
| 13 |
"""
|
|
|
|
| 159 |
|
| 160 |
|
| 161 |
@gpu
|
| 162 |
+
def tts_clone(text, reference_audio, prompt_text):
|
| 163 |
+
"""聲音複製:以「參考語音 + 其逐字稿」做 zero-shot 複製。
|
| 164 |
+
|
| 165 |
+
用 prompt_wav_path + prompt_text(語音接續)路徑 —— 這是此模型可靠的複製方式;
|
| 166 |
+
transcript-free 的 reference_wav_path 路徑在本 checkpoint 會吐出亂掉的內容,故不用。
|
| 167 |
+
"""
|
| 168 |
text = (text or "").strip()
|
| 169 |
+
prompt_text = (prompt_text or "").strip()
|
| 170 |
if not text:
|
| 171 |
raise gr.Error("請先輸入要合成的文字。")
|
| 172 |
if not reference_audio:
|
| 173 |
raise gr.Error("請先上傳或錄製一段參考音檔。")
|
| 174 |
+
if not prompt_text:
|
| 175 |
+
raise gr.Error("請填寫參考音檔的逐字稿(這段參考語音實際說的內容)。")
|
| 176 |
audio = model.generate(
|
| 177 |
target_text=text,
|
| 178 |
+
prompt_wav_path=reference_audio, # gr.Audio(type="filepath") 回傳路徑
|
| 179 |
+
prompt_text=prompt_text,
|
| 180 |
**GEN_KWARGS,
|
| 181 |
)
|
| 182 |
return (SR, _to_numpy(audio))
|
| 183 |
|
| 184 |
|
| 185 |
@gpu
|
| 186 |
+
def tts_stream(text):
|
| 187 |
+
"""長文逐句串流(李宏毅語者向量):把長文切成句子,逐句合成、合成一句就播一句。
|
| 188 |
|
| 189 |
逐句串流直到接近單次 GPU 時間配額(約 100s)為止,盡量把整段長文串完,而非固定
|
| 190 |
句數;真的超過才截斷並提示分批。每句都用一般 generate(含 retry_badcase)產生
|
| 191 |
+
完整音檔再 yield;句與句之間可能有短暫間隔(ZeroGPU 約 0.44x 實時所致)。
|
|
|
|
| 192 |
"""
|
| 193 |
text = (text or "").strip()
|
| 194 |
if not text:
|
|
|
|
| 204 |
# 至少先產出第一句;之後一旦逼近 GPU 時間配額就停,避免配額被回收而報錯
|
| 205 |
if done and time.time() - start > _GPU_BUDGET_S:
|
| 206 |
break
|
| 207 |
+
audio = model.generate(
|
| 208 |
+
target_text=sent, speaker_centroid=SPK_CENTROID, **GEN_KWARGS
|
| 209 |
+
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 210 |
yield (SR, _pcm16(_to_numpy(audio)))
|
| 211 |
done += 1
|
| 212 |
if done < total:
|
|
|
|
| 216 |
)
|
| 217 |
|
| 218 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 219 |
# --------------------------------------------------------------------------- #
|
| 220 |
# 介面
|
| 221 |
# --------------------------------------------------------------------------- #
|
|
|
|
| 271 |
|
| 272 |
with gr.Tab("聲音複製"):
|
| 273 |
gr.Markdown(
|
| 274 |
+
"上傳一段參考音檔**並填寫它的逐字稿**,輸出會模仿該語者的音色。\n\n"
|
| 275 |
+
"為什麼要逐字稿?本模型靠「參考語音+逐字稿」做複製最穩定;少了逐字稿的純參考"
|
| 276 |
+
"模式在目前 checkpoint 會吐出亂掉的內容。建議參考音檔約 3–10 秒、乾淨單一語者。\n\n"
|
| 277 |
"⚠️ **請只使用你已取得授權的聲音**,請勿在未經本人同意下複製他人聲音。"
|
| 278 |
)
|
| 279 |
with gr.Row():
|
|
|
|
| 286 |
label="參考音檔(上傳或錄製)", type="filepath",
|
| 287 |
sources=["upload", "microphone"],
|
| 288 |
)
|
| 289 |
+
c_prompt = gr.Textbox(
|
| 290 |
+
label="參考音檔的逐字稿", lines=2,
|
| 291 |
+
placeholder="輸入這段參考語音實際說的內容…",
|
| 292 |
+
)
|
| 293 |
c_btn = gr.Button("複製音色合成", variant="primary")
|
| 294 |
with gr.Column():
|
| 295 |
c_out = gr.Audio(label="合成結果", type="numpy")
|
| 296 |
+
c_btn.click(tts_clone, [c_text, c_ref, c_prompt], c_out)
|
| 297 |
|
| 298 |
with gr.Tab("長文逐句串流"):
|
| 299 |
gr.Markdown(
|
| 300 |
"**長文逐句串流**:把長文切成句子,合成一句就播一句,做出串流(邊聽邊等)效果。"
|
| 301 |
+
"第一句很快就能聽到,後面邊播邊合成。用**李宏毅語者向量**。\n\n"
|
|
|
|
| 302 |
"> ZeroGPU 約 0.44x 實時,句與句之間可能有短暫間隔;每句都套用最佳參數(含自動重試)。"
|
| 303 |
"單次串流以 ZeroGPU 時間配額為限(約可串數十句),超長會自動截斷並提示分批。"
|
| 304 |
)
|
|
|
|
| 308 |
label="要合成的長文", lines=5,
|
| 309 |
placeholder="貼上一段多句的文字,會逐句合成、逐句播放…",
|
| 310 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 311 |
t_btn = gr.Button("逐句串流合成", variant="primary")
|
| 312 |
with gr.Column():
|
| 313 |
t_out = gr.Audio(label="逐句串流播放", streaming=True, autoplay=True)
|
| 314 |
gr.Examples(STREAM_EXAMPLES, inputs=t_text, label="長文範例")
|
| 315 |
+
t_btn.click(tts_stream, t_text, t_out)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 316 |
|
| 317 |
gr.Markdown(
|
| 318 |
"---\n"
|