Update TTS service settings

This commit is contained in:
Mark Backman
2026-03-03 23:02:55 -05:00
parent 630d7bd7ab
commit d2c6adc0e5
18 changed files with 184 additions and 303 deletions

View File

@@ -9,8 +9,8 @@
import asyncio
import base64
import json
from dataclasses import dataclass, field
from typing import Any, AsyncGenerator, Mapping, Optional, Self
from dataclasses import dataclass
from typing import Any, AsyncGenerator, Optional
import aiohttp
from loguru import logger
@@ -27,7 +27,7 @@ from pipecat.frames.frames import (
TTSStoppedFrame,
)
from pipecat.processors.frame_processor import FrameDirection
from pipecat.services.settings import NOT_GIVEN, TTSSettings, _NotGiven, _warn_deprecated_param
from pipecat.services.settings import TTSSettings, _warn_deprecated_param
from pipecat.services.tts_service import AudioContextTTSService, TextAggregationMode, TTSService
from pipecat.transcriptions.language import Language, resolve_language
from pipecat.utils.tracing.service_decorators import traced_tts
@@ -75,28 +75,9 @@ def language_to_async_language(language: Language) -> Optional[str]:
@dataclass
class AsyncAITTSSettings(TTSSettings):
"""Settings for Async AI TTS services.
"""Settings for Async AI TTS services."""
Parameters:
output_container: Audio container format (e.g. "raw").
output_encoding: Audio encoding format (e.g. "pcm_s16le").
output_sample_rate: Audio sample rate in Hz.
"""
output_container: str | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
output_encoding: str | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
output_sample_rate: int | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
@classmethod
def from_mapping(cls, settings: Mapping[str, Any]) -> Self:
"""Construct settings from a plain dict, destructuring legacy nested ``output_format``."""
flat = dict(settings)
nested = flat.pop("output_format", None)
if isinstance(nested, dict):
flat.setdefault("output_container", nested.get("container"))
flat.setdefault("output_encoding", nested.get("encoding"))
flat.setdefault("output_sample_rate", nested.get("sample_rate"))
return super().from_mapping(flat)
pass
class AsyncAITTSService(AudioContextTTSService):
@@ -176,9 +157,6 @@ class AsyncAITTSService(AudioContextTTSService):
model="async_flash_v1.0",
voice=None,
language=None,
output_container=container,
output_encoding=encoding,
output_sample_rate=0,
)
# 2. Apply direct init arg overrides (deprecated)
@@ -215,6 +193,11 @@ class AsyncAITTSService(AudioContextTTSService):
self._api_version = version
self._url = url
# Init-only audio format config (not runtime-updatable).
self._output_container = container
self._output_encoding = encoding
self._output_sample_rate = 0 # Set in start()
self._receive_task = None
self._keepalive_task = None
@@ -262,7 +245,7 @@ class AsyncAITTSService(AudioContextTTSService):
frame: The start frame containing initialization parameters.
"""
await super().start(frame)
self._settings.output_sample_rate = self.sample_rate
self._output_sample_rate = self.sample_rate
await self._connect()
async def stop(self, frame: EndFrame):
@@ -319,9 +302,9 @@ class AsyncAITTSService(AudioContextTTSService):
"model_id": self._settings.model,
"voice": {"mode": "id", "id": self._settings.voice},
"output_format": {
"container": self._settings.output_container,
"encoding": self._settings.output_encoding,
"sample_rate": self._settings.output_sample_rate,
"container": self._output_container,
"encoding": self._output_encoding,
"sample_rate": self._output_sample_rate,
},
"language": self._settings.language,
}
@@ -567,9 +550,6 @@ class AsyncAIHttpTTSService(TTSService):
model="async_flash_v1.0",
voice=None,
language=None,
output_container=container,
output_encoding=encoding,
output_sample_rate=0,
)
# 2. Apply direct init arg overrides (deprecated)
@@ -602,6 +582,11 @@ class AsyncAIHttpTTSService(TTSService):
self._base_url = url
self._api_version = version
# Init-only audio format config (not runtime-updatable).
self._output_container = container
self._output_encoding = encoding
self._output_sample_rate = 0 # Set in start()
self._session = aiohttp_session
def can_generate_metrics(self) -> bool:
@@ -630,7 +615,7 @@ class AsyncAIHttpTTSService(TTSService):
frame: The start frame containing initialization parameters.
"""
await super().start(frame)
self._settings.output_sample_rate = self.sample_rate
self._output_sample_rate = self.sample_rate
@traced_tts
async def run_tts(self, text: str, context_id: str) -> AsyncGenerator[Frame, None]:
@@ -653,9 +638,9 @@ class AsyncAIHttpTTSService(TTSService):
"transcript": text,
"voice": voice_config,
"output_format": {
"container": self._settings.output_container,
"encoding": self._settings.output_encoding,
"sample_rate": self._settings.output_sample_rate,
"container": self._output_container,
"encoding": self._output_encoding,
"sample_rate": self._output_sample_rate,
},
"language": self._settings.language,
}

View File

