Remove self._voice_id from TTS Service implementations in favor of self._settings.voice

This commit is contained in:
Paul Kompfner
2026-02-20 10:48:16 -05:00
parent 5d8a5bf750
commit f4e9825c03
26 changed files with 103 additions and 115 deletions

View File

@@ -291,9 +291,8 @@ class NewTTSService(TTSService):
voice: Voice identifier to use.
**kwargs: Additional arguments passed to the parent service.
"""
super().__init__(**kwargs)
super().__init__(voice=voice, **kwargs)
self._api_key = api_key
self._voice_id = voice
```
---

View File

@@ -153,6 +153,7 @@ class AsyncAITTSService(AudioContextTTSService):
pause_frame_processing=True,
push_stop_frames=True,
sample_rate=sample_rate,
voice=voice_id,
**kwargs,
)
@@ -173,7 +174,6 @@ class AsyncAITTSService(AudioContextTTSService):
)
self.set_model_name(model)
self._voice_id = voice_id
self._receive_task = None
self._keepalive_task = None
@@ -278,7 +278,7 @@ class AsyncAITTSService(AudioContextTTSService):
)
init_msg = {
"model_id": self._model_name,
"voice": {"mode": "id", "id": self._voice_id},
"voice": {"mode": "id", "id": self._settings.voice},
"output_format": {
"container": self._settings.output_container,
"encoding": self._settings.output_encoding,
@@ -497,7 +497,7 @@ class AsyncAIHttpTTSService(TTSService):
params: Additional input parameters for voice customization.
**kwargs: Additional arguments passed to the parent TTSService.
"""
super().__init__(sample_rate=sample_rate, **kwargs)
super().__init__(sample_rate=sample_rate, voice=voice_id, **kwargs)
params = params or AsyncAIHttpTTSService.InputParams()
@@ -514,7 +514,6 @@ class AsyncAIHttpTTSService(TTSService):
if params.language
else None,
)
self._voice_id = voice_id
self.set_model_name(model)
self._session = aiohttp_session
@@ -561,7 +560,7 @@ class AsyncAIHttpTTSService(TTSService):
logger.debug(f"{self}: Generating TTS [{text}]")
try:
voice_config = {"mode": "id", "id": self._voice_id}
voice_config = {"mode": "id", "id": self._settings.voice}
await self.start_ttfb_metrics()
payload = {
"model_id": self._model_name,

View File

@@ -195,7 +195,7 @@ class AWSPollyTTSService(TTSService):
params: Additional input parameters for voice customization.
**kwargs: Additional arguments passed to parent TTSService class.
"""
super().__init__(sample_rate=sample_rate, **kwargs)
super().__init__(sample_rate=sample_rate, voice=voice_id, **kwargs)
params = params or AWSPollyTTSService.InputParams()
@@ -222,8 +222,6 @@ class AWSPollyTTSService(TTSService):
self._resampler = create_stream_resampler()
self._voice_id = voice_id
def can_generate_metrics(self) -> bool:
"""Check if this service can generate processing metrics.
@@ -299,7 +297,7 @@ class AWSPollyTTSService(TTSService):
"Text": ssml,
"TextType": "ssml",
"OutputFormat": "pcm",
"VoiceId": self._voice_id,
"VoiceId": self._settings.voice,
"Engine": self._settings.engine,
# AWS only supports 8000 and 16000 for PCM. We select 16000.
"SampleRate": "16000",

View File

@@ -165,12 +165,12 @@ class AzureBaseTTSService:
role=params.role,
style=params.style,
style_degree=params.style_degree,
voice=voice,
volume=params.volume,
)
self._api_key = api_key
self._region = region
self._voice_id = voice
self._speech_synthesizer = None
def language_to_service_language(self, language: Language) -> Optional[str]:
@@ -194,7 +194,7 @@ class AzureBaseTTSService:
f"<speak version='1.0' xml:lang='{language}' "
"xmlns='http://www.w3.org/2001/10/synthesis' "
"xmlns:mstts='http://www.w3.org/2001/mstts'>"
f"<voice name='{self._voice_id}'>"
f"<voice name='{self._settings.voice}'>"
"<mstts:silence type='Sentenceboundary' value='20ms' />"
)
@@ -295,6 +295,7 @@ class AzureTTSService(WordTTSService, AzureBaseTTSService):
push_stop_frames=True,
pause_frame_processing=True,
sample_rate=sample_rate,
voice=voice,
**kwargs,
)
@@ -733,7 +734,7 @@ class AzureHttpTTSService(TTSService, AzureBaseTTSService):
params: Voice and synthesis parameters configuration.
**kwargs: Additional arguments passed to parent TTSService.
"""
super().__init__(sample_rate=sample_rate, **kwargs)
super().__init__(sample_rate=sample_rate, voice=voice, **kwargs)
# Initialize Azure-specific functionality from mixin
self._init_azure_base(api_key=api_key, region=region, voice=voice, params=params)

View File

@@ -213,7 +213,7 @@ class CambTTSService(TTSService):
params: Additional voice parameters. If None, uses defaults.
**kwargs: Additional arguments passed to parent TTSService.
"""
super().__init__(sample_rate=sample_rate, **kwargs)
super().__init__(sample_rate=sample_rate, voice=voice_id, **kwargs)
self._api_key = api_key
self._timeout = timeout
@@ -238,7 +238,6 @@ class CambTTSService(TTSService):
)
self.set_model_name(model)
self._voice_id = voice_id
self._client = None
@@ -299,7 +298,7 @@ class CambTTSService(TTSService):
# Build SDK parameters
tts_kwargs: Dict[str, Any] = {
"text": text,
"voice_id": self._voice_id,
"voice_id": self._settings.voice,
"language": self._settings.language,
"speech_model": self.model_name,
"output_configuration": StreamTtsOutputConfiguration(format="pcm_s16le"),

View File

@@ -313,6 +313,7 @@ class CartesiaTTSService(AudioContextWordTTSService):
pause_frame_processing=True,
sample_rate=sample_rate,
text_aggregator=text_aggregator,
voice=voice_id,
**kwargs,
)
@@ -340,9 +341,9 @@ class CartesiaTTSService(AudioContextWordTTSService):
emotion=params.emotion,
generation_config=params.generation_config,
pronunciation_dict_id=params.pronunciation_dict_id,
voice=voice_id,
)
self.set_model_name(model)
self._voice_id = voice_id
self._context_id = None
self._receive_task = None
@@ -440,7 +441,7 @@ class CartesiaTTSService(AudioContextWordTTSService):
):
voice_config = {}
voice_config["mode"] = "id"
voice_config["id"] = self._voice_id
voice_config["id"] = self._settings.voice
if is_given(self._settings.emotion) and self._settings.emotion:
with warnings.catch_warnings():
@@ -720,7 +721,7 @@ class CartesiaHttpTTSService(TTSService):
params: Additional input parameters for voice customization.
**kwargs: Additional arguments passed to the parent TTSService.
"""
super().__init__(sample_rate=sample_rate, **kwargs)
super().__init__(sample_rate=sample_rate, voice=voice_id, **kwargs)
params = params or CartesiaHttpTTSService.InputParams()
@@ -741,7 +742,6 @@ class CartesiaHttpTTSService(TTSService):
generation_config=params.generation_config,
pronunciation_dict_id=params.pronunciation_dict_id,
)
self._voice_id = voice_id
self.set_model_name(model)
self._client = AsyncCartesia(
@@ -809,7 +809,7 @@ class CartesiaHttpTTSService(TTSService):
logger.debug(f"{self}: Generating TTS [{text}]")
try:
voice_config = {"mode": "id", "id": self._voice_id}
voice_config = {"mode": "id", "id": self._settings.voice}
if is_given(self._settings.emotion) and self._settings.emotion:
with warnings.catch_warnings():

View File

@@ -101,6 +101,7 @@ class DeepgramTTSService(WebsocketTTSService):
pause_frame_processing=True,
push_stop_frames=True,
append_trailing_space=True,
voice=voice,
**kwargs,
)
@@ -111,7 +112,6 @@ class DeepgramTTSService(WebsocketTTSService):
voice=voice,
encoding=encoding,
)
self._voice_id = voice
self._receive_task = None
self._context_id: Optional[str] = None
@@ -210,7 +210,7 @@ class DeepgramTTSService(WebsocketTTSService):
# Build WebSocket URL with query parameters
params = []
params.append(f"model={self._voice_id}")
params.append(f"model={self._settings.voice}")
params.append(f"encoding={self._settings.encoding}")
params.append(f"sample_rate={self.sample_rate}")
@@ -388,7 +388,7 @@ class DeepgramHttpTTSService(TTSService):
encoding: Audio encoding format. Defaults to "linear16".
**kwargs: Additional arguments passed to parent TTSService class.
"""
super().__init__(sample_rate=sample_rate, **kwargs)
super().__init__(sample_rate=sample_rate, voice=voice, **kwargs)
self._api_key = api_key
self._session = aiohttp_session
@@ -398,7 +398,6 @@ class DeepgramHttpTTSService(TTSService):
voice=voice,
encoding=encoding,
)
self._voice_id = voice
def can_generate_metrics(self) -> bool:
"""Check if the service can generate metrics.
@@ -427,7 +426,7 @@ class DeepgramHttpTTSService(TTSService):
headers = {"Authorization": f"Token {self._api_key}", "Content-Type": "application/json"}
params = {
"model": self._voice_id,
"model": self._settings.voice,
"encoding": self._settings.encoding,
"sample_rate": self.sample_rate,
"container": "none",

View File

@@ -400,6 +400,7 @@ class ElevenLabsTTSService(AudioContextWordTTSService):
push_stop_frames=True,
pause_frame_processing=True,
sample_rate=sample_rate,
voice=voice_id,
**kwargs,
)
@@ -424,7 +425,6 @@ class ElevenLabsTTSService(AudioContextWordTTSService):
apply_text_normalization=params.apply_text_normalization,
)
self.set_model_name(model)
self._voice_id = voice_id
self._output_format = "" # initialized in start()
self._voice_settings = self._set_voice_settings()
@@ -607,7 +607,7 @@ class ElevenLabsTTSService(AudioContextWordTTSService):
logger.debug("Connecting to ElevenLabs")
voice_id = self._voice_id
voice_id = self._settings.voice
model = self.model_name
output_format = self._output_format
url = f"{self._url}/v1/text-to-speech/{voice_id}/multi-stream-input?model_id={model}&output_format={output_format}&auto_mode={self._settings.auto_mode}"
@@ -906,6 +906,7 @@ class ElevenLabsHttpTTSService(WordTTSService):
push_text_frames=False,
push_stop_frames=True,
sample_rate=sample_rate,
voice=voice_id,
**kwargs,
)
@@ -931,7 +932,6 @@ class ElevenLabsHttpTTSService(WordTTSService):
apply_text_normalization=params.apply_text_normalization,
)
self.set_model_name(model)
self._voice_id = voice_id
self._output_format = "" # initialized in start()
self._voice_settings = self._set_voice_settings()
self._pronunciation_dictionary_locators = params.pronunciation_dictionary_locators
@@ -1098,7 +1098,7 @@ class ElevenLabsHttpTTSService(WordTTSService):
logger.debug(f"{self}: Generating TTS [{text}]")
# Use the with-timestamps endpoint
url = f"{self._base_url}/v1/text-to-speech/{self._voice_id}/stream/with-timestamps"
url = f"{self._base_url}/v1/text-to-speech/{self._settings.voice}/stream/with-timestamps"
payload: Dict[str, Union[str, Dict[str, Union[float, bool]]]] = {
"text": text,

View File

@@ -602,7 +602,7 @@ class GoogleHttpTTSService(TTSService):
params: Voice customization parameters including pitch, rate, volume, etc.
**kwargs: Additional arguments passed to parent TTSService.
"""
super().__init__(sample_rate=sample_rate, **kwargs)
super().__init__(sample_rate=sample_rate, voice=voice_id, **kwargs)
params = params or GoogleHttpTTSService.InputParams()
@@ -618,8 +618,8 @@ class GoogleHttpTTSService(TTSService):
else "en-US",
gender=params.gender,
google_style=params.google_style,
voice=voice_id,
)
self._voice_id = voice_id
self._client: texttospeech_v1.TextToSpeechAsyncClient = self._create_client(
credentials, credentials_path
)
@@ -707,7 +707,7 @@ class GoogleHttpTTSService(TTSService):
ssml = "<speak>"
# Voice tag
voice_attrs = [f"name='{self._voice_id}'"]
voice_attrs = [f"name='{self._settings.voice}'"]
language = self._settings.language
voice_attrs.append(f"language='{language}'")
@@ -766,8 +766,8 @@ class GoogleHttpTTSService(TTSService):
await self.start_ttfb_metrics()
# Check if the voice is a Chirp voice (including Chirp 3) or Journey voice
is_chirp_voice = "chirp" in self._voice_id.lower()
is_journey_voice = "journey" in self._voice_id.lower()
is_chirp_voice = "chirp" in self._settings.voice.lower()
is_journey_voice = "journey" in self._settings.voice.lower()
# Create synthesis input based on voice_id
if is_chirp_voice or is_journey_voice:
@@ -778,7 +778,7 @@ class GoogleHttpTTSService(TTSService):
synthesis_input = texttospeech_v1.SynthesisInput(ssml=ssml)
voice = texttospeech_v1.VoiceSelectionParams(
language_code=self._settings.language, name=self._voice_id
language_code=self._settings.language, name=self._settings.voice
)
# Build audio config with conditional speaking_rate
audio_config_params = {
@@ -1015,7 +1015,7 @@ class GoogleTTSService(GoogleBaseTTSService):
params: Language configuration parameters.
**kwargs: Additional arguments passed to parent TTSService.
"""
super().__init__(sample_rate=sample_rate, **kwargs)
super().__init__(sample_rate=sample_rate, voice=voice_id, **kwargs)
params = params or GoogleTTSService.InputParams()
@@ -1025,8 +1025,8 @@ class GoogleTTSService(GoogleBaseTTSService):
if params.language
else "en-US",
speaking_rate=params.speaking_rate,
voice=voice_id,
)
self._voice_id = voice_id
self._voice_cloning_key = voice_cloning_key
self._client: texttospeech_v1.TextToSpeechAsyncClient = self._create_client(
credentials, credentials_path
@@ -1073,7 +1073,7 @@ class GoogleTTSService(GoogleBaseTTSService):
)
else:
voice = texttospeech_v1.VoiceSelectionParams(
language_code=self._settings.language, name=self._voice_id
language_code=self._settings.language, name=self._settings.voice
)
# Create streaming config
@@ -1220,7 +1220,7 @@ class GeminiTTSService(GoogleBaseTTSService):
f"Google TTS only supports {self.GOOGLE_SAMPLE_RATE}Hz sample rate. "
f"Current rate of {sample_rate}Hz may cause issues."
)
super().__init__(sample_rate=sample_rate, **kwargs)
super().__init__(sample_rate=sample_rate, voice=voice_id, **kwargs)
params = params or GeminiTTSService.InputParams()
@@ -1229,7 +1229,6 @@ class GeminiTTSService(GoogleBaseTTSService):
self._location = location
self._model = model
self._voice_id = voice_id
self._settings = GeminiTTSSettings(
language=self.language_to_service_language(params.language)
if params.language
@@ -1237,6 +1236,7 @@ class GeminiTTSService(GoogleBaseTTSService):
prompt=params.prompt,
multi_speaker=params.multi_speaker,
speaker_configs=params.speaker_configs,
voice=voice_id,
)
self._client: texttospeech_v1.TextToSpeechAsyncClient = self._create_client(
@@ -1306,7 +1306,7 @@ class GeminiTTSService(GoogleBaseTTSService):
speaker_voice_configs.append(
texttospeech_v1.MultispeakerPrebuiltVoice(
speaker_alias=speaker_config["speaker_alias"],
speaker_id=speaker_config.get("speaker_id", self._voice_id),
speaker_id=speaker_config.get("speaker_id", self._settings.voice),
)
)
@@ -1323,7 +1323,7 @@ class GeminiTTSService(GoogleBaseTTSService):
# Single speaker mode
voice = texttospeech_v1.VoiceSelectionParams(
language_code=self._settings.language,
name=self._voice_id,
name=self._settings.voice,
model_name=self._model,
)

View File

@@ -91,6 +91,7 @@ class GradiumTTSService(InterruptibleWordTTSService):
push_stop_frames=True,
pause_frame_processing=True,
sample_rate=SAMPLE_RATE,
voice=voice_id,
**kwargs,
)
@@ -99,7 +100,6 @@ class GradiumTTSService(InterruptibleWordTTSService):
# Store service configuration
self._api_key = api_key
self._url = url
self._voice_id = voice_id
self._json_config = json_config
self._settings = GradiumTTSSettings(
model=model,
@@ -208,7 +208,7 @@ class GradiumTTSService(InterruptibleWordTTSService):
setup_msg = {
"type": "setup",
"output_format": "pcm",
"voice_id": self._voice_id,
"voice_id": self._settings.voice,
}
if self._json_config is not None:
setup_msg["json_config"] = self._json_config

View File

@@ -102,6 +102,7 @@ class GroqTTSService(TTSService):
super().__init__(
pause_frame_processing=True,
sample_rate=sample_rate,
voice=voice_id,
**kwargs,
)
@@ -110,7 +111,6 @@ class GroqTTSService(TTSService):
self._api_key = api_key
self._model_name = model_name
self._output_format = output_format
self._voice_id = voice_id
self._params = params
self._settings = GroqTTSSettings(
@@ -151,7 +151,7 @@ class GroqTTSService(TTSService):
try:
response = await self._client.audio.speech.create(
model=self._model_name,
voice=self._voice_id,
voice=self._settings.voice,
response_format=self._output_format,
input=text,
)

View File

@@ -109,6 +109,7 @@ class HathoraTTSService(TTSService):
"""
super().__init__(
sample_rate=sample_rate,
voice=voice_id,
**kwargs,
)
self._model = model
@@ -125,7 +126,6 @@ class HathoraTTSService(TTSService):
)
self.set_model_name(model)
self._voice_id = voice_id
def can_generate_metrics(self) -> bool:
"""Check if this service can generate processing metrics.
@@ -154,8 +154,8 @@ class HathoraTTSService(TTSService):
payload = {"model": self._model, "text": text}
if self._voice_id is not None:
payload["voice"] = self._voice_id
if self._settings.voice is not None:
payload["voice"] = self._settings.voice
if self._settings.speed is not None:
payload["speed"] = self._settings.speed
if self._settings.config is not None:

View File

@@ -126,6 +126,7 @@ class HumeTTSService(WordTTSService):
sample_rate=sample_rate,
push_text_frames=False,
push_stop_frames=True,
voice=voice_id,
**kwargs,
)
@@ -142,7 +143,6 @@ class HumeTTSService(WordTTSService):
speed=params.speed,
trailing_silence=params.trailing_silence,
)
self._voice_id = voice_id
self._audio_bytes = b""
@@ -263,7 +263,7 @@ class HumeTTSService(WordTTSService):
# Build the request payload
utterance_kwargs: dict[str, Any] = {
"text": text,
"voice": PostedUtteranceVoiceWithId(id=self._voice_id),
"voice": PostedUtteranceVoiceWithId(id=self._settings.voice),
}
if self._settings.description is not None:
utterance_kwargs["description"] = self._settings.description

View File

@@ -144,6 +144,7 @@ class InworldHttpTTSService(WordTTSService):
push_text_frames=False,
push_stop_frames=True,
sample_rate=sample_rate,
voice=voice_id,
**kwargs,
)
@@ -173,7 +174,6 @@ class InworldHttpTTSService(WordTTSService):
self._cumulative_time = 0.0
self._voice_id = voice_id
self.set_model_name(model)
def can_generate_metrics(self) -> bool:
@@ -519,6 +519,7 @@ class InworldTTSService(AudioContextWordTTSService):
sample_rate=sample_rate,
aggregate_sentences=aggregate_sentences,
append_trailing_space=append_trailing_space,
voice=voice_id,
**kwargs,
)
@@ -563,7 +564,6 @@ class InworldTTSService(AudioContextWordTTSService):
# Track the end time of the last word in the current generation
self._generation_end_time = 0.0
self._voice_id = voice_id
self.set_model_name(model)
def can_generate_metrics(self) -> bool:

View File

@@ -137,11 +137,10 @@ class KokoroTTSService(TTSService):
**kwargs: Additional arguments passed to parent `TTSService`.
"""
super().__init__(**kwargs)
super().__init__(voice=voice_id, **kwargs)
params = params or KokoroTTSService.InputParams()
self._voice_id = voice_id
self._lang_code = language_to_kokoro_language(params.language)
self._settings = KokoroTTSSettings(
@@ -182,7 +181,7 @@ class KokoroTTSService(TTSService):
yield TTSStartedFrame(context_id=context_id)
stream = self._kokoro.create_stream(
text, voice=self._voice_id, lang=self._lang_code, speed=1.0
text, voice=self._settings.voice, lang=self._lang_code, speed=1.0
)
async for samples, sample_rate in stream:

View File

@@ -118,11 +118,11 @@ class LmntTTSService(InterruptibleTTSService):
push_stop_frames=True,
pause_frame_processing=True,
sample_rate=sample_rate,
voice=voice_id,
**kwargs,
)
self._api_key = api_key
self._voice_id = voice_id
self.set_model_name(model)
self._settings = LmntTTSSettings(
model=model,
@@ -235,7 +235,7 @@ class LmntTTSService(InterruptibleTTSService):
# Build initial connection message
init_msg = {
"X-API-Key": self._api_key,
"voice": self._voice_id,
"voice": self._settings.voice,
"format": self._settings.format,
"sample_rate": self.sample_rate,
"language": self._settings.language,

View File

@@ -227,7 +227,7 @@ class MiniMaxHttpTTSService(TTSService):
params: Additional configuration parameters.
**kwargs: Additional arguments passed to parent TTSService.
"""
super().__init__(sample_rate=sample_rate, **kwargs)
super().__init__(sample_rate=sample_rate, voice=voice_id, **kwargs)
params = params or MiniMaxHttpTTSService.InputParams()
@@ -236,7 +236,6 @@ class MiniMaxHttpTTSService(TTSService):
self._base_url = f"{base_url}?GroupId={group_id}"
self._session = aiohttp_session
self._model_name = model
self._voice_id = voice_id
# Create voice settings
self._settings = MiniMaxTTSSettings(
@@ -251,8 +250,7 @@ class MiniMaxHttpTTSService(TTSService):
audio_channel=1,
)
# Set voice and model
self._voice_id = voice_id
# Set model
self.set_model_name(model)
# Add language boost if provided
@@ -359,7 +357,7 @@ class MiniMaxHttpTTSService(TTSService):
# Build voice_setting dict for API
voice_setting = {
"voice_id": self._voice_id,
"voice_id": self._settings.voice,
"speed": self._settings.speed,
"vol": self._settings.volume,
"pitch": self._settings.pitch,

View File

@@ -139,6 +139,7 @@ class NeuphonicTTSService(InterruptibleTTSService):
push_stop_frames=True,
stop_frame_timeout_s=2.0,
sample_rate=sample_rate,
voice=voice_id,
**kwargs,
)
@@ -151,8 +152,8 @@ class NeuphonicTTSService(InterruptibleTTSService):
speed=params.speed,
encoding=encoding,
sampling_rate=sample_rate,
voice=voice_id,
)
self._voice_id = voice_id
self._cumulative_time = 0
@@ -288,7 +289,7 @@ class NeuphonicTTSService(InterruptibleTTSService):
"speed": self._settings.speed,
"encoding": self._settings.encoding,
"sampling_rate": self._settings.sampling_rate,
"voice_id": self._voice_id,
"voice_id": self._settings.voice,
}
query_params = []
@@ -442,7 +443,7 @@ class NeuphonicHttpTTSService(TTSService):
params: Additional input parameters for TTS configuration.
**kwargs: Additional arguments passed to parent TTSService.
"""
super().__init__(sample_rate=sample_rate, **kwargs)
super().__init__(sample_rate=sample_rate, voice=voice_id, **kwargs)
params = params or NeuphonicHttpTTSService.InputParams()

View File

@@ -132,10 +132,9 @@ class OpenAITTSService(TTSService):
f"OpenAI TTS only supports {self.OPENAI_SAMPLE_RATE}Hz sample rate. "
f"Current rate of {sample_rate}Hz may cause issues."
)
super().__init__(sample_rate=sample_rate, **kwargs)
super().__init__(sample_rate=sample_rate, voice=voice, **kwargs)
self.set_model_name(model)
self._voice_id = voice
self._client = AsyncOpenAI(api_key=api_key, base_url=base_url)
if instructions or speed:
@@ -196,7 +195,7 @@ class OpenAITTSService(TTSService):
create_params = {
"input": text,
"model": self.model_name,
"voice": VALID_VOICES[self._voice_id],
"voice": VALID_VOICES[self._settings.voice],
"response_format": "pcm",
}

View File

@@ -173,6 +173,7 @@ class PlayHTTTSService(InterruptibleTTSService):
super().__init__(
pause_frame_processing=True,
sample_rate=sample_rate,
voice=voice_url,
**kwargs,
)
@@ -205,7 +206,6 @@ class PlayHTTTSService(InterruptibleTTSService):
seed=params.seed,
)
self.set_model_name(voice_engine)
self._voice_id = voice_url
def can_generate_metrics(self) -> bool:
"""Check if this service can generate processing metrics.
@@ -425,7 +425,7 @@ class PlayHTTTSService(InterruptibleTTSService):
tts_command = {
"text": text,
"voice": self._voice_id,
"voice": self._settings.voice,
"voice_engine": self._settings.voice_engine,
"output_format": self._settings.output_format,
"sample_rate": self.sample_rate,
@@ -511,7 +511,7 @@ class PlayHTHttpTTSService(TTSService):
params: Additional input parameters for voice customization.
**kwargs: Additional arguments passed to parent TTSService.
"""
super().__init__(sample_rate=sample_rate, **kwargs)
super().__init__(sample_rate=sample_rate, voice=voice_url, **kwargs)
# Warn about deprecated protocol parameter if explicitly provided
if protocol:
@@ -556,7 +556,6 @@ class PlayHTHttpTTSService(TTSService):
seed=params.seed,
)
self.set_model_name(voice_engine)
self._voice_id = voice_url
async def start(self, frame: StartFrame):
"""Start the PlayHT HTTP TTS service.
@@ -605,7 +604,7 @@ class PlayHTHttpTTSService(TTSService):
# Prepare the request payload
payload = {
"text": text,
"voice": self._voice_id,
"voice": self._settings.voice,
"voice_engine": self._settings.voice_engine,
"output_format": self._settings.output_format,
"sample_rate": self.sample_rate,

View File

@@ -94,11 +94,11 @@ class ResembleAITTSService(AudioContextWordTTSService):
"""
super().__init__(
sample_rate=sample_rate,
voice=voice_id,
**kwargs,
)
self._api_key = api_key
self._voice_id = voice_id
self._url = url
self._settings = ResembleAITTSSettings(
voice=voice_id,
@@ -126,8 +126,6 @@ class ResembleAITTSService(AudioContextWordTTSService):
self._jitter_buffer_bytes = 44100 # ~1000ms at 22050Hz to handle 400ms+ network gaps
self._playback_started: dict[str, bool] = {} # Track if we've started playback per request
self._voice_id = voice_id
def can_generate_metrics(self) -> bool:
"""Check if this service can generate processing metrics.
@@ -146,7 +144,7 @@ class ResembleAITTSService(AudioContextWordTTSService):
JSON string containing the request payload.
"""
msg = {
"voice_uuid": self._voice_id,
"voice_uuid": self._settings.voice,
"data": text,
"binary_response": False, # Use JSON frames to get timestamps
"request_id": self._request_id_counter, # ResembleAI only accepts number

View File

@@ -191,6 +191,7 @@ class RimeTTSService(AudioContextWordTTSService):
pause_frame_processing=True,
append_trailing_space=True,
sample_rate=sample_rate,
voice=voice_id,
**kwargs,
)
@@ -207,7 +208,6 @@ class RimeTTSService(AudioContextWordTTSService):
# Store service configuration
self._api_key = api_key
self._url = url
self._voice_id = voice_id
self._model = model
self._settings = RimeTTSSettings(
voice=voice_id,
@@ -582,7 +582,7 @@ class RimeHttpTTSService(TTSService):
params: Additional configuration parameters.
**kwargs: Additional arguments passed to parent TTSService.
"""
super().__init__(sample_rate=sample_rate, **kwargs)
super().__init__(sample_rate=sample_rate, voice=voice_id, **kwargs)
params = params or RimeHttpTTSService.InputParams()
@@ -596,8 +596,8 @@ class RimeHttpTTSService(TTSService):
pauseBetweenBrackets=params.pause_between_brackets,
phonemizeBetweenBrackets=params.phonemize_between_brackets,
inlineSpeedAlpha=params.inline_speed_alpha if params.inline_speed_alpha else NOT_GIVEN,
voice=voice_id,
)
self._voice_id = voice_id
self.set_model_name(model)
def can_generate_metrics(self) -> bool:
@@ -648,7 +648,7 @@ class RimeHttpTTSService(TTSService):
if is_given(self._settings.inlineSpeedAlpha):
payload["inlineSpeedAlpha"] = self._settings.inlineSpeedAlpha
payload["text"] = text
payload["speaker"] = self._voice_id
payload["speaker"] = self._settings.voice
payload["modelId"] = self._model_name
payload["samplingRate"] = self.sample_rate
@@ -762,12 +762,12 @@ class RimeNonJsonTTSService(InterruptibleTTSService):
aggregate_sentences=aggregate_sentences,
push_stop_frames=True,
pause_frame_processing=True,
voice=voice_id,
**kwargs,
)
params = params or RimeNonJsonTTSService.InputParams()
self._api_key = api_key
self._url = url
self._voice_id = voice_id
self._model = model
self._settings = RimeNonJsonTTSSettings(
voice=voice_id,

View File

@@ -281,7 +281,6 @@ class SarvamTTSSettings(TTSSettings):
Parameters:
target_language_code: Sarvam language code.
speaker: Voice speaker ID.
speech_sample_rate: Audio sample rate as string.
enable_preprocessing: Enable text preprocessing. Defaults to False.
**Note:** Always enabled for bulbul:v3-beta.
@@ -306,7 +305,6 @@ class SarvamTTSSettings(TTSSettings):
"""
target_language_code: str | None | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
speaker: str | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
speech_sample_rate: str | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
enable_preprocessing: bool | None | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
min_buffer_size: int | None | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
@@ -460,14 +458,14 @@ class SarvamHttpTTSService(TTSService):
if sample_rate is None:
sample_rate = self._config.default_sample_rate
super().__init__(sample_rate=sample_rate, **kwargs)
params = params or SarvamHttpTTSService.InputParams()
# Set default voice based on model if not specified
if voice_id is None:
voice_id = self._config.default_speaker
super().__init__(sample_rate=sample_rate, voice=voice_id, **kwargs)
self._api_key = api_key
self._base_url = base_url
self._session = aiohttp_session
@@ -489,6 +487,7 @@ class SarvamHttpTTSService(TTSService):
),
pace=pace,
model=model,
voice=voice_id,
)
# Add parameters based on model support
@@ -508,7 +507,6 @@ class SarvamHttpTTSService(TTSService):
logger.warning(f"temperature parameter is ignored for {model}")
self.set_model_name(model)
self._voice_id = voice_id
def can_generate_metrics(self) -> bool:
"""Check if this service can generate processing metrics.
@@ -558,7 +556,7 @@ class SarvamHttpTTSService(TTSService):
payload = {
"text": text,
"target_language_code": self._settings.language,
"speaker": self._voice_id,
"speaker": self._settings.voice,
"sample_rate": self.sample_rate,
"enable_preprocessing": self._settings.enable_preprocessing,
"model": self._model_name,
@@ -812,6 +810,10 @@ class SarvamTTSService(InterruptibleTTSService):
if sample_rate is None:
sample_rate = self._config.default_sample_rate
# Set default voice based on model if not specified
if voice_id is None:
voice_id = self._config.default_speaker
# Initialize parent class first
super().__init__(
aggregate_sentences=aggregate_sentences,
@@ -819,19 +821,15 @@ class SarvamTTSService(InterruptibleTTSService):
pause_frame_processing=True,
push_stop_frames=True,
sample_rate=sample_rate,
voice=voice_id,
**kwargs,
)
params = params or SarvamTTSService.InputParams()
# Set default voice based on model if not specified
if voice_id is None:
voice_id = self._config.default_speaker
# WebSocket endpoint URL with model query parameter
self._websocket_url = f"{url}?model={model}"
self._api_key = api_key
self.set_model_name(model)
self._voice_id = voice_id
# Validate and clamp pace to model's valid range
pace = params.pace
@@ -845,7 +843,6 @@ class SarvamTTSService(InterruptibleTTSService):
target_language_code=(
self.language_to_service_language(params.language) if params.language else "en-IN"
),
speaker=voice_id,
speech_sample_rate=str(sample_rate),
enable_preprocessing=(
True if self._config.preprocessing_always_enabled else params.enable_preprocessing
@@ -856,6 +853,7 @@ class SarvamTTSService(InterruptibleTTSService):
output_audio_bitrate=params.output_audio_bitrate,
pace=pace,
model=model,
voice=voice_id,
)
# Add parameters based on model support
@@ -1018,11 +1016,10 @@ class SarvamTTSService(InterruptibleTTSService):
"""Send initial configuration message."""
if not self._websocket:
raise Exception("WebSocket not connected")
self._settings.speaker = self._voice_id
# Build config dict for the API
config_data = {
"target_language_code": self._settings.target_language_code,
"speaker": self._settings.speaker,
"speaker": self._settings.voice,
"speech_sample_rate": self._settings.speech_sample_rate,
"enable_preprocessing": self._settings.enable_preprocessing,
"min_buffer_size": self._settings.min_buffer_size,

View File

@@ -145,6 +145,11 @@ class TTSService(AIService):
text_filter: Optional[BaseTextFilter] = None,
# Audio transport destination of the generated frames.
transport_destination: Optional[str] = None,
# Voice identifier or name to use for speech synthesis
voice: Optional[str] = None,
# Language to use for speech synthesis. This will be translated to a
# service-specific language identifier before being applied
language: Optional[Language] = None,
**kwargs,
):
"""Initialize the TTS service.
@@ -178,6 +183,10 @@ class TTSService(AIService):
Use `text_filters` instead, which allows multiple filters.
transport_destination: Destination for generated audio frames.
voice: Voice identifier or name to use for speech synthesis.
language: Language to use for speech synthesis. This will be
translated to a service-specific language identifier before
being applied.
**kwargs: Additional arguments passed to the parent AIService.
"""
super().__init__(**kwargs)
@@ -191,8 +200,9 @@ class TTSService(AIService):
self._append_trailing_space: bool = append_trailing_space
self._init_sample_rate = sample_rate
self._sample_rate = 0
self._voice_id: str = ""
self._settings = TTSSettings() # Here in case subclass doesn't implement more specific settings (hopefully shouldn't happen)
self._settings = TTSSettings(
voice=voice, language=language
) # Here in case subclass doesn't implement more specific settings (hopefully shouldn't happen)
self._text_aggregator: BaseTextAggregator = text_aggregator or SimpleTextAggregator()
if text_aggregator:
import warnings
@@ -427,11 +437,7 @@ class TTSService(AIService):
async def _update_settings(self, update: TTSSettings) -> dict[str, Any]:
"""Apply a TTS settings update.
Handles ``model`` (via parent) and syncs ``_voice_id`` when voice
changes. Translates language values before applying. Does **not**
call ``set_voice`` or ``set_model`` directly — concrete services
should override this method and handle reconnect logic based on the
returned changed-field dict.
Translates language to service-specific value before applying.
Args:
update: A TTS settings delta.
@@ -447,10 +453,6 @@ class TTSService(AIService):
changed = await super()._update_settings(update)
# Keep _voice_id in sync for code that reads it directly
if "voice" in changed:
self._voice_id = self._settings.voice
return changed
async def say(self, text: str):

