update CHANGELOG and fix formatting

This commit is contained in:
Aleix Conchillo Flaqué
2025-01-30 08:55:29 -08:00
parent f90c17ab30
commit e69c065a86
6 changed files with 38 additions and 17 deletions

View File

@@ -9,6 +9,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added ### 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 - Allow pushing silence audio frames before `TTSStoppedFrame`. This might be
useful for testing purposes, for example, passing bot audio to an STT service useful for testing purposes, for example, passing bot audio to an STT service
which usually needs additional audio data to detect the utterance stopped. which usually needs additional audio data to detect the utterance stopped.

View File

@@ -16,18 +16,15 @@ from pipecat.pipeline.pipeline import Pipeline
from pipecat.pipeline.runner import PipelineRunner from pipecat.pipeline.runner import PipelineRunner
from pipecat.pipeline.task import PipelineParams, PipelineTask from pipecat.pipeline.task import PipelineParams, PipelineTask
from pipecat.processors.aggregators.openai_llm_context import OpenAILLMContext from pipecat.processors.aggregators.openai_llm_context import OpenAILLMContext
from pipecat.serializers.telnyx import TelnyxFrameSerializer from pipecat.serializers.telnyx import TelnyxFrameSerializer
from pipecat.services.elevenlabs import ElevenLabsTTSService, Language
from pipecat.services.deepgram import DeepgramSTTService from pipecat.services.deepgram import DeepgramSTTService
from pipecat.services.elevenlabs import ElevenLabsTTSService, Language
from pipecat.services.openai import OpenAILLMService from pipecat.services.openai import OpenAILLMService
from pipecat.transports.network.fastapi_websocket import ( from pipecat.transports.network.fastapi_websocket import (
FastAPIWebsocketParams, FastAPIWebsocketParams,
FastAPIWebsocketTransport, FastAPIWebsocketTransport,
) )
load_dotenv(override=True) load_dotenv(override=True)
logger.remove(0) logger.remove(0)

View File

@@ -1,3 +1,9 @@
#
# Copyright (c) 2025, Daily
#
# SPDX-License-Identifier: BSD 2-Clause License
#
import json import json
import uvicorn import uvicorn
@@ -16,11 +22,13 @@ app.add_middleware(
allow_headers=["*"], allow_headers=["*"],
) )
@app.post("/") @app.post("/")
async def start_call(): async def start_call():
print("POST TeXML") print("POST TeXML")
return HTMLResponse(content=open("templates/streams.xml").read(), media_type="application/xml") return HTMLResponse(content=open("templates/streams.xml").read(), media_type="application/xml")
@app.websocket("/ws") @app.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket): async def websocket_endpoint(websocket: WebSocket):
await websocket.accept() await websocket.accept()
@@ -33,5 +41,6 @@ async def websocket_endpoint(websocket: WebSocket):
print("WebSocket connection accepted") print("WebSocket connection accepted")
await run_bot(websocket, stream_id, outbound_encoding, "PCMU") await run_bot(websocket, stream_id, outbound_encoding, "PCMU")
if __name__ == "__main__": if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=8765) uvicorn.run(app, host="0.0.0.0", port=8765)

View File

@@ -1,3 +1,9 @@
#
# Copyright (c) 2025, Daily
#
# SPDX-License-Identifier: BSD 2-Clause License
#
import json import json
import uvicorn import uvicorn

View File

@@ -81,7 +81,7 @@ def ulaw_to_pcm(ulaw_bytes: bytes, in_sample_rate: int, out_sample_rate: int):
# Resample # Resample
out_pcm_bytes = resample_audio(in_pcm_bytes, in_sample_rate, out_sample_rate) out_pcm_bytes = resample_audio(in_pcm_bytes, in_sample_rate, out_sample_rate)
return out_pcm_bytes 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 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 # Convert a-law to PCM
in_pcm_bytes = audioop.alaw2lin(alaw_bytes, 2) in_pcm_bytes = audioop.alaw2lin(alaw_bytes, 2)
# Resample # Resample
out_pcm_bytes = resample_audio(in_pcm_bytes, in_sample_rate, out_sample_rate) out_pcm_bytes = resample_audio(in_pcm_bytes, in_sample_rate, out_sample_rate)
return out_pcm_bytes return out_pcm_bytes

View File

@@ -9,7 +9,7 @@ import json
from pydantic import BaseModel 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 ( from pipecat.frames.frames import (
AudioRawFrame, AudioRawFrame,
Frame, Frame,
@@ -28,10 +28,16 @@ class TelnyxFrameSerializer(FrameSerializer):
inbound_encoding: str = "PCMU" inbound_encoding: str = "PCMU"
outbound_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 self._stream_id = stream_id
params.outbound_encoding = outbound_encoding params.outbound_encoding = outbound_encoding
params.inbound_encoding = inbound_encoding params.inbound_encoding = inbound_encoding
self._params = params self._params = params
@property @property
@@ -41,18 +47,18 @@ class TelnyxFrameSerializer(FrameSerializer):
def serialize(self, frame: Frame) -> str | bytes | None: def serialize(self, frame: Frame) -> str | bytes | None:
if isinstance(frame, AudioRawFrame): if isinstance(frame, AudioRawFrame):
data = frame.audio data = frame.audio
if self._params.inbound_encoding == "PCMU": if self._params.inbound_encoding == "PCMU":
serialized_data = pcm_to_ulaw( serialized_data = pcm_to_ulaw(
data, frame.sample_rate, self._params.telnyx_sample_rate data, frame.sample_rate, self._params.telnyx_sample_rate
) )
elif self._params.inbound_encoding == "PCMA": elif self._params.inbound_encoding == "PCMA":
serialized_data = pcm_to_alaw( serialized_data = pcm_to_alaw(
data, frame.sample_rate, self._params.telnyx_sample_rate data, frame.sample_rate, self._params.telnyx_sample_rate
) )
else: 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") payload = base64.b64encode(serialized_data).decode("utf-8")
answer = { answer = {
"event": "media", "event": "media",
@@ -67,7 +73,7 @@ class TelnyxFrameSerializer(FrameSerializer):
def deserialize(self, data: str | bytes) -> Frame | None: def deserialize(self, data: str | bytes) -> Frame | None:
message = json.loads(data) message = json.loads(data)
if message["event"] == "media": if message["event"] == "media":
payload_base64 = message["media"]["payload"] payload_base64 = message["media"]["payload"]
payload = base64.b64decode(payload_base64) payload = base64.b64decode(payload_base64)
@@ -81,7 +87,7 @@ class TelnyxFrameSerializer(FrameSerializer):
payload, self._params.telnyx_sample_rate, self._params.sample_rate payload, self._params.telnyx_sample_rate, self._params.sample_rate
) )
else: else:
raise ValueError(f"Unsupported encoding: {self._params.encoding}") raise ValueError(f"Unsupported encoding: {self._params.outbound_encoding}")
audio_frame = InputAudioRawFrame( audio_frame = InputAudioRawFrame(
audio=deserialized_data, num_channels=1, sample_rate=self._params.sample_rate audio=deserialized_data, num_channels=1, sample_rate=self._params.sample_rate