Spaces:
Sleeping
Sleeping
Upload app.py
Browse files
app.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
| 1 |
"""
|
| 2 |
-
Breeze2-VITS 繁體中文語音合成 -
|
| 3 |
-
|
| 4 |
"""
|
| 5 |
|
| 6 |
import gradio as gr
|
|
@@ -24,11 +24,12 @@ except ImportError:
|
|
| 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):
|
|
@@ -49,66 +50,154 @@ class TextConverter:
|
|
| 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.
|
| 55 |
except Exception as e:
|
| 56 |
print(f"❌ 載入轉換對照表失敗: {e}")
|
| 57 |
-
self.
|
| 58 |
|
| 59 |
-
def
|
| 60 |
-
"""創建
|
| 61 |
default_mappings = {
|
| 62 |
# 數字
|
| 63 |
'0': '零', '1': '一', '2': '二', '3': '三', '4': '四',
|
| 64 |
'5': '五', '6': '六', '7': '七', '8': '八', '9': '九',
|
| 65 |
-
'10': '十', '
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 66 |
|
| 67 |
-
# 常用
|
| 68 |
-
'
|
| 69 |
-
'
|
|
|
|
|
|
|
| 70 |
|
| 71 |
# 技術詞彙
|
| 72 |
-
'ai': '人工智慧', 'api': '程式介面', 'app': '應用程式',
|
| 73 |
-
'cpu': '中央處理器', 'gpu': '圖形處理器',
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 74 |
|
| 75 |
-
#
|
| 76 |
-
'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 77 |
}
|
| 78 |
|
| 79 |
self.conversion_map = default_mappings
|
| 80 |
-
print(f"✅ 使用
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 81 |
|
| 82 |
def convert_numbers(self, text):
|
| 83 |
-
"""轉換連續數字為中文"""
|
|
|
|
|
|
|
| 84 |
def number_to_chinese(match):
|
| 85 |
number = match.group()
|
| 86 |
-
|
|
|
|
|
|
|
| 87 |
result = ""
|
| 88 |
for digit in number:
|
| 89 |
-
|
|
|
|
|
|
|
| 90 |
return result
|
| 91 |
else:
|
| 92 |
# 複雜數字處理
|
| 93 |
-
|
|
|
|
|
|
|
| 94 |
|
| 95 |
# 匹配連續數字
|
| 96 |
-
|
| 97 |
-
|
|
|
|
|
|
|
| 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
|
|
@@ -123,48 +212,123 @@ class TextConverter:
|
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
| 132 |
except:
|
| 133 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
|
|
|
|
|
|
| 154 |
|
| 155 |
# 匹配獨立的英文字母
|
| 156 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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"🔄
|
| 166 |
|
| 167 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 168 |
text = self.convert_english(text)
|
| 169 |
|
| 170 |
# 2. 轉換數字
|
|
@@ -173,13 +337,35 @@ class TextConverter:
|
|
| 173 |
# 3. 轉換剩餘的單個字母
|
| 174 |
text = self.convert_single_letters(text)
|
| 175 |
|
| 176 |
-
# 4.
|
| 177 |
-
text =
|
| 178 |
|
| 179 |
if text != original_text:
|
| 180 |
-
print(f"✅ 轉換
|
|
|
|
|
|
|
| 181 |
|
| 182 |
return text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 183 |
|
| 184 |
|
| 185 |
class TaiwaneseVITSTTS:
|
|
@@ -188,9 +374,15 @@ class TaiwaneseVITSTTS:
|
|
| 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 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 194 |
def setup_jieba_dict(self):
|
| 195 |
"""設置 jieba 字典目錄"""
|
| 196 |
try:
|
|
@@ -275,6 +467,10 @@ class TaiwaneseVITSTTS:
|
|
| 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}")
|
|
@@ -282,24 +478,45 @@ class TaiwaneseVITSTTS:
|
|
| 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 |
|
|
@@ -307,6 +524,8 @@ class TaiwaneseVITSTTS:
|
|
| 307 |
samples = audio.samples
|
| 308 |
sample_rate = audio.sample_rate
|
| 309 |
|
|
|
|
|
|
|
| 310 |
if len(samples) == 0:
|
| 311 |
return None, "❌ 語音生成失敗:生成的音頻為空"
|
| 312 |
|
|
@@ -323,19 +542,52 @@ class TaiwaneseVITSTTS:
|
|
| 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🔄
|
|
|
|
|
|
|
|
|
|
|
|
|
| 327 |
|
| 328 |
return (sample_rate, audio_array), status_info
|
| 329 |
|
| 330 |
except Exception as e:
|
| 331 |
error_msg = f"❌ 語音合成失敗: {str(e)}"
|
| 332 |
print(error_msg)
|
|
|
|
| 333 |
return None, error_msg
|
| 334 |
|
| 335 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 336 |
# 全局 TTS 實例
|
| 337 |
print("🔧 正在初始化 TTS 模型...")
|
| 338 |
try:
|
|
|
|
|
|
|
|
|
|
| 339 |
tts_model = TaiwaneseVITSTTS()
|
| 340 |
print("✅ TTS 系統就緒!")
|
| 341 |
model_status = "🟢 模型已載入"
|
|
@@ -354,24 +606,26 @@ def generate_speech(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
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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:
|
| 375 |
margin: auto !important;
|
| 376 |
}
|
| 377 |
.status-box {
|
|
@@ -389,16 +643,29 @@ def create_interface():
|
|
| 389 |
margin: 10px 0;
|
| 390 |
text-align: center;
|
| 391 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 392 |
"""
|
| 393 |
) as demo:
|
| 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> | 🔄 自動英數轉換 | 🎯 智慧文本處理
|
|
@@ -418,7 +685,7 @@ def create_interface():
|
|
| 418 |
placeholder="請輸入要合成的文本,支援中文、英文、數字混合...",
|
| 419 |
lines=5,
|
| 420 |
max_lines=8,
|
| 421 |
-
value="
|
| 422 |
)
|
| 423 |
|
| 424 |
with gr.Row():
|
|
@@ -453,9 +720,9 @@ def create_interface():
|
|
| 453 |
)
|
| 454 |
|
| 455 |
status_msg = gr.Textbox(
|
| 456 |
-
label="📊 狀態資訊",
|
| 457 |
interactive=False,
|
| 458 |
-
lines=
|
| 459 |
value="準備就緒,請輸入文本並點擊生成語音" if tts_model else f"模型載入���敗: {model_status}"
|
| 460 |
)
|
| 461 |
|
|
@@ -466,18 +733,61 @@ def create_interface():
|
|
| 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 |
-
- **英文
|
|
|
|
|
|
|
| 478 |
- **數字轉換**: 123 → 一二三, 2024 → 二零二四
|
| 479 |
-
- **
|
| 480 |
-
- **
|
| 481 |
|
| 482 |
#### 🎯 支援內容
|
| 483 |
- 繁體中文文本
|
|
@@ -485,32 +795,20 @@ def create_interface():
|
|
| 485 |
- 阿拉伯數字
|
| 486 |
- 混合語言文本
|
| 487 |
- 常見縮寫和品牌
|
|
|
|
| 488 |
|
| 489 |
### 📝 使用技巧
|
| 490 |
-
1. **
|
| 491 |
-
2. **
|
| 492 |
-
3. **混合文本**:
|
| 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 |
# 事件綁定
|
|
|
|
| 1 |
"""
|
| 2 |
+
Breeze2-VITS 繁體中文語音合成 - 英文朗讀問題修復版
|
| 3 |
+
增強調試功能和轉換邏輯
|
| 4 |
"""
|
| 5 |
|
| 6 |
import gradio as gr
|
|
|
|
| 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.debug_mode = True # 啟用調試模式
|
| 33 |
self.load_mapping()
|
| 34 |
|
| 35 |
def load_mapping(self):
|
|
|
|
| 50 |
self.conversion_map[original.strip().lower()] = chinese.strip()
|
| 51 |
|
| 52 |
print(f"✅ 載入 {len(self.conversion_map)} 個轉換規則")
|
| 53 |
+
|
| 54 |
+
# 調試:顯示部分轉換規則
|
| 55 |
+
if self.debug_mode:
|
| 56 |
+
print("🔍 部分轉換規則:")
|
| 57 |
+
for i, (k, v) in enumerate(list(self.conversion_map.items())[:10]):
|
| 58 |
+
print(f" {k} → {v}")
|
| 59 |
+
if len(self.conversion_map) > 10:
|
| 60 |
+
print(f" ... 還有 {len(self.conversion_map) - 10} 個規則")
|
| 61 |
+
|
| 62 |
else:
|
| 63 |
print(f"⚠️ 轉換對照表文件不存在: {self.mapping_file}")
|
| 64 |
+
self.create_enhanced_mapping()
|
| 65 |
except Exception as e:
|
| 66 |
print(f"❌ 載入轉換對照表失敗: {e}")
|
| 67 |
+
self.create_enhanced_mapping()
|
| 68 |
|
| 69 |
+
def create_enhanced_mapping(self):
|
| 70 |
+
"""創建增強的轉換對照表"""
|
| 71 |
default_mappings = {
|
| 72 |
# 數字
|
| 73 |
'0': '零', '1': '一', '2': '二', '3': '三', '4': '四',
|
| 74 |
'5': '五', '6': '六', '7': '七', '8': '八', '9': '九',
|
| 75 |
+
'10': '十', '11': '十一', '12': '十二', '13': '十三', '14': '十四', '15': '十五',
|
| 76 |
+
'16': '十六', '17': '十七', '18': '十八', '19': '十九', '20': '二十',
|
| 77 |
+
'100': '一百', '1000': '一千', '10000': '一萬',
|
| 78 |
+
|
| 79 |
+
# 基本英文問候語
|
| 80 |
+
'hello': '哈囉', 'hi': '嗨', 'hey': '嘿', 'bye': '拜拜', 'goodbye': '再見',
|
| 81 |
+
'yes': '是的', 'no': '不', 'ok': '好的', 'okay': '好的',
|
| 82 |
+
'good': '好的', 'bad': '不好', 'nice': '很棒', 'great': '很好',
|
| 83 |
+
'thank': '謝謝', 'thanks': '謝謝', 'please': '請',
|
| 84 |
+
'sorry': '對不起', 'excuse': '不好意思',
|
| 85 |
+
|
| 86 |
+
# 時間相關
|
| 87 |
+
'today': '今天', 'tomorrow': '明天', 'yesterday': '昨天',
|
| 88 |
+
'morning': '早上', 'afternoon': '下午', 'evening': '晚上', 'night': '晚上',
|
| 89 |
+
'monday': '星期一', 'tuesday': '星期二', 'wednesday': '星期三',
|
| 90 |
+
'thursday': '星期四', 'friday': '星期五', 'saturday': '星期六', 'sunday': '星期日',
|
| 91 |
|
| 92 |
+
# 常用動詞
|
| 93 |
+
'go': '去', 'come': '來', 'see': '看', 'look': '看', 'do': '做', 'make': '做',
|
| 94 |
+
'get': '得到', 'take': '拿', 'give': '給', 'have': '有', 'be': '是',
|
| 95 |
+
'know': '知道', 'think': '想', 'want': '想要', 'need': '需要',
|
| 96 |
+
'like': '喜歡', 'love': '愛', 'help': '幫助', 'work': '工作',
|
| 97 |
|
| 98 |
# 技術詞彙
|
| 99 |
+
'ai': '人工智慧', 'api': '程式介面', 'app': '應用程式', 'web': '網路',
|
| 100 |
+
'cpu': '中央處理器', 'gpu': '圖形處理器', 'ram': '記憶體',
|
| 101 |
+
'computer': '電腦', 'laptop': '筆記型電腦', 'phone': '手機', 'mobile': '手機',
|
| 102 |
+
'internet': '網際網路', 'wifi': '無線網路', 'bluetooth': '藍牙',
|
| 103 |
+
'software': '軟體', 'hardware': '硬體', 'program': '程式', 'code': '程式碼',
|
| 104 |
+
'data': '資料', 'database': '資料庫', 'file': '檔案', 'folder': '資料夾',
|
| 105 |
+
|
| 106 |
+
# 品牌名稱
|
| 107 |
+
'apple': '蘋果', 'google': '谷歌', 'microsoft': '微軟', 'amazon': '亞馬遜',
|
| 108 |
+
'facebook': '臉書', 'twitter': '推特', 'youtube': '油管', 'instagram': 'instagram',
|
| 109 |
+
'samsung': '三星', 'sony': '索尼', 'lg': 'LG', 'htc': 'HTC',
|
| 110 |
+
'iphone': '愛瘋', 'android': '安卓', 'windows': '視窗系統', 'ios': 'iOS',
|
| 111 |
+
|
| 112 |
+
# 常用形容詞
|
| 113 |
+
'big': '大', 'small': '小', 'new': '新', 'old': '舊',
|
| 114 |
+
'hot': '熱', 'cold': '冷', 'fast': '快', 'slow': '慢',
|
| 115 |
+
'easy': '容易', 'hard': '困難', 'simple': '簡單', 'complex': '複雜',
|
| 116 |
+
'important': '重要', 'useful': '有用', 'interesting': '有趣',
|
| 117 |
+
|
| 118 |
+
# 字母 (更自然的中文發音)
|
| 119 |
+
'a': '欸', 'b': '比', 'c': '西', 'd': '迪', 'e': '伊',
|
| 120 |
+
'f': '艾夫', 'g': '吉', 'h': '艾奇', 'i': '愛', 'j': '傑',
|
| 121 |
+
'k': '凱', 'l': '艾爾', 'm': '艾姆', 'n': '艾恩', 'o': '歐',
|
| 122 |
+
'p': '皮', 'q': '丘', 'r': '艾爾', 's': '艾斯', 't': '替',
|
| 123 |
+
'u': '優', 'v': '威', 'w': '達布爾優', 'x': '艾克斯', 'y': '歪', 'z': '萊德',
|
| 124 |
|
| 125 |
+
# 縮寫詞
|
| 126 |
+
'ceo': '執行長', 'cto': '技術長', 'cfo': '財務長',
|
| 127 |
+
'usa': '美國', 'uk': '英國', 'eu': '歐盟',
|
| 128 |
+
'nasa': '美國太空總署', 'fbi': '聯邦調查局',
|
| 129 |
+
'covid': '新冠肺炎', 'dna': 'DNA', 'gps': '全球定位系統',
|
| 130 |
+
|
| 131 |
+
# 網路用語
|
| 132 |
+
'email': '電子郵件', 'www': '全球資訊網', 'http': 'HTTP',
|
| 133 |
+
'url': '網址', 'link': '連結', 'click': '點擊',
|
| 134 |
+
'download': '下載', 'upload': '上傳', 'login': '登入', 'logout': '登出',
|
| 135 |
+
|
| 136 |
+
# 常見英文片語的關鍵詞
|
| 137 |
+
'how': '如何', 'what': '什麼', 'where': '哪裡', 'when': '什麼時候',
|
| 138 |
+
'why': '為什麼', 'who': '誰', 'which': '哪個',
|
| 139 |
+
'this': '這個', 'that': '那個', 'here': '這裡', 'there': '那裡',
|
| 140 |
+
'and': '和', 'or': '或', 'but': '但是', 'so': '所以',
|
| 141 |
+
'very': '非常', 'much': '很多', 'many': '很多', 'some': '一些',
|
| 142 |
+
'all': '全部', 'every': '每個', 'any': '任何',
|
| 143 |
}
|
| 144 |
|
| 145 |
self.conversion_map = default_mappings
|
| 146 |
+
print(f"✅ 使用增強轉換規則: {len(default_mappings)} 個")
|
| 147 |
+
|
| 148 |
+
def debug_print(self, message):
|
| 149 |
+
"""調試打印函數"""
|
| 150 |
+
if self.debug_mode:
|
| 151 |
+
print(f"🔍 [DEBUG] {message}")
|
| 152 |
|
| 153 |
def convert_numbers(self, text):
|
| 154 |
+
"""轉換連續數字為中文 - 增強版"""
|
| 155 |
+
self.debug_print(f"數字轉換前: {repr(text)}")
|
| 156 |
+
|
| 157 |
def number_to_chinese(match):
|
| 158 |
number = match.group()
|
| 159 |
+
self.debug_print(f"處理數字: {number}")
|
| 160 |
+
|
| 161 |
+
if len(number) <= 2:
|
| 162 |
result = ""
|
| 163 |
for digit in number:
|
| 164 |
+
chinese_digit = self.conversion_map.get(digit, digit)
|
| 165 |
+
result += chinese_digit
|
| 166 |
+
self.debug_print(f" {digit} → {chinese_digit}")
|
| 167 |
return result
|
| 168 |
else:
|
| 169 |
# 複雜數字處理
|
| 170 |
+
converted = self.convert_large_number(number)
|
| 171 |
+
self.debug_print(f" 大數字 {number} → {converted}")
|
| 172 |
+
return converted
|
| 173 |
|
| 174 |
# 匹配連續數字
|
| 175 |
+
result = re.sub(r'\d+', number_to_chinese, text)
|
| 176 |
+
if result != text:
|
| 177 |
+
self.debug_print(f"數字轉換後: {repr(result)}")
|
| 178 |
+
return result
|
| 179 |
|
| 180 |
def convert_large_number(self, number_str):
|
| 181 |
+
"""轉換大數字為中文 - 改進版"""
|
| 182 |
try:
|
| 183 |
num = int(number_str)
|
| 184 |
if num == 0:
|
| 185 |
return '零'
|
| 186 |
|
| 187 |
+
# 使用更完整的數字轉換
|
| 188 |
+
if str(num) in self.conversion_map:
|
| 189 |
+
return self.conversion_map[str(num)]
|
| 190 |
+
|
| 191 |
# 簡化的數字轉換(支援到萬)
|
|
|
|
| 192 |
digits = ['零', '一', '二', '三', '四', '五', '六', '七', '八', '九']
|
| 193 |
|
| 194 |
if num < 10:
|
| 195 |
return digits[num]
|
| 196 |
+
elif num < 20:
|
| 197 |
+
if num == 10:
|
| 198 |
+
return '十'
|
| 199 |
+
else:
|
| 200 |
+
return '十' + digits[num % 10]
|
| 201 |
elif num < 100:
|
| 202 |
tens = num // 10
|
| 203 |
ones = num % 10
|
|
|
|
| 212 |
if remainder > 0:
|
| 213 |
if remainder < 10:
|
| 214 |
result += '零' + digits[remainder]
|
| 215 |
+
elif remainder < 20:
|
| 216 |
+
result += '一十' if remainder == 10 else '一十' + digits[remainder % 10]
|
| 217 |
+
else:
|
| 218 |
+
result += self.convert_large_number(str(remainder))
|
| 219 |
+
return result
|
| 220 |
+
elif num < 10000:
|
| 221 |
+
thousands = num // 1000
|
| 222 |
+
remainder = num % 1000
|
| 223 |
+
result = digits[thousands] + '千'
|
| 224 |
+
if remainder > 0:
|
| 225 |
+
if remainder < 100:
|
| 226 |
+
result += '零' + self.convert_large_number(str(remainder))
|
| 227 |
else:
|
| 228 |
result += self.convert_large_number(str(remainder))
|
| 229 |
return result
|
| 230 |
else:
|
| 231 |
+
# 對於更大的數字,逐位轉換
|
| 232 |
+
result = ""
|
| 233 |
+
for digit in number_str:
|
| 234 |
+
result += digits[int(digit)]
|
| 235 |
+
return result
|
| 236 |
except:
|
| 237 |
+
# 如果轉換失敗,逐位轉換數字
|
| 238 |
+
result = ""
|
| 239 |
+
for digit in number_str:
|
| 240 |
+
if digit.isdigit():
|
| 241 |
+
result += self.conversion_map.get(digit, digit)
|
| 242 |
+
else:
|
| 243 |
+
result += digit
|
| 244 |
+
return result
|
| 245 |
|
| 246 |
def convert_english(self, text):
|
| 247 |
+
"""轉換英文單詞為中文 - 增強調試版"""
|
| 248 |
+
self.debug_print(f"英文轉換前: {repr(text)}")
|
| 249 |
+
original_text = text
|
| 250 |
+
|
| 251 |
# 按長度排序,先處理長詞彙
|
| 252 |
sorted_words = sorted(self.conversion_map.keys(), key=len, reverse=True)
|
| 253 |
|
| 254 |
+
conversion_count = 0
|
| 255 |
for english_word in sorted_words:
|
| 256 |
if len(english_word) > 1: # 跳過單字母,後面單獨處理
|
| 257 |
chinese_word = self.conversion_map[english_word]
|
| 258 |
# 使用單詞邊界匹配,不區分大小寫
|
| 259 |
pattern = r'\b' + re.escape(english_word) + r'\b'
|
| 260 |
+
new_text = re.sub(pattern, chinese_word, text, flags=re.IGNORECASE)
|
| 261 |
+
|
| 262 |
+
if new_text != text:
|
| 263 |
+
self.debug_print(f" 轉換: {english_word} → {chinese_word}")
|
| 264 |
+
conversion_count += 1
|
| 265 |
+
text = new_text
|
| 266 |
|
| 267 |
+
if conversion_count > 0:
|
| 268 |
+
self.debug_print(f"英文轉換後: {repr(text)} (共轉換 {conversion_count} 個詞)")
|
| 269 |
+
else:
|
| 270 |
+
self.debug_print("沒有找到可轉換的英文詞彙")
|
| 271 |
+
|
| 272 |
return text
|
| 273 |
|
| 274 |
def convert_single_letters(self, text):
|
| 275 |
+
"""轉換單個英文字母 - 增強版"""
|
| 276 |
+
self.debug_print(f"字母轉換前: {repr(text)}")
|
| 277 |
+
|
| 278 |
def letter_to_chinese(match):
|
| 279 |
letter = match.group().lower()
|
| 280 |
+
chinese = self.conversion_map.get(letter, letter)
|
| 281 |
+
self.debug_print(f" 字母轉換: {letter} → {chinese}")
|
| 282 |
+
return chinese
|
| 283 |
|
| 284 |
# 匹配獨立的英文字母
|
| 285 |
+
result = re.sub(r'\b[a-zA-Z]\b', letter_to_chinese, text)
|
| 286 |
+
if result != text:
|
| 287 |
+
self.debug_print(f"字母轉換後: {repr(result)}")
|
| 288 |
+
return result
|
| 289 |
+
|
| 290 |
+
def preprocess_text(self, text):
|
| 291 |
+
"""預處理文本 - 處理特殊情況"""
|
| 292 |
+
# 處理常見的英文縮寫
|
| 293 |
+
text = re.sub(r'\bDr\.', 'Doctor', text, flags=re.IGNORECASE)
|
| 294 |
+
text = re.sub(r'\bMr\.', 'Mister', text, flags=re.IGNORECASE)
|
| 295 |
+
text = re.sub(r'\bMrs\.', 'Missis', text, flags=re.IGNORECASE)
|
| 296 |
+
text = re.sub(r'\bMs\.', 'Miss', text, flags=re.IGNORECASE)
|
| 297 |
+
|
| 298 |
+
# 處理email地址中的@符號
|
| 299 |
+
text = re.sub(r'@', ' at ', text)
|
| 300 |
+
|
| 301 |
+
# 處理網址中的點
|
| 302 |
+
text = re.sub(r'\.com\b', ' dot com', text, flags=re.IGNORECASE)
|
| 303 |
+
text = re.sub(r'\.org\b', ' dot org', text, flags=re.IGNORECASE)
|
| 304 |
+
text = re.sub(r'\.net\b', ' dot net', text, flags=re.IGNORECASE)
|
| 305 |
+
|
| 306 |
+
return text
|
| 307 |
+
|
| 308 |
+
def postprocess_text(self, text):
|
| 309 |
+
"""後處理文本 - 清理和優化"""
|
| 310 |
+
# 清理多餘空格
|
| 311 |
+
text = re.sub(r'\s+', ' ', text).strip()
|
| 312 |
+
|
| 313 |
+
# 處理標點符號前的空格
|
| 314 |
+
text = re.sub(r'\s+([,。!?;:])', r'\1', text)
|
| 315 |
+
|
| 316 |
return text
|
| 317 |
|
| 318 |
def convert_text(self, text):
|
| 319 |
+
"""主要轉換函數 - 增強調試版"""
|
| 320 |
if not text:
|
| 321 |
return text
|
| 322 |
|
| 323 |
original_text = text
|
| 324 |
+
print(f"🔄 開始轉換文本: {repr(original_text)}")
|
| 325 |
|
| 326 |
+
# 預處理
|
| 327 |
+
text = self.preprocess_text(text)
|
| 328 |
+
if text != original_text:
|
| 329 |
+
self.debug_print(f"預處理後: {repr(text)}")
|
| 330 |
+
|
| 331 |
+
# 1. 轉換英文單詞(先處理多字母詞彙)
|
| 332 |
text = self.convert_english(text)
|
| 333 |
|
| 334 |
# 2. 轉換數字
|
|
|
|
| 337 |
# 3. 轉換剩餘的單個字母
|
| 338 |
text = self.convert_single_letters(text)
|
| 339 |
|
| 340 |
+
# 4. 後處理
|
| 341 |
+
text = self.postprocess_text(text)
|
| 342 |
|
| 343 |
if text != original_text:
|
| 344 |
+
print(f"✅ 轉換完成: {repr(original_text)} → {repr(text)}")
|
| 345 |
+
else:
|
| 346 |
+
print(f"ℹ️ 文本未發生變化: {repr(text)}")
|
| 347 |
|
| 348 |
return text
|
| 349 |
+
|
| 350 |
+
def test_conversion(self, test_texts=None):
|
| 351 |
+
"""測試轉換功能"""
|
| 352 |
+
if test_texts is None:
|
| 353 |
+
test_texts = [
|
| 354 |
+
"Hello world",
|
| 355 |
+
"I have 123 apples",
|
| 356 |
+
"My email is test@gmail.com",
|
| 357 |
+
"Apple iPhone 15 is good",
|
| 358 |
+
"AI and ML are useful",
|
| 359 |
+
"CPU speed is 3.5 GHz"
|
| 360 |
+
]
|
| 361 |
+
|
| 362 |
+
print("\n🧪 測試文本轉換功能:")
|
| 363 |
+
print("=" * 50)
|
| 364 |
+
for text in test_texts:
|
| 365 |
+
converted = self.convert_text(text)
|
| 366 |
+
print(f"原文: {text}")
|
| 367 |
+
print(f"轉換: {converted}")
|
| 368 |
+
print("-" * 50)
|
| 369 |
|
| 370 |
|
| 371 |
class TaiwaneseVITSTTS:
|
|
|
|
| 374 |
self.model_dir = Path("./models")
|
| 375 |
self.dict_dir = Path("./dict")
|
| 376 |
self.text_converter = TextConverter()
|
| 377 |
+
self.debug_mode = True # 啟用調試模式
|
| 378 |
self.setup_jieba_dict()
|
| 379 |
self.setup_model()
|
| 380 |
|
| 381 |
+
def debug_print(self, message):
|
| 382 |
+
"""調試打印函數"""
|
| 383 |
+
if self.debug_mode:
|
| 384 |
+
print(f"🔍 [TTS DEBUG] {message}")
|
| 385 |
+
|
| 386 |
def setup_jieba_dict(self):
|
| 387 |
"""設置 jieba 字典目錄"""
|
| 388 |
try:
|
|
|
|
| 467 |
test_audio = self.tts.generate(text="測試", sid=0, speed=1.0)
|
| 468 |
if len(test_audio.samples) > 0:
|
| 469 |
print("✅ 模型測試通過!")
|
| 470 |
+
|
| 471 |
+
# 測試轉換功能
|
| 472 |
+
print("\n🧪 測試文本轉換:")
|
| 473 |
+
self.text_converter.test_conversion()
|
| 474 |
|
| 475 |
except Exception as e:
|
| 476 |
print(f"❌ 模型設置失敗: {e}")
|
|
|
|
| 478 |
print(f"詳細錯誤: {traceback.format_exc()}")
|
| 479 |
raise
|
| 480 |
|
| 481 |
+
def validate_converted_text(self, text):
|
| 482 |
+
"""驗證轉換後的文本是否適合TTS"""
|
| 483 |
+
# 檢查是否還有英文字母
|
| 484 |
+
english_chars = re.findall(r'[a-zA-Z]+', text)
|
| 485 |
+
if english_chars:
|
| 486 |
+
self.debug_print(f"警告:轉換後仍有英文字母: {english_chars}")
|
| 487 |
+
|
| 488 |
+
# 檢查是否有不支持的字符
|
| 489 |
+
unsupported_chars = re.findall(r'[^\u4e00-\u9fff\u3000-\u303f\uff00-\uffef\s\d,。!?;:]', text)
|
| 490 |
+
if unsupported_chars:
|
| 491 |
+
self.debug_print(f"警告:發現可能不支持的字符: {set(unsupported_chars)}")
|
| 492 |
+
|
| 493 |
+
return text
|
| 494 |
+
|
| 495 |
def synthesize(self, text, speed=1.0, enable_conversion=True):
|
| 496 |
+
"""合成語音 - 增強調試版"""
|
| 497 |
if not text or not text.strip():
|
| 498 |
return None, "❌ 請輸入文本"
|
| 499 |
|
| 500 |
original_text = text.strip()
|
| 501 |
+
self.debug_print(f"開始語音合成,原始文本: {repr(original_text)}")
|
| 502 |
|
| 503 |
# 文本轉換
|
| 504 |
if enable_conversion:
|
| 505 |
text = self.text_converter.convert_text(original_text)
|
| 506 |
+
# 驗證轉換結果
|
| 507 |
+
text = self.validate_converted_text(text)
|
| 508 |
else:
|
| 509 |
text = original_text
|
| 510 |
+
self.debug_print("跳過文本轉換")
|
| 511 |
|
| 512 |
if len(text) > 500:
|
| 513 |
text = text[:500]
|
| 514 |
+
self.debug_print("文本過長,已截斷至500字符")
|
| 515 |
|
| 516 |
try:
|
| 517 |
print(f"🎤 正在合成語音...")
|
| 518 |
+
self.debug_print(f"最終TTS輸入文本: {repr(text)}")
|
| 519 |
+
|
| 520 |
if enable_conversion and text != original_text:
|
| 521 |
print(f"📝 使用轉換後文本: {text}")
|
| 522 |
|
|
|
|
| 524 |
samples = audio.samples
|
| 525 |
sample_rate = audio.sample_rate
|
| 526 |
|
| 527 |
+
self.debug_print(f"TTS輸出 - 樣本數: {len(samples)}, 採樣率: {sample_rate}")
|
| 528 |
+
|
| 529 |
if len(samples) == 0:
|
| 530 |
return None, "❌ 語音生成失敗:生成的音頻為空"
|
| 531 |
|
|
|
|
| 542 |
|
| 543 |
status_info = f"✅ 語音合成成功!\n📊 採樣率: {sample_rate}Hz\n⏱️ 時長: {duration:.2f}秒"
|
| 544 |
if enable_conversion and text != original_text:
|
| 545 |
+
status_info += f"\n🔄 文本轉換: {original_text} → {text}"
|
| 546 |
+
|
| 547 |
+
# 添加調試信息
|
| 548 |
+
if self.debug_mode:
|
| 549 |
+
status_info += f"\n🔍 調試信息:\n 原始長度: {len(original_text)}\n 轉換後長度: {len(text)}"
|
| 550 |
|
| 551 |
return (sample_rate, audio_array), status_info
|
| 552 |
|
| 553 |
except Exception as e:
|
| 554 |
error_msg = f"❌ 語音合成失敗: {str(e)}"
|
| 555 |
print(error_msg)
|
| 556 |
+
self.debug_print(f"合成失敗詳情: {e}")
|
| 557 |
return None, error_msg
|
| 558 |
|
| 559 |
|
| 560 |
+
# 初始化時運行測試
|
| 561 |
+
def run_initialization_tests():
|
| 562 |
+
"""運行初始化測試"""
|
| 563 |
+
print("\n" + "="*60)
|
| 564 |
+
print("🔧 運行系統診斷測試")
|
| 565 |
+
print("="*60)
|
| 566 |
+
|
| 567 |
+
# 測試文本轉換器
|
| 568 |
+
converter = TextConverter()
|
| 569 |
+
test_cases = [
|
| 570 |
+
"Hello world",
|
| 571 |
+
"I love Apple iPhone 15",
|
| 572 |
+
"AI technology is amazing",
|
| 573 |
+
"My email is user@gmail.com",
|
| 574 |
+
"CPU speed is 2.5 GHz"
|
| 575 |
+
]
|
| 576 |
+
|
| 577 |
+
print("\n📝 測試文本轉換功能:")
|
| 578 |
+
for test_text in test_cases:
|
| 579 |
+
result = converter.convert_text(test_text)
|
| 580 |
+
print(f" 輸入: {test_text}")
|
| 581 |
+
print(f" 輸出: {result}")
|
| 582 |
+
print()
|
| 583 |
+
|
| 584 |
+
|
| 585 |
# 全局 TTS 實例
|
| 586 |
print("🔧 正在初始化 TTS 模型...")
|
| 587 |
try:
|
| 588 |
+
# 運行診斷測試
|
| 589 |
+
run_initialization_tests()
|
| 590 |
+
|
| 591 |
tts_model = TaiwaneseVITSTTS()
|
| 592 |
print("✅ TTS 系統就緒!")
|
| 593 |
model_status = "🟢 模型已載入"
|
|
|
|
| 606 |
|
| 607 |
|
| 608 |
def create_interface():
|
| 609 |
+
# 預設範例文本 - 增加更多測試用例
|
| 610 |
examples = [
|
| 611 |
["你好,歡迎使用繁體中文語音合成系統!", 1.0, True],
|
|
|
|
|
|
|
|
|
|
| 612 |
["Hello world! 這是一個測試。", 1.0, True],
|
| 613 |
+
["I love Apple iPhone 15 and Samsung Galaxy", 1.0, True],
|
| 614 |
+
["AI technology is amazing, CPU speed is 3.5 GHz", 1.0, True],
|
| 615 |
+
["My email is test@gmail.com, please contact me", 1.0, True],
|
| 616 |
+
["今天是2024年1月1日,天氣很好。", 1.0, True],
|
| 617 |
+
["Google and Microsoft are big tech companies", 1.0, True],
|
| 618 |
+
["API development with Python is easy", 1.0, True],
|
| 619 |
]
|
| 620 |
|
| 621 |
device_info = "🎮 GPU" if torch.cuda.is_available() else "💻 CPU"
|
| 622 |
|
| 623 |
with gr.Blocks(
|
| 624 |
+
title="繁體中文語音合成 - Breeze2-VITS Enhanced Debug",
|
| 625 |
theme=gr.themes.Soft(),
|
| 626 |
css="""
|
| 627 |
.gradio-container {
|
| 628 |
+
max-width: 1200px !important;
|
| 629 |
margin: auto !important;
|
| 630 |
}
|
| 631 |
.status-box {
|
|
|
|
| 643 |
margin: 10px 0;
|
| 644 |
text-align: center;
|
| 645 |
}
|
| 646 |
+
.debug-box {
|
| 647 |
+
background: linear-gradient(135deg, #11998e 0%, #38ef7d 100%);
|
| 648 |
+
color: white;
|
| 649 |
+
padding: 10px 15px;
|
| 650 |
+
border-radius: 8px;
|
| 651 |
+
margin: 10px 0;
|
| 652 |
+
}
|
| 653 |
"""
|
| 654 |
) as demo:
|
| 655 |
|
| 656 |
gr.HTML(f"""
|
| 657 |
<div class="status-box">
|
| 658 |
+
<h1>🎙️ 繁體中文語音合成 - Breeze2-VITS Enhanced Debug</h1>
|
| 659 |
<p><strong>狀態:</strong> {model_status} | <strong>設備:</strong> {device_info}</p>
|
| 660 |
</div>
|
| 661 |
""")
|
| 662 |
|
| 663 |
+
gr.HTML("""
|
| 664 |
+
<div class="debug-box">
|
| 665 |
+
<strong>🔍 調試增強版</strong> | 詳細轉換日志 | 問題診斷 | 性能分析
|
| 666 |
+
</div>
|
| 667 |
+
""")
|
| 668 |
+
|
| 669 |
gr.HTML("""
|
| 670 |
<div class="feature-box">
|
| 671 |
<strong>🇹🇼 專業台灣國語 TTS</strong> | 🔄 自動英數轉換 | 🎯 智慧文本處理
|
|
|
|
| 685 |
placeholder="請輸入要合成的文本,支援中文、英文、數字混合...",
|
| 686 |
lines=5,
|
| 687 |
max_lines=8,
|
| 688 |
+
value="Hello world! 今天是2024年,歡迎使用AI語音合成系統。"
|
| 689 |
)
|
| 690 |
|
| 691 |
with gr.Row():
|
|
|
|
| 720 |
)
|
| 721 |
|
| 722 |
status_msg = gr.Textbox(
|
| 723 |
+
label="📊 狀態資訊與調試信息",
|
| 724 |
interactive=False,
|
| 725 |
+
lines=8,
|
| 726 |
value="準備就緒,請輸入文本並點擊生成語音" if tts_model else f"模型載入���敗: {model_status}"
|
| 727 |
)
|
| 728 |
|
|
|
|
| 733 |
outputs=[audio_output, status_msg],
|
| 734 |
fn=generate_speech,
|
| 735 |
cache_examples=False,
|
| 736 |
+
label="📚 範例文本 (包含英文朗讀測試)"
|
| 737 |
)
|
| 738 |
|
| 739 |
+
with gr.Accordion("🔍 調試信息與故障排除", open=True):
|
| 740 |
+
gr.Markdown(f"""
|
| 741 |
+
### 🚀 調試功能
|
| 742 |
+
|
| 743 |
+
#### 🔄 轉換規則狀態
|
| 744 |
+
- **載入規則數**: {len(tts_model.text_converter.conversion_map) if tts_model else 0} 個
|
| 745 |
+
- **調試模式**: {'✅ 已啟用' if tts_model and tts_model.debug_mode else '❌ 未啟用'}
|
| 746 |
+
- **模型狀態**: {model_status}
|
| 747 |
+
|
| 748 |
+
#### 🧪 常見問題診斷
|
| 749 |
+
|
| 750 |
+
**問題1: 英文不發音**
|
| 751 |
+
- ✅ 確保啟用「英數轉換」功能
|
| 752 |
+
- ✅ 檢查控制台轉換日志
|
| 753 |
+
- ✅ 測試單獨的英文單詞
|
| 754 |
+
|
| 755 |
+
**問題2: 轉換後仍有英文**
|
| 756 |
+
- 可能是詞典中缺少該詞彙
|
| 757 |
+
- 查看調試信息中的轉換過程
|
| 758 |
+
- 考慮添加自定義轉換規則
|
| 759 |
+
|
| 760 |
+
**問題3: 發音不自然**
|
| 761 |
+
- 嘗試調整轉換後的中文用詞
|
| 762 |
+
- 使用更常見的中文表達
|
| 763 |
+
- 關閉轉換使用純中文測試
|
| 764 |
+
|
| 765 |
+
#### 🔧 調試步驟
|
| 766 |
+
1. 打開瀏覽器開發者工具查看控制台
|
| 767 |
+
2. 輸入測試文本並生成語音
|
| 768 |
+
3. 觀察轉換過程的調試信息
|
| 769 |
+
4. 檢查哪些詞彙被成功轉換
|
| 770 |
+
5. 分析未轉換詞彙的原因
|
| 771 |
+
|
| 772 |
+
#### 📝 測試建議
|
| 773 |
+
- 先測試純英文: "Hello world"
|
| 774 |
+
- 再測試中英混合: "Hello 世界"
|
| 775 |
+
- 測試數字: "I have 123 apples"
|
| 776 |
+
- 測試品牌: "Apple iPhone Samsung"
|
| 777 |
+
- 測試技術詞彙: "AI CPU GPU API"
|
| 778 |
+
""")
|
| 779 |
+
|
| 780 |
with gr.Accordion("📋 使用說明與功能特色", open=False):
|
| 781 |
gr.Markdown(f"""
|
| 782 |
### 🚀 主要功能
|
| 783 |
|
| 784 |
+
#### 🔄 智慧文本轉換 (增強版)
|
| 785 |
+
- **基本英文**: hello → 哈囉, good → 好的, thank → 謝謝
|
| 786 |
+
- **技術詞彙**: AI → 人工智慧, CPU → 中央處理器, API → 程式介面
|
| 787 |
+
- **品牌名稱**: Apple → 蘋果, Google → 谷歌, iPhone → 愛瘋
|
| 788 |
- **數字轉換**: 123 → 一二三, 2024 → 二零二四
|
| 789 |
+
- **字母發音**: A → 欸, B → 比, C → 西
|
| 790 |
+
- **縮寫詞**: CEO → 執行長, USA → 美國, GPS → 全球定位系統
|
| 791 |
|
| 792 |
#### 🎯 支援內容
|
| 793 |
- 繁體中文文本
|
|
|
|
| 795 |
- 阿拉伯數字
|
| 796 |
- 混合語言文本
|
| 797 |
- 常見縮寫和品牌
|
| 798 |
+
- 網路用語和技術術語
|
| 799 |
|
| 800 |
### 📝 使用技巧
|
| 801 |
+
1. **測試英文**: 使用範例中的英文測試案例
|
| 802 |
+
2. **調試轉換**: 查看控制台的詳細轉換過程
|
| 803 |
+
3. **混合文本**: 嘗試「Hello world 這是測試」
|
| 804 |
+
4. **數字處理**: 測試不同長度的數字
|
| 805 |
|
| 806 |
### 🔧 技術資訊
|
| 807 |
- **模型**: MediaTek Breeze2-VITS-onnx
|
| 808 |
- **轉換規則**: {len(tts_model.text_converter.conversion_map) if tts_model else 0} 個內建對照
|
| 809 |
+
- **調試模式**: {'啟用' if tts_model and tts_model.debug_mode else '未啟用'}
|
| 810 |
- **運行設備**: {device_info}
|
| 811 |
- **模型狀態**: {model_status}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 812 |
""")
|
| 813 |
|
| 814 |
# 事件綁定
|