Files
call-center/services/ai_voice_runtime_service/runtime_tts_provider.py
T

124 lines
4.8 KiB
Python

from __future__ import annotations
from threading import Lock
from services.ai_voice_runtime_service.providers.tts import (
ElevenLabsTTSProvider,
OpenAITTSProvider,
TTSProvider,
YandexTTSProvider,
build_tts_provider,
)
from services.shared.db import get_session
from services.shared.voice_tts_config import load_effective_voice_tts_config
class RuntimeConfiguredTTSProvider(TTSProvider):
name = "dynamic"
def __init__(self, *, default_provider_name: str = "yandex") -> None:
self._default_provider_name = str(default_provider_name or "yandex").strip().lower() or "yandex"
self._provider_cache: dict[tuple[str, tuple[str | None, ...]], TTSProvider] = {}
self._lock = Lock()
def current_provider_name(self) -> str:
session = get_session()
try:
config = load_effective_voice_tts_config(session)
normalized = str(config.provider or self._default_provider_name).strip().lower()
if normalized in {"yandex", "elevenlabs", "openai"}:
return normalized
return self._default_provider_name
except Exception:
return self._default_provider_name
finally:
session.close()
def _provider_for_language(self, language: str | None) -> TTSProvider:
session = get_session()
try:
config = load_effective_voice_tts_config(session)
except Exception:
return build_tts_provider(self._default_provider_name)
finally:
session.close()
provider_name = str(config.provider or self._default_provider_name).strip().lower()
if provider_name not in {"yandex", "elevenlabs", "openai"}:
provider_name = self._default_provider_name
cache_key: tuple[str, tuple[str | None, ...]]
provider: TTSProvider
if provider_name == "yandex":
cache_key = (
provider_name,
(
config.yandex.ru.voice,
config.yandex.kz.voice,
),
)
with self._lock:
provider = self._provider_cache.get(cache_key)
if provider is None:
provider = YandexTTSProvider(
ru_voice=config.yandex.ru.voice,
kz_voice=config.yandex.kz.voice,
)
self._provider_cache[cache_key] = provider
return provider
if provider_name == "elevenlabs":
cache_key = (
provider_name,
(
config.elevenlabs.ru.voice,
config.elevenlabs.kz.voice,
config.elevenlabs.ru.model_id,
config.elevenlabs.kz.model_id,
config.elevenlabs.ru.language_code,
config.elevenlabs.kz.language_code,
),
)
with self._lock:
provider = self._provider_cache.get(cache_key)
if provider is None:
provider = ElevenLabsTTSProvider(
ru_voice=config.elevenlabs.ru.voice,
kz_voice=config.elevenlabs.kz.voice,
ru_model=config.elevenlabs.ru.model_id,
kz_model=config.elevenlabs.kz.model_id,
ru_language_code=config.elevenlabs.ru.language_code,
kz_language_code=config.elevenlabs.kz.language_code,
)
self._provider_cache[cache_key] = provider
return provider
cache_key = (
"openai",
(
config.openai.ru.voice,
config.openai.kz.voice,
config.openai.ru.model_id,
config.openai.kz.model_id,
),
)
with self._lock:
provider = self._provider_cache.get(cache_key)
if provider is None:
provider = OpenAITTSProvider(
ru_voice=config.openai.ru.voice,
kz_voice=config.openai.kz.voice,
ru_model=config.openai.ru.model_id,
kz_model=config.openai.kz.model_id,
)
self._provider_cache[cache_key] = provider
return provider
def synthesize(self, text: str, *, language: str | None = None, style_hints: dict[str, object] | None = None):
provider = self._provider_for_language(language)
return provider.synthesize(text, language=language, style_hints=style_hints)
def synthesize_chunks(self, text: str, *, language: str | None = None, style_hints: dict[str, object] | None = None):
provider = self._provider_for_language(language)
yield from provider.synthesize_chunks(text, language=language, style_hints=style_hints)