""" Vietnamese TTS Server - Edge TTS """ import gradio as gr import edge_tts import asyncio import os import hashlib import re import shutil CACHE_DIR = "/tmp/tts_cache" # Clear cache on startup if os.path.exists(CACHE_DIR): shutil.rmtree(CACHE_DIR) os.makedirs(CACHE_DIR, exist_ok=True) # Giọng Việt Nam từ Edge TTS VOICES = { "Hoài My (Nữ)": "vi-VN-HoaiMyNeural", "Nam Minh (Nam)": "vi-VN-NamMinhNeural", } # Từ điển phiên âm tiếng Anh -> tiếng Việt ENGLISH_TO_VIETNAMESE = { # Job titles r'\bFrontend Developer\b': 'Phờ-ron-en Đê-vê-lốp-pơ', r'\bBackend Developer\b': 'Béc-en Đê-vê-lốp-pơ', r'\bFullstack Developer\b': 'Phu-xtéc Đê-vê-lốp-pơ', r'\bSoftware Engineer\b': 'Sóp-que Én-gi-nia', r'\bDeveloper\b': 'Đê-vê-lốp-pơ', r'\bEngineer\b': 'Én-gi-nia', r'\bDesigner\b': 'Đi-zai-nơ', r'\bManager\b': 'Ma-na-giơ', r'\bLeader\b': 'Li-đơ', r'\bSenior\b': 'Xi-ni-ơ', r'\bJunior\b': 'Giu-ni-ơ', r'\bIntern\b': 'In-tơn', # Tech terms r'\bReact\b': 'Ri-ắc', r'\bVue\b': 'Viu', r'\bAngular\b': 'Ang-giu-la', r'\bJavaScript\b': 'Gia-va-xcrip', r'\bTypeScript\b': 'Thai-xcrip', r'\bPython\b': 'Pai-thon', r'\bNode\.js\b': 'Nốt giây-ét', r'\bNodeJS\b': 'Nốt giây-ét', r'\bAPI\b': 'A-Pi-Ai', r'\bCSS\b': 'Xi-ét-ét', r'\bHTML\b': 'Ết-ti-ém-eo', r'\bGit\b': 'Gít', r'\bGitHub\b': 'Gít-hấp', r'\bDocker\b': 'Đốc-cơ', r'\bFramework\b': 'Phrêm-guốc', r'\bComponent\b': 'Com-pô-nần', # Interview terms r'\bInterview\b': 'In-tơ-viu', r'\bSkill\b': 'Xkiu', r'\bSkills\b': 'Xkiu', r'\bProject\b': 'Prô-giéc', r'\bTeam\b': 'Tim', r'\bFeedback\b': 'Phít-béc', r'\bOnline\b': 'On-lai', r'\bOffline\b': 'Óp-lai', # Common r'\bOK\b': 'Ô-kê', } def preprocess_text(text: str) -> str: """Thay thế từ tiếng Anh bằng phiên âm tiếng Việt""" result = text for pattern, replacement in ENGLISH_TO_VIETNAMESE.items(): result = re.sub(pattern, replacement, result, flags=re.IGNORECASE) return result async def synthesize_async(text: str, voice: str): if not text or not text.strip(): return None # Preprocess text processed_text = preprocess_text(text) # Get voice ID voice_id = VOICES.get(voice) if not voice_id: # Fallback - try exact match voice_id = "vi-VN-HoaiMyNeural" print(f"[WARN] Voice '{voice}' not found, using default") print(f"[TTS] Input voice: '{voice}'") print(f"[TTS] Voice ID: '{voice_id}'") print(f"[TTS] Text preview: '{text[:80]}...'") # Generate unique cache key including voice cache_key = hashlib.md5(f"{processed_text}_{voice_id}".encode()).hexdigest()[:16] cache_path = os.path.join(CACHE_DIR, f"{cache_key}.mp3") # Always regenerate for debugging communicate = edge_tts.Communicate(processed_text, voice_id) await communicate.save(cache_path) print(f"[TTS] Saved to: {cache_path}") return cache_path def synthesize(text: str, voice: str): print(f"\n{'='*50}") print(f"[REQUEST] Voice: '{voice}', Text length: {len(text)}") return asyncio.run(synthesize_async(text, voice)) with gr.Blocks(title="Vietnamese TTS") as demo: gr.Markdown("# 🎙️ Vietnamese TTS Server") gr.Markdown("Text-to-Speech tiếng Việt - Powered by Microsoft Edge TTS") with gr.Row(): text_input = gr.Textbox( label="Nhập văn bản", placeholder="Xin chào, tôi là AI Interview Coach...", lines=3 ) with gr.Row(): voice_select = gr.Dropdown( choices=list(VOICES.keys()), value="Hoài My (Nữ)", label="Giọng đọc" ) submit_btn = gr.Button("🔊 Tạo giọng nói", variant="primary") audio_output = gr.Audio(label="Kết quả", type="filepath") submit_btn.click( fn=synthesize, inputs=[text_input, voice_select], outputs=audio_output ) print("[STARTUP] Vietnamese TTS Server starting...") print(f"[STARTUP] Available voices: {list(VOICES.keys())}") demo.launch(server_name="0.0.0.0", server_port=7860)