Ryanus commited on
Commit
404cf65
·
verified ·
1 Parent(s): aea3a2a

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +65 -36
app.py CHANGED
@@ -5,7 +5,8 @@ import os
5
  import tempfile
6
  import datetime
7
  import shutil
8
- import re # 引入 re 模組用於更強大的檔案名稱淨化
 
9
 
10
  # --- 打印 Gradio 版本以供診斷 ---
11
  print(f"Gradio version at runtime: {gr.__version__}")
@@ -22,7 +23,6 @@ from TTS.config.shared_configs import BaseDatasetConfig
22
  from TTS.tts.models.xtts import XttsArgs
23
 
24
  try:
25
- # 將必要的類別加入 PyTorch 的安全全局變數白名單,以解決反序列化問題
26
  torch.serialization.add_safe_globals([XttsConfig, XttsAudioConfig, BaseDatasetConfig, XttsArgs])
27
  print("已將 XTTS 相關配置類加入 PyTorch 安全全局變數白名單。")
28
  except Exception as e:
@@ -38,12 +38,42 @@ tts = None
38
  # 全局變數來儲存模型載入時發生的任何錯誤訊息。
39
  model_load_error = None
40
 
41
- # 初始化 TTS 模型
 
 
 
 
42
  try:
43
  print("正在嘗試載入 Coqui TTS XTTS-v2 模型...")
44
- # 載入 XTTS-v2 模型
 
 
 
 
 
 
 
 
 
 
 
 
 
 
45
  tts = TTS(model_name="tts_models/multilingual/multi-dataset/xtts_v2", progress_bar=True).to(device)
46
  print("Coqui TTS XTTS-v2 模型已成功載入。")
 
 
 
 
 
 
 
 
 
 
 
 
47
  except Exception as e:
