voidful commited on
Commit
5fc2aef
·
verified ·
1 Parent(s): 5e31159

Voice cloning: extract ECAPA speaker centroid from upload (no transcript); add speechbrain

Browse files
Files changed (2) hide show
  1. app.py +60 -21
  2. requirements.txt +2 -0
app.py CHANGED
@@ -2,11 +2,13 @@
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 永遠跟著模型走。
@@ -98,6 +100,47 @@ _centroids = torch.load(
98
  SPK_CENTROID = _centroids["centroids"][_centroids["speaker_ids"].index(SPEAKER_ID)]
99
  print(f"[BlueMagpie] ready. sample_rate={SR}, speaker={SPEAKER_ID}")
100
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
101
  # 註:repo 的 bluemagpie.serving 加速引擎(torch.compile / 連續批次)是為「常駐
102
  # 專用 GPU、追求吞吐」設計。在 ZeroGPU(GPU 逐請求序列化掛載)上實測後,單一
103
  # 請求延遲沒有改善(warm ~7.0s vs eager ~6.2s),反而帶來首呼叫 ~50s 編譯成本與
@@ -159,24 +202,23 @@ def tts_speaker(text):
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))
@@ -271,9 +313,10 @@ with gr.Blocks(title="BlueMagpie-TTS Demo", theme=gr.themes.Soft()) as demo:
271
 
272
  with gr.Tab("聲音複製"):
273
  gr.Markdown(
274
- "上傳一段參考音檔**並填寫它的逐字稿**,輸出會模仿該語者的音色。\n\n"
275
- "為什麼要逐字稿?本模型靠「參考語音+逐字稿」做複製最穩定;少了逐字稿的純參考"
276
- "模式在目前 checkpoint 會吐出亂掉的內容。建議參考音檔約 3–10 秒、乾淨單一語者。\n\n"
 
277
  "⚠️ **請只使用你已取得授權的聲音**,請勿在未經本人同意下複製他人聲音。"
278
  )
279
  with gr.Row():
@@ -283,17 +326,13 @@ with gr.Blocks(title="BlueMagpie-TTS Demo", theme=gr.themes.Soft()) as demo:
283
  placeholder="輸入中文或中英混合的文字…",
284
  )
