dseditor commited on
Commit
b321319
·
1 Parent(s): c156867
Files changed (1) hide show
  1. app.py +130 -44
app.py CHANGED
@@ -1,11 +1,13 @@
1
  """
2
- Breeze2-VITS 繁體中文語音合成 - 包含本地模型文件
3
- 使用預先下載的模型文件,無需動態下載
4
  """
5
 
6
  import gradio as gr
7
  import numpy as np
8
  import os
 
 
9
  from pathlib import Path
10
  import torch
11
 
@@ -15,21 +17,80 @@ except ImportError:
15
  os.system("pip install sherpa-onnx")
16
  import sherpa_onnx
17
 
 
 
 
 
 
 
18
 
19
  class TaiwaneseVITSTTS:
20
  def __init__(self):
21
  self.tts = None
22
- # 模型文件直接放在 Space 根目錄的 models 文件夾
23
  self.model_dir = Path("./models")
 
 
24
  self.setup_model()
25
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
  def verify_model_files(self):
27
  """檢查本地模型文件是否存在"""
28
- required_files = [
29
- "breeze2-vits.onnx",
30
- "lexicon.txt",
31
- "tokens.txt"
32
- ]
33
 
34
  missing_files = []
35
  for file_name in required_files:
@@ -43,7 +104,9 @@ class TaiwaneseVITSTTS:
43
  print(f"❌ 缺少模型文件: {missing_files}")
44
  print("📂 當前目錄結構:")
45
  for item in Path(".").rglob("*"):
46
- print(f" {item}")
 
 
47
  return False
48
 
49
  print("✅ 所有模型文件都存在")
@@ -57,31 +120,36 @@ class TaiwaneseVITSTTS:
57
  def setup_model(self):
58
  """設置和初始化模型"""
59
  try:
60
- # 檢查模型文件
61
  if not self.verify_model_files():
62
- raise FileNotFoundError("模型文件缺失,請確保 models/ 目錄包含所有必要文件")
63
 
64
- # 檢查 CUDA 可用性
65
  device = "cuda" if torch.cuda.is_available() else "cpu"
66
  provider = "cuda" if device == "cuda" else "cpu"
67
 
68
  print(f"🔧 使用設備: {device.upper()}")
69
  if device == "cuda":
70
- print(f"🎮 GPU: {torch.cuda.get_device_name()}")
71
- print(f"💾 GPU 記憶體: {torch.cuda.get_device_properties(0).total_memory / 1e9:.1f} GB")
 
 
 
72
 
73
- # 配置 VITS 模型
74
  vits_config = sherpa_onnx.OfflineTtsVitsModelConfig(
75
  model=str(self.model_dir / "breeze2-vits.onnx"),
76
  lexicon=str(self.model_dir / "lexicon.txt"),
77
  tokens=str(self.model_dir / "tokens.txt"),
 
78
  )
79
 
 
 
 
80
  # 配置 TTS 模型
81
  model_config = sherpa_onnx.OfflineTtsModelConfig(
82
  vits=vits_config,
83
- num_threads=4 if device == "cpu" else 1, # CPU 使用多線程,GPU 使用單線程
84
- debug=False,
85
  provider=provider,
86
  )
87
 
@@ -89,10 +157,9 @@ class TaiwaneseVITSTTS:
89
  config = sherpa_onnx.OfflineTtsConfig(
90
  model=model_config,
91
  rule_fsts="",
92
- max_num_sentences=2, # 支援較長文本
93
  )
94
 
95
- # 初始化 TTS
96
  print("🔄 正在載入 TTS 模型...")
97
  self.tts = sherpa_onnx.OfflineTts(config)
98
 
@@ -104,10 +171,13 @@ class TaiwaneseVITSTTS:
104
  if len(test_audio.samples) > 0:
105
  print("✅ 模型測試通過!")
106
  else:
107
- print("⚠️ 模型測試失敗,但模型已載入")
108
 
109
  except Exception as e:
