Ryanus commited on
Commit
c7d85cb
·
verified ·
1 Parent(s): 774228a

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +107 -84
app.py CHANGED
@@ -3,8 +3,9 @@ from TTS.api import TTS
3
  import gradio as gr
4
  import os
5
  import tempfile
6
- import datetime # 用於生成唯一檔案名
7
- import shutil # 用於檔案複製
 
8
 
9
  # --- 打印 Gradio 版本以供診斷 ---
10
  print(f"Gradio version at runtime: {gr.__version__}")
@@ -21,13 +22,13 @@ from TTS.config.shared_configs import BaseDatasetConfig
21
  from TTS.tts.models.xtts import XttsArgs
22
 
23
  try:
 
24
  torch.serialization.add_safe_globals([XttsConfig, XttsAudioConfig, BaseDatasetConfig, XttsArgs])
25
  print("已將 XTTS 相關配置類加入 PyTorch 安全全局變數白名單。")
26
  except Exception as e:
27
  print(f"警告:無法將安全全局變數加入 PyTorch 白名單: {e}")
28
  print("如果遇到模型載入錯誤,請檢查 PyTorch 和 TTS 庫版本。")
29
 
30
-
31
  # 檢查是否有 CUDA 可用,否則使用 CPU
32
  device = "cuda" if torch.cuda.is_available() else "cpu"
33
  print(f"使用設備: {device}")
@@ -40,57 +41,21 @@ model_load_error = None
40
  # 初始化 TTS 模型
41
  try:
42
  print("正在嘗試載入 Coqui TTS XTTS-v2 模型...")
 
43
  tts = TTS(model_name="tts_models/multilingual/multi-dataset/xtts_v2", progress_bar=True).to(device)
44
  print("Coqui TTS XTTS-v2 模型已成功載入。")
45
  except Exception as e:
46
- model_load_error = f"載入 Coqui TTS XTTS-v2 模型時發生錯誤: {e}。\n請確保你的網路連接正常,並且模型名稱正確。此外,請檢查 Hugging Face Space 的日誌以獲取更多詳細資訊。"
 
 
 
47
  print(model_load_error)
48
 
49
  # XTTS-v2 支援的語言列表
50
  SUPPORTED_LANGUAGES = [
51
- "en", # English
52
- "zh-cn", # Chinese (Simplified)
53
- "es", # Spanish
54
- "fr", # French
55
- "de", # German
56
- "it", # Italian
57
- "pt", # Portuguese
58
- "pl", # Polish
59
- "ru", # Russian
60
- "ja", # Japanese
61
- "ko", # Korean
62
- "ar", # Arabic
63
- "hi", # Hindi
64
- "tr", # Turkish
65
- "nl", # Dutch
66
- "sv", # Swedish
67
- "da", # Danish
68
- "fi", # Finnish
69
- "no", # Norwegian
70
- "cs", # Czech
71
- "hu", # Hungarian
72
- "el", # Greek
73
- "uk", # Ukrainian
74
- "vi", # Vietnamese
75
- "th", # Thai
76
- "id", # Indonesian
77
- "ms", # Malay
78
- "ro", # Romanian
79
- "sk", # Slovak
80
- "hr", # Croatian
81
- "bg", # Bulgarian
82
- "ca", # Catalan
83
- "fa", # Persian
84
- "he", # Hebrew
85
- "ur", # Urdu
86
- "bn", # Bengali
87
- "gu", # Gujarati
88
- "kn", # Kannada
89
- "ml", # Malayalam
90
- "mr", # Marathi
91
- "pa", # Punjabi
92
- "ta", # Tamil
93
- "te", # Telugu
94
  ]
95
 
96
  # --- 預設語音參考檔案路徑 ---