285
  c_ref = gr.Audio(
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(
 
2
 
3
  提供三種試用情境,並一律套用模型官方建議的最佳生成參數:
4
  1. 指定語者:李宏毅 —— 用模型內附、已取得授權的語者向量控制音色
5
+ 2. 聲音複製 —— 從參考音檔抽取語者向量(ECAPA centroid),走 speaker_centroid 路徑
6
  3. 長文逐句串流 —— 把長文切句,合成一句播一句,做出串流效果(每句含自動重試)
7
 
8
+ 註:本 checkpoint 只訓練過 speaker_centroid(內附李宏毅向量同一機制)與 prompt 接續兩條
9
+ 路徑;transcript-free 的 reference_wav_path 路徑從未餵過訓練資料(data._make_sample 丟掉
10
+ ref_audio),會吐出亂掉內容。故聲音複製改為「從錄音抽 centroid → speaker_centroid」——
11
+ 免逐字稿、且走的是有訓練、可靠的路徑。
12
 
13
  最佳參數讀自模型發佈中繼資料 release_metadata.json 的 recommended_generation_defaults
14
  (以 500 句難句 + ASR CER 調出,最佳 trial:cfg=2.8 / steps=9),確保 demo 永遠跟著模型走。
 
100
  SPK_CENTROID = _centroids["centroids"][_centroids["speaker_ids"].index(SPEAKER_ID)]
101
  print(f"[BlueMagpie] ready. sample_rate={SR}, speaker={SPEAKER_ID}")
102
 
103
+
104
+ # --------------------------------------------------------------------------- #
105
+ # 聲音複製:從參考音檔抽取語者向量(ECAPA),走「已訓練、可靠」的 speaker_centroid
106
+ # 路徑。與訓練用的 build_speaker_centroids.py 完全一致:同一個 ECAPA 模型、每段
107
+ # embedding 先 L2-normalize、取平均、再 L2-normalize(192 維),確保與內附李宏毅向量
108
+ # 同一向量空間。ECAPA 在 CPU 上跑、lazy 載入一次(不佔 GPU 時間配額)。
109
+ # --------------------------------------------------------------------------- #
110
+ _ECAPA = None
111
+
112
+
113
+ def _get_ecapa():
114
+ global _ECAPA
115
+ if _ECAPA is None:
116
+ from speechbrain.inference.speaker import EncoderClassifier
117
+
118
+ _ECAPA = EncoderClassifier.from_hparams(
119
+ source="speechbrain/spkrec-ecapa-voxceleb", run_opts={"device": "cpu"}
120
+ )
121
+ return _ECAPA
122
+
123
+
124
+ @torch.no_grad()
125
+ def extract_centroid(wav_path: str) -> torch.Tensor:
126
+ """參考音檔 -> [192] 語者向量(與訓練 centroid 同空間、同處理)。"""
127
+ import librosa
128
+
129
+ wav, _ = librosa.load(wav_path, sr=16000, mono=True) # 16 kHz 單聲道
130
+ x = torch.from_numpy(np.ascontiguousarray(wav)).float()
131
+ if x.numel() < 16000: # 太短(<1s)難抽穩定向量
132
+ raise ValueError("參考音檔太短,請提供約 3–10 秒的乾淨語音。")
133
+ clf = _get_ecapa()
134
+ win = 6 * 16000 # 切約 6 秒一段,分段抽再平均
135
+ chunks = [x[i : i + win] for i in range(0, x.numel(), win)]
136
+ chunks = [c for c in chunks if c.numel() >= 16000] or [x] # 每段至少 1 秒
137
+ embs = []
138
+ for c in chunks:
139
+ e = clf.encode_batch(c.unsqueeze(0)).reshape(-1) # [192]
140
+ embs.append(torch.nn.functional.normalize(e, dim=0))
141
+ mean = torch.stack(embs).mean(0)
142
+ return torch.nn.functional.normalize(mean, dim=0).cpu()
143
+
144
  # 註:repo 的 bluemagpie.serving 加速引擎(torch.compile / 連續批次)是為「常駐
145
  # 專用 GPU、追求吞吐」設計。在 ZeroGPU(GPU 逐請求序列化掛載)上實測後,單一
146
  # 請求延遲沒有改善(warm ~7.0s vs eager ~6.2s),反而帶來首呼叫 ~50s 編譯成本與
 
202
 
203
 
204
  @gpu
205
+ def tts_clone(text, reference_audio):
206
+ """聲音複製:從參考音檔抽出語者向量(ECAPA centroid),走 speaker_centroid 路徑。
207
 
208
+ 這條路徑在本 checkpoint 有被訓練(與內附李宏毅向量同一機制),可靠;**免逐字稿**。
 
209
  """
210
  text = (text or "").strip()
 
211
  if not text:
212
  raise gr.Error("請先輸入要合成的文字。")
213
  if not reference_audio:
214
  raise gr.Error("請先上傳或錄製一段參考音檔。")
215
+ try:
216
+ centroid = extract_centroid(reference_audio)
217
+ except Exception as e:
218
+ raise gr.Error(f"無法從參考音檔抽取語者向量:{e}")
219
  audio = model.generate(
220
  target_text=text,
221
+ speaker_centroid=centroid,
 
222
  **GEN_KWARGS,
223
  )
224
  return (SR, _to_numpy(audio))
 
313
 
314
  with gr.Tab("聲音複製"):
315
  gr.Markdown(
316
+ "上傳或錄製一段參考音檔,系統會從中**抽取語者向量(音色指紋)**,再用它合成你的文字"
317
+ "——**不需要逐字稿**。建議參考音檔約 3–10 秒、乾淨單一語者。\n\n"
318
+ "(原理:和內附的李宏毅語者向量走同一條、**已訓練**的 `speaker_centroid` 路徑;"
319
+ "用 ECAPA-TDNN 從你的音檔算出同樣的 192 維向量。)\n\n"
320
  "⚠️ **請只使用你已取得授權的聲音**,請勿在未經本人同意下複製他人聲音。"
321
  )
322
  with gr.Row():
 
326
  placeholder="輸入中文或中英混合的文字…",
327
  )
328
  c_ref = gr.Audio(
329
+ label="參考音檔(上傳或錄製,約 3–10 秒)", type="filepath",
330
  sources=["upload", "microphone"],
331
  )
 
 
 
 
332
  c_btn = gr.Button("複製音色合成", variant="primary")
333
  with gr.Column():
334
  c_out = gr.Audio(label="合成結果", type="numpy")
335
+ c_btn.click(tts_clone, [c_text, c_ref], c_out)
336
 
337
  with gr.Tab("長文逐句串流"):
338
  gr.Markdown(
requirements.txt CHANGED
@@ -5,3 +5,5 @@
5
  git+https://github.com/OpenFormosa/Barbet.git
6
  git+https://github.com/OpenFormosa/BlueMagpie-TTS.git@7d415b1b2f54d6264b62bc62160f0123069053fa
7
  soundfile
 
 
 
5
  git+https://github.com/OpenFormosa/Barbet.git
6
  git+https://github.com/OpenFormosa/BlueMagpie-TTS.git@7d415b1b2f54d6264b62bc62160f0123069053fa
7
  soundfile
8
+ # 聲音複製:用 ECAPA-TDNN 從參考音檔抽語者向量(speaker_centroid 路徑)。
9
+ speechbrain