110
  print(f"❌ 模型設置失敗: {e}")
 
 
 
111
  raise
112
 
113
  def synthesize(self, text, speaker_id=0, speed=1.0):
@@ -118,7 +188,7 @@ class TaiwaneseVITSTTS:
118
  # 文本預處理
119
  text = text.strip()
120
  if len(text) > 200:
121
- text = text[:200] # 限制文本長度
122
 
123
  try:
124
  print(f"🎤 正在合成語音: {text[:30]}...")
@@ -145,19 +215,21 @@ class TaiwaneseVITSTTS:
145
  if len(audio_array.shape) > 1:
146
  audio_array = audio_array.mean(axis=1)
147
 
148
- # 正規化音頻 (更保守的正規化)
149
  max_val = np.max(np.abs(audio_array))
150
  if max_val > 0:
151
- audio_array = audio_array / max_val * 0.9 # 避免削波
152
 
153
  duration = len(audio_array) / sample_rate
154
  print(f"✅ 語音合成完成! 長度: {duration:.2f}秒")
155
 
156
- return (sample_rate, audio_array), f"✅ 語音合成成功!\n📊 採樣率: {sample_rate}Hz\n⏱️ 時長: {duration:.2f}秒\n🎭 說話人: {speaker_id}"
157
 
158
  except Exception as e:
159
  error_msg = f"❌ 語音合成失敗: {str(e)}"
160
  print(error_msg)
 
 
161
  return None, error_msg
162
 
163
 
@@ -166,15 +238,17 @@ print("🔧 正在初始化 TTS 模型...")
166
  try:
167
  tts_model = TaiwaneseVITSTTS()
168
  print("✅ TTS 系統就緒!")
 
169
  except Exception as e:
170
  print(f"❌ TTS 初始化失敗: {e}")
171
  tts_model = None
 
172
 
173
 
174
  def generate_speech(text, speaker_id, speed):
175
  """Gradio 介面函數"""
176
  if tts_model is None:
177
- return None, "❌ TTS 模型未正確載入"
178
 
179
  return tts_model.synthesize(text, speaker_id, speed)
180
 
@@ -186,12 +260,10 @@ def create_interface():
186
  ["今天天氣很好,適合出去走走。", 1, 1.0],
187
  ["人工智慧技術正在快速發展,為我們的生活帶來許多便利。", 2, 1.2],
188
  ["台灣是一個美麗的島嶼,有著豐富的文化和美食。", 3, 0.9],
189
- ["科技改變生活,創新引領未來。讓我們一起擁抱智慧時代的到來。", 4, 1.1],
190
- ["春天來了,櫻花盛開,微風輕拂,真是個美好的季節。", 5, 0.8],
191
  ]
192
 
193
  # 檢查模型狀態
194
- model_status = "🟢 模型已載入" if tts_model else "🔴 模型載入失敗"
195
  device_info = "🎮 GPU" if torch.cuda.is_available() else "💻 CPU"
196
 
197
  with gr.Blocks(
@@ -226,7 +298,18 @@ def create_interface():
226
  """)
227
 
228
  if not tts_model:
229
- gr.Warning("⚠️ 模型載入失敗,請檢查模型文件是否正確放置")
 
 
 
 
 
 
 
 
 
 
 
230
 
231
  with gr.Row():
232
  with gr.Column(scale=1):
@@ -281,19 +364,20 @@ def create_interface():
281
  status_msg = gr.Textbox(
282
  label="📊 狀態資訊",
283
  interactive=False,
284
- lines=3,
285
- value="準備就緒,請輸入文本並點擊生成語音" if tts_model else "模型載入失敗"
286
  )
287
 
288
  # 範例
289
- gr.Examples(
290
- examples=examples,
291
- inputs=[text_input, speaker_id, speed],
292
- outputs=[audio_output, status_msg],
293
- fn=generate_speech,
294
- cache_examples=False, # 不快取範例以節省空間
295
- label="📚 範例文本 (點擊即可使用)"
296
- )
 
297
 
298
  # 使用說明和技術資訊
299
  with gr.Accordion("📋 使用說明與技術資訊", open=False):
@@ -312,12 +396,14 @@ def create_interface():
312
  - **推理引擎**: Sherpa-ONNX
313
  - **運行設備**: {device_info}
314
  - **模型狀態**: {model_status}
 
315
 
316
- ### 最佳實踐
317
- - 文本長度建議在 10-100 字之間,效最佳
318
- - 避免使用過多標點符號或特殊字符
319
- - 不同說話人有不同的聲音特色,可多嘗試
320
- - 語音速度建議在 0.8-1.5 之間,太快或太慢可能影響清晰度
 
321
  """)
