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. voice: Voice identifier to use.
**kwargs: Additional arguments passed to the parent service. **kwargs: Additional arguments passed to the parent service.
""" """
super().__init__(**kwargs) super().__init__(voice=voice, **kwargs)
self._api_key = api_key self._api_key = api_key
self._voice_id = voice
``` ```
--- ---

View File

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

View File

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

View File

@@ -165,12 +165,12 @@ class AzureBaseTTSService:
role=params.role, role=params.role,
style=params.style, style=params.style,
style_degree=params.style_degree, style_degree=params.style_degree,
voice=voice,
volume=params.volume, volume=params.volume,
) )
self._api_key = api_key self._api_key = api_key
self._region = region self._region = region
self._voice_id = voice
self._speech_synthesizer = None self._speech_synthesizer = None
def language_to_service_language(self, language: Language) -> Optional[str]: def language_to_service_language(self, language: Language) -> Optional[str]:
@@ -194,7 +194,7 @@ class AzureBaseTTSService:
f"<speak version='1.0' xml:lang='{language}' " f"<speak version='1.0' xml:lang='{language}' "
"xmlns='http://www.w3.org/2001/10/synthesis' " "xmlns='http://www.w3.org/2001/10/synthesis' "
"xmlns:mstts='http://www.w3.org/2001/mstts'>" "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' />" "<mstts:silence type='Sentenceboundary' value='20ms' />"
) )
@@ -295,6 +295,7 @@ class AzureTTSService(WordTTSService, AzureBaseTTSService):
push_stop_frames=True, push_stop_frames=True,
pause_frame_processing=True, pause_frame_processing=True,
sample_rate=sample_rate, sample_rate=sample_rate,
voice=voice,
**kwargs, **kwargs,
) )
@@ -733,7 +734,7 @@ class AzureHttpTTSService(TTSService, AzureBaseTTSService):
params: Voice and synthesis parameters configuration. params: Voice and synthesis parameters configuration.
**kwargs: Additional arguments passed to parent TTSService. **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 # Initialize Azure-specific functionality from mixin
self._init_azure_base(api_key=api_key, region=region, voice=voice, params=params) 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. params: Additional voice parameters. If None, uses defaults.
**kwargs: Additional arguments passed to parent TTSService. **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._api_key = api_key
self._timeout = timeout self._timeout = timeout
@@ -238,7 +238,6 @@ class CambTTSService(TTSService):
) )
self.set_model_name(model) self.set_model_name(model)
self._voice_id = voice_id
self._client = None self._client = None
@@ -299,7 +298,7 @@ class CambTTSService(TTSService):
# Build SDK parameters # Build SDK parameters
tts_kwargs: Dict[str, Any] = { tts_kwargs: Dict[str, Any] = {
"text": text, "text": text,
"voice_id": self._voice_id, "voice_id": self._settings.voice,
"language": self._settings.language, "language": self._settings.language,
"speech_model": self.model_name, "speech_model": self.model_name,
"output_configuration": StreamTtsOutputConfiguration(format="pcm_s16le"), "output_configuration": StreamTtsOutputConfiguration(format="pcm_s16le"),

View File

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

View File

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

View File

@@ -400,6 +400,7 @@ class ElevenLabsTTSService(AudioContextWordTTSService):
push_stop_frames=True, push_stop_frames=True,
pause_frame_processing=True, pause_frame_processing=True,
sample_rate=sample_rate, sample_rate=sample_rate,
voice=voice_id,
**kwargs, **kwargs,
) )
@@ -424,7 +425,6 @@ class ElevenLabsTTSService(AudioContextWordTTSService):
apply_text_normalization=params.apply_text_normalization, apply_text_normalization=params.apply_text_normalization,
) )
self.set_model_name(model) self.set_model_name(model)
self._voice_id = voice_id
self._output_format = "" # initialized in start() self._output_format = "" # initialized in start()
self._voice_settings = self._set_voice_settings() self._voice_settings = self._set_voice_settings()
@@ -607,7 +607,7 @@ class ElevenLabsTTSService(AudioContextWordTTSService):
logger.debug("Connecting to ElevenLabs") logger.debug("Connecting to ElevenLabs")
voice_id = self._voice_id voice_id = self._settings.voice
model = self.model_name model = self.model_name
output_format = self._output_format 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}" 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_text_frames=False,
push_stop_frames=True, push_stop_frames=True,
sample_rate=sample_rate, sample_rate=sample_rate,
voice=voice_id,
**kwargs, **kwargs,
) )
@@ -931,7 +932,6 @@ class ElevenLabsHttpTTSService(WordTTSService):
apply_text_normalization=params.apply_text_normalization, apply_text_normalization=params.apply_text_normalization,
) )
self.set_model_name(model) self.set_model_name(model)
self._voice_id = voice_id
self._output_format = "" # initialized in start() self._output_format = "" # initialized in start()
self._voice_settings = self._set_voice_settings() self._voice_settings = self._set_voice_settings()
self._pronunciation_dictionary_locators = params.pronunciation_dictionary_locators self._pronunciation_dictionary_locators = params.pronunciation_dictionary_locators
@@ -1098,7 +1098,7 @@ class ElevenLabsHttpTTSService(WordTTSService):
logger.debug(f"{self}: Generating TTS [{text}]") logger.debug(f"{self}: Generating TTS [{text}]")
# Use the with-timestamps endpoint # 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]]]] = { payload: Dict[str, Union[str, Dict[str, Union[float, bool]]]] = {
"text": text, "text": text,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -139,6 +139,7 @@ class NeuphonicTTSService(InterruptibleTTSService):
push_stop_frames=True, push_stop_frames=True,
stop_frame_timeout_s=2.0, stop_frame_timeout_s=2.0,
sample_rate=sample_rate, sample_rate=sample_rate,
voice=voice_id,
**kwargs, **kwargs,
) )
@@ -151,8 +152,8 @@ class NeuphonicTTSService(InterruptibleTTSService):
speed=params.speed, speed=params.speed,
encoding=encoding, encoding=encoding,
sampling_rate=sample_rate, sampling_rate=sample_rate,
voice=voice_id,
) )
self._voice_id = voice_id
self._cumulative_time = 0 self._cumulative_time = 0
@@ -288,7 +289,7 @@ class NeuphonicTTSService(InterruptibleTTSService):
"speed": self._settings.speed, "speed": self._settings.speed,
"encoding": self._settings.encoding, "encoding": self._settings.encoding,
"sampling_rate": self._settings.sampling_rate, "sampling_rate": self._settings.sampling_rate,
"voice_id": self._voice_id, "voice_id": self._settings.voice,
} }
query_params = [] query_params = []
@@ -442,7 +443,7 @@ class NeuphonicHttpTTSService(TTSService):
params: Additional input parameters for TTS configuration. params: Additional input parameters for TTS configuration.
**kwargs: Additional arguments passed to parent TTSService. **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() 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"OpenAI TTS only supports {self.OPENAI_SAMPLE_RATE}Hz sample rate. "
f"Current rate of {sample_rate}Hz may cause issues." 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.set_model_name(model)
self._voice_id = voice
self._client = AsyncOpenAI(api_key=api_key, base_url=base_url) self._client = AsyncOpenAI(api_key=api_key, base_url=base_url)
if instructions or speed: if instructions or speed:
@@ -196,7 +195,7 @@ class OpenAITTSService(TTSService):
create_params = { create_params = {
"input": text, "input": text,
"model": self.model_name, "model": self.model_name,
"voice": VALID_VOICES[self._voice_id], "voice": VALID_VOICES[self._settings.voice],
"response_format": "pcm", "response_format": "pcm",
} }

