import io import wave import struct from openai import AsyncOpenAI from app.ai.sarvam_client import SarvamClient from app.config import OPENAI_API_KEY from app.voice.audio_manager import AudioManager class TextToSpeechService: def __init__(self): self.sarvam = SarvamClient() self.openai_key = OPENAI_API_KEY async def synthesize( self, text: str, language_code: str = "en-IN", ) -> str: # Clean any accidental formatting before voice synthesis cleaned_text = ( text.replace("(", "") .replace(")", "") .replace("*", "") .replace("#", "") .replace("- ", "") .strip() ) # 1. Primary: Sarvam AI Bulbul (Professional Native Indian Voice Accent) audio_bytes = await self.sarvam.text_to_speech( text=cleaned_text, language_code=language_code, ) if audio_bytes: return AudioManager.save_audio(audio_bytes, "wav") # 2. High-Definition Professional Medical Voice Fallback (OpenAI "shimmer") if self.openai_key and self.openai_key != "your_openai_key": client = AsyncOpenAI(api_key=self.openai_key, timeout=10.0) try: resp = await client.audio.speech.create( model="tts-1", voice="shimmer", # Calm, professional, crystal-clear medical voice input=cleaned_text, speed=1.0, ) return AudioManager.save_audio(resp.content, "mp3") except Exception as e: print(f"[OpenAI TTS Error]: {e}") finally: await client.close() # 3. Fallback tone fallback_data = self._generate_fallback_wav() return AudioManager.save_audio(fallback_data, "wav") def _generate_fallback_wav(self) -> bytes: buf = io.BytesIO() with wave.open(buf, "wb") as wf: wf.setnchannels(1) wf.setsampwidth(2) wf.setframerate(16000) data = [int(1000 * (i % 32 < 16)) for i in range(4000)] wf.writeframes(struct.pack(f"<{len(data)}h", *data)) return buf.getvalue()