View File

@@ -111,14 +111,13 @@ class XTTSService(TTSService):
sample_rate: Audio sample rate. If None, uses default.
**kwargs: Additional arguments passed to parent TTSService.
"""
super().__init__(sample_rate=sample_rate, **kwargs)
super().__init__(sample_rate=sample_rate, voice=voice_id, **kwargs)
self._settings = XTTSTTSSettings(
voice=voice_id,
language=self.language_to_service_language(language),
base_url=base_url,
)
self._voice_id = voice_id
self._studio_speakers: Optional[Dict[str, Any]] = None
self._aiohttp_session = aiohttp_session
@@ -180,7 +179,7 @@ class XTTSService(TTSService):
logger.error(f"{self} no studio speakers available")
return
embeddings = self._studio_speakers[self._voice_id]
embeddings = self._studio_speakers[self._settings.voice]
url = self._settings.base_url + "/tts_stream"

View File

@@ -190,13 +190,14 @@ def traced_tts(func: Optional[Callable] = None, *, name: Optional[str] = None) -
tracer = trace.get_tracer("pipecat")
with tracer.start_as_current_span(span_name, context=parent_context) as span:
try:
settings = getattr(self, "_settings", {})
add_tts_span_attributes(
span=span,
service_name=service_class_name,
model=getattr(self, "model_name") or "unknown",
voice_id=getattr(self, "_voice_id", "unknown"),
voice_id=getattr(settings, "voice", "unknown"),
text=text,
settings=getattr(self, "_settings", {}),
settings=settings,
character_count=len(text),
operation_name="tts",
cartesia_version=getattr(self, "_cartesia_version", None),