322
 
323
  # 事件綁定
 
1
  """
2
+ Breeze2-VITS 繁體中文語音合成 - 修復版
3
+ 添加 jieba 字典支援以解決中文 TTS 模型問題
4
  """
5
 
6
  import gradio as gr
7
  import numpy as np
8
  import os
9
+ import tempfile
10
+ import shutil
11
  from pathlib import Path
12
  import torch
13
 
 
17
  os.system("pip install sherpa-onnx")
18
  import sherpa_onnx
19
 
20
+ try:
21
+ from huggingface_hub import hf_hub_download
22
+ except ImportError:
23
+ os.system("pip install huggingface_hub")
24
+ from huggingface_hub import hf_hub_download
25
+
26
 
27
  class TaiwaneseVITSTTS:
28
  def __init__(self):
29
  self.tts = None
 
30
  self.model_dir = Path("./models")
31
+ self.dict_dir = Path("./dict")
32
+ self.setup_jieba_dict()
33
  self.setup_model()
34
 
35
+ def setup_jieba_dict(self):
36
+ """設置 jieba 字典目錄"""
37
+ try:
38
+ print("🔧 設置 jieba 字典...")
39
+
40
+ # 創建字典目錄
41
+ self.dict_dir.mkdir(exist_ok=True)
42
+
43
+ # 檢查是否需要下載字典文件
44
+ dict_files_needed = [
45
+ "jieba.dict.utf8",
46
+ "user.dict.utf8",
47
+ "idf.txt.big",
48
+ "stop_words.txt"
49
+ ]
50
+
51
+ # 嘗試從 Hugging Face 下載字典文件(如果有的話)
52
+ # 或者創建基本的字典文件
53
+ self.create_basic_jieba_dict()
54
+
55
+ print(f"✅ jieba 字典設置完成: {self.dict_dir}")
56
+
57
+ except Exception as e:
58
+ print(f"⚠️ jieba 字典設置失敗: {e}")
59
+ # 創建空目錄作為後備
60
+ self.dict_dir.mkdir(exist_ok=True)
61
+
62
+ def create_basic_jieba_dict(self):
63
+ """創建基本的 jieba 字典文件"""
64
+ try:
65
+ # 創建基本的 jieba 字典文件
66
+ jieba_dict_path = self.dict_dir / "jieba.dict.utf8"
67
+ user_dict_path = self.dict_dir / "user.dict.utf8"
68
+ idf_path = self.dict_dir / "idf.txt.big"
69
+ stop_words_path = self.dict_dir / "stop_words.txt"
70
+
71
+ # 如果字典文件不存在,創建空文件
72
+ if not jieba_dict_path.exists():
73
+ jieba_dict_path.touch()
74
+ print(f"📝 創建空字典文件: {jieba_dict_path}")
75
+
76
+ if not user_dict_path.exists():
77
+ user_dict_path.touch()
78
+ print(f"📝 創建用戶字典文件: {user_dict_path}")
79
+
80
+ if not idf_path.exists():
81
+ idf_path.touch()
82
+ print(f"📝 創建 IDF 文件: {idf_path}")
83
+
84
+ if not stop_words_path.exists():
85
+ stop_words_path.touch()
86
+ print(f"📝 創建停用詞文件: {stop_words_path}")
87
+
88
+ except Exception as e:
89
+ print(f"⚠️ 創建基本字典文件失敗: {e}")
90
+
91
  def verify_model_files(self):
