# male_voice.py import pyttsx3 from pathlib import Path import asyncio from typing import Union def text_to_speech(text: str, output_path: Union[str, Path]) -> bool: """ Convert text to speech and save to file using pyttsx3. Returns True on success. """ try: engine = pyttsx3.init() engine.setProperty('rate', 150) # Speaking speed # Select male voice (prioritize David, then any non-Zira) voices = engine.getProperty('voices') selected = False for voice in voices: name = voice.name.lower() if 'david' in name or ('male' in name and 'zira' not in name): engine.setProperty('voice', voice.id) selected = True break if not selected and voices: engine.setProperty('voice', voices[0].id) # fallback # Ensure output path is Path object output_path = Path(output_path) output_path.parent.mkdir(parents=True, exist_ok=True) # Save to file engine.save_to_file(text, str(output_path)) engine.runAndWait() print(f"[pyttsx3] Saved: {output_path}") return True except Exception as e: print(f"[pyttsx3] Error: {e}") return False async def text_to_speech_async(text: str, output_path: Union[str, Path]) -> bool: """ Async wrapper for text_to_speech using asyncio.to_thread. Prevents blocking the Streamlit event loop. """ return await asyncio.to_thread(text_to_speech, text, output_path)