@@ -98,18 +63,33 @@ SUPPORTED_LANGUAGES = [
98
  DEFAULT_SPEAKER_WAV = "speaker.wav"
99
 
100
  # --- 自動儲存設定 ---
101
- SAVE_DIR = "generated_audio" # 儲存生成的語音檔案的資料夾
 
102
 
103
  # 確保儲存資料夾存在
104
- os.makedirs(SAVE_DIR, exist_ok=True)
 
105
  # --- 結束自動儲存設定 ---
106
 
107
-
108
- def generate_speech(text, language, uploaded_speaker_audio_path):
 
 
 
 
 
 
 
 
 
 
 
 
 
109
  """
110
  根據輸入文字、語言和語音參考檔案生成語音。
111
  如果用戶上傳了檔案,則使用上傳的檔案;否則使用預設的 speaker.wav。
112
- 生成的語音將自動儲存到指定資料夾。
113
  """
114
  if model_load_error:
115
  return None, f"應用程式啟動錯誤:{model_load_error}"
@@ -122,15 +102,30 @@ def generate_speech(text, language, uploaded_speaker_audio_path):
122
  if not language:
123
  return None, "請選擇一個語言!"
124
 
 
 
 
125
  # --- 決定使用哪個語音參考檔案 ---
126
- speaker_wav_to_use = uploaded_speaker_audio_path
127
- if not speaker_wav_to_use: # 如果用戶沒有上傳檔案
 
 
 
 
 
 
 
 
 
 
 
 
 
 
128
  speaker_wav_to_use = DEFAULT_SPEAKER_WAV
129
  if not os.path.exists(speaker_wav_to_use):
130
  return None, f"錯誤:預設語音參考檔案 ({DEFAULT_SPEAKER_WAV}) 未找到。請上傳一個檔案或確保預設檔案存在。"
131
  print(f"沒有上傳語音參考檔案,將使用預設檔案: {speaker_wav_to_use}")
132
- else:
133
- print(f"使用上傳的語音參考檔案: {speaker_wav_to_use}")
134
  # --- 結束決定 ---
135
 
136
  output_file = None # 用於 Gradio 播放的臨時檔案
@@ -143,44 +138,49 @@ def generate_speech(text, language, uploaded_speaker_audio_path):
143
  print(f"語音已生成到臨時檔案:{output_file}")
144
 
145
  # --- 自動儲存生成的語音檔案 ---
146
- timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
147
- # 清理文本,用於檔案名,避免特殊字元
148
- safe_text = "".join(c for c in text if c.isalnum() or c.isspace()).strip()
149
- if len(safe_text) > 30: # 限制檔案名長度
150
- safe_text = safe_text[:30] + "..."
151
 
152
- # 組合最終儲存路徑
153
- saved_file_name = f"{timestamp}_{language}_{safe_text}.wav"
154
- saved_file_path = os.path.join(SAVE_DIR, saved_file_name)
155
 
156
- # 將臨時檔案複製到儲存目錄
157
  shutil.copy(output_file, saved_file_path)
158
- print(f"語音已自動儲存到:{saved_file_path}")
 
159
  # --- 結束自動儲存 ---
160
 
161
- return output_file, f"語音生成成功!已儲存為:{saved_file_path}"
162
  except Exception as e:
163
  print(f"生成語音時發生錯誤: {e}")
164
  if output_file and os.path.exists(output_file):
165
  os.remove(output_file) # 清理臨時檔案
166
  return None, f"生成語音失敗: {e}"
167
 
168
- # --- 新增:查看已儲存語音的功能 ---
169
- def list_saved_audio_files():
170
- """掃描儲存資料夾,返回所有 .wav 檔案的完整路徑列表。"""
171
  audio_files = []
172
- if os.path.exists(SAVE_DIR) and os.path.isdir(SAVE_DIR):
173
- for filename in os.listdir(SAVE_DIR):
174
  if filename.lower().endswith(".wav"):
175
- audio_files.append(os.path.join(SAVE_DIR, filename))
176
  audio_files.sort(key=os.path.getmtime, reverse=True) # 按修改時間倒序排列,最新檔案在前
177
  return audio_files
178
- # --- 結束新增 ---
 
 
 
 
 
 
 
 
 
179
 
180
  # Gradio 介面配置 (使用 gr.Blocks 實現多 Tab 介面)
181
  with gr.Blocks(title="Coqui TTS XTTS-v2 語音生成") as demo:
182
  gr.Markdown("# Coqui TTS XTTS-v2 語音生成 (CPU)")
183
- gr.Markdown("此演示使用 CPU 運行,請注意 XTTS-v2 在 CPU 上運行會非常慢。您可以上傳自己的語音,或使用預設語音。生成的語音將自動儲存到 Space 專案中。")
 
184
 
185
  with gr.Tab("語音生成"):
186
  with gr.Row():
@@ -191,7 +191,14 @@ with gr.Blocks(title="Coqui TTS XTTS-v2 語音生成") as demo:
191
  type="filepath",
192
  label="上傳語音參考檔案 (WAV) (可選)",
193
  sources=["microphone", "upload"],
194
- # 移除 waveform_options 和 info 參數以避免 Gradio 版本兼容性問題
 
 
 
 
 
 
 
195
  )