View File

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

View File

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

View File

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

View File

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

View File

@@ -145,6 +145,11 @@ class TTSService(AIService):
text_filter: Optional[BaseTextFilter] = None, text_filter: Optional[BaseTextFilter] = None,
# Audio transport destination of the generated frames. # Audio transport destination of the generated frames.
transport_destination: Optional[str] = None, 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, **kwargs,
): ):
"""Initialize the TTS service. """Initialize the TTS service.
@@ -178,6 +183,10 @@ class TTSService(AIService):
Use `text_filters` instead, which allows multiple filters. Use `text_filters` instead, which allows multiple filters.
transport_destination: Destination for generated audio frames. 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. **kwargs: Additional arguments passed to the parent AIService.
""" """
super().__init__(**kwargs) super().__init__(**kwargs)
@@ -191,8 +200,9 @@ class TTSService(AIService):
self._append_trailing_space: bool = append_trailing_space self._append_trailing_space: bool = append_trailing_space
self._init_sample_rate = sample_rate self._init_sample_rate = sample_rate
self._sample_rate = 0 self._sample_rate = 0
self._voice_id: str = "" self._settings = TTSSettings(
self._settings = TTSSettings() # Here in case subclass doesn't implement more specific settings (hopefully shouldn't happen) 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() self._text_aggregator: BaseTextAggregator = text_aggregator or SimpleTextAggregator()
if text_aggregator: if text_aggregator:
import warnings import warnings
@@ -427,11 +437,7 @@ class TTSService(AIService):
async def _update_settings(self, update: TTSSettings) -> dict[str, Any]: async def _update_settings(self, update: TTSSettings) -> dict[str, Any]:
"""Apply a TTS settings update. """Apply a TTS settings update.
Handles ``model`` (via parent) and syncs ``_voice_id`` when voice Translates language to service-specific value before applying.
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.
Args: Args:
update: A TTS settings delta. update: A TTS settings delta.
@@ -447,10 +453,6 @@ class TTSService(AIService):
changed = await super()._update_settings(update) 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 return changed
async def say(self, text: str): async def say(self, text: str):

View File

@@ -111,14 +111,13 @@ class XTTSService(TTSService):
sample_rate: Audio sample rate. If None, uses default. sample_rate: Audio sample rate. If None, uses default.
**kwargs: Additional arguments passed to parent TTSService. **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( self._settings = XTTSTTSSettings(
voice=voice_id, voice=voice_id,
language=self.language_to_service_language(language), language=self.language_to_service_language(language),
base_url=base_url, base_url=base_url,
) )
self._voice_id = voice_id
self._studio_speakers: Optional[Dict[str, Any]] = None self._studio_speakers: Optional[Dict[str, Any]] = None
self._aiohttp_session = aiohttp_session self._aiohttp_session = aiohttp_session
@@ -180,7 +179,7 @@ class XTTSService(TTSService):
logger.error(f"{self} no studio speakers available") logger.error(f"{self} no studio speakers available")
return return
embeddings = self._studio_speakers[self._voice_id] embeddings = self._studio_speakers[self._settings.voice]
url = self._settings.base_url + "/tts_stream" 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") tracer = trace.get_tracer("pipecat")
with tracer.start_as_current_span(span_name, context=parent_context) as span: with tracer.start_as_current_span(span_name, context=parent_context) as span:
try: try:
settings = getattr(self, "_settings", {})
add_tts_span_attributes( add_tts_span_attributes(
span=span, span=span,
service_name=service_class_name, service_name=service_class_name,
model=getattr(self, "model_name") or "unknown", model=getattr(self, "model_name") or "unknown",
voice_id=getattr(self, "_voice_id", "unknown"), voice_id=getattr(settings, "voice", "unknown"),
text=text, text=text,
settings=getattr(self, "_settings", {}), settings=settings,
character_count=len(text), character_count=len(text),
operation_name="tts", operation_name="tts",
cartesia_version=getattr(self, "_cartesia_version", None), cartesia_version=getattr(self, "_cartesia_version", None),