rocket-welder-sdk 1.1.44__py3-none-any.whl → 1.1.45__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.
- rocket_welder_sdk/__init__.py +26 -0
- rocket_welder_sdk/graphics/__init__.py +42 -0
- rocket_welder_sdk/graphics/layer_canvas.py +157 -0
- rocket_welder_sdk/graphics/protocol.py +72 -0
- rocket_welder_sdk/graphics/rgb_color.py +109 -0
- rocket_welder_sdk/graphics/stage.py +494 -0
- rocket_welder_sdk/graphics/vector_graphics_encoder.py +575 -0
- rocket_welder_sdk/high_level/connection_strings.py +85 -0
- rocket_welder_sdk/rocket_welder_client.py +210 -12
- rocket_welder_sdk/session_id.py +1 -0
- {rocket_welder_sdk-1.1.44.dist-info → rocket_welder_sdk-1.1.45.dist-info}/METADATA +1 -1
- {rocket_welder_sdk-1.1.44.dist-info → rocket_welder_sdk-1.1.45.dist-info}/RECORD +14 -8
- {rocket_welder_sdk-1.1.44.dist-info → rocket_welder_sdk-1.1.45.dist-info}/WHEEL +1 -1
- {rocket_welder_sdk-1.1.44.dist-info → rocket_welder_sdk-1.1.45.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,494 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Stage sink and writer for VectorGraphics streaming.
|
|
3
|
+
|
|
4
|
+
Matches C# IStageSink, IStageWriter, StageSink, StageWriter
|
|
5
|
+
from RocketWelder.SDK.Graphics.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from typing import TYPE_CHECKING, Dict, List, Protocol, Sequence, Tuple, runtime_checkable
|
|
11
|
+
|
|
12
|
+
from rocket_welder_sdk.transport.frame_sink import IFrameSink # noqa: TC001 - used at runtime
|
|
13
|
+
|
|
14
|
+
from .protocol import FrameType
|
|
15
|
+
from .rgb_color import RgbColor # noqa: TC001 - used at runtime in method bodies
|
|
16
|
+
from .vector_graphics_encoder import VectorGraphicsEncoder
|
|
17
|
+
|
|
18
|
+
if TYPE_CHECKING:
|
|
19
|
+
from .layer_canvas import ILayerCanvas
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@runtime_checkable
|
|
23
|
+
class IStageWriter(Protocol):
|
|
24
|
+
"""
|
|
25
|
+
Stage writer for vector graphics overlays.
|
|
26
|
+
|
|
27
|
+
User-facing API only - auto-flushes on close like other writers.
|
|
28
|
+
Matches C# IStageWriter interface.
|
|
29
|
+
|
|
30
|
+
Example:
|
|
31
|
+
with stage_sink.create_writer(frame_id) as writer:
|
|
32
|
+
# Draw on layer 0 (background)
|
|
33
|
+
writer[0].set_stroke(RgbColor.Red)
|
|
34
|
+
writer[0].draw_polygon(contour_points)
|
|
35
|
+
|
|
36
|
+
# Draw on layer 1 (labels)
|
|
37
|
+
writer[1].draw_text(f"Frame: {writer.frame_id}", 10, 20)
|
|
38
|
+
# writer auto-flushes on context exit
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
@property
|
|
42
|
+
def frame_id(self) -> int:
|
|
43
|
+
"""Gets the current frame ID."""
|
|
44
|
+
...
|
|
45
|
+
|
|
46
|
+
def __getitem__(self, layer_id: int) -> ILayerCanvas:
|
|
47
|
+
"""
|
|
48
|
+
Gets the layer canvas for the specified layer ID.
|
|
49
|
+
|
|
50
|
+
Layers are composited with lower IDs at the back (0 = bottom).
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
layer_id: Layer ID (0-15)
|
|
54
|
+
|
|
55
|
+
Returns:
|
|
56
|
+
The layer canvas for drawing operations
|
|
57
|
+
"""
|
|
58
|
+
...
|
|
59
|
+
|
|
60
|
+
def layer(self, layer_id: int) -> ILayerCanvas:
|
|
61
|
+
"""
|
|
62
|
+
Gets the layer canvas for the specified layer ID.
|
|
63
|
+
|
|
64
|
+
Alternative method syntax for the indexer.
|
|
65
|
+
|
|
66
|
+
Args:
|
|
67
|
+
layer_id: Layer ID (0-15)
|
|
68
|
+
|
|
69
|
+
Returns:
|
|
70
|
+
The layer canvas for drawing operations
|
|
71
|
+
"""
|
|
72
|
+
...
|
|
73
|
+
|
|
74
|
+
def close(self) -> None:
|
|
75
|
+
"""Flushes and closes the writer."""
|
|
76
|
+
...
|
|
77
|
+
|
|
78
|
+
def __enter__(self) -> IStageWriter:
|
|
79
|
+
"""Context manager entry."""
|
|
80
|
+
...
|
|
81
|
+
|
|
82
|
+
def __exit__(self, exc_type: object, exc_val: object, exc_tb: object) -> None:
|
|
83
|
+
"""Context manager exit - auto-flushes."""
|
|
84
|
+
...
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
@runtime_checkable
|
|
88
|
+
class IStageSink(Protocol):
|
|
89
|
+
"""
|
|
90
|
+
Factory for creating per-frame stage writers (transport-agnostic).
|
|
91
|
+
|
|
92
|
+
Follows the same pattern as ISegmentationResultSink and IKeyPointsSink.
|
|
93
|
+
Matches C# IStageSink interface.
|
|
94
|
+
"""
|
|
95
|
+
|
|
96
|
+
def create_writer(self, frame_id: int) -> IStageWriter:
|
|
97
|
+
"""
|
|
98
|
+
Creates a writer for the specified frame.
|
|
99
|
+
|
|
100
|
+
The writer auto-flushes on close.
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
frame_id: Frame identifier
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
Stage writer that auto-flushes on close
|
|
107
|
+
"""
|
|
108
|
+
...
|
|
109
|
+
|
|
110
|
+
def close(self) -> None:
|
|
111
|
+
"""Closes the sink and releases resources."""
|
|
112
|
+
...
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
class LayerEncoder:
|
|
116
|
+
"""
|
|
117
|
+
Internal layer encoder that writes operations directly to buffer.
|
|
118
|
+
|
|
119
|
+
Implements ILayerCanvas protocol by encoding operations to binary.
|
|
120
|
+
"""
|
|
121
|
+
|
|
122
|
+
__slots__ = (
|
|
123
|
+
"_buffer",
|
|
124
|
+
"_frame_type",
|
|
125
|
+
"_header_reserve",
|
|
126
|
+
"_layer_id",
|
|
127
|
+
"_offset",
|
|
128
|
+
"_operation_count",
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
# Reserve space for layer header at start of buffer
|
|
132
|
+
HEADER_RESERVE = 16
|
|
133
|
+
LAYER_BUFFER_SIZE = 256 * 1024 # 256KB per layer to accommodate JPEG frames
|
|
134
|
+
|
|
135
|
+
def __init__(self, layer_id: int) -> None:
|
|
136
|
+
"""
|
|
137
|
+
Creates a new layer encoder.
|
|
138
|
+
|
|
139
|
+
Args:
|
|
140
|
+
layer_id: Layer ID (0-15)
|
|
141
|
+
"""
|
|
142
|
+
self._layer_id = layer_id
|
|
143
|
+
self._frame_type = FrameType.MASTER
|
|
144
|
+
self._buffer = bytearray(self.LAYER_BUFFER_SIZE)
|
|
145
|
+
self._offset = self.HEADER_RESERVE
|
|
146
|
+
self._operation_count = 0
|
|
147
|
+
self._header_reserve = self.HEADER_RESERVE
|
|
148
|
+
|
|
149
|
+
@property
|
|
150
|
+
def layer_id(self) -> int:
|
|
151
|
+
"""The layer ID."""
|
|
152
|
+
return self._layer_id
|
|
153
|
+
|
|
154
|
+
def copy_encoded_data(self, dest_buffer: bytearray, dest_offset: int) -> int:
|
|
155
|
+
"""
|
|
156
|
+
Copies the encoded layer data (with header) to the destination buffer.
|
|
157
|
+
|
|
158
|
+
Args:
|
|
159
|
+
dest_buffer: Destination buffer
|
|
160
|
+
dest_offset: Starting offset in destination
|
|
161
|
+
|
|
162
|
+
Returns:
|
|
163
|
+
Number of bytes written
|
|
164
|
+
"""
|
|
165
|
+
pos = dest_offset
|
|
166
|
+
|
|
167
|
+
if self._frame_type == FrameType.MASTER:
|
|
168
|
+
pos += VectorGraphicsEncoder.write_layer_master(
|
|
169
|
+
dest_buffer, pos, self._layer_id, self._operation_count
|
|
170
|
+
)
|
|
171
|
+
data_length = self._offset - self._header_reserve
|
|
172
|
+
dest_buffer[pos : pos + data_length] = self._buffer[self._header_reserve : self._offset]
|
|
173
|
+
pos += data_length
|
|
174
|
+
elif self._frame_type == FrameType.REMAIN:
|
|
175
|
+
pos += VectorGraphicsEncoder.write_layer_remain(dest_buffer, pos, self._layer_id)
|
|
176
|
+
elif self._frame_type == FrameType.CLEAR:
|
|
177
|
+
pos += VectorGraphicsEncoder.write_layer_clear(dest_buffer, pos, self._layer_id)
|
|
178
|
+
|
|
179
|
+
return pos - dest_offset
|
|
180
|
+
|
|
181
|
+
# ============== Frame Type ==============
|
|
182
|
+
|
|
183
|
+
def master(self) -> None:
|
|
184
|
+
"""Sets this layer to Master mode."""
|
|
185
|
+
self._frame_type = FrameType.MASTER
|
|
186
|
+
|
|
187
|
+
def remain(self) -> None:
|
|
188
|
+
"""Sets this layer to Remain mode."""
|
|
189
|
+
self._frame_type = FrameType.REMAIN
|
|
190
|
+
|
|
191
|
+
def clear(self) -> None:
|
|
192
|
+
"""Sets this layer to Clear mode."""
|
|
193
|
+
self._frame_type = FrameType.CLEAR
|
|
194
|
+
|
|
195
|
+
# ============== Context State - Styling ==============
|
|
196
|
+
|
|
197
|
+
def set_stroke(self, color: RgbColor) -> None:
|
|
198
|
+
"""Sets the stroke color."""
|
|
199
|
+
self._offset += VectorGraphicsEncoder.write_set_stroke(self._buffer, self._offset, color)
|
|
200
|
+
self._operation_count += 1
|
|
201
|
+
|
|
202
|
+
def set_fill(self, color: RgbColor) -> None:
|
|
203
|
+
"""Sets the fill color."""
|
|
204
|
+
self._offset += VectorGraphicsEncoder.write_set_fill(self._buffer, self._offset, color)
|
|
205
|
+
self._operation_count += 1
|
|
206
|
+
|
|
207
|
+
def set_thickness(self, width: int) -> None:
|
|
208
|
+
"""Sets the stroke thickness."""
|
|
209
|
+
self._offset += VectorGraphicsEncoder.write_set_thickness(self._buffer, self._offset, width)
|
|
210
|
+
self._operation_count += 1
|
|
211
|
+
|
|
212
|
+
def set_font_size(self, size: int) -> None:
|
|
213
|
+
"""Sets the font size."""
|
|
214
|
+
self._offset += VectorGraphicsEncoder.write_set_font_size(self._buffer, self._offset, size)
|
|
215
|
+
self._operation_count += 1
|
|
216
|
+
|
|
217
|
+
def set_font_color(self, color: RgbColor) -> None:
|
|
218
|
+
"""Sets the font color."""
|
|
219
|
+
self._offset += VectorGraphicsEncoder.write_set_font_color(
|
|
220
|
+
self._buffer, self._offset, color
|
|
221
|
+
)
|
|
222
|
+
self._operation_count += 1
|
|
223
|
+
|
|
224
|
+
# ============== Context State - Transforms ==============
|
|
225
|
+
|
|
226
|
+
def translate(self, dx: float, dy: float) -> None:
|
|
227
|
+
"""Sets the translation offset."""
|
|
228
|
+
self._offset += VectorGraphicsEncoder.write_set_offset(self._buffer, self._offset, dx, dy)
|
|
229
|
+
self._operation_count += 1
|
|
230
|
+
|
|
231
|
+
def rotate(self, degrees: float) -> None:
|
|
232
|
+
"""Sets the rotation."""
|
|
233
|
+
self._offset += VectorGraphicsEncoder.write_set_rotation(
|
|
234
|
+
self._buffer, self._offset, degrees
|
|
235
|
+
)
|
|
236
|
+
self._operation_count += 1
|
|
237
|
+
|
|
238
|
+
def scale(self, sx: float, sy: float) -> None:
|
|
239
|
+
"""Sets the scale."""
|
|
240
|
+
self._offset += VectorGraphicsEncoder.write_set_scale(self._buffer, self._offset, sx, sy)
|
|
241
|
+
self._operation_count += 1
|
|
242
|
+
|
|
243
|
+
def skew(self, kx: float, ky: float) -> None:
|
|
244
|
+
"""Sets the skew."""
|
|
245
|
+
self._offset += VectorGraphicsEncoder.write_set_skew(self._buffer, self._offset, kx, ky)
|
|
246
|
+
self._operation_count += 1
|
|
247
|
+
|
|
248
|
+
def set_matrix(
|
|
249
|
+
self,
|
|
250
|
+
scale_x: float,
|
|
251
|
+
skew_x: float,
|
|
252
|
+
trans_x: float,
|
|
253
|
+
skew_y: float,
|
|
254
|
+
scale_y: float,
|
|
255
|
+
trans_y: float,
|
|
256
|
+
) -> None:
|
|
257
|
+
"""Sets the transformation matrix."""
|
|
258
|
+
self._offset += VectorGraphicsEncoder.write_set_matrix(
|
|
259
|
+
self._buffer, self._offset, scale_x, skew_x, trans_x, skew_y, scale_y, trans_y
|
|
260
|
+
)
|
|
261
|
+
self._operation_count += 1
|
|
262
|
+
|
|
263
|
+
# ============== Context Stack ==============
|
|
264
|
+
|
|
265
|
+
def save(self) -> None:
|
|
266
|
+
"""Pushes the current context state."""
|
|
267
|
+
self._offset += VectorGraphicsEncoder.write_save_context(self._buffer, self._offset)
|
|
268
|
+
self._operation_count += 1
|
|
269
|
+
|
|
270
|
+
def restore(self) -> None:
|
|
271
|
+
"""Pops and restores the context state."""
|
|
272
|
+
self._offset += VectorGraphicsEncoder.write_restore_context(self._buffer, self._offset)
|
|
273
|
+
self._operation_count += 1
|
|
274
|
+
|
|
275
|
+
def reset_context(self) -> None:
|
|
276
|
+
"""Resets the context to defaults."""
|
|
277
|
+
self._offset += VectorGraphicsEncoder.write_reset_context(self._buffer, self._offset)
|
|
278
|
+
self._operation_count += 1
|
|
279
|
+
|
|
280
|
+
# ============== Draw Operations ==============
|
|
281
|
+
|
|
282
|
+
def draw_polygon(self, points: Sequence[Tuple[float, float]]) -> None:
|
|
283
|
+
"""Draws a polygon."""
|
|
284
|
+
self._offset += VectorGraphicsEncoder.write_draw_polygon(self._buffer, self._offset, points)
|
|
285
|
+
self._operation_count += 1
|
|
286
|
+
|
|
287
|
+
def draw_text(self, text: str, x: int, y: int) -> None:
|
|
288
|
+
"""Draws text."""
|
|
289
|
+
self._offset += VectorGraphicsEncoder.write_draw_text(
|
|
290
|
+
self._buffer, self._offset, text, x, y
|
|
291
|
+
)
|
|
292
|
+
self._operation_count += 1
|
|
293
|
+
|
|
294
|
+
def draw_circle(self, center_x: int, center_y: int, radius: int) -> None:
|
|
295
|
+
"""Draws a circle."""
|
|
296
|
+
self._offset += VectorGraphicsEncoder.write_draw_circle(
|
|
297
|
+
self._buffer, self._offset, center_x, center_y, radius
|
|
298
|
+
)
|
|
299
|
+
self._operation_count += 1
|
|
300
|
+
|
|
301
|
+
def draw_rectangle(self, x: int, y: int, width: int, height: int) -> None:
|
|
302
|
+
"""Draws a rectangle."""
|
|
303
|
+
self._offset += VectorGraphicsEncoder.write_draw_rect(
|
|
304
|
+
self._buffer, self._offset, x, y, width, height
|
|
305
|
+
)
|
|
306
|
+
self._operation_count += 1
|
|
307
|
+
|
|
308
|
+
def draw_line(self, x1: int, y1: int, x2: int, y2: int) -> None:
|
|
309
|
+
"""Draws a line."""
|
|
310
|
+
self._offset += VectorGraphicsEncoder.write_draw_line(
|
|
311
|
+
self._buffer, self._offset, x1, y1, x2, y2
|
|
312
|
+
)
|
|
313
|
+
self._operation_count += 1
|
|
314
|
+
|
|
315
|
+
def draw_jpeg(self, jpeg_data: bytes, x: int, y: int, width: int, height: int) -> None:
|
|
316
|
+
"""Draws a JPEG image."""
|
|
317
|
+
self._offset += VectorGraphicsEncoder.write_draw_jpeg(
|
|
318
|
+
self._buffer, self._offset, jpeg_data, x, y, width, height
|
|
319
|
+
)
|
|
320
|
+
self._operation_count += 1
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
class StageWriter:
|
|
324
|
+
"""
|
|
325
|
+
Per-frame stage writer that auto-flushes on close.
|
|
326
|
+
|
|
327
|
+
Follows the same pattern as SegmentationResultWriter and KeyPointsWriter.
|
|
328
|
+
Implements IStageWriter protocol.
|
|
329
|
+
"""
|
|
330
|
+
|
|
331
|
+
__slots__ = ("_active_layer_ids", "_buffer", "_closed", "_frame_id", "_frame_sink", "_layers")
|
|
332
|
+
|
|
333
|
+
DEFAULT_BUFFER_SIZE = 1024 * 1024 # 1MB
|
|
334
|
+
|
|
335
|
+
def __init__(
|
|
336
|
+
self, frame_id: int, frame_sink: IFrameSink, buffer_size: int = DEFAULT_BUFFER_SIZE
|
|
337
|
+
) -> None:
|
|
338
|
+
"""
|
|
339
|
+
Creates a new stage writer.
|
|
340
|
+
|
|
341
|
+
Args:
|
|
342
|
+
frame_id: Frame identifier
|
|
343
|
+
frame_sink: Transport for sending encoded frames
|
|
344
|
+
buffer_size: Size of the encoding buffer (default 1MB)
|
|
345
|
+
"""
|
|
346
|
+
self._frame_id = frame_id
|
|
347
|
+
self._frame_sink = frame_sink
|
|
348
|
+
self._buffer = bytearray(buffer_size)
|
|
349
|
+
self._layers: Dict[int, LayerEncoder] = {}
|
|
350
|
+
self._active_layer_ids: List[int] = []
|
|
351
|
+
self._closed = False
|
|
352
|
+
|
|
353
|
+
@property
|
|
354
|
+
def frame_id(self) -> int:
|
|
355
|
+
"""Gets the frame ID for this writer."""
|
|
356
|
+
return self._frame_id
|
|
357
|
+
|
|
358
|
+
def __getitem__(self, layer_id: int) -> LayerEncoder:
|
|
359
|
+
"""Gets the layer canvas for the specified layer ID."""
|
|
360
|
+
return self.layer(layer_id)
|
|
361
|
+
|
|
362
|
+
def layer(self, layer_id: int) -> LayerEncoder:
|
|
363
|
+
"""
|
|
364
|
+
Gets the layer canvas for the specified layer ID.
|
|
365
|
+
|
|
366
|
+
Args:
|
|
367
|
+
layer_id: Layer ID (0-15)
|
|
368
|
+
|
|
369
|
+
Returns:
|
|
370
|
+
The layer encoder for drawing operations
|
|
371
|
+
"""
|
|
372
|
+
if self._closed:
|
|
373
|
+
raise RuntimeError("StageWriter is closed")
|
|
374
|
+
|
|
375
|
+
if layer_id not in self._layers:
|
|
376
|
+
self._layers[layer_id] = LayerEncoder(layer_id)
|
|
377
|
+
|
|
378
|
+
# Track that this layer was accessed
|
|
379
|
+
if layer_id not in self._active_layer_ids:
|
|
380
|
+
self._active_layer_ids.append(layer_id)
|
|
381
|
+
|
|
382
|
+
return self._layers[layer_id]
|
|
383
|
+
|
|
384
|
+
def _flush(self) -> None:
|
|
385
|
+
"""
|
|
386
|
+
Encodes and sends all layer operations via transport.
|
|
387
|
+
|
|
388
|
+
Called automatically on close.
|
|
389
|
+
"""
|
|
390
|
+
if not self._active_layer_ids:
|
|
391
|
+
return
|
|
392
|
+
|
|
393
|
+
offset = 0
|
|
394
|
+
|
|
395
|
+
# Write message header
|
|
396
|
+
offset += VectorGraphicsEncoder.write_message_header(
|
|
397
|
+
self._buffer, offset, self._frame_id, len(self._active_layer_ids)
|
|
398
|
+
)
|
|
399
|
+
|
|
400
|
+
# Encode each active layer
|
|
401
|
+
for layer_id in self._active_layer_ids:
|
|
402
|
+
layer = self._layers[layer_id]
|
|
403
|
+
offset += layer.copy_encoded_data(self._buffer, offset)
|
|
404
|
+
|
|
405
|
+
# Write end marker
|
|
406
|
+
offset += VectorGraphicsEncoder.write_end_marker(self._buffer, offset)
|
|
407
|
+
|
|
408
|
+
# Send via transport
|
|
409
|
+
self._frame_sink.write_frame(bytes(self._buffer[:offset]))
|
|
410
|
+
|
|
411
|
+
def close(self) -> None:
|
|
412
|
+
"""Flushes and closes the writer."""
|
|
413
|
+
if self._closed:
|
|
414
|
+
return
|
|
415
|
+
self._closed = True
|
|
416
|
+
|
|
417
|
+
# Auto-flush on close (same pattern as other writers)
|
|
418
|
+
self._flush()
|
|
419
|
+
|
|
420
|
+
# Clear state
|
|
421
|
+
self._layers.clear()
|
|
422
|
+
self._active_layer_ids.clear()
|
|
423
|
+
|
|
424
|
+
def __enter__(self) -> StageWriter:
|
|
425
|
+
"""Context manager entry."""
|
|
426
|
+
return self
|
|
427
|
+
|
|
428
|
+
def __exit__(self, exc_type: object, exc_val: object, exc_tb: object) -> None:
|
|
429
|
+
"""Context manager exit - auto-flushes."""
|
|
430
|
+
self.close()
|
|
431
|
+
|
|
432
|
+
|
|
433
|
+
class StageSink:
|
|
434
|
+
"""
|
|
435
|
+
Factory for creating per-frame stage writers.
|
|
436
|
+
|
|
437
|
+
Follows the same pattern as SegmentationResultSink and KeyPointsSink.
|
|
438
|
+
Implements IStageSink protocol.
|
|
439
|
+
"""
|
|
440
|
+
|
|
441
|
+
__slots__ = ("_buffer_size", "_closed", "_frame_sink", "_owns_sink")
|
|
442
|
+
|
|
443
|
+
def __init__(
|
|
444
|
+
self,
|
|
445
|
+
frame_sink: IFrameSink,
|
|
446
|
+
buffer_size: int = StageWriter.DEFAULT_BUFFER_SIZE,
|
|
447
|
+
owns_sink: bool = True,
|
|
448
|
+
) -> None:
|
|
449
|
+
"""
|
|
450
|
+
Creates a StageSink with the specified transport.
|
|
451
|
+
|
|
452
|
+
Args:
|
|
453
|
+
frame_sink: The transport for sending encoded frames
|
|
454
|
+
buffer_size: Size of the encoding buffer per writer (default 1MB)
|
|
455
|
+
owns_sink: If True, closes the sink when this factory is closed
|
|
456
|
+
"""
|
|
457
|
+
self._frame_sink = frame_sink
|
|
458
|
+
self._buffer_size = buffer_size
|
|
459
|
+
self._owns_sink = owns_sink
|
|
460
|
+
self._closed = False
|
|
461
|
+
|
|
462
|
+
def create_writer(self, frame_id: int) -> StageWriter:
|
|
463
|
+
"""
|
|
464
|
+
Creates a writer for the specified frame.
|
|
465
|
+
|
|
466
|
+
The writer auto-flushes on close.
|
|
467
|
+
|
|
468
|
+
Args:
|
|
469
|
+
frame_id: Frame identifier
|
|
470
|
+
|
|
471
|
+
Returns:
|
|
472
|
+
Stage writer that auto-flushes on close
|
|
473
|
+
"""
|
|
474
|
+
if self._closed:
|
|
475
|
+
raise RuntimeError("StageSink is closed")
|
|
476
|
+
|
|
477
|
+
return StageWriter(frame_id, self._frame_sink, self._buffer_size)
|
|
478
|
+
|
|
479
|
+
def close(self) -> None:
|
|
480
|
+
"""Closes the sink and releases resources."""
|
|
481
|
+
if self._closed:
|
|
482
|
+
return
|
|
483
|
+
self._closed = True
|
|
484
|
+
|
|
485
|
+
if self._owns_sink:
|
|
486
|
+
self._frame_sink.close()
|
|
487
|
+
|
|
488
|
+
def __enter__(self) -> StageSink:
|
|
489
|
+
"""Context manager entry."""
|
|
490
|
+
return self
|
|
491
|
+
|
|
492
|
+
def __exit__(self, exc_type: object, exc_val: object, exc_tb: object) -> None:
|
|
493
|
+
"""Context manager exit."""
|
|
494
|
+
self.close()
|