196
  generate_button = gr.Button("生成語音")
197
  with gr.Column():
@@ -206,21 +213,37 @@ with gr.Blocks(title="Coqui TTS XTTS-v2 語音生成") as demo:
206
  )
207
 
208
  with gr.Tab("查看已儲存語音"):
209
- gr.Markdown("### 已儲存的語音檔案")
210
- gr.Markdown("請注意:每次生成語音並儲存後,Hugging Face Space 會自動重啟,因此您可能需要點擊刷新按鈕來查看最新檔案。")
211
 
212
- saved_files_output = gr.File(
213
- label="已儲存的語音檔案",
214
  file_count="multiple", # 允許顯示多個檔案
215
  interactive=False # 不允許用戶上傳,只用於顯示和下載
216
  )
217
- refresh_button = gr.Button("刷新檔案列表")
218
 
219
  # 應用程式載入時,自動載入檔案列表
220
- demo.load(list_saved_audio_files, outputs=[saved_files_output])
221
  # 點擊刷新按鈕時,重新載入檔案列表
222
- refresh_button.click(list_saved_audio_files, outputs=[saved_files_output])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
223
 
224
  # 啟動 Gradio 應用
225
  if __name__ == "__main__":
226
- demo.launch()
 
3
  import gradio as gr
4
  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
  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:
29
  print(f"警告:無法將安全全局變數加入 PyTorch 白名單: {e}")
30
  print("如果遇到模型載入錯誤,請檢查 PyTorch 和 TTS 庫版本。")
31
 
 
32
  # 檢查是否有 CUDA 可用,否則使用 CPU
33
  device = "cuda" if torch.cuda.is_available() else "cpu"
34
  print(f"使用設備: {device}")
 
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"
50
+ "請確保你的網路連接正常,並且模型名稱正確。此外,請檢查 Hugging Face Space 的日誌以獲取更多詳細資訊。"
51
+ )
52
  print(model_load_error)
53
 
54
  # XTTS-v2 支援的語言列表
55
  SUPPORTED_LANGUAGES = [
56
+ "en", "zh-cn", "es", "fr", "de", "it", "pt", "pl", "ru", "ja", "ko", "ar", "hi", "tr",
57
+ "nl", "sv", "da", "fi", "no", "cs", "hu", "el", "uk", "vi", "th", "id", "ms", "ro",
58
+ "sk", "hr", "bg", "ca", "fa", "he", "ur", "bn", "gu", "kn", "ml", "mr", "pa", "ta", "te",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59
  ]
60
 
61
  # --- 預設語音參考檔案路徑 ---
 
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
  # --- 結束自動儲存設定 ---
73
 
74
+ def sanitize_filename(text: str, max_len: int = 50) -> str:
75
+ """
76
+ 淨化字串以用於檔案名稱。
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]
86
+ return safe_text
87
+
88
+ def generate_speech(text: str, language: str, uploaded_speaker_audio_path: str):
89
  """
90
  根據輸入文字、語言和語音參考檔案生成語音。
91
  如果用戶上傳了檔案,則使用上傳的檔案;否則使用預設的 speaker.wav。
