68 lines
2.2 KiB
Python
68 lines
2.2 KiB
Python
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()
|