dseditor commited on
Commit
7bca07a
·
verified ·
1 Parent(s): a1194fa

Upload 2 files

Browse files
Files changed (2) hide show
  1. app.py +257 -137
  2. text_mapping.txt +217 -0
app.py CHANGED
@@ -1,13 +1,12 @@
1
  """
2
- Breeze2-VITS 繁體中文語音合成 - 單說話人版本
3
- 專為台灣國語優化的高品質語音合成系統
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
 
@@ -24,11 +23,171 @@ except ImportError:
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
 
@@ -36,35 +195,25 @@ class TaiwaneseVITSTTS:
36
  """設置 jieba 字典目錄"""
37
  try:
38
  print("🔧 設置 jieba 字典...")
39
-
40
- # 創建字典目錄
41
  self.dict_dir.mkdir(exist_ok=True)
42
-
43
- # 創建基本的字典文件
44
  self.create_basic_jieba_dict()
45
-
46
  print(f"✅ jieba 字典設置完成: {self.dict_dir}")
47
-
48
  except Exception as e:
49
  print(f"⚠️ jieba 字典設置失敗: {e}")
50
- # 創建空目錄作為後備
51
  self.dict_dir.mkdir(exist_ok=True)
52
 
53
  def create_basic_jieba_dict(self):
54
  """創建基本的 jieba 字典文件"""
55
  try:
56
- # 創建基本的 jieba 字典文件
57
  jieba_dict_path = self.dict_dir / "jieba.dict.utf8"
58
  user_dict_path = self.dict_dir / "user.dict.utf8"
59
  idf_path = self.dict_dir / "idf.txt.big"
60
  stop_words_path = self.dict_dir / "stop_words.txt"
61
 
62
- # 如果字典文件不存在,創建空文件
63
  for file_path in [jieba_dict_path, user_dict_path, idf_path, stop_words_path]:
64
  if not file_path.exists():
65
  file_path.touch()
66
  print(f"📝 創建字典文件: {file_path.name}")
67
-
68
  except Exception as e:
69
  print(f"⚠️ 創建基本字典文件失敗: {e}")
70
 
@@ -85,11 +234,6 @@ class TaiwaneseVITSTTS:
85
  return False
86
 
87
  print("✅ 所有模型文件都存在")
88
- for file_name in required_files:
89
- file_path = self.model_dir / file_name
90
- size_mb = file_path.stat().st_size / (1024 * 1024)
91
- print(f" 📄 {file_name}: {size_mb:.1f} MB")
92
-
93
  return True
94
 
95
  def setup_model(self):
@@ -102,14 +246,7 @@ class TaiwaneseVITSTTS:
102
  provider = "cuda" if device == "cuda" else "cpu"
103
 
104
  print(f"🔧 使用設備: {device.upper()}")
105
- if device == "cuda":
106
- try:
107
- print(f"🎮 GPU: {torch.cuda.get_device_name()}")
108
- print(f"💾 GPU 記憶體: {torch.cuda.get_device_properties(0).total_memory / 1e9:.1f} GB")
109
- except:
110
- print("🎮 GPU 資訊獲取失敗,但將嘗試使用 GPU")
111
 
112
- # 配置 VITS 模型
113
  vits_config = sherpa_onnx.OfflineTtsVitsModelConfig(
114
  model=str(self.model_dir / "breeze2-vits.onnx"),
115
  lexicon=str(self.model_dir / "lexicon.txt"),
@@ -117,79 +254,66 @@ class TaiwaneseVITSTTS:
117
  dict_dir=str(self.dict_dir),
118
  )
119
 
120
- print(f"📚 字典目錄: {self.dict_dir}")
121
-
122
- # 配置 TTS 模型
123
  model_config = sherpa_onnx.OfflineTtsModelConfig(
124
  vits=vits_config,
125
  num_threads=2 if device == "cpu" else 1,
126
- debug=False, # 關閉調試模式以減少日誌
127
  provider=provider,
128
  )
129
 
130
- # 創建 TTS 配置
131
  config = sherpa_onnx.OfflineTtsConfig(
132
  model=model_config,
133
  rule_fsts="",
134
- max_num_sentences=2, # 支援較長句子
135
  )
136
 
137
  print("🔄 正在載入 TTS 模型...")
138
  self.tts = sherpa_onnx.OfflineTts(config)
139
-
140
  print("🚀 TTS 模型初始化成功!")
141
 
142
  # 測試模型
143
- print("🧪 進行模型測試...")
144
  test_audio = self.tts.generate(text="測試", sid=0, speed=1.0)
145
  if len(test_audio.samples) > 0:
146
  print("✅ 模型測試通過!")
147
- else:
148
- print("⚠️ 模型測試失敗,但模型已載入")
149
 
150
  except Exception as e:
151
  print(f"❌ 模型設置失敗: {e}")
152
- print(f"錯誤類型: {type(e).__name__}")
153
  import traceback
154
  print(f"詳細錯誤: {traceback.format_exc()}")
155
  raise
156
 
157
- def synthesize(self, text, speed=1.0):
158
- """合成語音 - 單說話人版本"""
159
  if not text or not text.strip():
160
  return None, "❌ 請輸入文本"
161
 
162
- # 文本預處理
163
- text = text.strip()
164
- if len(text) > 500: # 增加文本長度限制
 
 
 
 
 
 
165
  text = text[:500]
166
 
167
  try:
168
- print(f"🎤 正在合成語音: {text[:50]}...")
169
- print(f"⚡ 語音速度: {speed}x")
170
-
171
- # 生成語音 - 固定使用說話人 ID 0
172
- audio = self.tts.generate(
173
- text=text,
174
- sid=0, # 固定使用第一個說話人
175
- speed=speed
176
- )
177
 
178
- # 獲取音頻數據
179
  samples = audio.samples
180
  sample_rate = audio.sample_rate
181
 
182
  if len(samples) == 0:
183
  return None, "❌ 語音生成失敗:生成的音頻為空"
184
 
185
- # 轉換為 numpy 陣列
186
  audio_array = np.array(samples, dtype=np.float32)
187
-
188
- # 確保是單聲道
189
  if len(audio_array.shape) > 1:
190
  audio_array = audio_array.mean(axis=1)
191
 
192
- # 正規化音頻
193
  max_val = np.max(np.abs(audio_array))
194
  if max_val > 0:
195
  audio_array = audio_array / max_val * 0.9
@@ -197,7 +321,11 @@ class TaiwaneseVITSTTS:
197
  duration = len(audio_array) / sample_rate
198
  print(f"✅ 語音合成完成! 長度: {duration:.2f}秒")
199
 
200
- return (sample_rate, audio_array), f"✅ 語音合成成功!\n📊 採樣率: {sample_rate}Hz\n⏱️ 時長: {duration:.2f}秒\n🎭 台灣國語聲音"
 
 
 
 
201
 
202
  except Exception as e:
203
  error_msg = f"❌ 語音合成失敗: {str(e)}"
@@ -217,34 +345,33 @@ except Exception as e:
217
  model_status = f"🔴 模型載入失敗: {str(e)}"
218
 
219
 
220
- def generate_speech(text, speed):
221
- """Gradio 介面函數 - 移除說話人參數"""
222
  if tts_model is None:
223
  return None, f"❌ TTS 模型未正確載入\n\n詳情: {model_status}"
224
 
225
- return tts_model.synthesize(text, speed)
226
 
227
 
228
  def create_interface():
229
- # 預設範例文本 - 移除說話人參數
230
  examples = [
231
- ["你好,歡迎使用繁體中文語音合成系統!", 1.0],
232
- ["今天天氣很好,適合出去走走。", 1.0],
233
- ["人工智慧技術正在快速發展,為我們的生活帶來許多便利。", 1.1],
234
- ["台灣是一個美麗的島嶼,有著豐富的文化和美食。", 0.9],
235
- ["科技改變生活,創新引領未來。讓我們一起擁抱智慧時代的到來。", 1.2],
236
- ["春天來了,櫻花盛開,微風輕拂,真是個美好的季節。", 0.8],
237
  ]
238
 
239
- # 檢查模型狀態
240
  device_info = "🎮 GPU" if torch.cuda.is_available() else "💻 CPU"
241
 
242
  with gr.Blocks(
243
- title="繁體中文語音合成 - Breeze2-VITS",
244
  theme=gr.themes.Soft(),
245
  css="""