92
+ 生成的語音和上傳的參考語音(如果有的話)都將自動儲存到指定資料夾。
93
  """
94
  if model_load_error:
95
  return None, f"應用程式啟動錯誤:{model_load_error}"
 
102
  if not language:
103
  return None, "請選擇一個語言!"
104
 
105
+ speaker_wav_to_use = None
106
+ status_message = ""
107
+
108
  # --- 決定使用哪個語音參考檔案 ---
109
+ if uploaded_speaker_audio_path:
110
+ speaker_wav_to_use = uploaded_speaker_audio_path
111
+ try:
112
+ timestamp_ref = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
113
+ original_ext = os.path.splitext(uploaded_speaker_audio_path)[1]
114
+ saved_ref_file_name = f"{timestamp_ref}_uploaded_ref{original_ext}"
115
+ saved_ref_file_path = os.path.join(SAVE_UPLOADED_REFERENCES_DIR, saved_ref_file_name)
116
+ shutil.copy(uploaded_speaker_audio_path, saved_ref_file_path)
117
+ print(f"上傳的參考語音已儲存到:{saved_ref_file_path}")
118
+ status_message += f"參考語音已儲存到:{saved_ref_file_path}\n"
119
+ except Exception as e:
120
+ print(f"儲存上傳的參考語音時發生錯誤: {e}")
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 播放的臨時檔案
 
138
  print(f"語音已生成到臨時檔案:{output_file}")
139
 
140
  # --- 自動儲存生成的語音檔案 ---
141
+ timestamp_gen = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
142
+ sanitized_text = sanitize_filename(text)
 
 
 
143
 
144
+ saved_file_name = f"{timestamp_gen}_{language}_{sanitized_text}.wav"
145
+ saved_file_path = os.path.join(SAVE_GENERATED_AUDIO_DIR, saved_file_name)
 
146
 
 
147
  shutil.copy(output_file, saved_file_path)
148
+ print(f"生成的語音已自動儲存到:{saved_file_path}")
149
+ status_message += f"語音生成成功!已儲存為:{saved_file_path}"
150
  # --- 結束自動儲存 ---
151
 
152
+ return output_file, status_message
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:
160
+ """掃描儲存生成的語音資料夾,返回所有 .wav 檔案的完整路徑列表。"""
 
161
  audio_files = []
162
+ if os.path.exists(SAVE_GENERATED_AUDIO_DIR) and os.path.isdir(SAVE_GENERATED_AUDIO_DIR):
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:
170
+ """掃描上傳參考語音資料夾,返回所有 .wav 檔案的完整路徑列表。"""
171
+ ref_files = []
172
+ if os.path.exists(SAVE_UPLOADED_REFERENCES_DIR) and os.path.isdir(SAVE_UPLOADED_REFERENCES_DIR):
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 介面)
180
  with gr.Blocks(title="Coqui TTS XTTS-v2 語音生成") as demo:
181
  gr.Markdown("# Coqui TTS XTTS-v2 語音生成 (CPU)")
182
+ gr.Markdown("此演示使用 CPU 運行,請注意 XTTS-v2 在 CPU 上運行會非常慢。您可以上傳自己的語音,或使用預設語音。**生成的語音和上傳的參考語音都將自動儲存到 Space 專案中。**")
183
+ gr.Markdown("**重要提示:** 每次儲存檔案都會觸發 Hugging Face Space 的自動重建,導致應用程式暫時不可用並重新載入模型。")
184
 
185
  with gr.Tab("語音生成"):
186
  with gr.Row():
 
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():
 
213
  )
214
 
215
  with gr.Tab("查看已儲存語音"):
216
+ gr.Markdown("### 已儲存的生成語音檔案")
217
+ gr.Markdown("這些是您生成的語音檔案。")
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("查看已上傳參考語音"):
232
+ gr.Markdown("### 已儲存的上傳參考語音檔案")
233
+ gr.Markdown("這些是您上傳的語音參考檔案。")
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__":