92
  """檢查本地模型文件是否存在"""
93
+ required_files = ["breeze2-vits.onnx", "lexicon.txt", "tokens.txt"]
 
 
 
 
94
 
95
  missing_files = []
96
  for file_name in required_files:
 
104
  print(f"❌ 缺少模型文件: {missing_files}")
105
  print("📂 當前目錄結構:")
106
  for item in Path(".").rglob("*"):
107
+ if item.is_file():
108
+ size = item.stat().st_size
109
+ print(f" {item}: {size} bytes")
110
  return False
111
 
112
  print("✅ 所有模型文件都存在")
 
120
  def setup_model(self):
121
  """設置和初始化模型"""
122
  try:
 
123
  if not self.verify_model_files():
124
+ raise FileNotFoundError("模型文件缺失")
125
 
 
126
  device = "cuda" if torch.cuda.is_available() else "cpu"
127
  provider = "cuda" if device == "cuda" else "cpu"
128
 
129
  print(f"🔧 使用設備: {device.upper()}")
130
  if device == "cuda":
131
+ try:
132
+ print(f"🎮 GPU: {torch.cuda.get_device_name()}")
133
+ print(f"💾 GPU 記憶體: {torch.cuda.get_device_properties(0).total_memory / 1e9:.1f} GB")
134
+ except:
135
+ print("🎮 GPU 資訊獲取失敗,但將嘗試使用 GPU")
136
 
137
+ # 配置 VITS 模型 - 關鍵修改:添加字典目錄
138
  vits_config = sherpa_onnx.OfflineTtsVitsModelConfig(
139
  model=str(self.model_dir / "breeze2-vits.onnx"),
140
  lexicon=str(self.model_dir / "lexicon.txt"),
141
  tokens=str(self.model_dir / "tokens.txt"),
142
+ dict_dir=str(self.dict_dir), # 添加字典目錄
143
  )
144
 
145
+ print(f"📚 字典目錄: {self.dict_dir}")
146
+ print(f"📁 字典目錄內容: {list(self.dict_dir.iterdir()) if self.dict_dir.exists() else '目錄不存在'}")
147
+
148
  # 配置 TTS 模型
149
  model_config = sherpa_onnx.OfflineTtsModelConfig(
150
  vits=vits_config,
151
+ num_threads=2 if device == "cpu" else 1,
152
+ debug=True, # 啟用調試模式以獲得更多資訊
153
  provider=provider,
154
  )
155
 
 
157
  config = sherpa_onnx.OfflineTtsConfig(
158
  model=model_config,
159
  rule_fsts="",
160
+ max_num_sentences=1,
161
  )
162
 
 
163
  print("🔄 正在載入 TTS 模型...")
164
  self.tts = sherpa_onnx.OfflineTts(config)
165
 
 
171
  if len(test_audio.samples) > 0:
172
  print("✅ 模型測試通過!")
173
  else:
174
+ print("⚠️ 模型測試失敗,但模型已載入")
175
 
176
  except Exception as e:
177
  print(f"❌ 模型設置失敗: {e}")
178
+ print(f"錯誤類型: {type(e).__name__}")
179
+ import traceback
180
+ print(f"詳細錯誤: {traceback.format_exc()}")
181
  raise
182
 
183
  def synthesize(self, text, speaker_id=0, speed=1.0):
 
188
  # 文本預處理
189
  text = text.strip()
190
  if len(text) > 200:
191
+ text = text[:200]
192
 
193
  try:
194
  print(f"🎤 正在合成語音: {text[:30]}...")
 
215
  if len(audio_array.shape) > 1:
216
  audio_array = audio_array.mean(axis=1)
217
 
218
+ # 正規化音頻
219
  max_val = np.max(np.abs(audio_array))
220
  if max_val > 0:
221
+ audio_array = audio_array / max_val * 0.9
222
 
223
  duration = len(audio_array) / sample_rate
224
  print(f"✅ 語音合成完成! 長度: {duration:.2f}秒")
225
 
226
+ return (sample_rate, audio_array), f"✅ 語音合成成功!\n📊 採樣率: {sample_rate}Hz\n⏱️ 時長: {duration:.2f}秒\n🎭 說話人: {speaker_id}"
227
 
228
  except Exception as e:
229
  error_msg = f"❌ 語音合成失敗: {str(e)}"
230
  print(error_msg)
231
+ import traceback
232
+ print(f"詳細錯誤: {traceback.format_exc()}")
233
  return None, error_msg
234
 
235
 
 
238
  try:
239
  tts_model = TaiwaneseVITSTTS()
240
  print("✅ TTS 系統就緒!")
241
+ model_status = "🟢 模型已載入"
242
  except Exception as e:
243
  print(f"❌ TTS 初始化失敗: {e}")
244
  tts_model = None
245
+ model_status = f"🔴 模型載入失敗: {str(e)}"
246
 
247
 
248
  def generate_speech(text, speaker_id, speed):
249
  """Gradio 介面函數"""
250
  if tts_model is None:
251
+ return None, f"❌ TTS 模型未正確載入\n\n詳情: {model_status}"
252
 
253
  return tts_model.synthesize(text, speaker_id, speed)
254
 
 
260
  ["今天天氣很好,適合出去走走。", 1, 1.0],
261
  ["人工智慧技術正在快速發展,為我們的生活帶來許多便利。", 2, 1.2],
262
  ["台灣是一個美麗的島嶼,有著豐富的文化和美食。", 3, 0.9],
263
+ ["科技改變生活,創新引領未來。", 4, 1.1],
 
264
  ]
265
 
266
  # 檢查模型狀態
 
267
  device_info = "🎮 GPU" if torch.cuda.is_available() else "💻 CPU"
268
 
269
  with gr.Blocks(
 
298
  """)
299
 
300
  if not tts_model:
301
+ gr.Markdown(f"""
302
+ ### ⚠️ 模型載入失敗
303
+
304
+ **錯誤詳情**: {model_status}
305
+
306
+ **可能原因**:
307
+ - 模型文件缺失或損壞
308
+ - jieba 字典配置問題
309
+ - 記憶體不足
310
+
311
+ 請檢查日誌獲取更多資訊。
312
+ """)
313
 
314
  with gr.Row():
315
  with gr.Column(scale=1):
 
364
  status_msg = gr.Textbox(
365
  label="📊 狀態資訊",
366
  interactive=False,
367
+ lines=4,
368
+ value="準備就緒,請輸入文本並點擊生成語音" if tts_model else f"模型載入失敗: {model_status}"
369
  )
370
 
371
  # 範例
372
+ if tts_model: # 只有在模型正常載入時才顯示範例
373
+ gr.Examples(
374
+ examples=examples,
375
+ inputs=[text_input, speaker_id, speed],
376
+ outputs=[audio_output, status_msg],
377
+ fn=generate_speech,
378
+ cache_examples=False,
379
+ label="📚 範例文本 (點擊即可使用)"
380
+ )
381
 
382
  # 使用說明和技術資訊
383
  with gr.Accordion("📋 使用說明與技術資訊", open=False):
 
396
  - **推理引擎**: Sherpa-ONNX
397
  - **運行設備**: {device_info}
398
  - **模型狀態**: {model_status}
399
+ - **jieba 字典**: {'✅ 已配置' if Path('./dict').exists() else '❌ 未配置'}
400
 
401
+ ### 故障排除
402
+ 遇到問題:
403
+ 1. 檢查文本是否為繁體中文
404
+ 2. 嘗試較短的文本 (10-50字)
405
+ 3. 重新整理頁面
406
+ 4. 檢查瀏覽器控制台錯誤
407
  """)
408
 
409
  # 事件綁定