246
  .gradio-container {
247
- max-width: 900px !important;
248
  margin: auto !important;
249
  }
250
  .status-box {
@@ -267,53 +394,49 @@ def create_interface():
267
 
268
  gr.HTML(f"""
269
  <div class="status-box">
270
- <h1>🎙️ 繁體中文語音合成 - Breeze2-VITS</h1>
271
  <p><strong>狀態:</strong> {model_status} | <strong>設備:</strong> {device_info}</p>
272
  </div>
273
  """)
274
 
275
  gr.HTML("""
276
  <div class="feature-box">
277
- <strong>🇹🇼 專業台灣國語 TTS</strong> | MediaTek 開發,專為繁體中文優化
278
  </div>
279
  """)
280
 
281
  if not tts_model:
282
  gr.Markdown(f"""
283
  ### ⚠️ 模型載入失敗
284
-
285
  **錯誤詳情**: {model_status}
286
-
287
- **可能原因**:
288
- - 模型文件缺失或損壞
289
- - jieba 字典配置問題
290
- - 記憶體不足
291
-
292
- 請檢查日誌獲取更多資訊。
293
  """)
294
 
295
  with gr.Row():
296
  with gr.Column(scale=1):
297
- # 文本輸入
298
  text_input = gr.Textbox(
299
- label="📝 輸入文本 (最多500字)",
300
- placeholder="請輸入要合成的繁體中文文本...",
301
  lines=5,
302
  max_lines=8,
303
- value="你好,這是一個語音合成測試。歡迎使用繁體中文TTS系統!"
304
  )
305
 
306
- # 只保留語音速度控制
307
- speed = gr.Slider(
308
- label="⚡ 語音速度",
309
- minimum=0.5,
310
- maximum=2.0,
311
- step=0.1,
312
- value=1.0,
313
- info="調節語音播放速度 (0.5x 慢速 ↔ 2.0x 快速)"
314
- )
 
 
 
 
 
 
315
 
316
- # 生成按鈕
317
  generate_btn = gr.Button(
318
  "🎵 生成台灣國語語音",
319
  variant="primary",
@@ -322,7 +445,6 @@ def create_interface():
322
  )
323
 
324
  with gr.Column(scale=1):
325
- # 音頻輸出
326
  audio_output = gr.Audio(
327
  label="🔊 生成的語音",
328
  type="numpy",
@@ -330,80 +452,78 @@ def create_interface():
330
  show_download_button=True
331
  )
332
 
333
- # 狀態訊息
334
  status_msg = gr.Textbox(
335
  label="📊 狀態資訊",
336
  interactive=False,
337
- lines=4,
338
  value="準備就緒,請輸入文本並點擊生成語音" if tts_model else f"模型載入失敗: {model_status}"
339
  )
340
 
341
- # 範例
342
- if tts_model: # 只有在模型正常載入時才顯示範例
343
  gr.Examples(
344
  examples=examples,
345
- inputs=[text_input, speed], # 移除說話人參數
346
  outputs=[audio_output, status_msg],
347
  fn=generate_speech,
348
  cache_examples=False,
349
- label="📚 範例文本 (點擊即可使用)"
350
  )
351
 
352
- # 使用說明和技術資訊
353
- with gr.Accordion("📋 使用說明與技術資訊", open=False):
354
  gr.Markdown(f"""
355
- ### 🚀 使用說明
356
- 1. 在文本框中輸入繁體中文文本 (支援最多500字)
357
- 2. 調整語音速度 (建議範圍 0.8x - 1.5x)
358
- 3. 點擊「生成台灣國語語音」按鈕
359
- 4. 在右側播放和下載生成的語音
360
-
361
- ### 🎯 模型特色
362
- - **專業台灣國語**: 經過台灣語料訓練,發音自然
363
- - **高品質合成**: 使用 VITS 架構,語音清晰流暢
364
- - **移動優化**: 輕量化設計,適合各種設備
365
- - **即時生成**: 快速推理,支援即時語音合成
 
 
 
 
 
 
 
 
 
366
 
367
  ### 🔧 技術資訊
368
  - **模型**: MediaTek Breeze2-VITS-onnx
369
- - **語言**: 繁體中文 (台灣國語)
370
- - **採樣率**: 22050 Hz
371
- - **推理引擎**: Sherpa-ONNX
372
  - **運行設備**: {device_info}
373
  - **模型狀態**: {model_status}
374
- - **字典配置**: {'✅ 已配置' if Path('./dict').exists() else '❌ 未配置'}
375
 
376
- ### 📝 最佳實踐
377
- - **文本長度**: 建議單次合成 10-100 字,效果最佳
378
- - **標點符號**: 適當使用逗號和句號來控制語調停頓
379
- - **語音速度**: 一般對話建議 1.0x,朗讀建議 0.9x,快速播報建議 1.3x
380
- - **特殊字符**: 避免使用過多英文或特殊符號
 
381
 
382
  ### 🛠️ 故障排除
383
- 如果遇到問題:
384
- 1. 檢查文本是否為繁體中文
385
- 2. 嘗試較短的文本 (10-50字)
386
- 3. 重新整理頁面重新載入模型
387
- 4. 檢查瀏覽器控制台錯誤訊息
388
-
389
- ### 📄 授權資訊
390
- - **模型**: MediaTek Research 開源模型
391
- - **使用範圍**: 研究和個人使用
392
- - **商業使用**: 請參考 MediaTek 授權條款
393
  """)
394
 
395
- # 事件綁定 - 移除說話人參數
396
  generate_btn.click(
397
  fn=generate_speech,
398
- inputs=[text_input, speed],
399
  outputs=[audio_output, status_msg],
400
  api_name="generate_speech"
401
  )
402
 
403
- # 鍵盤快捷鍵
404
  text_input.submit(
405
  fn=generate_speech,
406
- inputs=[text_input, speed],
407
  outputs=[audio_output, status_msg]
408
  )
409
 
 
1
  """
2
+ Breeze2-VITS 繁體中文語音合成 - 增強版
3
+ 支援英文和數字自動轉換為中文發音
4
  """
5
 
6
  import gradio as gr
7
  import numpy as np
8
  import os
9
+ import re
 
10
  from pathlib import Path
11
  import torch
12
 
 
23
  from huggingface_hub import hf_hub_download
24
 
25
 
26
+ class TextConverter:
27
+ """文本轉換器,將英文和數字轉換為中文發音"""
28
+
29
+ def __init__(self, mapping_file="text_mapping.txt"):
30
+ self.mapping_file = Path(mapping_file)
31
+ self.conversion_map = {}
32
+ self.load_mapping()
33
+
34
+ def load_mapping(self):
35
+ """載入轉換對照表"""
36
+ try:
37
+ if self.mapping_file.exists():
38
+ with open(self.mapping_file, 'r', encoding='utf-8') as f:
39
+ lines = f.readlines()
40
+
41
+ for line in lines:
42
+ line = line.strip()
43
+ # 跳過註釋和空行
44
+ if line.startswith('#') or not line:
45
+ continue
46
+
47
+ if '|' in line:
48
+ original, chinese = line.split('|', 1)
49
+ self.conversion_map[original.strip().lower()] = chinese.strip()
50
+
51
+ print(f"✅ 載入 {len(self.conversion_map)} 個轉換規則")
52
+ else:
53
+ print(f"⚠️ 轉換對照表文件不存在: {self.mapping_file}")
54
+ self.create_default_mapping()
55
+ except Exception as e:
56
+ print(f"❌ 載入轉換對照表失敗: {e}")
57
+ self.create_default_mapping()
58
+
59
+ def create_default_mapping(self):
60
+ """創建預設的轉換對照表"""
61
+ default_mappings = {
62
+ # 數字
63
+ '0': '零', '1': '一', '2': '二', '3': '三', '4': '四',
64
+ '5': '五', '6': '六', '7': '七', '8': '八', '9': '九',
65
+ '10': '十', '100': '一百', '1000': '一千',
66
+
67
+ # 常用英文
68
+ 'hello': '哈囉', 'hi': '嗨', 'bye': '拜拜', 'ok': '歐凱',
69
+ 'yes': '是的', 'no': '不', 'good': '好的', 'bad': '不好',
70
+
71
+ # 技術詞彙
72
+ 'ai': '人工智慧', 'api': '程式介面', 'app': '應用程式',
73
+ 'cpu': '中央處理器', 'gpu': '圖形處理器',
74
+
75
+ # 字母
76
+ 'a': '欸', 'b': '比', 'c': '西', 'd': '迪', 'e': '伊'
77
+ }
78
+
79
+ self.conversion_map = default_mappings
80
+ print(f"✅ 使用預設轉換規則: {len(default_mappings)} 個")
81
+
82
+ def convert_numbers(self, text):
83
+ """轉換連續數字為中文"""
84
+ def number_to_chinese(match):
85
+ number = match.group()
86
+ if len(number) <= 2: # 簡單數字直接對應
87
+ result = ""
88
+ for digit in number:
89
+ result += self.conversion_map.get(digit, digit)
90
+ return result
91
+ else:
92
+ # 複雜數字處理
93
+ return self.convert_large_number(number)
94
+
95
+ # 匹配連續數字
96
+ text = re.sub(r'\d+', number_to_chinese, text)
97
+ return text
98
+
99
+ def convert_large_number(self, number_str):
100
+ """轉換大數字為中文"""
101
+ try:
102
+ num = int(number_str)
103
+ if num == 0:
104
+ return '零'
105
+
106
+ # 簡化的數字轉換(支援到萬)
107
+ units = ['', '十', '百', '千', '萬']
108
+ digits = ['零', '一', '二', '三', '四', '五', '六', '七', '八', '九']
109
+
110
+ if num < 10:
111
+ return digits[num]
112
+ elif num < 100:
113
+ tens = num // 10
114
+ ones = num % 10
115
+ result = digits[tens] + '十'
116
+ if ones > 0:
117
+ result += digits[ones]
118
+ return result
119
+ elif num < 1000:
120
+ hundreds = num // 100
121
+ remainder = num % 100
122
+ result = digits[hundreds] + '百'
123
+ if remainder > 0:
124
+ if remainder < 10:
125
+ result += '零' + digits[remainder]
126
+ else:
127
+ result += self.convert_large_number(str(remainder))
128
+ return result
129
+ else:
130
+ # 對於更大的數字,簡化處理
131
+ return number_str # 保持原樣
132
+ except:
133
+ return number_str
134
+
135
+ def convert_english(self, text):
136
+ """轉換英文單詞為中文"""
137
+ # 按長度排序,先處理長詞彙
138
+ sorted_words = sorted(self.conversion_map.keys(), key=len, reverse=True)
139
+
140
+ for english_word in sorted_words:
141
+ if len(english_word) > 1: # 跳過單字母,後面單��處理
142
+ chinese_word = self.conversion_map[english_word]
143
+ # 使用單詞邊界匹配,不區分大小寫
144
+ pattern = r'\b' + re.escape(english_word) + r'\b'
145
+ text = re.sub(pattern, chinese_word, text, flags=re.IGNORECASE)
146
+
147
+ return text
148
+
149
+ def convert_single_letters(self, text):
150
+ """轉換單個英文字母"""
151
+ def letter_to_chinese(match):
152
+ letter = match.group().lower()
153
+ return self.conversion_map.get(letter, letter)
154
+
155
+ # 匹配獨立的英文字母
156
+ text = re.sub(r'\b[a-zA-Z]\b', letter_to_chinese, text)
157
+ return text
158
+
159
+ def convert_text(self, text):
160
+ """主要轉換函數"""
161
+ if not text:
162
+ return text
163
+
164
+ original_text = text
165
+ print(f"🔄 原始文本: {original_text}")
166
+
167
+ # 1. 轉換英文單詞
168
+ text = self.convert_english(text)
169
+
170
+ # 2. 轉換數字
171
+ text = self.convert_numbers(text)
172
+
173
+ # 3. 轉換剩餘的單個字母
174
+ text = self.convert_single_letters(text)
175
+
176
+ # 4. 清理多餘空格
177
+ text = re.sub(r'\s+', ' ', text).strip()
178
+
179
+ if text != original_text:
180
+ print(f"✅ 轉換後文本: {text}")
181
+
182
+ return text
183
+
184
+
185
  class TaiwaneseVITSTTS:
186
  def __init__(self):
187
  self.tts = None
188
  self.model_dir = Path("./models")
189
  self.dict_dir = Path("./dict")
190
+ self.text_converter = TextConverter()
191
  self.setup_jieba_dict()
192
  self.setup_model()
193
 
 
195
  """設置 jieba 字典目錄"""
196
  try:
197
  print("🔧 設置 jieba 字典...")
 
 
198
  self.dict_dir.mkdir(exist_ok=True)
 
 
199
  self.create_basic_jieba_dict()
 
200
  print(f"✅ jieba 字典設置完成: {self.dict_dir}")
 
201
  except Exception as e:
202
  print(f"⚠️ jieba 字典設置失敗: {e}")
 
203
  self.dict_dir.mkdir(exist_ok=True)
204
 
205
  def create_basic_jieba_dict(self):
206
  """創建基本的 jieba 字典文件"""
207
  try:
 
208
  jieba_dict_path = self.dict_dir / "jieba.dict.utf8"
209
  user_dict_path = self.dict_dir / "user.dict.utf8"
210
  idf_path = self.dict_dir / "idf.txt.big"
211
  stop_words_path = self.dict_dir / "stop_words.txt"
212
 
 
213
  for file_path in [jieba_dict_path, user_dict_path, idf_path, stop_words_path]:
214
  if not file_path.exists():
215
  file_path.touch()
216
  print(f"📝 創建字典文件: {file_path.name}")
 
217
  except Exception as e:
218
  print(f"⚠️ 創建基本字典文件失敗: {e}")
219
 
 
234
  return False
235
 
236
  print("✅ 所有模型文件都存在")
 
 
 
 
 
237
  return True
238
 
239
  def setup_model(self):
 
246
  provider = "cuda" if device == "cuda" else "cpu"
247
 
248
  print(f"🔧 使用設備: {device.upper()}")
 
 
 
 
 
 
249
 
 
250
  vits_config = sherpa_onnx.OfflineTtsVitsModelConfig(
251
  model=str(self.model_dir / "breeze2-vits.onnx"),
252
  lexicon=str(self.model_dir / "lexicon.txt"),
 
254
  dict_dir=str(self.dict_dir),
255
  )
256
 
 
 
 
257
  model_config = sherpa_onnx.OfflineTtsModelConfig(
258
  vits=vits_config,
259
  num_threads=2 if device == "cpu" else 1,
260
+ debug=False,
261
  provider=provider,
262
  )
263
 
 
264
  config = sherpa_onnx.OfflineTtsConfig(
265
  model=model_config,
266
  rule_fsts="",
267
+ max_num_sentences=2,
268
  )
269
 
270
  print("🔄 正在載入 TTS 模型...")
271
  self.tts = sherpa_onnx.OfflineTts(config)
 
272
  print("🚀 TTS 模型初始化成功!")
273
 
274
  # 測試模型
 
275
  test_audio = self.tts.generate(text="測試", sid=0, speed=1.0)
276
  if len(test_audio.samples) > 0:
277
  print("✅ 模型測試通過!")
 
 
278
 
279
  except Exception as e:
280
  print(f"❌ 模型設置失敗: {e}")
 
281
  import traceback
282
  print(f"詳細錯誤: {traceback.format_exc()}")
283
  raise
284
 
285
+ def synthesize(self, text, speed=1.0, enable_conversion=True):
286
+ """合成語音"""
287
  if not text or not text.strip():
288
  return None, "❌ 請輸入文本"
289
 
290
+ original_text = text.strip()
291
+
292
+ # 文本轉換
293
+ if enable_conversion:
294
+ text = self.text_converter.convert_text(original_text)
295
+ else:
296
+ text = original_text
297
+
298
+ if len(text) > 500:
299
  text = text[:500]
300
 
301
  try:
302
+ print(f"🎤 正在合成語音...")
303
+ if enable_conversion and text != original_text:
304
+ print(f"📝 使用轉換後文本: {text}")
 
 
 
 
 
 
305
 
306
+ audio = self.tts.generate(text=text, sid=0, speed=speed)
307
  samples = audio.samples
308
  sample_rate = audio.sample_rate
309
 
310
  if len(samples) == 0:
311
  return None, "❌ 語音生成失敗:生成的音頻為空"
312
 
 
313
  audio_array = np.array(samples, dtype=np.float32)
 
 
314
  if len(audio_array.shape) > 1:
315
  audio_array = audio_array.mean(axis=1)
316
 
 
317
  max_val = np.max(np.abs(audio_array))
318
  if max_val > 0:
319
  audio_array = audio_array / max_val * 0.9
 
321
  duration = len(audio_array) / sample_rate
322
  print(f"✅ 語音合成完成! 長度: {duration:.2f}秒")
323
 
324
+ status_info = f"✅ 語音合成成功!\n📊 採樣率: {sample_rate}Hz\n⏱️ 時長: {duration:.2f}"
325
+ if enable_conversion and text != original_text:
326
+ status_info += f"\n🔄 已轉換: {original_text} → {text}"
327
+
328
+ return (sample_rate, audio_array), status_info
329
 
330
  except Exception as e:
331
  error_msg = f"❌ 語音合成失敗: {str(e)}"
 
345
  model_status = f"🔴 模型載入失敗: {str(e)}"
346
 
347
 
348
+ def generate_speech(text, speed, enable_conversion):
349
+ """Gradio 介面函數"""
350
  if tts_model is None:
351
  return None, f"❌ TTS 模型未正確載入\n\n詳情: {model_status}"
352
 
353
+ return tts_model.synthesize(text, speed, enable_conversion)
354
 
355
 
356
  def create_interface():
357
+ # 預設範例文本
358
  examples = [
359
+ ["你好,歡迎使用繁體中文語音合成系統!", 1.0, True],
360
+ ["今天是2024年1月1日,天氣很好。", 1.0, True],
361
+ ["我的email是test@gmail.com,請聯繫我。", 1.0, True],
362
+ ["這是一個AI技術的demo,使用Python開發。", 1.1, True],
363
+ ["Hello world! 這是一個測試。", 1.0, True],
364
+ ["iPhone 15和Samsung Galaxy哪個比較好?", 0.9, True],
365
  ]
366
 
 
367
  device_info = "🎮 GPU" if torch.cuda.is_available() else "💻 CPU"
368
 
369
  with gr.Blocks(
370
+ title="繁體中文語音合成 - Breeze2-VITS Enhanced",
371
  theme=gr.themes.Soft(),
372
  css="""
373
  .gradio-container {
374
+ max-width: 1000px !important;
375
  margin: auto !important;
376
  }
377
  .status-box {
 
394
 
395
  gr.HTML(f"""
396
  <div class="status-box">
397
+ <h1>🎙️ 繁體中文語音合成 - Breeze2-VITS Enhanced</h1>
398
  <p><strong>狀態:</strong> {model_status} | <strong>設備:</strong> {device_info}</p>
399
  </div>
400
  """)
401
 
402
  gr.HTML("""
403
  <div class="feature-box">
404
+ <strong>🇹🇼 專業台灣國語 TTS</strong> | 🔄 自動英數轉換 | 🎯 智慧文本處理
405
  </div>
406
  """)
407
 
408
  if not tts_model:
409
  gr.Markdown(f"""
410
  ### ⚠️ 模型載入失敗
 
411
  **錯誤詳情**: {model_status}
 
 
 
 
 
 
 
412
  """)
413
 
414
  with gr.Row():
415
  with gr.Column(scale=1):
 
416
  text_input = gr.Textbox(
417
+ label="📝 輸入文本 (支援中英混合、數字)",
418
+ placeholder="請輸入要合成的文本,支援中文、英文、數字混合...",
419
  lines=5,
420
  max_lines=8,
421
+ value="你好!今天是2024年,歡迎使用AI語音合成系統。"
422
  )
423
 
424
+ with gr.Row():
425
+ speed = gr.Slider(
426
+ label="⚡ 語音速度",
427
+ minimum=0.5,
428
+ maximum=2.0,
429
+ step=0.1,
430
+ value=1.0,
431
+ info="調節語音播放速度"
432
+ )
433
+
434
+ enable_conversion = gr.Checkbox(
435
+ label="🔄 啟用英數轉換",
436
+ value=True,
437
+ info="自動將英文和數字轉換為中文發音"
438
+ )
439
 
 
440
  generate_btn = gr.Button(
441
  "🎵 生成台灣國語語音",
442
  variant="primary",
 
445
  )
446
 
447
  with gr.Column(scale=1):
 
448
  audio_output = gr.Audio(
449
  label="🔊 生成的語音",
450
  type="numpy",
 
452
  show_download_button=True
453
  )
454
 
 
455
  status_msg = gr.Textbox(
456
  label="📊 狀態資訊",
457
  interactive=False,
458
+ lines=5,
459
  value="準備就緒,請輸入文本並點擊生成語音" if tts_model else f"模型載入失敗: {model_status}"
460
  )
461
 
462
+ if tts_model:
 
463
  gr.Examples(
464
  examples=examples,
465
+ inputs=[text_input, speed, enable_conversion],
466
  outputs=[audio_output, status_msg],
467
  fn=generate_speech,
468
  cache_examples=False,
469
+ label="📚 範例文本 (支援中英數混合)"
470
  )
471
 
472
+ with gr.Accordion("📋 使用說明與功能特色", open=False):
 
473
  gr.Markdown(f"""
474
+ ### 🚀 主要功能
475
+
476
+ #### 🔄 智慧文本轉換
477
+ - **英文轉換**: hello → 哈囉, AI → 人工智慧
478
+ - **數字轉換**: 123 → 一二三, 2024 → 二零二四
479
+ - **品牌名稱**: Apple → 蘋果, Google → 谷歌
480
+ - **技術詞彙**: API → 程式介面, CPU → 中央處理器
481
+
482
+ #### 🎯 支援內容
483
+ - 繁體中文文本
484
+ - 英文單詞和句子
485
+ - 阿拉伯數字
486
+ - 混合語言文本
487
+ - 常見縮寫和品牌
488
+
489
+ ### 📝 使用技巧
490
+ 1. **啟用轉換**: 勾選「啟用英數轉換」自動處理英文和數字
491
+ 2. **關閉轉換**: 取消勾選以使用原始文本(純中文效果最佳)
492
+ 3. **混合文本**: 支援「今天天氣很好,temperature是25度」這樣的混合文本
493
+ 4. **專有名詞**: 系統已內建常見品牌和技術詞彙的中文發音
494
 
495
  ### 🔧 技術資訊
496
  - **模型**: MediaTek Breeze2-VITS-onnx
497
+ - **轉換規則**: {len(tts_model.text_converter.conversion_map) if tts_model else 0} 個內建對照
498
+ - **支援格式**: 中文、英文、數字、符號
 
499
  - **運行設備**: {device_info}
500
  - **模型狀態**: {model_status}
 
501
 
502
+ ### ⚙️ 自定義轉換
503
+ 您可以編輯 `text_mapping.txt` 文件來添加自定義的轉換規則:
504
+ ```
505
+ your_word|您的中文發音
506
+ brand_name|品牌中文名
507
+ ```
508
 
509
  ### 🛠️ 故障排除
510
+ - **英文不發音**: 確保啟用「英數轉換」功能
511
+ - **數字不發音**: 檢查轉換功能是否開啟
512
+ - **發音不準**: 嘗試關閉轉換使用純中文文本
513
+ - **載入失敗**: 檢查模型文件是否完整
 
 
 
 
 
 
514
  """)
515
 
516
+ # 事件綁定
517
  generate_btn.click(
518
  fn=generate_speech,
519
+ inputs=[text_input, speed, enable_conversion],
520
  outputs=[audio_output, status_msg],
521
  api_name="generate_speech"
522
  )
523
 
 
524
  text_input.submit(
525
  fn=generate_speech,
526
+ inputs=[text_input, speed, enable_conversion],
527
  outputs=[audio_output, status_msg]
528
  )
529
 
text_mapping.txt ADDED
@@ -0,0 +1,217 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Breeze2-VITS 英文和數字轉中文對照表
2
+ # 格式:原文|中文發音
3
+ # 支援正則表達式匹配
4
+
5
+ # === 數字轉換 ===
6
+ 0|零
7
+ 1|一
8
+ 2|二
9
+ 3|三
10
+ 4|四
11
+ 5|五
12
+ 6|六
13
+ 7|七
14
+ 8|八
15
+ 9|九
16
+ 10|十
17
+ 11|十一
18
+ 12|十二
19
+ 13|十三
20
+ 14|十四
21
+ 15|十五
22
+ 16|十六
23
+ 17|十七
24
+ 18|十八
25
+ 19|十九
26
+ 20|二十
27
+
28
+ # === 常用英文單詞 ===
29
+ hello|哈囉
30
+ hi|嗨
31
+ hey|嘿
32
+ bye|拜拜
33
+ goodbye|再見
34
+ ok|歐凱
35
+ okay|歐凱
36
+ yes|是的
37
+ no|不
38
+ good|好的
39
+ bad|不好
40
+ new|新的
41
+ old|舊的
42
+ big|大的
43
+ small|小的
44
+ long|長的
45
+ short|短的
46
+
47
+ # === 技術詞彙 ===
48
+ ai|人工智慧
49
+ cpu|中央處理器
50
+ gpu|圖形處理器
51
+ app|應用程式
52
+ api|程式介面
53
+ url|網址
54
+ html|超文本標記語言
55
+ css|層疊樣式表
56
+ js|腳本語言
57
+ javascript|腳本語言
58
+ python|派森
59
+ java|爪哇
60
+ php|皮H皮
61
+ sql|結構化查詢語言
62
+ json|資料格式
63
+ xml|標記語言
64
+ http|超文本傳輸協定
65
+ https|安全超文本傳輸協定
66
+ ftp|檔案傳輸協定
67
+ ssh|安全外殼協定
68
+
69
+ # === 社交媒體和品牌 ===
70
+ facebook|臉書
71
+ instagram|照片分享平台
72
+ twitter|推特
73
+ youtube|影片平台
74
+ google|谷歌
75
+ apple|蘋果
76
+ microsoft|微軟
77
+ amazon|亞馬遜
78
+ netflix|網飛
79
+ spotify|音樂平台
80
+ uber|優步
81
+ tesla|特斯拉
82
+ nike|耐吉
83
+ samsung|三星
84
+ sony|索尼
85
+
86
+ # === 常用英文字母 ===
87
+ a|欸
88
+ b|比
89
+ c|西
90
+ d|迪
91
+ e|伊
92
+ f|艾芙
93
+ g|吉
94
+ h|艾奇
95
+ i|愛
96
+ j|傑
97
+ k|凱
98
+ l|艾爾
99
+ m|艾姆
100
+ n|恩
101
+ o|歐
102
+ p|皮
103
+ q|克優
104
+ r|艾爾
105
+ s|艾斯
106
+ t|提
107
+ u|優
108
+ v|威
109
+ w|達布優
110
+ x|艾克斯
111
+ y|歪
112
+ z|資德
113
+
114
+ # === 時間相關 ===
115
+ am|上午
116
+ pm|下午
117
+ monday|星期一
118
+ tuesday|星期二
119
+ wednesday|星期三
120
+ thursday|星期四
121
+ friday|星期五
122
+ saturday|星期六
123
+ sunday|星期日
124
+ january|一月
125
+ february|二月
126
+ march|三月
127
+ april|四月
128
+ may|五月
129
+ june|六月
130
+ july|七月
131
+ august|八月
132
+ september|九月
133
+ october|十月
134
+ november|十一月
135
+ december|十二月
136
+
137
+ # === 常用縮寫 ===
138
+ diy|自己動手做
139
+ wifi|無線網路
140
+ usb|通用序列匯流排
141
+ cd|光碟
142
+ dvd|數位影音光碟
143
+ mp3|音樂檔案
144
+ mp4|影片檔案
145
+ pdf|可攜式文件格式
146
+ jpg|圖片格式
147
+ png|圖片格式
148
+ gif|動圖格式
149
+ zip|壓縮檔案
150
+ rar|壓縮檔案
151
+
152
+ # === 學術和專業詞彙 ===
153
+ phd|博士
154
+ mba|企業管理碩士
155
+ ceo|執行長
156
+ cto|技術長
157
+ cfo|財務長
158
+ hr|人力資源
159
+ pr|公關
160
+ it|資訊技術
161
+ iot|物聯網
162
+ ar|擴增實境
163
+ vr|虛擬實境
164
+ ml|機器學習
165
+ dl|深度學習
166
+ nlp|自然語言處理
167
+ cv|電腦視覺
168
+
169
+ # === 日常用語 ===
170
+ email|電子郵件
171
+ e-mail|電子郵件
172
+ website|網站
173
+ online|線上
174
+ offline|離線
175
+ download|下載
176
+ upload|上傳
177
+ login|登入
178
+ logout|登出
179
+ password|密碼
180
+ username|使用者名稱
181
+ update|更新
182
+ upgrade|升級
183
+ install|安裝
184
+ uninstall|解除安裝
185
+ backup|備份
186
+ restore|還原
187
+
188
+ # === 常見組織和地名 ===
189
+ usa|美國
190
+ uk|英國
191
+ nyc|紐約市
192
+ la|洛杉磯
193
+ sf|舊金山
194
+ nasa|美國太空總署
195
+ fbi|聯邦調查局
196
+ cia|中央情報局
197
+ who|世界衛生組織
198
+ un|聯合國
199
+ eu|歐盟
200
+ nato|北大西洋公約組織
201
+
202
+ # === 其他常用 ===
203
+ tv|電視
204
+ pc|個人電腦
205
+ mac|蘋果電腦
206
+ ios|蘋果作業系統
207
+ android|安卓系統
208
+ windows|視窗系統
209
+ linux|林納克斯系統
210
+ xbox|遊戲主機
211
+ ps|遊戲主機
212
+ nintendo|任天堂
213
+ steam|遊戲平台
214
+ twitch|直播平台
215
+ discord|語音平台
216
+ zoom|視訊會議
217
+ slack|辦公通訊軟體