@@ -73,7 +73,6 @@ class AzureTTSSettings(TTSSettings):
Parameters:
emphasis: Emphasis level for speech ("strong", "moderate", "reduced").
language: Language for synthesis. Defaults to English (US).
pitch: Voice pitch adjustment (e.g., "+10%", "-5Hz", "high").
rate: Speech rate adjustment (e.g., "1.0", "1.25", "slow", "fast").
role: Voice role for expression (e.g., "YoungAdultFemale").
@@ -83,7 +82,6 @@ class AzureTTSSettings(TTSSettings):
"""
emphasis: str | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
language: str | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
pitch: str | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
rate: str | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
role: str | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
@@ -143,7 +141,6 @@ class AzureBaseTTSService:
*,
api_key: str,
region: str,
voice: str = "en-US-SaraNeural",
):
"""Initialize Azure-specific configuration.
@@ -152,7 +149,6 @@ class AzureBaseTTSService:
Args:
api_key: Azure Cognitive Services subscription key.
region: Azure region identifier (e.g., "eastus", "westus2").
voice: Voice name to use for synthesis. Defaults to "en-US-SaraNeural".
"""
self._api_key = api_key
self._region = region
@@ -343,7 +339,7 @@ class AzureTTSService(TTSService, AzureBaseTTSService):
)
# Initialize Azure-specific functionality from mixin
self._init_azure_base(api_key=api_key, region=region, voice=default_settings.voice)
self._init_azure_base(api_key=api_key, region=region)
self._speech_config = None
self._speech_synthesizer = None
@@ -842,7 +838,7 @@ class AzureHttpTTSService(TTSService, AzureBaseTTSService):
)
# Initialize Azure-specific functionality from mixin
self._init_azure_base(api_key=api_key, region=region, voice=default_settings.voice)
self._init_azure_base(api_key=api_key, region=region)
self._speech_config = None
self._speech_synthesizer = None

View File

@@ -14,7 +14,7 @@ streaming audio output.
import asyncio
import json
from dataclasses import dataclass, field
from dataclasses import dataclass
from typing import Any, AsyncGenerator, Optional
from loguru import logger
@@ -33,20 +33,16 @@ from pipecat.frames.frames import (
)
from pipecat.processors.frame_processor import FrameDirection
from pipecat.services.aws.sagemaker.bidi_client import SageMakerBidiClient
from pipecat.services.settings import NOT_GIVEN, TTSSettings, _NotGiven, _warn_deprecated_param
from pipecat.services.settings import TTSSettings, _warn_deprecated_param
from pipecat.services.tts_service import TTSService
from pipecat.utils.tracing.service_decorators import traced_tts
@dataclass
class DeepgramSageMakerTTSSettings(TTSSettings):
"""Settings for Deepgram SageMaker TTS service.
"""Settings for Deepgram SageMaker TTS service."""
Parameters:
encoding: Audio encoding format (e.g. "linear16").
"""
encoding: str | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
pass
class DeepgramSageMakerTTSService(TTSService):
@@ -107,10 +103,9 @@ class DeepgramSageMakerTTSService(TTSService):
voice = voice or "aura-2-helena-en"
default_settings = DeepgramSageMakerTTSSettings(
model=voice,
model=None,
voice=voice,
language=None,
encoding=encoding,
)
if settings is not None:
default_settings.apply_update(settings)
@@ -126,6 +121,7 @@ class DeepgramSageMakerTTSService(TTSService):
self._endpoint_name = endpoint_name
self._region = region
self._encoding = encoding
self._client: Optional[SageMakerBidiClient] = None
self._response_task: Optional[asyncio.Task] = None

View File

@@ -11,7 +11,7 @@ for generating speech from text using various voice models.
"""
import json
from dataclasses import dataclass, field
from dataclasses import dataclass
from typing import Any, AsyncGenerator, Optional
import aiohttp
@@ -30,7 +30,7 @@ from pipecat.frames.frames import (
TTSStoppedFrame,
)
from pipecat.processors.frame_processor import FrameDirection
from pipecat.services.settings import NOT_GIVEN, TTSSettings, _NotGiven, _warn_deprecated_param
from pipecat.services.settings import TTSSettings, _warn_deprecated_param
from pipecat.services.tts_service import TTSService, WebsocketTTSService
from pipecat.utils.tracing.service_decorators import traced_tts
@@ -47,13 +47,9 @@ except ModuleNotFoundError as e:
@dataclass
class DeepgramTTSSettings(TTSSettings):
"""Settings for Deepgram TTS service.
"""Settings for Deepgram TTS service."""
Parameters:
encoding: Audio encoding format (linear16, mulaw, alaw).
"""
encoding: str | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
pass
class DeepgramTTSService(WebsocketTTSService):
@@ -105,10 +101,9 @@ class DeepgramTTSService(WebsocketTTSService):
# 1. Initialize default_settings with hardcoded defaults
default_settings = DeepgramTTSSettings(
model="aura-2-helena-en",
model=None,
voice="aura-2-helena-en",
language=None,
encoding=encoding,
)
# 2. Apply direct init arg overrides (deprecated)
@@ -132,6 +127,7 @@ class DeepgramTTSService(WebsocketTTSService):
self._api_key = api_key
self._base_url = base_url
self._encoding = encoding
self._receive_task = None
self._context_id: Optional[str] = None
@@ -236,7 +232,7 @@ class DeepgramTTSService(WebsocketTTSService):
# Build WebSocket URL with query parameters
params = []
params.append(f"model={self._settings.voice}")
params.append(f"encoding={self._settings.encoding}")
params.append(f"encoding={self._encoding}")
params.append(f"sample_rate={self.sample_rate}")
url = f"{self._base_url}/v1/speak?{'&'.join(params)}"
@@ -422,10 +418,9 @@ class DeepgramHttpTTSService(TTSService):
"""
# 1. Initialize default_settings with hardcoded defaults
default_settings = DeepgramTTSSettings(
model="aura-2-helena-en",
model=None,
voice="aura-2-helena-en",
language=None,
encoding=encoding,
)
# 2. Apply direct init arg overrides (deprecated)
@@ -447,6 +442,7 @@ class DeepgramHttpTTSService(TTSService):
self._api_key = api_key
self._session = aiohttp_session
self._base_url = base_url
self._encoding = encoding
def can_generate_metrics(self) -> bool:
"""Check if the service can generate metrics.
@@ -476,7 +472,7 @@ class DeepgramHttpTTSService(TTSService):
params = {
"model": self._settings.voice,
"encoding": self._settings.encoding,
"encoding": self._encoding,
"sample_rate": self.sample_rate,
"container": "none",
}

View File

