inworld: docstring fix

This commit is contained in:
padillamt
2025-07-18 15:13:19 -07:00
parent f3984aec33
commit 1bc442e329

View File

@@ -7,6 +7,7 @@
"""Inworld's text-to-speech service implementations."""
import base64
import io
import json
import uuid
import warnings
@@ -15,7 +16,6 @@ from typing import AsyncGenerator, List, Optional, Union
import aiohttp
from loguru import logger
from pydantic import BaseModel, Field
import io, json, base64
from pipecat.frames.frames import (
CancelFrame,
@@ -89,7 +89,7 @@ class InworldHttpTTSService(TTSService):
"""
language: Optional[Language] = Language.EN
voice_id: str = "Ashley" ## QUESTION: How to make this modifyable/how to modify?
voice_id: str = "Ashley" ## QUESTION: How to make this modifyable/how to modify?
# QUESTION: What about speed, pitch, and temperature??
def __init__(
@@ -109,9 +109,8 @@ class InworldHttpTTSService(TTSService):
Args:
api_key: Inworld API key for authentication.
aiohttp_session: Shared aiohttp session for HTTP requests.
voice_id: ID of the voice to use for synthesis.
model: TTS model to use (e.g., "sonic-2").
endpoint_url: Base URL for Inworld HTTP API.
model: TTS model to use (e.g., "inworld-tts-1").
base_url: Base URL for Inworld HTTP API.
sample_rate: Audio sample rate. If None, uses default.
encoding: Audio encoding format.
params: Additional input parameters for voice customization.
@@ -138,7 +137,6 @@ class InworldHttpTTSService(TTSService):
self.set_voice(params.voice_id)
self.set_model_name(model)
def can_generate_metrics(self) -> bool:
"""Check if this service can generate processing metrics.
@@ -187,6 +185,8 @@ class InworldHttpTTSService(TTSService):
async def run_tts(self, text: str) -> AsyncGenerator[Frame, None]:
"""Generate speech from text using Inworld's HTTP API.
This implementation streams audio chunk by chunk as it's received.
Args:
text: The text to synthesize into speech.
@@ -213,52 +213,134 @@ class InworldHttpTTSService(TTSService):
yield TTSStartedFrame()
async with self._session.post(self._base_url, json=payload, headers=headers) as response:
# A flag to ensure we only strip the header from the very first chunk.
is_first_chunk = True
async with self._session.post(
self._base_url, json=payload, headers=headers
) as response:
if response.status != 200:
error_text = await response.text()
logger.error(f"Inworld API error: {error_text}")
await self.push_error(ErrorFrame(f"Inworld API error: {error_text}"))
return
raw_audio_data = io.BytesIO()
# Process the stream line by line.
async for line in response.content.iter_lines():
line_str = line.decode('utf-8').strip()
line_str = line.decode("utf-8").strip()
if not line_str:
continue
try:
chunk = json.loads(line_str)
if "result" in chunk and "audioContent" in chunk["result"]:
audio_chunk = base64.b64decode(chunk["result"]["audioContent"])
# Skip WAV header if present (first 44 bytes)
if len(audio_chunk) > 44 and audio_chunk.startswith(b"RIFF"):
audio_data = audio_chunk
# Correctly strip the header only from the first chunk.
if (
is_first_chunk
and len(audio_chunk) > 44
and audio_chunk.startswith(b"RIFF")
):
audio_data = audio_chunk[44:]
else:
audio_data = audio_chunk
raw_audio_data.write(audio_data)
is_first_chunk = False # Unset the flag.
# Yield each audio frame as it's processed.
yield TTSAudioRawFrame(
audio=audio_data,
sample_rate=self.sample_rate,
num_channels=1,
)
except json.JSONDecodeError:
continue
await self.start_tts_usage_metrics(text)
audio_bytes = raw_audio_data.getvalue()
if not audio_bytes:
logger.error("No audio data received from Inworld API")
await self.push_error(ErrorFrame("No audio data received"))
return
frame = TTSAudioRawFrame(
audio=audio_bytes,
sample_rate=self.sample_rate,
num_channels=1,
)
yield frame
except Exception as e:
logger.error(f"{self} exception: {e}")
await self.push_error(ErrorFrame(f"Error generating TTS: {e}"))
finally:
await self.stop_ttfb_metrics()
yield TTSStoppedFrame()
# @traced_tts
# async def run_tts(self, text: str) -> AsyncGenerator[Frame, None]:
# """Generate speech from text using Inworld's HTTP API.
# Args:
# text: The text to synthesize into speech.
# Yields:
# Frame: Audio frames containing the synthesized speech.
# """
# logger.debug(f"{self}: Generating TTS [{text}]")
# payload = {
# "text": text,
# "voiceId": self._settings["voiceId"],
# "modelId": self._settings["modelId"],
# "audio_config": self._settings["audio_config"],
# "language": self._settings["language"],
# }
# headers = {
# "Authorization": f"Basic {self._api_key}",
# "Content-Type": "application/json",
# }
# try:
# await self.start_ttfb_metrics()
# yield TTSStartedFrame()
# async with self._session.post(self._base_url, json=payload, headers=headers) as response:
# if response.status != 200:
# error_text = await response.text()
# logger.error(f"Inworld API error: {error_text}")
# await self.push_error(ErrorFrame(f"Inworld API error: {error_text}"))
# return
# raw_audio_data = io.BytesIO()
# async for line in response.content.iter_lines():
# line_str = line.decode('utf-8').strip()
# if not line_str:
# continue
# try:
# chunk = json.loads(line_str)
# if "result" in chunk and "audioContent" in chunk["result"]:
# audio_chunk = base64.b64decode(chunk["result"]["audioContent"])
# # Skip WAV header if present (first 44 bytes)
# if len(audio_chunk) > 44 and audio_chunk.startswith(b"RIFF"):
# audio_data = audio_chunk[44:]
# else:
# audio_data = audio_chunk
# raw_audio_data.write(audio_data)
# except json.JSONDecodeError:
# continue
# await self.start_tts_usage_metrics(text)
# audio_bytes = raw_audio_data.getvalue()
# if not audio_bytes:
# logger.error("No audio data received from Inworld API")
# await self.push_error(ErrorFrame("No audio data received"))
# return
# frame = TTSAudioRawFrame(
# audio=audio_bytes,
# sample_rate=self.sample_rate,
# num_channels=1,
# )
# yield frame
# except Exception as e:
# logger.error(f"{self} exception: {e}")
# await self.push_error(ErrorFrame(f"Error generating TTS: {e}"))
# finally:
# await self.stop_ttfb_metrics()
# yield TTSStoppedFrame()