From e69c065a86fafe8ca5ff3000df57df30339266e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aleix=20Conchillo=20Flaqu=C3=A9?= Date: Thu, 30 Jan 2025 08:55:29 -0800 Subject: [PATCH] update CHANGELOG and fix formatting --- CHANGELOG.md | 3 +++ examples/telnyx-chatbot/bot.py | 5 +---- examples/telnyx-chatbot/server.py | 9 +++++++++ examples/twilio-chatbot/server.py | 6 ++++++ src/pipecat/audio/utils.py | 6 +++--- src/pipecat/serializers/telnyx.py | 26 ++++++++++++++++---------- 6 files changed, 38 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 86a4da21b..ed9e1d5d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- Added `TelnyxFrameSerializer` to support Telnyx calls. A full running example + has also been added to `examples/telnyx-chatbot`. + - Allow pushing silence audio frames before `TTSStoppedFrame`. This might be useful for testing purposes, for example, passing bot audio to an STT service which usually needs additional audio data to detect the utterance stopped. diff --git a/examples/telnyx-chatbot/bot.py b/examples/telnyx-chatbot/bot.py index 8156256a3..ce0cf052b 100644 --- a/examples/telnyx-chatbot/bot.py +++ b/examples/telnyx-chatbot/bot.py @@ -16,18 +16,15 @@ from pipecat.pipeline.pipeline import Pipeline from pipecat.pipeline.runner import PipelineRunner from pipecat.pipeline.task import PipelineParams, PipelineTask from pipecat.processors.aggregators.openai_llm_context import OpenAILLMContext - from pipecat.serializers.telnyx import TelnyxFrameSerializer - -from pipecat.services.elevenlabs import ElevenLabsTTSService, Language from pipecat.services.deepgram import DeepgramSTTService +from pipecat.services.elevenlabs import ElevenLabsTTSService, Language from pipecat.services.openai import OpenAILLMService from pipecat.transports.network.fastapi_websocket import ( FastAPIWebsocketParams, FastAPIWebsocketTransport, ) - load_dotenv(override=True) logger.remove(0) diff --git a/examples/telnyx-chatbot/server.py b/examples/telnyx-chatbot/server.py index 840e1157f..7bc1c0615 100644 --- a/examples/telnyx-chatbot/server.py +++ b/examples/telnyx-chatbot/server.py @@ -1,3 +1,9 @@ +# +# Copyright (c) 2025, Daily +# +# SPDX-License-Identifier: BSD 2-Clause License +# + import json import uvicorn @@ -16,11 +22,13 @@ app.add_middleware( allow_headers=["*"], ) + @app.post("/") async def start_call(): print("POST TeXML") return HTMLResponse(content=open("templates/streams.xml").read(), media_type="application/xml") + @app.websocket("/ws") async def websocket_endpoint(websocket: WebSocket): await websocket.accept() @@ -33,5 +41,6 @@ async def websocket_endpoint(websocket: WebSocket): print("WebSocket connection accepted") await run_bot(websocket, stream_id, outbound_encoding, "PCMU") + if __name__ == "__main__": uvicorn.run(app, host="0.0.0.0", port=8765) diff --git a/examples/twilio-chatbot/server.py b/examples/twilio-chatbot/server.py index c59b6e2ee..bb67a86f3 100644 --- a/examples/twilio-chatbot/server.py +++ b/examples/twilio-chatbot/server.py @@ -1,3 +1,9 @@ +# +# Copyright (c) 2025, Daily +# +# SPDX-License-Identifier: BSD 2-Clause License +# + import json import uvicorn diff --git a/src/pipecat/audio/utils.py b/src/pipecat/audio/utils.py index 36dc31c91..8e95ebc31 100644 --- a/src/pipecat/audio/utils.py +++ b/src/pipecat/audio/utils.py @@ -81,7 +81,7 @@ def ulaw_to_pcm(ulaw_bytes: bytes, in_sample_rate: int, out_sample_rate: int): # Resample out_pcm_bytes = resample_audio(in_pcm_bytes, in_sample_rate, out_sample_rate) - + return out_pcm_bytes @@ -95,13 +95,13 @@ def pcm_to_ulaw(pcm_bytes: bytes, in_sample_rate: int, out_sample_rate: int): return ulaw_bytes -def alaw_to_pcm(alaw_bytes: bytes, in_sample_rate: int, out_sample_rate: int) -> bytes: +def alaw_to_pcm(alaw_bytes: bytes, in_sample_rate: int, out_sample_rate: int) -> bytes: # Convert a-law to PCM in_pcm_bytes = audioop.alaw2lin(alaw_bytes, 2) # Resample out_pcm_bytes = resample_audio(in_pcm_bytes, in_sample_rate, out_sample_rate) - + return out_pcm_bytes diff --git a/src/pipecat/serializers/telnyx.py b/src/pipecat/serializers/telnyx.py index 11111afb4..14482531d 100644 --- a/src/pipecat/serializers/telnyx.py +++ b/src/pipecat/serializers/telnyx.py @@ -9,7 +9,7 @@ import json from pydantic import BaseModel -from pipecat.audio.utils import pcm_to_ulaw, ulaw_to_pcm, pcm_to_alaw, alaw_to_pcm +from pipecat.audio.utils import alaw_to_pcm, pcm_to_alaw, pcm_to_ulaw, ulaw_to_pcm from pipecat.frames.frames import ( AudioRawFrame, Frame, @@ -28,10 +28,16 @@ class TelnyxFrameSerializer(FrameSerializer): inbound_encoding: str = "PCMU" outbound_encoding: str = "PCMU" - def __init__(self, stream_id: str, outbound_encoding: str, inbound_encoding: str, params: InputParams = InputParams()): + def __init__( + self, + stream_id: str, + outbound_encoding: str, + inbound_encoding: str, + params: InputParams = InputParams(), + ): self._stream_id = stream_id params.outbound_encoding = outbound_encoding - params.inbound_encoding = inbound_encoding + params.inbound_encoding = inbound_encoding self._params = params @property @@ -41,18 +47,18 @@ class TelnyxFrameSerializer(FrameSerializer): def serialize(self, frame: Frame) -> str | bytes | None: if isinstance(frame, AudioRawFrame): data = frame.audio - + if self._params.inbound_encoding == "PCMU": serialized_data = pcm_to_ulaw( data, frame.sample_rate, self._params.telnyx_sample_rate - ) + ) elif self._params.inbound_encoding == "PCMA": serialized_data = pcm_to_alaw( data, frame.sample_rate, self._params.telnyx_sample_rate - ) + ) else: - raise ValueError(f"Unsupported encoding: {self._params.encoding}") - + raise ValueError(f"Unsupported encoding: {self._params.inbound_encoding}") + payload = base64.b64encode(serialized_data).decode("utf-8") answer = { "event": "media", @@ -67,7 +73,7 @@ class TelnyxFrameSerializer(FrameSerializer): def deserialize(self, data: str | bytes) -> Frame | None: message = json.loads(data) - + if message["event"] == "media": payload_base64 = message["media"]["payload"] payload = base64.b64decode(payload_base64) @@ -81,7 +87,7 @@ class TelnyxFrameSerializer(FrameSerializer): payload, self._params.telnyx_sample_rate, self._params.sample_rate ) else: - raise ValueError(f"Unsupported encoding: {self._params.encoding}") + raise ValueError(f"Unsupported encoding: {self._params.outbound_encoding}") audio_frame = InputAudioRawFrame( audio=deserialized_data, num_channels=1, sample_rate=self._params.sample_rate