@@ -201,9 +201,6 @@ class ElevenLabsTTSSettings(TTSSettings):
style: Style control for voice expression (0.0 to 1.0).
use_speaker_boost: Whether to use speaker boost enhancement.
speed: Voice speed control (0.7 to 1.2).
auto_mode: Whether to enable automatic mode optimization.
enable_ssml_parsing: Whether to parse SSML tags in text.
enable_logging: Whether to enable ElevenLabs logging.
apply_text_normalization: Text normalization mode ("auto", "on", "off").
"""
@@ -212,9 +209,6 @@ class ElevenLabsTTSSettings(TTSSettings):
style: float | None | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
use_speaker_boost: bool | None | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
speed: float | None | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
auto_mode: bool | None | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
enable_ssml_parsing: bool | None | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
enable_logging: bool | None | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
apply_text_normalization: Literal["auto", "on", "off"] | None | _NotGiven = field(
default_factory=lambda: NOT_GIVEN
)
@@ -228,8 +222,6 @@ class ElevenLabsTTSSettings(TTSSettings):
{"stability", "similarity_boost", "style", "use_speaker_boost", "speed"}
)
_aliases: ClassVar[Dict[str, str]] = {"voice_id": "voice"}
@dataclass
class ElevenLabsHttpTTSSettings(TTSSettings):
@@ -255,8 +247,6 @@ class ElevenLabsHttpTTSSettings(TTSSettings):
default_factory=lambda: NOT_GIVEN
)
_aliases: ClassVar[Dict[str, str]] = {"voice_id": "voice"}
def calculate_word_times(
alignment_info: Mapping[str, Any],
@@ -430,6 +420,11 @@ class ElevenLabsTTSService(AudioContextTTSService):
model="eleven_turbo_v2_5",
)
# Track init-only URL params through the override chain
_auto_mode = True
_enable_ssml_parsing = None
_enable_logging = None
# 2. Apply direct init arg overrides (deprecated)
if voice_id is not None:
_warn_deprecated_param("voice_id", ElevenLabsTTSSettings, "voice")
@@ -456,11 +451,11 @@ class ElevenLabsTTSService(AudioContextTTSService):
if params.speed is not None:
default_settings.speed = params.speed
if params.auto_mode is not None:
default_settings.auto_mode = str(params.auto_mode).lower()
_auto_mode = str(params.auto_mode).lower()
if params.enable_ssml_parsing is not None:
default_settings.enable_ssml_parsing = params.enable_ssml_parsing
_enable_ssml_parsing = params.enable_ssml_parsing
if params.enable_logging is not None:
default_settings.enable_logging = params.enable_logging
_enable_logging = params.enable_logging
if params.apply_text_normalization is not None:
default_settings.apply_text_normalization = params.apply_text_normalization
if _pronunciation_dictionary_locators is None:
@@ -485,6 +480,11 @@ class ElevenLabsTTSService(AudioContextTTSService):
self._api_key = api_key
self._url = url
# Init-only WebSocket URL params (not runtime-updatable).
self._auto_mode = _auto_mode
self._enable_ssml_parsing = _enable_ssml_parsing
self._enable_logging = _enable_logging
self._output_format = "" # initialized in start()
self._voice_settings = self._set_voice_settings()
self._pronunciation_dictionary_locators = _pronunciation_dictionary_locators
@@ -518,20 +518,7 @@ class ElevenLabsTTSService(AudioContextTTSService):
return language_to_elevenlabs_language(language)
def _set_voice_settings(self):
ts = self._settings
voice_setting_keys = [
"stability",
"similarity_boost",
"style",
"use_speaker_boost",
"speed",
]
voice_settings = {}
for key in voice_setting_keys:
val = getattr(ts, key, None)
if val is not None:
voice_settings[key] = val
return voice_settings or None
return build_elevenlabs_voice_settings(self._settings)
async def _update_settings(self, delta: TTSSettings) -> dict[str, Any]:
"""Apply a settings delta, reconnecting as needed.
@@ -670,13 +657,13 @@ class ElevenLabsTTSService(AudioContextTTSService):
voice_id = self._settings.voice
model = self._settings.model
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._auto_mode}"
if self._settings.enable_ssml_parsing:
url += f"&enable_ssml_parsing={self._settings.enable_ssml_parsing}"
if self._enable_ssml_parsing:
url += f"&enable_ssml_parsing={self._enable_ssml_parsing}"
if self._settings.enable_logging:
url += f"&enable_logging={self._settings.enable_logging}"
if self._enable_logging:
url += f"&enable_logging={self._enable_logging}"
if self._settings.apply_text_normalization is not None:
url += f"&apply_text_normalization={self._settings.apply_text_normalization}"

View File