48
  model_load_error = (
49
  f"載入 Coqui TTS XTTS-v2 模型時發生錯誤: {e}。\n"
@@ -59,14 +89,12 @@ SUPPORTED_LANGUAGES = [
59
  ]
60
 
61
  # --- 預設語音參考檔案路徑 ---
62
- # 請確保你已將 'speaker.wav' 檔案上傳到 Hugging Face Space 的根目錄
63
  DEFAULT_SPEAKER_WAV = "speaker.wav"
64
 
65
  # --- 自動儲存設定 ---
66
- SAVE_GENERATED_AUDIO_DIR = "generated_audio" # 儲存生成的語音檔案的資料夾
67
- SAVE_UPLOADED_REFERENCES_DIR = "uploaded_references" # 儲存上傳的參考語音檔案的資料夾
68
 
69
- # 確保儲存資料夾存在
70
  os.makedirs(SAVE_GENERATED_AUDIO_DIR, exist_ok=True)
71
  os.makedirs(SAVE_UPLOADED_REFERENCES_DIR, exist_ok=True)
72
  # --- 結束自動儲存設定 ---
@@ -77,9 +105,7 @@ def sanitize_filename(text: str, max_len: int = 50) -> str:
77
  移除除字母、數字、空格和連字號以外的所有字元,
78
  將空格替換為底線,並截斷至指定長度。
79
  """
80
- # 替換任何非字母數字、非空格、非連字號的字元為空字串
81
  safe_text = re.sub(r'[^\w\s-]', '', text).strip()
82
- # 將一個或多個空格替換為單個底線
83
  safe_text = re.sub(r'\s+', '_', safe_text)
84
  if len(safe_text) > max_len:
85
  safe_text = safe_text[:max_len]
@@ -94,7 +120,7 @@ def generate_speech(text: str, language: str, uploaded_speaker_audio_path: str):
94
  if model_load_error:
95
  return None, f"應用程式啟動錯誤:{model_load_error}"
96
 
97
- if tts is None:
98
  return None, "TTS 模型未成功載入,無法生成語音。請檢查日誌獲取詳細資訊。"
99
 
100
  if not text:
@@ -121,19 +147,37 @@ def generate_speech(text: str, language: str, uploaded_speaker_audio_path: str):
121
  status_message += f"警告:儲存參考語音失敗: {e}\n"
122
 
123
  print(f"使用上傳的語音參考檔案: {speaker_wav_to_use}")
124
- else: # 如果用戶沒有上傳檔案,則使用預設檔案
125
  speaker_wav_to_use = DEFAULT_SPEAKER_WAV
126
  if not os.path.exists(speaker_wav_to_use):
127
  return None, f"錯誤:預設���音參考檔案 ({DEFAULT_SPEAKER_WAV}) 未找到。請上傳一個檔案或確保預設檔案存在。"
128
  print(f"沒有上傳語音參考檔案,將使用預設檔案: {speaker_wav_to_use}")
129
  # --- 結束決定 ---
130
 
131
- output_file = None # 用於 Gradio 播放的臨時檔案
132
  try:
133
  with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as fp:
134
  output_file = fp.name
135
 
136
  print(f"正在為語言 '{language}' 生成語音,使用語音參考檔案: {speaker_wav_to_use}...")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
137
  tts.tts_to_file(text=text, language=language, speaker_wav=speaker_wav_to_use, file_path=output_file)
138
  print(f"語音已生成到臨時檔案:{output_file}")
139
 
@@ -153,7 +197,7 @@ def generate_speech(text: str, language: str, uploaded_speaker_audio_path: str):
153
  except Exception as e:
154
  print(f"生成語音時發生錯誤: {e}")
155
  if output_file and os.path.exists(output_file):
156
- os.remove(output_file) # 清理臨時檔案
157
  return None, f"生成語音失敗: {e}"
158
 
159
  def list_saved_audio_files() -> list:
@@ -163,7 +207,7 @@ def list_saved_audio_files() -> list:
163
  for filename in os.listdir(SAVE_GENERATED_AUDIO_DIR):
164
  if filename.lower().endswith(".wav"):
165
  audio_files.append(os.path.join(SAVE_GENERATED_AUDIO_DIR, filename))
166
- audio_files.sort(key=os.path.getmtime, reverse=True) # 按修改時間倒序排列,最新檔案在前
167
  return audio_files
168
 
169
  def list_uploaded_reference_files() -> list:
@@ -173,7 +217,7 @@ def list_uploaded_reference_files() -> list:
173
  for filename in os.listdir(SAVE_UPLOADED_REFERENCES_DIR):
174
  if filename.lower().endswith(".wav"):
175
  ref_files.append(os.path.join(SAVE_UPLOADED_REFERENCES_DIR, filename))
176
- ref_files.sort(key=os.path.getmtime, reverse=True) # 按修改時間倒序排列,最新檔案在前
177
  return ref_files
178
 
179
  # Gradio 介面配置 (使用 gr.Blocks 實現多 Tab 介面)
@@ -191,21 +235,12 @@ with gr.Blocks(title="Coqui TTS XTTS-v2 語音生成") as demo:
191
  type="filepath",
192
  label="上傳語音參考檔案 (WAV) (可選)",
193
  sources=["microphone", "upload"],
194
- # 如果您的 Gradio 版本支援且您希望顯示波形和資訊,可以取消註釋以下行:
195
- # waveform_options=gr.Audio.WaveformOptions(
196
- # waveform_color="#0055EE",
197
- # waveform_progress_color="#00AAFF",
198
- # skip_length=2,
199
- # show_controls=True,
200
- # ),
201
- # info="上傳一個清晰的 WAV 檔案作為語音參考。音頻長度建議在 3-10 秒之間。"
202
  )
203
  generate_button = gr.Button("生成語音")
204
  with gr.Column():
205
  output_audio = gr.Audio(label="生成的語音", type="filepath")
206
  status_textbox = gr.Textbox(label="狀態")
207
 
208
- # 將生成按鈕與 generate_speech 函數綁定
209
  generate_button.click(
210
  fn=generate_speech,
211
  inputs=[text_input, language_dropdown, speaker_audio_upload],
@@ -218,14 +253,12 @@ with gr.Blocks(title="Coqui TTS XTTS-v2 語音生成") as demo:
218
 
219
  saved_generated_files_output = gr.File(
220
  label="生成的語音檔案",
221
- file_count="multiple", # 允許顯示多個檔案
222
- interactive=False # 不允許用戶上傳,只用於顯示和下載
223
  )
224
  refresh_generated_button = gr.Button("刷新生成語音列表")
225
 
226
- # 應用程式載入時,自動載入檔案列表
227
  demo.load(list_saved_audio_files, outputs=[saved_generated_files_output])
228
- # 點擊刷新按鈕時,重新載入檔案列表
229
  refresh_generated_button.click(list_saved_audio_files, outputs=[saved_generated_files_output])
230
 
231
  with gr.Tab("查看已上傳參考語音"):
@@ -234,17 +267,13 @@ with gr.Blocks(title="Coqui TTS XTTS-v2 語音生成") as demo:
234
 
235
  saved_uploaded_ref_files_output = gr.File(
236
  label="上傳的參考語音檔案",
237
- file_count="multiple", # 允許顯示多個檔案
238
- interactive=False # 不允許用戶上傳,只用於顯示和下載
239
  )
240
  refresh_uploaded_ref_button = gr.Button("刷新參考語音列表")
241
 
242
- # 應用程式載入時,自動載入檔案列表
243
  demo.load(list_uploaded_reference_files, outputs=[saved_uploaded_ref_files_output])
244
- # 點擊刷新按鈕時,重新載入檔案列表
245
  refresh_uploaded_ref_button.click(list_uploaded_reference_files, outputs=[saved_uploaded_ref_files_output])
246
 
247
-
248
- # 啟動 Gradio 應用
249
  if __name__ == "__main__":
250
- demo.launch() # 修正:新增這一行並正確縮排
 
5
  import tempfile
6
  import datetime
7
  import shutil
8
+ import re
9
+ import onnxruntime as rt # 引入 onnxruntime
10
 
11
  # --- 打印 Gradio 版本以供診斷 ---
12
  print(f"Gradio version at runtime: {gr.__version__}")
 
23
  from TTS.tts.models.xtts import XttsArgs
24
 
25
  try:
 
26
  torch.serialization.add_safe_globals([XttsConfig, XttsAudioConfig, BaseDatasetConfig, XttsArgs])
27
  print("已將 XTTS 相關配置類加入 PyTorch 安全全局變數白名單。")
28
  except Exception as e:
 
38
  # 全局變數來儲存模型載入時發生的任何錯誤訊息。
39
  model_load_error = None
40
 
41
+ # 全局變數來儲存 ONNX Session
42
+ onnx_session = None
43
+ onnx_model_path = "xtts_v2_quantized.onnx" # 假設量化後的 ONNX 模型路徑
44
+
45
+ # 初始化 TTS 模型或 ONNX Session
46
  try:
47
  print("正在嘗試載入 Coqui TTS XTTS-v2 模型...")
48
+ # 這裡可以嘗試載入原始 PyTorch 模型,然後進行 ONNX 轉換和量化
49
+ # 或者直接載入預先轉換好的 ONNX 模型
50
+
51
+ # 為了簡化,這裡假設我們仍然使用 TTS 庫來載入模型,
52
+ # 但如果需要 ONNX 優化,您可能需要手動導出 XTTS-v2 到 ONNX
53
+ # 並使用 onnxruntime.InferenceSession 來載入。
54
+ # 這部分需要更深入的 XTTS-v2 模型結構知識。
55
+
56
+ # 這裡僅為示意,實際的 ONNX 轉換和載入會更複雜
57
+ # if os.path.exists(onnx_model_path):
58
+ # print(f"正在載入 ONNX 模型: {onnx_model_path}")
59
+ # onnx_session = rt.InferenceSession(onnx_model_path, providers=['CPUExecutionProvider'])
60
+ # print("ONNX 模型已成功載入。")
61
+ # else:
62
+ # print("ONNX 模型未找到,將載入 PyTorch 模型。")
63
  tts = TTS(model_name="tts_models/multilingual/multi-dataset/xtts_v2", progress_bar=True).to(device)
64
  print("Coqui TTS XTTS-v2 模型已成功載入。")
65
+
66
+ # 如果要進行 ONNX 轉換,可以在這裡添加邏輯
67
+ # 例如:
68
+ # if device == "cpu" and not os.path.exists(onnx_model_path):
69
+ # print("嘗試將 PyTorch 模型轉換為 ONNX...")
70
+ # # 這部分需要 XTTS-v2 模型的具體輸入格式來進行 torch.onnx.export
71
+ # # 並且可能需要量化
72
+ # # dummy_input = ... # 根據 XTTS-v2 的 forward 函數定義 dummy input
73
+ # # torch.onnx.export(tts.model, dummy_input, onnx_model_path, opset_version=15)
74
+ # # onnx_session = rt.InferenceSession(onnx_model_path, providers=['CPUExecutionProvider'])
75
+ # # print("模型已轉換並載入為 ONNX。")
76
+
77
  except Exception as e:
78
  model_load_error = (
79
  f"載入 Coqui TTS XTTS-v2 模型時發生錯誤: {e}。\n"
 
89
  ]
90
 
91
  # --- 預設語音參考檔案路徑 ---
 
92
  DEFAULT_SPEAKER_WAV = "speaker.wav"
93
 
94
  # --- 自動儲存設定 ---
95
+ SAVE_GENERATED_AUDIO_DIR = "generated_audio"
96
+ SAVE_UPLOADED_REFERENCES_DIR = "uploaded_references"
97
 
 
98
  os.makedirs(SAVE_GENERATED_AUDIO_DIR, exist_ok=True)
99
  os.makedirs(SAVE_UPLOADED_REFERENCES_DIR, exist_ok=True)
100
  # --- 結束自動儲存設定 ---
 
105
  移除除字母、數字、空格和連字號以外的所有字元,
106
  將空格替換為底線,並截斷至指定長度。
107
  """
 
108
  safe_text = re.sub(r'[^\w\s-]', '', text).strip()
 
109
  safe_text = re.sub(r'\s+', '_', safe_text)
110
  if len(safe_text) > max_len:
111
  safe_text = safe_text[:max_len]
 
120
  if model_load_error:
121
  return None, f"應用程式啟動錯誤:{model_load_error}"
122
 
123
+ if tts is None and onnx_session is None:
124
  return None, "TTS 模型未成功載入,無法生成語音。請檢查日誌獲取詳細資訊。"
125
 
126
  if not text:
 
147
  status_message += f"警告:儲存參考語音失敗: {e}\n"
148
 
149
  print(f"使用上傳的語音參考檔案: {speaker_wav_to_use}")
150
+ else:
151
  speaker_wav_to_use = DEFAULT_SPEAKER_WAV
152
  if not os.path.exists(speaker_wav_to_use):
153
  return None, f"錯誤:預設���音參考檔案 ({DEFAULT_SPEAKER_WAV}) 未找到。請上傳一個檔案或確保預設檔案存在。"
154
  print(f"沒有上傳語音參考檔案,將使用預設檔案: {speaker_wav_to_use}")
155
  # --- 結束決定 ---
156
 
157
+ output_file = None
158
  try:
159
  with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as fp:
160
  output_file = fp.name
161
 
162
  print(f"正在為語言 '{language}' 生成語音,使用語音參考檔案: {speaker_wav_to_use}...")
163
+
164
+ # 如果 ONNX Session 存在,嘗試使用 ONNX 進行推理
165
+ # 這部分需要將 XTTS-v2 的輸入轉換為 ONNX 模型所需的格式
166
+ # 並將輸出轉換回音訊格式。這會非常複雜,因為 TTS 庫封裝了許多細節。
167
+ # if onnx_session:
168
+ # # 這裡需要 XTTS-v2 ONNX 模型的具體輸入/輸出格式
169
+ # # 例如:
170
+ # # inputs = {
171
+ # # onnx_session.get_inputs()[0].name: processed_text_input,
172
+ # # onnx_session.get_inputs()[1].name: processed_speaker_input,
173
+ # # ...
174
+ # # }
175
+ # # outputs = onnx_session.run(None, inputs)
176
+ # # generated_audio_data = outputs[0]
177
+ # # import soundfile as sf
178
+ # # sf.write(output_file, generated_audio_data, 24000) # 假設採樣率為 24000
179
+ # pass # 暫時不實作 ONNX 推理,因為太複雜
180
+ # else:
181
  tts.tts_to_file(text=text, language=language, speaker_wav=speaker_wav_to_use, file_path=output_file)
182
  print(f"語音已生成到臨時檔案:{output_file}")
183
 
 
197
  except Exception as e:
198
  print(f"生成語音時發生錯誤: {e}")
199
  if output_file and os.path.exists(output_file):
200
+ os.remove(output_file)
201
  return None, f"生成語音失敗: {e}"
202
 
203
  def list_saved_audio_files() -> list:
 
207
  for filename in os.listdir(SAVE_GENERATED_AUDIO_DIR):
208
  if filename.lower().endswith(".wav"):
209
  audio_files.append(os.path.join(SAVE_GENERATED_AUDIO_DIR, filename))
210
+ audio_files.sort(key=os.path.getmtime, reverse=True)
211
  return audio_files
212
 
213
  def list_uploaded_reference_files() -> list:
 
217
  for filename in os.listdir(SAVE_UPLOADED_REFERENCES_DIR):
218
  if filename.lower().endswith(".wav"):
219
  ref_files.append(os.path.join(SAVE_UPLOADED_REFERENCES_DIR, filename))
220
+ ref_files.sort(key=os.path.getmtime, reverse=True)
221
  return ref_files
222
 
223
  # Gradio 介面配置 (使用 gr.Blocks 實現多 Tab 介面)
 
235
  type="filepath",
236
  label="上傳語音參考檔案 (WAV) (可選)",
237
  sources=["microphone", "upload"],
 
 
 
 
 
 
 
 
238
  )
