File size: 1,686 Bytes
88fe4e7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
import asyncio
import threading
import tempfile
from typing import Optional
import edge_tts
from src.config import CONFIG

# Voice mappings for supported locales
VOICE_MAP = {
    "English (US) - Aria": "en-US-AriaNeural",
    "English (US) - Guy": "en-US-GuyNeural",
    "English (UK) - Sonia": "en-GB-SoniaNeural",
    "Hindi - Swara": "hi-IN-SwaraNeural",
    "Hindi - Madhur": "hi-IN-MadhurNeural",
}

def init_tts_loop() -> asyncio.AbstractEventLoop:
    """Create a dedicated event loop for TTS in a background thread."""
    loop = asyncio.new_event_loop()

    def _run():
        asyncio.set_event_loop(loop)
        loop.run_forever()

    threading.Thread(target=_run, daemon=True).start()
    return loop

_TTS_LOOP = init_tts_loop()

def text_to_speech(text: str, voice_name: str = "English (US) - Aria") -> Optional[str]:
    """Convert text to speech, returning the audio file path."""
    if not text or not text.strip():
        return None

    try:
        voice_id = VOICE_MAP.get(voice_name, "en-US-AriaNeural")

        async def _generate():
            with tempfile.NamedTemporaryFile(delete=False, suffix=f".{CONFIG.AUDIO_FORMAT}") as f:
                path = f.name
            communicate = edge_tts.Communicate(
                text.strip(),
                voice=voice_id,
                rate=CONFIG.TTS_RATE,
            )
            await communicate.save(path)
            return path

        future = asyncio.run_coroutine_threadsafe(_generate(), _TTS_LOOP)
        return future.result(timeout=CONFIG.TTS_TIMEOUT)
    except Exception as e:
        print(f"TTS error: {e}")
        return None