@@ -52,25 +52,19 @@ class FishAudioTTSSettings(TTSSettings):
"""Settings for Fish Audio TTS service.
Parameters:
fish_sample_rate: Audio sample rate sent to the API.
latency: Latency mode ("normal" or "balanced"). Defaults to "normal".
format: Audio output format.
normalize: Whether to normalize audio output. Defaults to True.
prosody_speed: Speech speed multiplier (0.5-2.0). Defaults to 1.0.
prosody_volume: Volume adjustment in dB. Defaults to 0.
reference_id: Reference ID of the voice model.
"""
fish_sample_rate: int | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
latency: str | None | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
format: str | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
normalize: bool | None | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
prosody_speed: float | None | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
prosody_volume: int | None | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
reference_id: str | None | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
_aliases: ClassVar[Dict[str, str]] = {"voice_id": "voice", "sample_rate": "fish_sample_rate"}
@classmethod
def from_mapping(cls, settings: Mapping[str, Any]) -> Self:
"""Construct settings from a plain dict, destructuring legacy nested ``prosody``."""
@@ -179,9 +173,7 @@ class FishAudioTTSService(InterruptibleTTSService):
default_settings = FishAudioTTSSettings(
model="s1",
voice=None,
fish_sample_rate=0,
latency="normal",
format=output_format,
normalize=True,
prosody_speed=1.0,
prosody_volume=0,
@@ -228,6 +220,10 @@ class FishAudioTTSService(InterruptibleTTSService):
self._receive_task = None
self._request_id = None
# Init-only audio format config (not runtime-updatable).
self._fish_sample_rate = 0 # Set in start()
self._output_format = output_format
def can_generate_metrics(self) -> bool:
"""Check if this service can generate processing metrics.
@@ -262,7 +258,7 @@ class FishAudioTTSService(InterruptibleTTSService):
frame: The start frame containing initialization parameters.
"""
await super().start(frame)
self._settings.fish_sample_rate = self.sample_rate
self._fish_sample_rate = self.sample_rate
await self._connect()
async def stop(self, frame: EndFrame):
@@ -312,9 +308,9 @@ class FishAudioTTSService(InterruptibleTTSService):
# Send initial start message with ormsgpack
request_settings = {
"sample_rate": self._settings.fish_sample_rate,
"sample_rate": self._fish_sample_rate,
"latency": self._settings.latency,
"format": self._settings.format,
"format": self._output_format,
"normalize": self._settings.normalize,
"prosody": {
"speed": self._settings.prosody_speed,

View File

@@ -494,7 +494,6 @@ class GoogleHttpTTSSettings(TTSSettings):
Range [0.25, 2.0].
volume: Volume adjustment (e.g., "loud", "soft", "+6dB").
emphasis: Emphasis level for the text.
language: Language for synthesis. Defaults to English.
gender: Voice gender preference.
google_style: Google-specific voice style.
"""
@@ -506,7 +505,6 @@ class GoogleHttpTTSSettings(TTSSettings):
emphasis: Literal["strong", "moderate", "reduced", "none"] | None | _NotGiven = field(
default_factory=lambda: NOT_GIVEN
)
language: str | None | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
gender: Literal["male", "female", "neutral"] | None | _NotGiven = field(
default_factory=lambda: NOT_GIVEN
)
@@ -520,11 +518,9 @@ class GoogleStreamTTSSettings(TTSSettings):
"""Settings for Google streaming TTS service.
Parameters:
language: Language for synthesis. Defaults to English.
speaking_rate: The speaking rate, in the range [0.25, 2.0].
"""
language: str | None | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
speaking_rate: float | None | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
@@ -533,13 +529,11 @@ class GeminiTTSSettings(TTSSettings):
"""Settings for Gemini TTS service.
Parameters:
language: Language for synthesis. Defaults to English.
prompt: Optional style instructions for how to synthesize the content.
multi_speaker: Whether to enable multi-speaker support.
speaker_configs: List of speaker configurations for multi-speaker mode.
"""
language: str | None | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
prompt: str | None | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
multi_speaker: bool | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
speaker_configs: list[dict[str, Any]] | None | _NotGiven = field(

View File

@@ -6,7 +6,7 @@
import base64
import json
from dataclasses import dataclass, field
from dataclasses import dataclass
from typing import Any, AsyncGenerator, Optional
from loguru import logger
@@ -22,7 +22,7 @@ from pipecat.frames.frames import (
TTSStartedFrame,
TTSStoppedFrame,
)
from pipecat.services.settings import NOT_GIVEN, TTSSettings, _NotGiven, _warn_deprecated_param
from pipecat.services.settings import TTSSettings, _warn_deprecated_param
from pipecat.services.tts_service import AudioContextTTSService
from pipecat.utils.tracing.service_decorators import traced_tts
@@ -40,13 +40,9 @@ SAMPLE_RATE = 48000
@dataclass
class GradiumTTSSettings(TTSSettings):
"""Settings for the Gradium TTS service.
"""Settings for the Gradium TTS service."""
Parameters:
output_format: Audio output format.
"""
output_format: str | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
pass
class GradiumTTSService(AudioContextTTSService):
@@ -108,7 +104,6 @@ class GradiumTTSService(AudioContextTTSService):
model="default",
voice="YTpq7expH9539ERJ",
language=None,
output_format="pcm",
)
# 2. Apply direct init arg overrides (deprecated)

View File

@@ -9,7 +9,7 @@
import io
import wave
from dataclasses import dataclass, field
from typing import AsyncGenerator, ClassVar, Dict, Optional
from typing import AsyncGenerator, Optional
from loguru import logger
from pydantic import BaseModel
@@ -39,16 +39,10 @@ class GroqTTSSettings(TTSSettings):
"""Settings for the Groq TTS service.
Parameters:
output_format: Audio output format.
speed: Speech speed multiplier. Defaults to 1.0.
groq_sample_rate: Audio sample rate.
"""
output_format: str | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
speed: float | None | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
groq_sample_rate: int | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
_aliases: ClassVar[Dict[str, str]] = {"voice_id": "voice", "sample_rate": "groq_sample_rate"}
class GroqTTSService(TTSService):
@@ -122,9 +116,7 @@ class GroqTTSService(TTSService):
model="canopylabs/orpheus-v1-english",
voice="autumn",
language="en",
output_format=output_format,
speed=1.0,
groq_sample_rate=sample_rate,
)
# 2. Apply direct init arg overrides (deprecated)

View File

@@ -71,8 +71,6 @@ class InworldTTSSettings(TTSSettings):
"""Settings for Inworld TTS services.
Parameters:
audio_encoding: Audio encoding format (e.g. LINEAR16).
audio_sample_rate: Audio sample rate in Hz.
speaking_rate: Speaking rate for speech synthesis.
temperature: Temperature for speech synthesis.
auto_mode: Whether to use auto mode. Recommended when texts are sent
@@ -84,8 +82,6 @@ class InworldTTSSettings(TTSSettings):
timestamp_transport_strategy: Strategy for timestamp transport ("ASYNC" or "SYNC").
"""
audio_encoding: str | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
audio_sample_rate: int | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
speaking_rate: float | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
temperature: float | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
auto_mode: bool | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
@@ -93,7 +89,6 @@ class InworldTTSSettings(TTSSettings):
timestamp_transport_strategy: str | None | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
_aliases: ClassVar[Dict[str, str]] = {
"voice_id": "voice",
"voiceId": "voice",
"modelId": "model",
"applyTextNormalization": "apply_text_normalization",
@@ -107,8 +102,6 @@ class InworldTTSSettings(TTSSettings):
flat = dict(settings)
nested = flat.pop("audioConfig", None)
if isinstance(nested, dict):
flat.setdefault("audio_encoding", nested.get("audioEncoding"))
flat.setdefault("audio_sample_rate", nested.get("sampleRateHertz"))
flat.setdefault("speaking_rate", nested.get("speakingRate"))
return super().from_mapping(flat)
@@ -184,8 +177,6 @@ class InworldHttpTTSService(TTSService):
model="inworld-tts-1.5-max",
voice="Ashley",
language=None,
audio_encoding=encoding,
audio_sample_rate=0,
speaking_rate=None,
temperature=None,
timestamp_transport_strategy="ASYNC",
@@ -239,6 +230,10 @@ class InworldHttpTTSService(TTSService):
self._cumulative_time = 0.0
# Init-only audio format config (not runtime-updatable).
self._audio_encoding = encoding
self._audio_sample_rate = 0 # Set in start()
def can_generate_metrics(self) -> bool:
"""Check if this service can generate processing metrics.
@@ -254,7 +249,7 @@ class InworldHttpTTSService(TTSService):
frame: The start frame.
"""
await super().start(frame)
self._settings.audio_sample_rate = self.sample_rate
self._audio_sample_rate = self.sample_rate
async def stop(self, frame: EndFrame):
"""Stop the Inworld TTS service.
@@ -334,8 +329,8 @@ class InworldHttpTTSService(TTSService):
logger.debug(f"{self}: Generating TTS [{text}] (streaming={self._streaming})")
audio_config = {
"audioEncoding": self._settings.audio_encoding,
"sampleRateHertz": self._settings.audio_sample_rate,
"audioEncoding": self._audio_encoding,
"sampleRateHertz": self._audio_sample_rate,
}
if self._settings.speaking_rate is not None:
audio_config["speakingRate"] = self._settings.speaking_rate
@@ -611,8 +606,6 @@ class InworldTTSService(AudioContextTTSService):
model="inworld-tts-1.5-max",
voice="Ashley",
language=None,
audio_encoding=encoding,
audio_sample_rate=0,
speaking_rate=None,
temperature=None,
apply_text_normalization=None,
@@ -686,6 +679,10 @@ class InworldTTSService(AudioContextTTSService):
# Track the end time of the last word in the current generation
self._generation_end_time = 0.0
# Init-only audio format config (not runtime-updatable).
self._audio_encoding = encoding
self._audio_sample_rate = 0 # Set in start()
def can_generate_metrics(self) -> bool:
"""Check if this service can generate processing metrics.
@@ -701,7 +698,7 @@ class InworldTTSService(AudioContextTTSService):
frame: The start frame.
"""
await super().start(frame)
self._settings.audio_sample_rate = self.sample_rate
self._audio_sample_rate = self.sample_rate
await self._connect()
async def stop(self, frame: EndFrame):
@@ -1051,8 +1048,8 @@ class InworldTTSService(AudioContextTTSService):
context_id: The context ID.
"""
audio_config = {
"audioEncoding": self._settings.audio_encoding,
"sampleRateHertz": self._settings.audio_sample_rate,
"audioEncoding": self._audio_encoding,
"sampleRateHertz": self._audio_sample_rate,
}
if self._settings.speaking_rate is not None:
audio_config["speakingRate"] = self._settings.speaking_rate

View File

@@ -7,7 +7,7 @@
"""Kokoro TTS service implementation using kokoro-onnx."""
import os
from dataclasses import dataclass, field
from dataclasses import dataclass
from pathlib import Path
from typing import AsyncGenerator, Optional
@@ -23,7 +23,7 @@ from pipecat.frames.frames import (
TTSStartedFrame,
TTSStoppedFrame,
)
from pipecat.services.settings import NOT_GIVEN, TTSSettings, _NotGiven, _warn_deprecated_param
from pipecat.services.settings import TTSSettings, _warn_deprecated_param
from pipecat.services.tts_service import TTSService
from pipecat.transcriptions.language import Language, resolve_language
from pipecat.utils.tracing.service_decorators import traced_tts
@@ -91,13 +91,9 @@ def language_to_kokoro_language(language: Language) -> str:
@dataclass
class KokoroTTSSettings(TTSSettings):
"""Settings for the Kokoro TTS service.
"""Settings for the Kokoro TTS service."""
Parameters:
lang_code: Kokoro language code for synthesis.
"""
lang_code: str | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
pass
class KokoroTTSService(TTSService):
@@ -156,7 +152,6 @@ class KokoroTTSService(TTSService):
model=None,
voice=None,
language=language_to_kokoro_language(Language.EN),
lang_code=language_to_kokoro_language(Language.EN),
)
# 2. Apply direct init arg overrides (deprecated)
@@ -168,9 +163,7 @@ class KokoroTTSService(TTSService):
if params is not None:
_warn_deprecated_param("params", KokoroTTSSettings)
if not settings:
lang_code = language_to_kokoro_language(params.language)
default_settings.language = lang_code
default_settings.lang_code = lang_code
default_settings.language = language_to_kokoro_language(params.language)
# 4. Apply settings delta (canonical API, always wins)
if settings is not None:
@@ -181,8 +174,6 @@ class KokoroTTSService(TTSService):
**kwargs,
)
self._lang_code = default_settings.lang_code
model_file = Path(model_path) if model_path else KOKORO_CACHE_DIR / "kokoro-v1.0.onnx"
voices = Path(voices_path) if voices_path else KOKORO_CACHE_DIR / "voices-v1.0.bin"
@@ -196,6 +187,17 @@ class KokoroTTSService(TTSService):
"""Indicate that this service supports TTFB and usage metrics."""
return True
def language_to_service_language(self, language: Language) -> str:
"""Convert a Language enum to kokoro-onnx language format.
Args:
language: The language to convert.
Returns:
The kokoro-onnx language code.
"""
return language_to_kokoro_language(language)
@traced_tts
async def run_tts(self, text: str, context_id: str) -> AsyncGenerator[Frame, None]:
"""Synthesize speech from text using kokoro-onnx.
@@ -215,7 +217,7 @@ class KokoroTTSService(TTSService):
yield TTSStartedFrame(context_id=context_id)
stream = self._kokoro.create_stream(
text, voice=self._settings.voice, lang=self._lang_code, speed=1.0
text, voice=self._settings.voice, lang=self._settings.language, speed=1.0
)
async for samples, sample_rate in stream:

View File

@@ -7,7 +7,7 @@
"""LMNT text-to-speech service implementation."""
import json
from dataclasses import dataclass, field
from dataclasses import dataclass
from typing import Any, AsyncGenerator, Optional
from loguru import logger
@@ -17,14 +17,13 @@ from pipecat.frames.frames import (
EndFrame,
ErrorFrame,
Frame,
InterruptionFrame,
StartFrame,
TTSAudioRawFrame,
TTSStartedFrame,
TTSStoppedFrame,
)
from pipecat.processors.frame_processor import FrameDirection
from pipecat.services.settings import NOT_GIVEN, TTSSettings, _NotGiven, _warn_deprecated_param
from pipecat.services.settings import TTSSettings, _warn_deprecated_param
from pipecat.services.tts_service import InterruptibleTTSService
from pipecat.transcriptions.language import Language, resolve_language
from pipecat.utils.tracing.service_decorators import traced_tts
@@ -75,13 +74,9 @@ def language_to_lmnt_language(language: Language) -> Optional[str]:
@dataclass
class LmntTTSSettings(TTSSettings):
"""Settings for LMNT TTS service.
"""Settings for LMNT TTS service."""
Parameters:
format: Audio output format. Defaults to "raw".
"""
format: str | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
pass
class LmntTTSService(InterruptibleTTSService):
@@ -130,7 +125,6 @@ class LmntTTSService(InterruptibleTTSService):
model="blizzard",
voice=None,
language=self.language_to_service_language(language),
format="raw",
)
# 2. Apply direct init arg overrides (deprecated)
@@ -156,6 +150,7 @@ class LmntTTSService(InterruptibleTTSService):
)
self._api_key = api_key
self._output_format = "raw"
self._receive_task = None
self._context_id: Optional[str] = None
@@ -262,7 +257,7 @@ class LmntTTSService(InterruptibleTTSService):
init_msg = {
"X-API-Key": self._api_key,
"voice": self._settings.voice,
"format": self._settings.format,
"format": self._output_format,
"sample_rate": self.sample_rate,
"language": self._settings.language,
"model": self._settings.model,

View File

@@ -12,7 +12,7 @@ for streaming text-to-speech synthesis.
import json
from dataclasses import dataclass, field
from typing import Any, AsyncGenerator, ClassVar, Dict, Mapping, Optional, Self
from typing import Any, AsyncGenerator, Mapping, Optional, Self
import aiohttp
from loguru import logger
@@ -100,10 +100,6 @@ class MiniMaxTTSSettings(TTSSettings):
"disgusted", "surprised", "calm", "fluent").
text_normalization: Enable text normalization (Chinese/English).
latex_read: Enable LaTeX formula reading.
audio_bitrate: Audio bitrate in bps.
audio_format: Audio output format.
audio_channel: Number of audio channels.
audio_sample_rate: Audio sample rate in Hz.
language_boost: Language boost string for multilingual support.
"""
@@ -114,14 +110,8 @@ class MiniMaxTTSSettings(TTSSettings):
emotion: str | None | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
text_normalization: bool | None | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
latex_read: bool | None | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
audio_bitrate: int | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
audio_format: str | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
audio_channel: int | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
audio_sample_rate: int | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
language_boost: str | None | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
_aliases: ClassVar[Dict[str, str]] = {"voice_id": "voice"}
@classmethod
def from_mapping(cls, settings: Mapping[str, Any]) -> Self:
"""Construct settings from a plain dict, destructuring legacy nested dicts.
@@ -140,13 +130,6 @@ class MiniMaxTTSSettings(TTSSettings):
flat.setdefault("text_normalization", voice.get("text_normalization"))
flat.setdefault("latex_read", voice.get("latex_read"))
audio = flat.pop("audio_setting", None)
if isinstance(audio, dict):
flat.setdefault("audio_bitrate", audio.get("bitrate"))
flat.setdefault("audio_format", audio.get("format"))
flat.setdefault("audio_channel", audio.get("channel"))
flat.setdefault("audio_sample_rate", audio.get("sample_rate"))
return super().from_mapping(flat)
@@ -258,10 +241,6 @@ class MiniMaxHttpTTSService(TTSService):
emotion=None,
text_normalization=None,
latex_read=None,
audio_bitrate=128000,
audio_format="pcm",
audio_channel=1,
audio_sample_rate=0,
)
# 2. Apply direct init arg overrides (deprecated)
@@ -335,6 +314,12 @@ class MiniMaxHttpTTSService(TTSService):
self._base_url = f"{base_url}?GroupId={group_id}"
self._session = aiohttp_session
# Init-only audio format config
self._audio_bitrate = 128000
self._audio_format = "pcm"
self._audio_channel = 1
self._audio_sample_rate = 0 # Set in start()
def can_generate_metrics(self) -> bool:
"""Check if this service can generate processing metrics.
@@ -361,7 +346,7 @@ class MiniMaxHttpTTSService(TTSService):
frame: The start frame containing initialization parameters.
"""
await super().start(frame)
self._settings.audio_sample_rate = self.sample_rate
self._audio_sample_rate = self.sample_rate
logger.debug(f"MiniMax TTS initialized with sample_rate: {self.sample_rate}")
@traced_tts
@@ -399,10 +384,10 @@ class MiniMaxHttpTTSService(TTSService):
# Build audio_setting dict for API
audio_setting = {
"bitrate": self._settings.audio_bitrate,
"format": self._settings.audio_format,
"channel": self._settings.audio_channel,
"sample_rate": self._settings.audio_sample_rate,
"bitrate": self._audio_bitrate,
"format": self._audio_format,
"channel": self._audio_channel,
"sample_rate": self._audio_sample_rate,
}
# Create payload from settings