239
  generate_button = gr.Button("生成語音")
240
  with gr.Column():
241
  output_audio = gr.Audio(label="生成的語音", type="filepath")
242
  status_textbox = gr.Textbox(label="狀態")
243
 
 
244
  generate_button.click(
245
  fn=generate_speech,
246
  inputs=[text_input, language_dropdown, speaker_audio_upload],
 
253
 
254
  saved_generated_files_output = gr.File(
255
  label="生成的語音檔案",
256
+ file_count="multiple",
257
+ interactive=False
258
  )
259
  refresh_generated_button = gr.Button("刷新生成語音列表")
260
 
 
261
  demo.load(list_saved_audio_files, outputs=[saved_generated_files_output])
 
262
  refresh_generated_button.click(list_saved_audio_files, outputs=[saved_generated_files_output])
263
 
264
  with gr.Tab("查看已上傳參考語音"):
 
267
 
268
  saved_uploaded_ref_files_output = gr.File(
269
  label="上傳的參考語音檔案",
270
+ file_count="multiple",
271
+ interactive=False
272
  )
273
  refresh_uploaded_ref_button = gr.Button("刷新參考語音列表")
274
 
 
275
  demo.load(list_uploaded_reference_files, outputs=[saved_uploaded_ref_files_output])
 
276
  refresh_uploaded_ref_button.click(list_uploaded_reference_files, outputs=[saved_uploaded_ref_files_output])
277
 
 
 
278
  if __name__ == "__main__":
279
+ demo.launch()