"""Text-to-Speech Service using ElevenLabs""" import os from typing import Optional, List, Dict from elevenlabs import ElevenLabs, VoiceSettings # Voice options mapping (name -> voice_id) VOICE_OPTIONS = { "Rachel (Female)": "21m00Tcm4TlvDq8ikWAM", # Default - Rachel "Lera (Female)": "EXAVITQu4vr4xnSDxMaL", # Lera - female voice "Bella (Female)": "EXAVITQu4vr4xnSDxMaL", # Alternative female "Antoni (Male)": "ErXwobaYiN019PkySvjV", # Antoni - male voice "Arnold (Male)": "VR6AewLTigWG4xSOukaG", # Arnold - male voice "Adam (Male)": "pNInz6obpgDQGcFmaJgB", # Adam - male voice "Domi (Female)": "AZnzlk1XvdvUeBnXmlld", # Domi - female voice "Elli (Female)": "MF3mGyEYCl7XYWbV9V6O", # Elli - female voice "Josh (Male)": "TxGEqnHWrfWFTfGW9XjX", # Josh - male voice "Sam (Male)": "yoZ06aMxZJJ28mfd3POQ", # Sam - male voice } class TTSService: """Text-to-Speech service using ElevenLabs API""" def __init__(self, api_key: str, voice_id: str = "21m00Tcm4TlvDq8ikWAM"): """ Initialize TTS service Args: api_key: ElevenLabs API key voice_id: Voice ID to use (default: Rachel) """ self.api_key = api_key self.voice_id = voice_id self.client = None self.available = False if api_key: try: self.client = ElevenLabs(api_key=api_key) self.available = True except Exception as e: print(f"Error initializing ElevenLabs client: {e}") def text_to_speech(self, text: str, voice_id: Optional[str] = None) -> Optional[bytes]: """ Convert text to speech Args: text: Text to convert voice_id: Optional voice ID override Returns: Audio bytes or None if error """ if not self.client: print("ElevenLabs client not initialized. Please check API key.") return None try: voice_to_use = voice_id or self.voice_id # Generate audio using newer model (free tier compatible) audio = self.client.generate( text=text, voice=voice_to_use, model="eleven_turbo_v2_5" # Free tier compatible model ) # Convert generator to bytes audio_bytes = b"" for chunk in audio: audio_bytes += chunk return audio_bytes except Exception as e: print(f"Error generating speech: {e}") return None def text_to_speech_stream(self, text: str, voice_id: Optional[str] = None): """ Convert text to speech with streaming Args: text: Text to convert voice_id: Optional voice ID override Yields: Audio chunks """ if not self.client: print("ElevenLabs client not initialized. Please check API key.") return try: voice_to_use = voice_id or self.voice_id # Stream audio using newer model (free tier compatible) audio_stream = self.client.generate( text=text, voice=voice_to_use, model="eleven_turbo_v2_5", stream=True ) for chunk in audio_stream: yield chunk except Exception as e: print(f"Error streaming speech: {e}") return def save_audio(self, audio_bytes: bytes, filename: str) -> bool: """ Save audio bytes to file Args: audio_bytes: Audio data filename: Output filename Returns: Success status """ try: with open(filename, 'wb') as f: f.write(audio_bytes) return True except Exception as e: print(f"Error saving audio: {e}") return False def get_available_voices(self) -> List[Dict[str, str]]: """ Get list of available voices Returns: List of voice information """ if not self.client: return [] try: voices = self.client.voices.get_all() return [{"voice_id": v.voice_id, "name": v.name} for v in voices.voices] except Exception as e: print(f"Error getting voices: {e}") return [ {"voice_id": "21m00Tcm4TlvDq8ikWAM", "name": "Rachel (Default)"}, {"voice_id": "ErXwobaYiN019PkySvjV", "name": "Antoni"}, {"voice_id": "MF3mGyEYCl7XYWbV9V6O", "name": "Elli"}, ]