View File

@@ -80,13 +80,9 @@ class NeuphonicTTSSettings(TTSSettings):
Parameters:
speed: Speech speed multiplier. Defaults to 1.0.
encoding: Audio encoding format.
sampling_rate: Audio sample rate.
"""
speed: float | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
encoding: str | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
sampling_rate: int | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
class NeuphonicTTSService(InterruptibleTTSService):
@@ -160,8 +156,6 @@ class NeuphonicTTSService(InterruptibleTTSService):
voice=None,
language=self.language_to_service_language(Language.EN),
speed=1.0,
encoding=encoding,
sampling_rate=sample_rate,
)
# 2. Apply direct init arg overrides (deprecated)
@@ -200,6 +194,8 @@ class NeuphonicTTSService(InterruptibleTTSService):
self._receive_task = None
self._keepalive_task = None
self._context_id: Optional[str] = None
self._encoding = encoding
self._sampling_rate = sample_rate
def can_generate_metrics(self) -> bool:
"""Check if this service can generate processing metrics.
@@ -327,8 +323,8 @@ class NeuphonicTTSService(InterruptibleTTSService):
tts_config = {
"lang_code": self._settings.language,
"speed": self._settings.speed,
"encoding": self._settings.encoding,
"sampling_rate": self._settings.sampling_rate,
"encoding": self._encoding,
"sampling_rate": self._sampling_rate,
"voice_id": self._settings.voice,
}
@@ -503,8 +499,6 @@ class NeuphonicHttpTTSService(TTSService):
voice=None,
language=self.language_to_service_language(Language.EN) or "en",
speed=1.0,
encoding=encoding,
sampling_rate=sample_rate,
)
# 2. Apply direct init arg overrides (deprecated)
@@ -536,6 +530,7 @@ class NeuphonicHttpTTSService(TTSService):
self._api_key = api_key
self._session = aiohttp_session
self._base_url = url.rstrip("/")
self._encoding = encoding
def can_generate_metrics(self) -> bool:
"""Check if this service can generate processing metrics.
@@ -629,7 +624,7 @@ class NeuphonicHttpTTSService(TTSService):
payload = {
"text": text,
"lang_code": self._settings.language,
"encoding": self._settings.encoding,
"encoding": self._encoding,
"sampling_rate": self.sample_rate,
"speed": self._settings.speed,
}

View File

@@ -8,8 +8,8 @@
import base64
import json
from dataclasses import dataclass, field
from typing import AsyncGenerator, ClassVar, Dict, Optional
from dataclasses import dataclass
from typing import AsyncGenerator, Optional
from loguru import logger
@@ -23,7 +23,7 @@ from pipecat.frames.frames import (
TTSStartedFrame,
TTSStoppedFrame,
)
from pipecat.services.settings import NOT_GIVEN, TTSSettings, _NotGiven, _warn_deprecated_param
from pipecat.services.settings import TTSSettings, _warn_deprecated_param
from pipecat.services.tts_service import AudioContextTTSService
from pipecat.utils.tracing.service_decorators import traced_tts
@@ -38,22 +38,9 @@ except ModuleNotFoundError as e:
@dataclass
class ResembleAITTSSettings(TTSSettings):
"""Settings for Resemble AI TTS service.
"""Settings for Resemble AI TTS service."""
Parameters:
precision: PCM bit depth (PCM_32, PCM_24, PCM_16, or MULAW).
output_format: Audio format (wav or mp3).
resemble_sample_rate: Audio sample rate sent to the API.
"""
precision: str | None | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
output_format: str | None | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
resemble_sample_rate: int | None | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
_aliases: ClassVar[Dict[str, str]] = {
"voice_id": "voice",
"sample_rate": "resemble_sample_rate",
}
pass
class ResembleAITTSService(AudioContextTTSService):
@@ -100,21 +87,12 @@ class ResembleAITTSService(AudioContextTTSService):
model=None,
voice=None,
language=None,
precision="PCM_16",
output_format="wav",
resemble_sample_rate=22050,
)
# 2. Apply direct init arg overrides (deprecated)
if voice_id is not None:
_warn_deprecated_param("voice_id", ResembleAITTSSettings, "voice")
default_settings.voice = voice_id
if precision is not None:
default_settings.precision = precision
if output_format is not None:
default_settings.output_format = output_format
if sample_rate is not None:
default_settings.resemble_sample_rate = sample_rate
# 3. No params for this service
@@ -133,6 +111,11 @@ class ResembleAITTSService(AudioContextTTSService):
self._api_key = api_key
self._url = url
# Init-only audio format config (not runtime-updatable).
self._precision = precision or "PCM_16"
self._output_format = output_format or "wav"
self._resemble_sample_rate = 0 # Set in start()
self._websocket = None
self._request_id_counter = 0
self._receive_task = None
@@ -174,9 +157,9 @@ class ResembleAITTSService(AudioContextTTSService):
"data": text,
"binary_response": False, # Use JSON frames to get timestamps
"request_id": self._request_id_counter, # ResembleAI only accepts number
"output_format": self._settings.output_format,
"sample_rate": self._settings.resemble_sample_rate,
"precision": self._settings.precision,
"output_format": self._output_format,
"sample_rate": self._resemble_sample_rate,
"precision": self._precision,
"no_audio_header": True,
}
@@ -190,7 +173,7 @@ class ResembleAITTSService(AudioContextTTSService):
frame: The start frame containing initialization parameters.
"""
await super().start(frame)
self._settings.resemble_sample_rate = self.sample_rate
self._resemble_sample_rate = self.sample_rate
await self._connect()
async def stop(self, frame: EndFrame):

