dv-pipecat-ai 0.0.82.dev815__py3-none-any.whl → 0.0.82.dev857__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of dv-pipecat-ai might be problematic. Click here for more details.
- {dv_pipecat_ai-0.0.82.dev815.dist-info → dv_pipecat_ai-0.0.82.dev857.dist-info}/METADATA +8 -3
- {dv_pipecat_ai-0.0.82.dev815.dist-info → dv_pipecat_ai-0.0.82.dev857.dist-info}/RECORD +106 -79
- pipecat/adapters/base_llm_adapter.py +44 -6
- pipecat/adapters/services/anthropic_adapter.py +302 -2
- pipecat/adapters/services/aws_nova_sonic_adapter.py +40 -2
- pipecat/adapters/services/bedrock_adapter.py +40 -2
- pipecat/adapters/services/gemini_adapter.py +276 -6
- pipecat/adapters/services/open_ai_adapter.py +88 -7
- pipecat/adapters/services/open_ai_realtime_adapter.py +39 -1
- pipecat/audio/dtmf/__init__.py +0 -0
- pipecat/audio/dtmf/types.py +47 -0
- pipecat/audio/dtmf/utils.py +70 -0
- pipecat/audio/filters/aic_filter.py +199 -0
- pipecat/audio/utils.py +9 -7
- pipecat/extensions/ivr/__init__.py +0 -0
- pipecat/extensions/ivr/ivr_navigator.py +452 -0
- pipecat/frames/frames.py +156 -43
- pipecat/pipeline/llm_switcher.py +76 -0
- pipecat/pipeline/parallel_pipeline.py +3 -3
- pipecat/pipeline/service_switcher.py +144 -0
- pipecat/pipeline/task.py +68 -28
- pipecat/pipeline/task_observer.py +10 -0
- pipecat/processors/aggregators/dtmf_aggregator.py +2 -2
- pipecat/processors/aggregators/llm_context.py +277 -0
- pipecat/processors/aggregators/llm_response.py +48 -15
- pipecat/processors/aggregators/llm_response_universal.py +840 -0
- pipecat/processors/aggregators/openai_llm_context.py +3 -3
- pipecat/processors/dtmf_aggregator.py +0 -2
- pipecat/processors/filters/stt_mute_filter.py +0 -2
- pipecat/processors/frame_processor.py +18 -11
- pipecat/processors/frameworks/rtvi.py +17 -10
- pipecat/processors/metrics/sentry.py +2 -0
- pipecat/runner/daily.py +137 -36
- pipecat/runner/run.py +1 -1
- pipecat/runner/utils.py +7 -7
- pipecat/serializers/asterisk.py +20 -4
- pipecat/serializers/exotel.py +1 -1
- pipecat/serializers/plivo.py +1 -1
- pipecat/serializers/telnyx.py +1 -1
- pipecat/serializers/twilio.py +1 -1
- pipecat/services/__init__.py +2 -2
- pipecat/services/anthropic/llm.py +113 -28
- pipecat/services/asyncai/tts.py +4 -0
- pipecat/services/aws/llm.py +82 -8
- pipecat/services/aws/tts.py +0 -10
- pipecat/services/aws_nova_sonic/aws.py +5 -0
- pipecat/services/cartesia/tts.py +28 -16
- pipecat/services/cerebras/llm.py +15 -10
- pipecat/services/deepgram/stt.py +8 -0
- pipecat/services/deepseek/llm.py +13 -8
- pipecat/services/fireworks/llm.py +13 -8
- pipecat/services/fish/tts.py +8 -6
- pipecat/services/gemini_multimodal_live/gemini.py +5 -0
- pipecat/services/gladia/config.py +7 -1
- pipecat/services/gladia/stt.py +23 -15
- pipecat/services/google/llm.py +159 -59
- pipecat/services/google/llm_openai.py +18 -3
- pipecat/services/grok/llm.py +2 -1
- pipecat/services/llm_service.py +38 -3
- pipecat/services/mem0/memory.py +2 -1
- pipecat/services/mistral/llm.py +5 -6
- pipecat/services/nim/llm.py +2 -1
- pipecat/services/openai/base_llm.py +88 -26
- pipecat/services/openai/image.py +6 -1
- pipecat/services/openai_realtime_beta/openai.py +5 -2
- pipecat/services/openpipe/llm.py +6 -8
- pipecat/services/perplexity/llm.py +13 -8
- pipecat/services/playht/tts.py +9 -6
- pipecat/services/rime/tts.py +1 -1
- pipecat/services/sambanova/llm.py +18 -13
- pipecat/services/sarvam/tts.py +415 -10
- pipecat/services/speechmatics/stt.py +2 -2
- pipecat/services/tavus/video.py +1 -1
- pipecat/services/tts_service.py +15 -5
- pipecat/services/vistaar/llm.py +2 -5
- pipecat/transports/base_input.py +32 -19
- pipecat/transports/base_output.py +39 -5
- pipecat/transports/daily/__init__.py +0 -0
- pipecat/transports/daily/transport.py +2371 -0
- pipecat/transports/daily/utils.py +410 -0
- pipecat/transports/livekit/__init__.py +0 -0
- pipecat/transports/livekit/transport.py +1042 -0
- pipecat/transports/network/fastapi_websocket.py +12 -546
- pipecat/transports/network/small_webrtc.py +12 -922
- pipecat/transports/network/webrtc_connection.py +9 -595
- pipecat/transports/network/websocket_client.py +12 -481
- pipecat/transports/network/websocket_server.py +12 -487
- pipecat/transports/services/daily.py +9 -2334
- pipecat/transports/services/helpers/daily_rest.py +12 -396
- pipecat/transports/services/livekit.py +12 -975
- pipecat/transports/services/tavus.py +12 -757
- pipecat/transports/smallwebrtc/__init__.py +0 -0
- pipecat/transports/smallwebrtc/connection.py +612 -0
- pipecat/transports/smallwebrtc/transport.py +936 -0
- pipecat/transports/tavus/__init__.py +0 -0
- pipecat/transports/tavus/transport.py +770 -0
- pipecat/transports/websocket/__init__.py +0 -0
- pipecat/transports/websocket/client.py +494 -0
- pipecat/transports/websocket/fastapi.py +559 -0
- pipecat/transports/websocket/server.py +500 -0
- pipecat/transports/whatsapp/__init__.py +0 -0
- pipecat/transports/whatsapp/api.py +345 -0
- pipecat/transports/whatsapp/client.py +364 -0
- {dv_pipecat_ai-0.0.82.dev815.dist-info → dv_pipecat_ai-0.0.82.dev857.dist-info}/WHEEL +0 -0
- {dv_pipecat_ai-0.0.82.dev815.dist-info → dv_pipecat_ai-0.0.82.dev857.dist-info}/licenses/LICENSE +0 -0
- {dv_pipecat_ai-0.0.82.dev815.dist-info → dv_pipecat_ai-0.0.82.dev857.dist-info}/top_level.txt +0 -0
|
@@ -11,490 +11,15 @@ audio and data streaming, including client connection management, session
|
|
|
11
11
|
handling, and frame serialization.
|
|
12
12
|
"""
|
|
13
13
|
|
|
14
|
-
import
|
|
15
|
-
|
|
16
|
-
import
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
Frame,
|
|
27
|
-
InputAudioRawFrame,
|
|
28
|
-
OutputAudioRawFrame,
|
|
29
|
-
StartFrame,
|
|
30
|
-
StartInterruptionFrame,
|
|
31
|
-
TransportMessageFrame,
|
|
32
|
-
TransportMessageUrgentFrame,
|
|
33
|
-
)
|
|
34
|
-
from pipecat.processors.frame_processor import FrameDirection
|
|
35
|
-
from pipecat.serializers.base_serializer import FrameSerializer
|
|
36
|
-
from pipecat.transports.base_input import BaseInputTransport
|
|
37
|
-
from pipecat.transports.base_output import BaseOutputTransport
|
|
38
|
-
from pipecat.transports.base_transport import BaseTransport, TransportParams
|
|
39
|
-
|
|
40
|
-
try:
|
|
41
|
-
import websockets
|
|
42
|
-
from websockets.asyncio.server import serve as websocket_serve
|
|
43
|
-
from websockets.protocol import State
|
|
44
|
-
except ModuleNotFoundError as e:
|
|
45
|
-
logger.error(f"Exception: {e}")
|
|
46
|
-
logger.error("In order to use websockets, you need to `pip install pipecat-ai[websocket]`.")
|
|
47
|
-
raise Exception(f"Missing module: {e}")
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
class WebsocketServerParams(TransportParams):
|
|
51
|
-
"""Configuration parameters for WebSocket server transport.
|
|
52
|
-
|
|
53
|
-
Parameters:
|
|
54
|
-
add_wav_header: Whether to add WAV headers to audio frames.
|
|
55
|
-
serializer: Frame serializer for message encoding/decoding.
|
|
56
|
-
session_timeout: Timeout in seconds for client sessions.
|
|
57
|
-
"""
|
|
58
|
-
|
|
59
|
-
add_wav_header: bool = False
|
|
60
|
-
serializer: Optional[FrameSerializer] = None
|
|
61
|
-
session_timeout: Optional[int] = None
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
class WebsocketServerCallbacks(BaseModel):
|
|
65
|
-
"""Callback functions for WebSocket server events.
|
|
66
|
-
|
|
67
|
-
Parameters:
|
|
68
|
-
on_client_connected: Called when a client connects to the server.
|
|
69
|
-
on_client_disconnected: Called when a client disconnects from the server.
|
|
70
|
-
on_session_timeout: Called when a client session times out.
|
|
71
|
-
on_websocket_ready: Called when the WebSocket server is ready to accept connections.
|
|
72
|
-
"""
|
|
73
|
-
|
|
74
|
-
on_client_connected: Callable[[websockets.WebSocketServerProtocol], Awaitable[None]]
|
|
75
|
-
on_client_disconnected: Callable[[websockets.WebSocketServerProtocol], Awaitable[None]]
|
|
76
|
-
on_session_timeout: Callable[[websockets.WebSocketServerProtocol], Awaitable[None]]
|
|
77
|
-
on_websocket_ready: Callable[[], Awaitable[None]]
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
class WebsocketServerInputTransport(BaseInputTransport):
|
|
81
|
-
"""WebSocket server input transport for receiving client data.
|
|
82
|
-
|
|
83
|
-
Handles incoming WebSocket connections, message processing, and client
|
|
84
|
-
session management including timeout monitoring and connection lifecycle.
|
|
85
|
-
"""
|
|
86
|
-
|
|
87
|
-
def __init__(
|
|
88
|
-
self,
|
|
89
|
-
transport: BaseTransport,
|
|
90
|
-
host: str,
|
|
91
|
-
port: int,
|
|
92
|
-
params: WebsocketServerParams,
|
|
93
|
-
callbacks: WebsocketServerCallbacks,
|
|
94
|
-
**kwargs,
|
|
95
|
-
):
|
|
96
|
-
"""Initialize the WebSocket server input transport.
|
|
97
|
-
|
|
98
|
-
Args:
|
|
99
|
-
transport: The parent transport instance.
|
|
100
|
-
host: Host address to bind the WebSocket server to.
|
|
101
|
-
port: Port number to bind the WebSocket server to.
|
|
102
|
-
params: WebSocket server configuration parameters.
|
|
103
|
-
callbacks: Callback functions for WebSocket events.
|
|
104
|
-
**kwargs: Additional arguments passed to parent class.
|
|
105
|
-
"""
|
|
106
|
-
super().__init__(params, **kwargs)
|
|
107
|
-
|
|
108
|
-
self._transport = transport
|
|
109
|
-
self._host = host
|
|
110
|
-
self._port = port
|
|
111
|
-
self._params = params
|
|
112
|
-
self._callbacks = callbacks
|
|
113
|
-
|
|
114
|
-
self._websocket: Optional[websockets.WebSocketServerProtocol] = None
|
|
115
|
-
|
|
116
|
-
self._server_task = None
|
|
117
|
-
|
|
118
|
-
# This task will monitor the websocket connection periodically.
|
|
119
|
-
self._monitor_task = None
|
|
120
|
-
|
|
121
|
-
self._stop_server_event = asyncio.Event()
|
|
122
|
-
|
|
123
|
-
# Whether we have seen a StartFrame already.
|
|
124
|
-
self._initialized = False
|
|
125
|
-
|
|
126
|
-
async def start(self, frame: StartFrame):
|
|
127
|
-
"""Start the WebSocket server and initialize components.
|
|
128
|
-
|
|
129
|
-
Args:
|
|
130
|
-
frame: The start frame containing initialization parameters.
|
|
131
|
-
"""
|
|
132
|
-
await super().start(frame)
|
|
133
|
-
|
|
134
|
-
if self._initialized:
|
|
135
|
-
return
|
|
136
|
-
|
|
137
|
-
self._initialized = True
|
|
138
|
-
|
|
139
|
-
if self._params.serializer:
|
|
140
|
-
await self._params.serializer.setup(frame)
|
|
141
|
-
if not self._server_task:
|
|
142
|
-
self._server_task = self.create_task(self._server_task_handler())
|
|
143
|
-
await self.set_transport_ready(frame)
|
|
144
|
-
|
|
145
|
-
async def stop(self, frame: EndFrame):
|
|
146
|
-
"""Stop the WebSocket server and cleanup resources.
|
|
147
|
-
|
|
148
|
-
Args:
|
|
149
|
-
frame: The end frame signaling transport shutdown.
|
|
150
|
-
"""
|
|
151
|
-
await super().stop(frame)
|
|
152
|
-
self._stop_server_event.set()
|
|
153
|
-
if self._monitor_task:
|
|
154
|
-
await self.cancel_task(self._monitor_task)
|
|
155
|
-
self._monitor_task = None
|
|
156
|
-
if self._server_task:
|
|
157
|
-
await self._server_task
|
|
158
|
-
self._server_task = None
|
|
159
|
-
|
|
160
|
-
async def cancel(self, frame: CancelFrame):
|
|
161
|
-
"""Cancel the WebSocket server and stop all processing.
|
|
162
|
-
|
|
163
|
-
Args:
|
|
164
|
-
frame: The cancel frame signaling immediate cancellation.
|
|
165
|
-
"""
|
|
166
|
-
await super().cancel(frame)
|
|
167
|
-
if self._monitor_task:
|
|
168
|
-
await self.cancel_task(self._monitor_task)
|
|
169
|
-
self._monitor_task = None
|
|
170
|
-
if self._server_task:
|
|
171
|
-
await self.cancel_task(self._server_task)
|
|
172
|
-
self._server_task = None
|
|
173
|
-
|
|
174
|
-
async def cleanup(self):
|
|
175
|
-
"""Cleanup resources and parent transport."""
|
|
176
|
-
await super().cleanup()
|
|
177
|
-
await self._transport.cleanup()
|
|
178
|
-
|
|
179
|
-
async def _server_task_handler(self):
|
|
180
|
-
"""Handle WebSocket server startup and client connections."""
|
|
181
|
-
logger.info(f"Starting websocket server on {self._host}:{self._port}")
|
|
182
|
-
async with websocket_serve(self._client_handler, self._host, self._port) as server:
|
|
183
|
-
await self._callbacks.on_websocket_ready()
|
|
184
|
-
await self._stop_server_event.wait()
|
|
185
|
-
|
|
186
|
-
async def _client_handler(self, websocket: websockets.WebSocketServerProtocol):
|
|
187
|
-
"""Handle individual client connections and message processing."""
|
|
188
|
-
logger.info(f"New client connection from {websocket.remote_address}")
|
|
189
|
-
if self._websocket:
|
|
190
|
-
await self._websocket.close()
|
|
191
|
-
logger.warning("Only one client connected, using new connection")
|
|
192
|
-
|
|
193
|
-
self._websocket = websocket
|
|
194
|
-
|
|
195
|
-
# Notify
|
|
196
|
-
await self._callbacks.on_client_connected(websocket)
|
|
197
|
-
|
|
198
|
-
# Create a task to monitor the websocket connection
|
|
199
|
-
if not self._monitor_task and self._params.session_timeout:
|
|
200
|
-
self._monitor_task = self.create_task(
|
|
201
|
-
self._monitor_websocket(websocket, self._params.session_timeout)
|
|
202
|
-
)
|
|
203
|
-
|
|
204
|
-
# Handle incoming messages
|
|
205
|
-
try:
|
|
206
|
-
async for message in websocket:
|
|
207
|
-
if not self._params.serializer:
|
|
208
|
-
continue
|
|
209
|
-
|
|
210
|
-
frame = await self._params.serializer.deserialize(message)
|
|
211
|
-
|
|
212
|
-
if not frame:
|
|
213
|
-
continue
|
|
214
|
-
|
|
215
|
-
if isinstance(frame, InputAudioRawFrame):
|
|
216
|
-
await self.push_audio_frame(frame)
|
|
217
|
-
else:
|
|
218
|
-
await self.push_frame(frame)
|
|
219
|
-
except Exception as e:
|
|
220
|
-
logger.error(f"{self} exception receiving data: {e.__class__.__name__} ({e})")
|
|
221
|
-
|
|
222
|
-
# Notify disconnection
|
|
223
|
-
await self._callbacks.on_client_disconnected(websocket)
|
|
224
|
-
|
|
225
|
-
await self._websocket.close()
|
|
226
|
-
self._websocket = None
|
|
227
|
-
|
|
228
|
-
logger.info(f"Client {websocket.remote_address} disconnected")
|
|
229
|
-
|
|
230
|
-
async def _monitor_websocket(
|
|
231
|
-
self, websocket: websockets.WebSocketServerProtocol, session_timeout: int
|
|
232
|
-
):
|
|
233
|
-
"""Monitor WebSocket connection for session timeout."""
|
|
234
|
-
try:
|
|
235
|
-
await asyncio.sleep(session_timeout)
|
|
236
|
-
if websocket.state is not State.CLOSED:
|
|
237
|
-
await self._callbacks.on_session_timeout(websocket)
|
|
238
|
-
except asyncio.CancelledError:
|
|
239
|
-
logger.info(f"Monitoring task cancelled for: {websocket.remote_address}")
|
|
240
|
-
raise
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
class WebsocketServerOutputTransport(BaseOutputTransport):
|
|
244
|
-
"""WebSocket server output transport for sending data to clients.
|
|
245
|
-
|
|
246
|
-
Handles outgoing frame serialization, audio streaming with timing control,
|
|
247
|
-
and client connection management for WebSocket communication.
|
|
248
|
-
"""
|
|
249
|
-
|
|
250
|
-
def __init__(self, transport: BaseTransport, params: WebsocketServerParams, **kwargs):
|
|
251
|
-
"""Initialize the WebSocket server output transport.
|
|
252
|
-
|
|
253
|
-
Args:
|
|
254
|
-
transport: The parent transport instance.
|
|
255
|
-
params: WebSocket server configuration parameters.
|
|
256
|
-
**kwargs: Additional arguments passed to parent class.
|
|
257
|
-
"""
|
|
258
|
-
super().__init__(params, **kwargs)
|
|
259
|
-
|
|
260
|
-
self._transport = transport
|
|
261
|
-
self._params = params
|
|
262
|
-
|
|
263
|
-
self._websocket: Optional[websockets.WebSocketServerProtocol] = None
|
|
264
|
-
|
|
265
|
-
# write_audio_frame() is called quickly, as soon as we get audio
|
|
266
|
-
# (e.g. from the TTS), and since this is just a network connection we
|
|
267
|
-
# would be sending it to quickly. Instead, we want to block to emulate
|
|
268
|
-
# an audio device, this is what the send interval is. It will be
|
|
269
|
-
# computed on StartFrame.
|
|
270
|
-
self._send_interval = 0
|
|
271
|
-
self._next_send_time = 0
|
|
272
|
-
|
|
273
|
-
# Whether we have seen a StartFrame already.
|
|
274
|
-
self._initialized = False
|
|
275
|
-
|
|
276
|
-
async def set_client_connection(self, websocket: Optional[websockets.WebSocketServerProtocol]):
|
|
277
|
-
"""Set the active client WebSocket connection.
|
|
278
|
-
|
|
279
|
-
Args:
|
|
280
|
-
websocket: The WebSocket connection to set as active, or None to clear.
|
|
281
|
-
"""
|
|
282
|
-
if self._websocket:
|
|
283
|
-
await self._websocket.close()
|
|
284
|
-
logger.warning("Only one client allowed, using new connection")
|
|
285
|
-
self._websocket = websocket
|
|
286
|
-
|
|
287
|
-
async def start(self, frame: StartFrame):
|
|
288
|
-
"""Start the output transport and initialize components.
|
|
289
|
-
|
|
290
|
-
Args:
|
|
291
|
-
frame: The start frame containing initialization parameters.
|
|
292
|
-
"""
|
|
293
|
-
await super().start(frame)
|
|
294
|
-
|
|
295
|
-
if self._initialized:
|
|
296
|
-
return
|
|
297
|
-
|
|
298
|
-
self._initialized = True
|
|
299
|
-
|
|
300
|
-
if self._params.serializer:
|
|
301
|
-
await self._params.serializer.setup(frame)
|
|
302
|
-
self._send_interval = (self.audio_chunk_size / self.sample_rate) / 2
|
|
303
|
-
await self.set_transport_ready(frame)
|
|
304
|
-
|
|
305
|
-
async def stop(self, frame: EndFrame):
|
|
306
|
-
"""Stop the output transport and send final frame.
|
|
307
|
-
|
|
308
|
-
Args:
|
|
309
|
-
frame: The end frame signaling transport shutdown.
|
|
310
|
-
"""
|
|
311
|
-
await super().stop(frame)
|
|
312
|
-
await self._write_frame(frame)
|
|
313
|
-
|
|
314
|
-
async def cancel(self, frame: CancelFrame):
|
|
315
|
-
"""Cancel the output transport and send cancellation frame.
|
|
316
|
-
|
|
317
|
-
Args:
|
|
318
|
-
frame: The cancel frame signaling immediate cancellation.
|
|
319
|
-
"""
|
|
320
|
-
await super().cancel(frame)
|
|
321
|
-
await self._write_frame(frame)
|
|
322
|
-
|
|
323
|
-
async def cleanup(self):
|
|
324
|
-
"""Cleanup resources and parent transport."""
|
|
325
|
-
await super().cleanup()
|
|
326
|
-
await self._transport.cleanup()
|
|
327
|
-
|
|
328
|
-
async def process_frame(self, frame: Frame, direction: FrameDirection):
|
|
329
|
-
"""Process frames and handle interruption timing.
|
|
330
|
-
|
|
331
|
-
Args:
|
|
332
|
-
frame: The frame to process.
|
|
333
|
-
direction: The direction of frame flow in the pipeline.
|
|
334
|
-
"""
|
|
335
|
-
await super().process_frame(frame, direction)
|
|
336
|
-
|
|
337
|
-
if isinstance(frame, StartInterruptionFrame):
|
|
338
|
-
await self._write_frame(frame)
|
|
339
|
-
self._next_send_time = 0
|
|
340
|
-
|
|
341
|
-
async def send_message(self, frame: TransportMessageFrame | TransportMessageUrgentFrame):
|
|
342
|
-
"""Send a transport message frame to the client.
|
|
343
|
-
|
|
344
|
-
Args:
|
|
345
|
-
frame: The transport message frame to send.
|
|
346
|
-
"""
|
|
347
|
-
await self._write_frame(frame)
|
|
348
|
-
|
|
349
|
-
async def write_audio_frame(self, frame: OutputAudioRawFrame):
|
|
350
|
-
"""Write an audio frame to the WebSocket client with timing control.
|
|
351
|
-
|
|
352
|
-
Args:
|
|
353
|
-
frame: The output audio frame to write.
|
|
354
|
-
"""
|
|
355
|
-
if not self._websocket:
|
|
356
|
-
return
|
|
357
|
-
|
|
358
|
-
frame = OutputAudioRawFrame(
|
|
359
|
-
audio=frame.audio,
|
|
360
|
-
sample_rate=self.sample_rate,
|
|
361
|
-
num_channels=self._params.audio_out_channels,
|
|
362
|
-
)
|
|
363
|
-
|
|
364
|
-
if self._params.add_wav_header:
|
|
365
|
-
with io.BytesIO() as buffer:
|
|
366
|
-
with wave.open(buffer, "wb") as wf:
|
|
367
|
-
wf.setsampwidth(2)
|
|
368
|
-
wf.setnchannels(frame.num_channels)
|
|
369
|
-
wf.setframerate(frame.sample_rate)
|
|
370
|
-
wf.writeframes(frame.audio)
|
|
371
|
-
wav_frame = OutputAudioRawFrame(
|
|
372
|
-
buffer.getvalue(),
|
|
373
|
-
sample_rate=frame.sample_rate,
|
|
374
|
-
num_channels=frame.num_channels,
|
|
375
|
-
)
|
|
376
|
-
frame = wav_frame
|
|
377
|
-
|
|
378
|
-
await self._write_frame(frame)
|
|
379
|
-
|
|
380
|
-
# Simulate audio playback with a sleep.
|
|
381
|
-
await self._write_audio_sleep()
|
|
382
|
-
|
|
383
|
-
async def _write_frame(self, frame: Frame):
|
|
384
|
-
"""Serialize and send a frame to the WebSocket client."""
|
|
385
|
-
if not self._params.serializer:
|
|
386
|
-
return
|
|
387
|
-
|
|
388
|
-
try:
|
|
389
|
-
payload = await self._params.serializer.serialize(frame)
|
|
390
|
-
if payload and self._websocket:
|
|
391
|
-
await self._websocket.send(payload)
|
|
392
|
-
except Exception as e:
|
|
393
|
-
logger.error(f"{self} exception sending data: {e.__class__.__name__} ({e})")
|
|
394
|
-
|
|
395
|
-
async def _write_audio_sleep(self):
|
|
396
|
-
"""Simulate audio device timing by sleeping between audio chunks."""
|
|
397
|
-
# Simulate a clock.
|
|
398
|
-
current_time = time.monotonic()
|
|
399
|
-
sleep_duration = max(0, self._next_send_time - current_time)
|
|
400
|
-
await asyncio.sleep(sleep_duration)
|
|
401
|
-
if sleep_duration == 0:
|
|
402
|
-
self._next_send_time = time.monotonic() + self._send_interval
|
|
403
|
-
else:
|
|
404
|
-
self._next_send_time += self._send_interval
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
class WebsocketServerTransport(BaseTransport):
|
|
408
|
-
"""WebSocket server transport for bidirectional real-time communication.
|
|
409
|
-
|
|
410
|
-
Provides a complete WebSocket server implementation with separate input and
|
|
411
|
-
output transports, client connection management, and event handling for
|
|
412
|
-
real-time audio and data streaming applications.
|
|
413
|
-
"""
|
|
414
|
-
|
|
415
|
-
def __init__(
|
|
416
|
-
self,
|
|
417
|
-
params: WebsocketServerParams,
|
|
418
|
-
host: str = "localhost",
|
|
419
|
-
port: int = 8765,
|
|
420
|
-
input_name: Optional[str] = None,
|
|
421
|
-
output_name: Optional[str] = None,
|
|
422
|
-
):
|
|
423
|
-
"""Initialize the WebSocket server transport.
|
|
424
|
-
|
|
425
|
-
Args:
|
|
426
|
-
params: WebSocket server configuration parameters.
|
|
427
|
-
host: Host address to bind the server to. Defaults to "localhost".
|
|
428
|
-
port: Port number to bind the server to. Defaults to 8765.
|
|
429
|
-
input_name: Optional name for the input processor.
|
|
430
|
-
output_name: Optional name for the output processor.
|
|
431
|
-
"""
|
|
432
|
-
super().__init__(input_name=input_name, output_name=output_name)
|
|
433
|
-
self._host = host
|
|
434
|
-
self._port = port
|
|
435
|
-
self._params = params
|
|
436
|
-
|
|
437
|
-
self._callbacks = WebsocketServerCallbacks(
|
|
438
|
-
on_client_connected=self._on_client_connected,
|
|
439
|
-
on_client_disconnected=self._on_client_disconnected,
|
|
440
|
-
on_session_timeout=self._on_session_timeout,
|
|
441
|
-
on_websocket_ready=self._on_websocket_ready,
|
|
442
|
-
)
|
|
443
|
-
self._input: Optional[WebsocketServerInputTransport] = None
|
|
444
|
-
self._output: Optional[WebsocketServerOutputTransport] = None
|
|
445
|
-
self._websocket: Optional[websockets.WebSocketServerProtocol] = None
|
|
446
|
-
|
|
447
|
-
# Register supported handlers. The user will only be able to register
|
|
448
|
-
# these handlers.
|
|
449
|
-
self._register_event_handler("on_client_connected")
|
|
450
|
-
self._register_event_handler("on_client_disconnected")
|
|
451
|
-
self._register_event_handler("on_session_timeout")
|
|
452
|
-
self._register_event_handler("on_websocket_ready")
|
|
453
|
-
|
|
454
|
-
def input(self) -> WebsocketServerInputTransport:
|
|
455
|
-
"""Get the input transport for receiving client data.
|
|
456
|
-
|
|
457
|
-
Returns:
|
|
458
|
-
The WebSocket server input transport instance.
|
|
459
|
-
"""
|
|
460
|
-
if not self._input:
|
|
461
|
-
self._input = WebsocketServerInputTransport(
|
|
462
|
-
self, self._host, self._port, self._params, self._callbacks, name=self._input_name
|
|
463
|
-
)
|
|
464
|
-
return self._input
|
|
465
|
-
|
|
466
|
-
def output(self) -> WebsocketServerOutputTransport:
|
|
467
|
-
"""Get the output transport for sending data to clients.
|
|
468
|
-
|
|
469
|
-
Returns:
|
|
470
|
-
The WebSocket server output transport instance.
|
|
471
|
-
"""
|
|
472
|
-
if not self._output:
|
|
473
|
-
self._output = WebsocketServerOutputTransport(
|
|
474
|
-
self, self._params, name=self._output_name
|
|
475
|
-
)
|
|
476
|
-
return self._output
|
|
477
|
-
|
|
478
|
-
async def _on_client_connected(self, websocket):
|
|
479
|
-
"""Handle client connection events."""
|
|
480
|
-
if self._output:
|
|
481
|
-
await self._output.set_client_connection(websocket)
|
|
482
|
-
await self._call_event_handler("on_client_connected", websocket)
|
|
483
|
-
else:
|
|
484
|
-
logger.error("A WebsocketServerTransport output is missing in the pipeline")
|
|
485
|
-
|
|
486
|
-
async def _on_client_disconnected(self, websocket):
|
|
487
|
-
"""Handle client disconnection events."""
|
|
488
|
-
if self._output:
|
|
489
|
-
await self._output.set_client_connection(None)
|
|
490
|
-
await self._call_event_handler("on_client_disconnected", websocket)
|
|
491
|
-
else:
|
|
492
|
-
logger.error("A WebsocketServerTransport output is missing in the pipeline")
|
|
493
|
-
|
|
494
|
-
async def _on_session_timeout(self, websocket):
|
|
495
|
-
"""Handle client session timeout events."""
|
|
496
|
-
await self._call_event_handler("on_session_timeout", websocket)
|
|
497
|
-
|
|
498
|
-
async def _on_websocket_ready(self):
|
|
499
|
-
"""Handle WebSocket server ready events."""
|
|
500
|
-
await self._call_event_handler("on_websocket_ready")
|
|
14
|
+
import warnings
|
|
15
|
+
|
|
16
|
+
from pipecat.transports.websocket.server import *
|
|
17
|
+
|
|
18
|
+
with warnings.catch_warnings():
|
|
19
|
+
warnings.simplefilter("always")
|
|
20
|
+
warnings.warn(
|
|
21
|
+
"Module `pipecat.transports.network.websocket_server` is deprecated, "
|
|
22
|
+
"use `pipecat.transports.websocket.server` instead.",
|
|
23
|
+
DeprecationWarning,
|
|
24
|
+
stacklevel=2,
|
|
25
|
+
)
|