From 5676920a6a9057c7805096d7fc0d1759dceb2a24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aleix=20Conchillo=20Flaqu=C3=A9?= Date: Wed, 28 May 2025 18:03:33 -0700 Subject: [PATCH] BaseOutputTransport/DailyTransport: allow sending DTMF keypress --- CHANGELOG.md | 9 +++++++++ src/pipecat/transports/base_output.py | 9 +++++++++ src/pipecat/transports/services/daily.py | 18 ++++++++++++++++++ 3 files changed, 36 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1022711be..abf343513 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- It is now possible to push `OutputDTMFFrame` or `OutputDTMFUrgentFrame` with + `DailyTransport`. This will be sent properly if a Daily dial-out connection + has been established. + - Added `OutputDTMFUrgentFrame` to send a DTMF keypress quickly. The previous `OutputDTMFFrame` queues the keypress with the rest of data frames. @@ -16,6 +20,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 `DailyTransport.stop_transcription()` to be able to start and stop Daily transcription dynamically (maybe with different settings). +### Deprecated + +- `DailyTransport.send_dtmf()` is deprecated, push an `OutputDTMFFrame` or an + `OutputDTMFUrgentFrame` instead. + ### Fixed - In `AWSBedrockLLMService`, worked around a possible bug in AWS Bedrock where diff --git a/src/pipecat/transports/base_output.py b/src/pipecat/transports/base_output.py index f26602ed7..ba8eb4916 100644 --- a/src/pipecat/transports/base_output.py +++ b/src/pipecat/transports/base_output.py @@ -25,6 +25,8 @@ from pipecat.frames.frames import ( Frame, MixerControlFrame, OutputAudioRawFrame, + OutputDTMFFrame, + OutputDTMFUrgentFrame, OutputImageRawFrame, SpriteFrame, StartFrame, @@ -140,6 +142,9 @@ class BaseOutputTransport(FrameProcessor): async def write_raw_audio_frames(self, frames: bytes, destination: Optional[str] = None): pass + async def write_dtmf(self, frame: OutputDTMFFrame | OutputDTMFUrgentFrame): + pass + async def send_audio(self, frame: OutputAudioRawFrame): await self.queue_frame(frame, FrameDirection.DOWNSTREAM) @@ -171,6 +176,8 @@ class BaseOutputTransport(FrameProcessor): await self._handle_frame(frame) elif isinstance(frame, TransportMessageUrgentFrame): await self.send_message(frame) + elif isinstance(frame, OutputDTMFUrgentFrame): + await self.write_dtmf(frame) elif isinstance(frame, SystemFrame): await self.push_frame(frame, direction) # Control frames. @@ -425,6 +432,8 @@ class BaseOutputTransport(FrameProcessor): await self._set_video_images(frame.images) elif isinstance(frame, TransportMessageFrame): await self._transport.send_message(frame) + elif isinstance(frame, OutputDTMFFrame): + await self._transport.write_dtmf(frame) def _next_frame(self) -> AsyncGenerator[Frame, None]: async def without_mixer(vad_stop_secs: float) -> AsyncGenerator[Frame, None]: diff --git a/src/pipecat/transports/services/daily.py b/src/pipecat/transports/services/daily.py index 0011dd2fd..075d57dfd 100644 --- a/src/pipecat/transports/services/daily.py +++ b/src/pipecat/transports/services/daily.py @@ -22,6 +22,8 @@ from pipecat.frames.frames import ( Frame, InterimTranscriptionFrame, OutputAudioRawFrame, + OutputDTMFFrame, + OutputDTMFUrgentFrame, OutputImageRawFrame, SpriteFrame, StartFrame, @@ -1220,6 +1222,14 @@ class DailyOutputTransport(BaseOutputTransport): async def register_audio_destination(self, destination: str): await self._client.register_audio_destination(destination) + async def write_dtmf(self, frame: OutputDTMFFrame | OutputDTMFUrgentFrame): + await self._client.send_dtmf( + { + "sessionId": frame.transport_destination, + "tones": frame.button.value, + } + ) + async def write_raw_audio_frames(self, frames: bytes, destination: Optional[str] = None): await self._client.write_raw_audio_frames(frames, destination) @@ -1372,6 +1382,14 @@ class DailyTransport(BaseTransport): await self._client.stop_dialout(participant_id) async def send_dtmf(self, settings): + import warnings + + with warnings.catch_warnings(): + warnings.simplefilter("always") + warnings.warn( + "`DailyTransport.send_dtmf()` is deprecated, push an `OutputDTMFFrame` or an `OutputDTMFUrgentFrame` instead.", + DeprecationWarning, + ) await self._client.send_dtmf(settings) async def sip_call_transfer(self, settings):