View File

@@ -76,8 +76,6 @@ class RimeTTSSettings(TTSSettings):
"""Settings for Rime WS JSON and HTTP TTS services.
Parameters:
audioFormat: Audio output format.
samplingRate: Audio sample rate.
segment: Text segmentation mode ("immediate", "bySentence", "never").
speedAlpha: Speech speed multiplier (mistv2 only).
reduceLatency: Whether to reduce latency at potential quality cost (mistv2 only).
@@ -91,8 +89,6 @@ class RimeTTSSettings(TTSSettings):
top_p: Cumulative probability threshold (arcana only, 0.0-1.0).
"""
audioFormat: str | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
samplingRate: int | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
segment: str | None | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
speedAlpha: float | None | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
reduceLatency: bool | None | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
@@ -113,16 +109,12 @@ class RimeNonJsonTTSSettings(TTSSettings):
"""Settings for Rime non-JSON WS TTS service.
Parameters:
audioFormat: Audio output format.
samplingRate: Audio sample rate.
segment: Text segmentation mode ("immediate", "bySentence", "never").
repetition_penalty: Token repetition penalty (1.0-2.0).
temperature: Sampling temperature (0.0-1.0).
top_p: Cumulative probability threshold (0.0-1.0).
"""
audioFormat: str | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
samplingRate: int | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
segment: str | None | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
repetition_penalty: float | None | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
temperature: float | None | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
@@ -230,8 +222,6 @@ class RimeTTSService(AudioContextTTSService):
default_settings = RimeTTSSettings(
model="arcana",
voice=None,
audioFormat="pcm",
samplingRate=0, # updated in start()
language=None,
segment=None,
inlineSpeedAlpha=None,
@@ -293,6 +283,10 @@ class RimeTTSService(AudioContextTTSService):
**kwargs,
)
# Init-only audio format fields (not runtime-updatable)
self._audio_format = "pcm"
self._sampling_rate = 0 # updated in start()
if not text_aggregator:
# Always skip tags added for spelled-out text
# Note: This is primarily to support backwards compatibility.
@@ -342,8 +336,8 @@ class RimeTTSService(AudioContextTTSService):
params: dict[str, Any] = {
"speaker": self._settings.voice,
"modelId": self._settings.model,
"audioFormat": self._settings.audioFormat,
"samplingRate": self._settings.samplingRate,
"audioFormat": self._audio_format,
"samplingRate": self._sampling_rate,
}
if self._settings.language is not None:
params["lang"] = self._settings.language
@@ -437,7 +431,7 @@ class RimeTTSService(AudioContextTTSService):
frame: The start frame containing initialization parameters.
"""
await super().start(frame)
self._settings.samplingRate = self.sample_rate
self._sampling_rate = self.sample_rate
await self._connect()
async def stop(self, frame: EndFrame):
@@ -737,8 +731,6 @@ class RimeHttpTTSService(TTSService):
model="mistv2",
voice=None,
language="eng",
audioFormat="pcm",
samplingRate=0,
segment=None,
speedAlpha=None,
reduceLatency=None,
@@ -789,6 +781,9 @@ class RimeHttpTTSService(TTSService):
self._session = aiohttp_session
self._base_url = "https://users.rime.ai/v1/rime-tts"
# Init-only audio format fields (not runtime-updatable)
self._audio_format = "pcm"
def can_generate_metrics(self) -> bool:
"""Check if this service can generate processing metrics.
@@ -974,8 +969,6 @@ class RimeNonJsonTTSService(InterruptibleTTSService):
default_settings = RimeNonJsonTTSSettings(
voice=None,
model="arcana",
audioFormat=audio_format,
samplingRate=sample_rate,
language=None,
segment=None,
repetition_penalty=None,
@@ -1017,6 +1010,11 @@ class RimeNonJsonTTSService(InterruptibleTTSService):
settings=default_settings,
**kwargs,
)
# Init-only audio format fields (not runtime-updatable)
self._audio_format = audio_format
self._sampling_rate = sample_rate
self._api_key = api_key
self._url = url
# Add any extra parameters for future compatibility
@@ -1053,7 +1051,7 @@ class RimeNonJsonTTSService(InterruptibleTTSService):
frame: The start frame containing initialization parameters.
"""
await super().start(frame)
self._settings.samplingRate = self.sample_rate
self._sampling_rate = self.sample_rate
await self._connect()
async def stop(self, frame: EndFrame):
@@ -1101,8 +1099,8 @@ class RimeNonJsonTTSService(InterruptibleTTSService):
settings_dict = {
"speaker": self._settings.voice,
"modelId": self._settings.model,
"audioFormat": self._settings.audioFormat,
"samplingRate": self._settings.samplingRate,
"audioFormat": self._audio_format,
"samplingRate": self._sampling_rate,
}
if self._settings.language is not None:
settings_dict["lang"] = self._settings.language

View File

@@ -250,7 +250,6 @@ class SarvamHttpTTSSettings(TTSSettings):
"""Settings for Sarvam HTTP TTS service.
Parameters:
language: Sarvam language code.
enable_preprocessing: Whether to enable text preprocessing. Defaults to False.
**Note:** Always enabled for bulbul:v3-beta (cannot be disabled).
pace: Speech pace multiplier. Defaults to 1.0.
@@ -263,16 +262,13 @@ class SarvamHttpTTSSettings(TTSSettings):
temperature: Controls output randomness for bulbul:v3-beta (0.01 to 1.0).
Lower values = more deterministic, higher = more random. Defaults to 0.6.
**Note:** Only supported for bulbul:v3-beta. Ignored for v2.
sample_rate: Audio sample rate.
"""
language: str | None | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
enable_preprocessing: bool | None | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
pace: float | None | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
pitch: float | None | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
loudness: float | None | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
temperature: float | None | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
sarvam_sample_rate: int | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
@dataclass
@@ -280,19 +276,12 @@ class SarvamTTSSettings(TTSSettings):
"""Settings for Sarvam WebSocket TTS service.
Parameters:
language: Sarvam language code (e.g. ``"hi-IN"``). Uses the standard
``TTSSettings.language`` field.
speech_sample_rate: Audio sample rate as string.
enable_preprocessing: Enable text preprocessing. Defaults to False.
**Note:** Always enabled for bulbul:v3-beta.
min_buffer_size: Minimum characters to buffer before generating audio.
Lower values reduce latency but may affect quality. Defaults to 50.
max_chunk_length: Maximum characters processed in a single chunk.
Controls memory usage and processing efficiency. Defaults to 150.
output_audio_codec: Audio codec format. Options: linear16, mulaw, alaw,
opus, flac, aac, wav, mp3. Defaults to "linear16".
output_audio_bitrate: Audio bitrate (32k, 64k, 96k, 128k, 192k).
Defaults to "128k".
pace: Speech pace multiplier. Defaults to 1.0.
- bulbul:v2: Range 0.3 to 3.0
- bulbul:v3-beta: Range 0.5 to 2.0
@@ -307,12 +296,9 @@ class SarvamTTSSettings(TTSSettings):
_aliases: ClassVar[Dict[str, str]] = {"target_language_code": "language"}
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)
max_chunk_length: int | None | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
output_audio_codec: str | None | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
output_audio_bitrate: str | None | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
pace: float | None | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
pitch: float | None | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
loudness: float | None | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
@@ -587,7 +573,6 @@ class SarvamHttpTTSService(TTSService):
frame: The start frame containing initialization parameters.
"""
await super().start(frame)
self._settings.sarvam_sample_rate = self.sample_rate
@traced_tts
async def run_tts(self, text: str, context_id: str) -> AsyncGenerator[Frame, None]:
@@ -881,12 +866,9 @@ class SarvamTTSService(InterruptibleTTSService):
model="bulbul:v2",
voice="anushka",
language="en-IN",
speech_sample_rate="22050",
enable_preprocessing=False,
min_buffer_size=50,
max_chunk_length=150,
output_audio_codec="linear16",
output_audio_bitrate="128k",
pace=1.0,
pitch=None,
loudness=None,
@@ -901,6 +883,10 @@ class SarvamTTSService(InterruptibleTTSService):
_warn_deprecated_param("voice_id", SarvamTTSSettings, "voice")
default_settings.voice = voice_id
# Init-only audio format fields (not runtime-updatable)
output_audio_codec = "linear16"
output_audio_bitrate = "128k"
# 3. Apply params overrides — only if settings not provided
if params is not None:
_warn_deprecated_param("params", SarvamTTSSettings)
@@ -916,9 +902,9 @@ class SarvamTTSService(InterruptibleTTSService):
if params.max_chunk_length is not None:
default_settings.max_chunk_length = params.max_chunk_length
if params.output_audio_codec is not None:
default_settings.output_audio_codec = params.output_audio_codec
output_audio_codec = params.output_audio_codec
if params.output_audio_bitrate is not None:
default_settings.output_audio_bitrate = params.output_audio_bitrate
output_audio_bitrate = params.output_audio_bitrate
if params.pace is not None:
default_settings.pace = params.pace
if params.pitch is not None:
@@ -943,7 +929,6 @@ class SarvamTTSService(InterruptibleTTSService):
# Set default sample rate based on model if not specified
if sample_rate is None:
sample_rate = self._config.default_sample_rate
default_settings.speech_sample_rate = str(sample_rate)
# Set default voice based on model if not specified via any mechanism
if voice_id is None and (settings is None or settings.voice is NOT_GIVEN):
@@ -986,6 +971,11 @@ class SarvamTTSService(InterruptibleTTSService):
**kwargs,
)
# Init-only audio format fields (not runtime-updatable)
self._speech_sample_rate = str(sample_rate)
self._output_audio_codec = output_audio_codec
self._output_audio_bitrate = output_audio_bitrate
# WebSocket endpoint URL with model query parameter
self._websocket_url = f"{url}?model={resolved_model}"
self._api_key = api_key
@@ -1022,7 +1012,7 @@ class SarvamTTSService(InterruptibleTTSService):
await super().start(frame)
# WebSocket API expects sample rate as string
self._settings.speech_sample_rate = str(self.sample_rate)
self._speech_sample_rate = str(self.sample_rate)
await self._connect()
async def stop(self, frame: EndFrame):
@@ -1140,12 +1130,12 @@ class SarvamTTSService(InterruptibleTTSService):
config_data = {
"target_language_code": self._settings.language,
"speaker": self._settings.voice,
"speech_sample_rate": self._settings.speech_sample_rate,
"speech_sample_rate": self._speech_sample_rate,
"enable_preprocessing": self._settings.enable_preprocessing,
"min_buffer_size": self._settings.min_buffer_size,
"max_chunk_length": self._settings.max_chunk_length,
"output_audio_codec": self._settings.output_audio_codec,
"output_audio_bitrate": self._settings.output_audio_bitrate,
"output_audio_codec": self._output_audio_codec,
"output_audio_bitrate": self._output_audio_bitrate,
"pace": self._settings.pace,
"model": self._settings.model,
}

View File

@@ -10,7 +10,7 @@ This module provides integration with Coqui XTTS streaming server for
text-to-speech synthesis using local Docker deployment.
"""
from dataclasses import dataclass, field
from dataclasses import dataclass
from typing import AsyncGenerator, Dict, Optional
import aiohttp
@@ -25,7 +25,7 @@ from pipecat.frames.frames import (
TTSStartedFrame,
TTSStoppedFrame,
)
from pipecat.services.settings import NOT_GIVEN, TTSSettings, _NotGiven, _warn_deprecated_param
from pipecat.services.settings import TTSSettings, _warn_deprecated_param
from pipecat.services.tts_service import TTSService
from pipecat.transcriptions.language import Language, resolve_language
from pipecat.utils.tracing.service_decorators import traced_tts
@@ -72,13 +72,9 @@ def language_to_xtts_language(language: Language) -> Optional[str]:
@dataclass
class XTTSTTSSettings(TTSSettings):
"""Settings for XTTS TTS service.
"""Settings for XTTS TTS service."""
Parameters:
base_url: Base URL of the XTTS streaming server.
"""
base_url: str | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
pass
class XTTSService(TTSService):
@@ -123,7 +119,6 @@ class XTTSService(TTSService):
model=None,
voice=None,
language=self.language_to_service_language(language),
base_url=base_url,
)
# 2. Apply direct init arg overrides (deprecated)
@@ -140,6 +135,10 @@ class XTTSService(TTSService):
settings=default_settings,
**kwargs,
)
# Init-only fields (not runtime-updatable)
self._base_url = base_url
self._studio_speakers: Optional[Dict[str, Any]] = None
self._aiohttp_session = aiohttp_session
@@ -175,7 +174,7 @@ class XTTSService(TTSService):
if self._studio_speakers:
return
async with self._aiohttp_session.get(self._settings.base_url + "/studio_speakers") as r:
async with self._aiohttp_session.get(self._base_url + "/studio_speakers") as r:
if r.status != 200:
text = await r.text()
await self.push_error(
@@ -203,7 +202,7 @@ class XTTSService(TTSService):
embeddings = self._studio_speakers[self._settings.voice]
url = self._settings.base_url + "/tts_stream"
url = self._base_url + "/tts_stream"
payload = {
"text": text.replace(".", "").replace("*", ""),