Merge pull request #4473 from timofey-TK/inworld-tts-v2
Add support for Inworld TTS v2 fields
This commit is contained in:
3
changelog/4473.added.md
Normal file
3
changelog/4473.added.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
- Inworld TTS updates:
|
||||||
|
- Added `delivery_mode` setting (`STABLE`/`BALANCED`/`CREATIVE`) to `InworldTTSService` and `InworldHttpTTSService`, enabling the stability-vs-creativity tradeoff in `inworld-tts-2`.
|
||||||
|
- Added language support to `InworldTTSService` and `InworldHttpTTSService`. The `language` setting is now forwarded to the API, and a new `language_to_inworld_language()` helper normalizes Pipecat `Language` enums to Inworld's BCP-47 locale tags.
|
||||||
@@ -60,9 +60,40 @@ from pipecat.frames.frames import (
|
|||||||
)
|
)
|
||||||
from pipecat.processors.frame_processor import FrameDirection
|
from pipecat.processors.frame_processor import FrameDirection
|
||||||
from pipecat.services.tts_service import TextAggregationMode, TTSService, WebsocketTTSService
|
from pipecat.services.tts_service import TextAggregationMode, TTSService, WebsocketTTSService
|
||||||
|
from pipecat.transcriptions.language import Language, resolve_language
|
||||||
from pipecat.utils.tracing.service_decorators import traced_tts
|
from pipecat.utils.tracing.service_decorators import traced_tts
|
||||||
|
|
||||||
|
|
||||||
|
def language_to_inworld_language(language: Language) -> str:
|
||||||
|
"""Convert a Language enum to an Inworld TTS BCP-47 language tag.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
language: The Language enum value to convert.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The corresponding Inworld BCP-47 language tag (e.g. ``"en-US"``).
|
||||||
|
Unverified languages fall back to their BCP-47 string value with a warning.
|
||||||
|
"""
|
||||||
|
LANGUAGE_MAP = {
|
||||||
|
Language.AR: "ar-SA",
|
||||||
|
Language.DE: "de-DE",
|
||||||
|
Language.EN: "en-US",
|
||||||
|
Language.ES: "es-ES",
|
||||||
|
Language.FR: "fr-FR",
|
||||||
|
Language.HE: "he-IL",
|
||||||
|
Language.HI: "hi-IN",
|
||||||
|
Language.IT: "it-IT",
|
||||||
|
Language.JA: "ja-JP",
|
||||||
|
Language.KO: "ko-KR",
|
||||||
|
Language.NL: "nl-NL",
|
||||||
|
Language.PL: "pl-PL",
|
||||||
|
Language.PT: "pt-BR",
|
||||||
|
Language.RU: "ru-RU",
|
||||||
|
Language.ZH: "zh-CN",
|
||||||
|
}
|
||||||
|
return resolve_language(language, LANGUAGE_MAP, use_base_code=False)
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class InworldTTSSettings(TTSSettings):
|
class InworldTTSSettings(TTSSettings):
|
||||||
"""Settings for InworldTTSService and InworldHttpTTSService.
|
"""Settings for InworldTTSService and InworldHttpTTSService.
|
||||||
@@ -70,10 +101,18 @@ class InworldTTSSettings(TTSSettings):
|
|||||||
Parameters:
|
Parameters:
|
||||||
speaking_rate: Speaking rate for speech synthesis.
|
speaking_rate: Speaking rate for speech synthesis.
|
||||||
temperature: Temperature for speech synthesis.
|
temperature: Temperature for speech synthesis.
|
||||||
|
delivery_mode: Controls the stability vs. creativity tradeoff.
|
||||||
|
``"STABLE"`` produces reliable, predictable speech.
|
||||||
|
``"BALANCED"`` is the default midpoint.
|
||||||
|
``"CREATIVE"`` produces more expressive, emotionally varied speech.
|
||||||
|
Only supported by ``inworld-tts-2``.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
speaking_rate: float | None | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
|
speaking_rate: float | None | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
|
||||||
temperature: float | None | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
|
temperature: float | None | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
|
||||||
|
delivery_mode: Literal["STABLE", "BALANCED", "CREATIVE"] | None | _NotGiven = field(
|
||||||
|
default_factory=lambda: NOT_GIVEN
|
||||||
|
)
|
||||||
|
|
||||||
_aliases: ClassVar[dict[str, str]] = {
|
_aliases: ClassVar[dict[str, str]] = {
|
||||||
"voiceId": "voice",
|
"voiceId": "voice",
|
||||||
@@ -167,6 +206,7 @@ class InworldHttpTTSService(TTSService):
|
|||||||
language=None,
|
language=None,
|
||||||
speaking_rate=None,
|
speaking_rate=None,
|
||||||
temperature=None,
|
temperature=None,
|
||||||
|
delivery_mode=None,
|
||||||
)
|
)
|
||||||
|
|
||||||
# 2. Apply direct init arg overrides (deprecated)
|
# 2. Apply direct init arg overrides (deprecated)
|
||||||
@@ -227,6 +267,17 @@ class InworldHttpTTSService(TTSService):
|
|||||||
"""
|
"""
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def language_to_service_language(self, language: Language) -> str | None:
|
||||||
|
"""Convert a Language enum to Inworld language format.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
language: The language to convert.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The Inworld-specific BCP-47 language code, or None if not supported.
|
||||||
|
"""
|
||||||
|
return language_to_inworld_language(language)
|
||||||
|
|
||||||
async def start(self, frame: StartFrame):
|
async def start(self, frame: StartFrame):
|
||||||
"""Start the Inworld TTS service.
|
"""Start the Inworld TTS service.
|
||||||
|
|
||||||
@@ -313,6 +364,10 @@ class InworldHttpTTSService(TTSService):
|
|||||||
|
|
||||||
if self._settings.temperature is not None:
|
if self._settings.temperature is not None:
|
||||||
payload["temperature"] = self._settings.temperature
|
payload["temperature"] = self._settings.temperature
|
||||||
|
if self._settings.delivery_mode is not None:
|
||||||
|
payload["deliveryMode"] = self._settings.delivery_mode
|
||||||
|
if self._settings.language is not None:
|
||||||
|
payload["language"] = self._settings.language
|
||||||
|
|
||||||
# Use WORD timestamps for simplicity and correct spacing/capitalization
|
# Use WORD timestamps for simplicity and correct spacing/capitalization
|
||||||
payload["timestampType"] = self._timestamp_type
|
payload["timestampType"] = self._timestamp_type
|
||||||
@@ -609,6 +664,7 @@ class InworldTTSService(WebsocketTTSService):
|
|||||||
language=None,
|
language=None,
|
||||||
speaking_rate=None,
|
speaking_rate=None,
|
||||||
temperature=None,
|
temperature=None,
|
||||||
|
delivery_mode=None,
|
||||||
)
|
)
|
||||||
|
|
||||||
# 2. Apply direct init arg overrides (deprecated)
|
# 2. Apply direct init arg overrides (deprecated)
|
||||||
@@ -700,6 +756,17 @@ class InworldTTSService(WebsocketTTSService):
|
|||||||
"""
|
"""
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def language_to_service_language(self, language: Language) -> str | None:
|
||||||
|
"""Convert a Language enum to Inworld language format.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
language: The language to convert.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The Inworld-specific BCP-47 language code, or None if not supported.
|
||||||
|
"""
|
||||||
|
return language_to_inworld_language(language)
|
||||||
|
|
||||||
async def start(self, frame: StartFrame):
|
async def start(self, frame: StartFrame):
|
||||||
"""Start the Inworld WebSocket TTS service.
|
"""Start the Inworld WebSocket TTS service.
|
||||||
|
|
||||||
@@ -1089,6 +1156,10 @@ class InworldTTSService(WebsocketTTSService):
|
|||||||
|
|
||||||
if self._settings.temperature is not None:
|
if self._settings.temperature is not None:
|
||||||
create_config["temperature"] = self._settings.temperature
|
create_config["temperature"] = self._settings.temperature
|
||||||
|
if self._settings.delivery_mode is not None:
|
||||||
|
create_config["deliveryMode"] = self._settings.delivery_mode
|
||||||
|
if self._settings.language is not None:
|
||||||
|
create_config["language"] = self._settings.language
|
||||||
if self._apply_text_normalization is not None:
|
if self._apply_text_normalization is not None:
|
||||||
create_config["applyTextNormalization"] = self._apply_text_normalization
|
create_config["applyTextNormalization"] = self._apply_text_normalization
|
||||||
if self._auto_mode is not None:
|
if self._auto_mode is not None:
|
||||||
|
|||||||
31
tests/test_inworld_tts_language.py
Normal file
31
tests/test_inworld_tts_language.py
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
#
|
||||||
|
# Copyright (c) 2024-2026, Daily
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: BSD 2-Clause License
|
||||||
|
#
|
||||||
|
|
||||||
|
"""Tests for Inworld TTS language code mapping."""
|
||||||
|
|
||||||
|
from pipecat.services.inworld.tts import language_to_inworld_language
|
||||||
|
from pipecat.transcriptions.language import Language
|
||||||
|
|
||||||
|
|
||||||
|
def test_inworld_base_languages_resolve_to_canonical_regional_tags():
|
||||||
|
"""Base GA languages should use the regional tags emitted by Inworld Playground."""
|
||||||
|
assert language_to_inworld_language(Language.EN) == "en-US"
|
||||||
|
assert language_to_inworld_language(Language.RU) == "ru-RU"
|
||||||
|
assert language_to_inworld_language(Language.FR) == "fr-FR"
|
||||||
|
assert language_to_inworld_language(Language.ZH) == "zh-CN"
|
||||||
|
|
||||||
|
|
||||||
|
def test_inworld_regional_languages_are_preserved():
|
||||||
|
"""Explicit regional variants should be passed through as supported BCP-47 tags."""
|
||||||
|
assert language_to_inworld_language(Language.EN_GB) == "en-GB"
|
||||||
|
assert language_to_inworld_language(Language.PT_PT) == "pt-PT"
|
||||||
|
assert language_to_inworld_language(Language.RU_RU) == "ru-RU"
|
||||||
|
|
||||||
|
|
||||||
|
def test_inworld_other_languages_are_passed_through_as_bcp47_tags():
|
||||||
|
"""Languages outside the canonical locale map should keep their BCP-47 enum value."""
|
||||||
|
assert language_to_inworld_language(Language.SV_SE) == "sv-SE"
|
||||||
|
assert language_to_inworld_language(Language.UK_UA) == "uk-UA"
|
||||||
Reference in New Issue
Block a user