1
1
import asyncio
2
+ import logging
2
3
from typing import AsyncGenerator , AsyncIterable , Iterable , List
3
4
from dailyai .pipeline .frame_processor import FrameProcessor
4
5
5
- from dailyai .pipeline .frames import EndPipeFrame , EndFrame , Frame
6
+ from dailyai .pipeline .frames import AudioFrame , EndPipeFrame , EndFrame , Frame
6
7
7
8
8
9
class Pipeline :
@@ -17,7 +18,8 @@ def __init__(
17
18
self ,
18
19
processors : List [FrameProcessor ],
19
20
source : asyncio .Queue | None = None ,
20
- sink : asyncio .Queue [Frame ] | None = None
21
+ sink : asyncio .Queue [Frame ] | None = None ,
22
+ name : str | None = None ,
21
23
):
22
24
"""Create a new pipeline. By default we create the sink and source queues
23
25
if they're not provided, but these can be overridden to point to other
@@ -29,6 +31,11 @@ def __init__(
29
31
self .source : asyncio .Queue [Frame ] = source or asyncio .Queue ()
30
32
self .sink : asyncio .Queue [Frame ] = sink or asyncio .Queue ()
31
33
34
+ self ._logger = logging .getLogger ("dailyai.pipeline" )
35
+ self ._last_log_line = ""
36
+ self ._shown_repeated_log = False
37
+ self ._name = name or str (id (self ))
38
+
32
39
def set_source (self , source : asyncio .Queue [Frame ]):
33
40
"""Set the source queue for this pipeline. Frames from this queue
34
41
will be processed by each frame_processor in the pipeline, or order
@@ -85,6 +92,7 @@ async def run_pipeline(self):
85
92
async for frame in self ._run_pipeline_recursively (
86
93
initial_frame , self ._processors
87
94
):
95
+ self ._log_frame (frame , len (self ._processors ) + 1 )
88
96
await self .sink .put (frame )
89
97
90
98
if isinstance (initial_frame , EndFrame ) or isinstance (
@@ -96,18 +104,46 @@ async def run_pipeline(self):
96
104
# here.
97
105
for processor in self ._processors :
98
106
await processor .interrupted ()
99
- pass
100
107
101
108
async def _run_pipeline_recursively (
102
- self , initial_frame : Frame , processors : List [FrameProcessor ]
109
+ self , initial_frame : Frame , processors : List [FrameProcessor ], depth = 1
103
110
) -> AsyncGenerator [Frame , None ]:
104
111
"""Internal function to add frames to the pipeline as they're yielded
105
112
by each processor."""
106
113
if processors :
114
+ self ._log_frame (initial_frame , depth )
107
115
async for frame in processors [0 ].process_frame (initial_frame ):
108
116
async for final_frame in self ._run_pipeline_recursively (
109
- frame , processors [1 :]
117
+ frame , processors [1 :], depth + 1
110
118
):
111
119
yield final_frame
112
120
else :
113
121
yield initial_frame
122
+
123
+ def _log_frame (self , frame : Frame , depth : int ):
124
+ """Log a frame as it moves through the pipeline. This is useful for debugging.
125
+ Note that this function inherits the logging level from the "dailyai" logger.
126
+ If you want debug output from dailyai in general but not this function (it is
127
+ noisy) you can silence this function by doing something like this:
128
+
129
+ # enable debug logging for the dailyai package.
130
+ logger = logging.getLogger("dailyai")
131
+ logger.setLevel(logging.DEBUG)
132
+
133
+ # silence the pipeline logging
134
+ logger = logging.getLogger("dailyai.pipeline")
135
+ logger.setLevel(logging.WARNING)
136
+ """
137
+ source = str (self ._processors [depth - 2 ]) if depth > 1 else "source"
138
+ dest = str (self ._processors [depth - 1 ]) if depth < (len (self ._processors ) + 1 ) else "sink"
139
+ prefix = self ._name + " " * depth
140
+ logline = prefix + " -> " .join ([source , frame .__class__ .__name__ , dest ])
141
+ if logline == self ._last_log_line :
142
+ if self ._shown_repeated_log :
143
+ return
144
+ self ._shown_repeated_log = True
145
+ self ._logger .debug (prefix + "... repeated" )
146
+ else :
147
+ self ._shown_repeated_log = False
148
+ self ._last_log_line = logline
149
+ self ._logger .debug (logline )
0 commit comments