AI-RADIO / src /tts_service.py
Nikita Makarov
v2.4
9b6a0be
"""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"},
]