rocket-welder-sdk 1.1.42__tar.gz → 1.1.44__tar.gz

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.
Files changed (81) hide show
  1. {rocket_welder_sdk-1.1.42 → rocket_welder_sdk-1.1.44}/PKG-INFO +1 -4
  2. rocket_welder_sdk-1.1.44/VERSION +1 -0
  3. {rocket_welder_sdk-1.1.42 → rocket_welder_sdk-1.1.44}/pyproject.toml +3 -7
  4. {rocket_welder_sdk-1.1.42 → rocket_welder_sdk-1.1.44}/rocket_welder_sdk/__init__.py +18 -22
  5. rocket_welder_sdk-1.1.44/rocket_welder_sdk/binary_frame_reader.py +222 -0
  6. rocket_welder_sdk-1.1.44/rocket_welder_sdk/binary_frame_writer.py +213 -0
  7. rocket_welder_sdk-1.1.44/rocket_welder_sdk/confidence.py +206 -0
  8. rocket_welder_sdk-1.1.44/rocket_welder_sdk/delta_frame.py +150 -0
  9. {rocket_welder_sdk-1.1.42 → rocket_welder_sdk-1.1.44}/rocket_welder_sdk/high_level/__init__.py +8 -1
  10. {rocket_welder_sdk-1.1.42 → rocket_welder_sdk-1.1.44}/rocket_welder_sdk/high_level/client.py +114 -3
  11. {rocket_welder_sdk-1.1.42 → rocket_welder_sdk-1.1.44}/rocket_welder_sdk/high_level/connection_strings.py +3 -15
  12. {rocket_welder_sdk-1.1.42 → rocket_welder_sdk-1.1.44}/rocket_welder_sdk/high_level/frame_sink_factory.py +2 -15
  13. rocket_welder_sdk-1.1.44/rocket_welder_sdk/high_level/transport_protocol.py +112 -0
  14. {rocket_welder_sdk-1.1.42 → rocket_welder_sdk-1.1.44}/rocket_welder_sdk/keypoints_protocol.py +520 -55
  15. {rocket_welder_sdk-1.1.42 → rocket_welder_sdk-1.1.44}/rocket_welder_sdk/rocket_welder_client.py +0 -77
  16. {rocket_welder_sdk-1.1.42 → rocket_welder_sdk-1.1.44}/rocket_welder_sdk/segmentation_result.py +387 -2
  17. rocket_welder_sdk-1.1.44/rocket_welder_sdk/session_id.py +62 -0
  18. {rocket_welder_sdk-1.1.42 → rocket_welder_sdk-1.1.44}/rocket_welder_sdk/transport/__init__.py +10 -3
  19. {rocket_welder_sdk-1.1.42 → rocket_welder_sdk-1.1.44}/rocket_welder_sdk/transport/frame_sink.py +3 -3
  20. {rocket_welder_sdk-1.1.42 → rocket_welder_sdk-1.1.44}/rocket_welder_sdk/transport/frame_source.py +2 -2
  21. rocket_welder_sdk-1.1.44/rocket_welder_sdk/transport/websocket_transport.py +316 -0
  22. rocket_welder_sdk-1.1.44/rocket_welder_sdk/varint.py +213 -0
  23. {rocket_welder_sdk-1.1.42 → rocket_welder_sdk-1.1.44}/rocket_welder_sdk.egg-info/PKG-INFO +1 -4
  24. {rocket_welder_sdk-1.1.42 → rocket_welder_sdk-1.1.44}/rocket_welder_sdk.egg-info/SOURCES.txt +11 -2
  25. {rocket_welder_sdk-1.1.42 → rocket_welder_sdk-1.1.44}/rocket_welder_sdk.egg-info/requires.txt +0 -4
  26. rocket_welder_sdk-1.1.44/tests/test_binary_frame.py +431 -0
  27. rocket_welder_sdk-1.1.44/tests/test_confidence.py +257 -0
  28. rocket_welder_sdk-1.1.44/tests/test_delta_frame.py +215 -0
  29. {rocket_welder_sdk-1.1.42 → rocket_welder_sdk-1.1.44}/tests/test_high_level_api.py +140 -105
  30. {rocket_welder_sdk-1.1.42 → rocket_welder_sdk-1.1.44}/tests/test_keypoints_protocol.py +255 -18
  31. {rocket_welder_sdk-1.1.42 → rocket_welder_sdk-1.1.44}/tests/test_segmentation_result.py +245 -1
  32. {rocket_welder_sdk-1.1.42 → rocket_welder_sdk-1.1.44}/tests/test_session_id.py +0 -59
  33. rocket_welder_sdk-1.1.44/tests/test_transport_cross_platform.py +475 -0
  34. rocket_welder_sdk-1.1.44/tests/test_websocket_transport.py +376 -0
  35. rocket_welder_sdk-1.1.42/VERSION +0 -1
  36. rocket_welder_sdk-1.1.42/rocket_welder_sdk/high_level/transport_protocol.py +0 -238
  37. rocket_welder_sdk-1.1.42/rocket_welder_sdk/session_id.py +0 -238
  38. rocket_welder_sdk-1.1.42/rocket_welder_sdk/transport/nng_transport.py +0 -197
  39. rocket_welder_sdk-1.1.42/tests/test_transport_cross_platform.py +0 -1207
  40. {rocket_welder_sdk-1.1.42 → rocket_welder_sdk-1.1.44}/MANIFEST.in +0 -0
  41. {rocket_welder_sdk-1.1.42 → rocket_welder_sdk-1.1.44}/README.md +0 -0
  42. {rocket_welder_sdk-1.1.42 → rocket_welder_sdk-1.1.44}/logo.png +0 -0
  43. {rocket_welder_sdk-1.1.42 → rocket_welder_sdk-1.1.44}/rocket_welder_sdk/bytes_size.py +0 -0
  44. {rocket_welder_sdk-1.1.42 → rocket_welder_sdk-1.1.44}/rocket_welder_sdk/connection_string.py +0 -0
  45. {rocket_welder_sdk-1.1.42 → rocket_welder_sdk-1.1.44}/rocket_welder_sdk/controllers.py +0 -0
  46. {rocket_welder_sdk-1.1.42 → rocket_welder_sdk-1.1.44}/rocket_welder_sdk/external_controls/__init__.py +0 -0
  47. {rocket_welder_sdk-1.1.42 → rocket_welder_sdk-1.1.44}/rocket_welder_sdk/external_controls/contracts.py +0 -0
  48. {rocket_welder_sdk-1.1.42 → rocket_welder_sdk-1.1.44}/rocket_welder_sdk/external_controls/contracts_old.py +0 -0
  49. {rocket_welder_sdk-1.1.42 → rocket_welder_sdk-1.1.44}/rocket_welder_sdk/frame_metadata.py +0 -0
  50. {rocket_welder_sdk-1.1.42 → rocket_welder_sdk-1.1.44}/rocket_welder_sdk/gst_metadata.py +0 -0
  51. {rocket_welder_sdk-1.1.42 → rocket_welder_sdk-1.1.44}/rocket_welder_sdk/high_level/data_context.py +0 -0
  52. {rocket_welder_sdk-1.1.42 → rocket_welder_sdk-1.1.44}/rocket_welder_sdk/high_level/schema.py +0 -0
  53. {rocket_welder_sdk-1.1.42 → rocket_welder_sdk-1.1.44}/rocket_welder_sdk/opencv_controller.py +0 -0
  54. {rocket_welder_sdk-1.1.42 → rocket_welder_sdk-1.1.44}/rocket_welder_sdk/periodic_timer.py +0 -0
  55. {rocket_welder_sdk-1.1.42 → rocket_welder_sdk-1.1.44}/rocket_welder_sdk/py.typed +0 -0
  56. {rocket_welder_sdk-1.1.42 → rocket_welder_sdk-1.1.44}/rocket_welder_sdk/transport/stream_transport.py +0 -0
  57. {rocket_welder_sdk-1.1.42 → rocket_welder_sdk-1.1.44}/rocket_welder_sdk/transport/tcp_transport.py +0 -0
  58. {rocket_welder_sdk-1.1.42 → rocket_welder_sdk-1.1.44}/rocket_welder_sdk/transport/unix_socket_transport.py +0 -0
  59. {rocket_welder_sdk-1.1.42 → rocket_welder_sdk-1.1.44}/rocket_welder_sdk/ui/__init__.py +0 -0
  60. {rocket_welder_sdk-1.1.42 → rocket_welder_sdk-1.1.44}/rocket_welder_sdk/ui/controls.py +0 -0
  61. {rocket_welder_sdk-1.1.42 → rocket_welder_sdk-1.1.44}/rocket_welder_sdk/ui/icons.py +0 -0
  62. {rocket_welder_sdk-1.1.42 → rocket_welder_sdk-1.1.44}/rocket_welder_sdk/ui/ui_events_projection.py +0 -0
  63. {rocket_welder_sdk-1.1.42 → rocket_welder_sdk-1.1.44}/rocket_welder_sdk/ui/ui_service.py +0 -0
  64. {rocket_welder_sdk-1.1.42 → rocket_welder_sdk-1.1.44}/rocket_welder_sdk/ui/value_types.py +0 -0
  65. {rocket_welder_sdk-1.1.42 → rocket_welder_sdk-1.1.44}/rocket_welder_sdk.egg-info/dependency_links.txt +0 -0
  66. {rocket_welder_sdk-1.1.42 → rocket_welder_sdk-1.1.44}/rocket_welder_sdk.egg-info/top_level.txt +0 -0
  67. {rocket_welder_sdk-1.1.42 → rocket_welder_sdk-1.1.44}/setup.cfg +0 -0
  68. {rocket_welder_sdk-1.1.42 → rocket_welder_sdk-1.1.44}/setup.py +0 -0
  69. {rocket_welder_sdk-1.1.42 → rocket_welder_sdk-1.1.44}/tests/test_bytes_size.py +0 -0
  70. {rocket_welder_sdk-1.1.42 → rocket_welder_sdk-1.1.44}/tests/test_connection_string.py +0 -0
  71. {rocket_welder_sdk-1.1.42 → rocket_welder_sdk-1.1.44}/tests/test_controllers.py +0 -0
  72. {rocket_welder_sdk-1.1.42 → rocket_welder_sdk-1.1.44}/tests/test_external_controls_serialization.py +0 -0
  73. {rocket_welder_sdk-1.1.42 → rocket_welder_sdk-1.1.44}/tests/test_external_controls_serialization_v2.py +0 -0
  74. {rocket_welder_sdk-1.1.42 → rocket_welder_sdk-1.1.44}/tests/test_frame_metadata.py +0 -0
  75. {rocket_welder_sdk-1.1.42 → rocket_welder_sdk-1.1.44}/tests/test_gst_metadata.py +0 -0
  76. {rocket_welder_sdk-1.1.42 → rocket_welder_sdk-1.1.44}/tests/test_icons.py +0 -0
  77. {rocket_welder_sdk-1.1.42 → rocket_welder_sdk-1.1.44}/tests/test_keypoints_cross_platform.py +0 -0
  78. {rocket_welder_sdk-1.1.42 → rocket_welder_sdk-1.1.44}/tests/test_rocket_welder_client.py +0 -0
  79. {rocket_welder_sdk-1.1.42 → rocket_welder_sdk-1.1.44}/tests/test_segmentation_cross_platform.py +0 -0
  80. {rocket_welder_sdk-1.1.42 → rocket_welder_sdk-1.1.44}/tests/test_ui_controls.py +0 -0
  81. {rocket_welder_sdk-1.1.42 → rocket_welder_sdk-1.1.44}/tests/test_ui_service_happy_path.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rocket-welder-sdk
3
- Version: 1.1.42
3
+ Version: 1.1.44
4
4
  Summary: High-performance video streaming SDK for RocketWelder services using ZeroBuffer IPC
5
5
  Home-page: https://github.com/modelingevolution/rocket-welder-sdk
6
6
  Author: ModelingEvolution
@@ -32,8 +32,6 @@ Requires-Dist: zerobuffer-ipc>=1.1.17
32
32
  Requires-Dist: pydantic>=2.5.0
33
33
  Requires-Dist: py-micro-plumberd>=0.1.8
34
34
  Requires-Dist: typing-extensions>=4.0.0
35
- Provides-Extra: nng
36
- Requires-Dist: pynng>=0.7.2; extra == "nng"
37
35
  Provides-Extra: dev
38
36
  Requires-Dist: pytest>=7.0; extra == "dev"
39
37
  Requires-Dist: pytest-cov>=4.0; extra == "dev"
@@ -42,7 +40,6 @@ Requires-Dist: black>=22.0; extra == "dev"
42
40
  Requires-Dist: mypy>=1.0; extra == "dev"
43
41
  Requires-Dist: ruff>=0.1.0; extra == "dev"
44
42
  Requires-Dist: types-setuptools; extra == "dev"
45
- Requires-Dist: pynng>=0.7.2; extra == "dev"
46
43
  Dynamic: author
47
44
  Dynamic: home-page
48
45
  Dynamic: requires-python
@@ -0,0 +1 @@
1
+ 1.1.44
@@ -42,9 +42,6 @@ dependencies = [
42
42
  ]
43
43
 
44
44
  [project.optional-dependencies]
45
- nng = [
46
- "pynng>=0.7.2",
47
- ]
48
45
  dev = [
49
46
  "pytest>=7.0",
50
47
  "pytest-cov>=4.0",
@@ -53,7 +50,6 @@ dev = [
53
50
  "mypy>=1.0",
54
51
  "ruff>=0.1.0",
55
52
  "types-setuptools",
56
- "pynng>=0.7.2",
57
53
  ]
58
54
 
59
55
  [project.urls]
@@ -96,8 +92,8 @@ module = [
96
92
  "py_micro_plumberd.*",
97
93
  "esdbclient",
98
94
  "esdbclient.*",
99
- "pynng",
100
- "pynng.*",
95
+ "websockets",
96
+ "websockets.*",
101
97
  ]
102
98
  ignore_missing_imports = true
103
99
 
@@ -147,7 +143,7 @@ ignore = [
147
143
  [tool.ruff.lint.per-file-ignores]
148
144
  "__init__.py" = ["F401"] # imported but unused
149
145
  "tests/*" = ["S101"] # use of assert
150
- "examples/*" = ["N999", "SIM112"] # Module names and env var casing (matches C# SDK)
146
+ "examples/*" = ["N999", "SIM112", "F401", "SIM108"] # Module names, env var casing, unused imports, if/else style
151
147
 
152
148
  [tool.pytest.ini_options]
153
149
  minversion = "7.0"
@@ -7,9 +7,13 @@ High-performance video streaming using shared memory (ZeroBuffer) for zero-copy
7
7
  import logging
8
8
  import os
9
9
 
10
+ from .binary_frame_reader import BinaryFrameReader
11
+ from .binary_frame_writer import BinaryFrameWriter
10
12
  from .bytes_size import BytesSize
13
+ from .confidence import Confidence
11
14
  from .connection_string import ConnectionMode, ConnectionString, Protocol
12
15
  from .controllers import DuplexShmController, IController, OneWayShmController
16
+ from .delta_frame import DeltaFrame
13
17
  from .frame_metadata import FRAME_METADATA_SIZE, FrameMetadata, GstVideoFormat
14
18
  from .gst_metadata import GstCaps, GstMetadata
15
19
  from .keypoints_protocol import (
@@ -23,27 +27,21 @@ from .periodic_timer import PeriodicTimer, PeriodicTimerSync
23
27
  from .rocket_welder_client import RocketWelderClient
24
28
  from .segmentation_result import (
25
29
  ISegmentationResultSink,
30
+ ISegmentationResultSource,
26
31
  ISegmentationResultWriter,
32
+ SegmentationFrame,
33
+ SegmentationProtocol,
27
34
  SegmentationResultSink,
35
+ SegmentationResultSource,
28
36
  SegmentationResultWriter,
29
37
  )
30
38
  from .session_id import (
31
- # Explicit URL functions (PREFERRED - set by rocket-welder2)
39
+ # Explicit URL environment variable names (set by rocket-welder2)
32
40
  ACTIONS_SINK_URL_ENV,
33
41
  KEYPOINTS_SINK_URL_ENV,
34
42
  SEGMENTATION_SINK_URL_ENV,
35
- # SessionId-derived URL functions (fallback for backwards compatibility)
36
- get_actions_url,
37
- get_actions_url_from_env,
38
- get_configured_nng_urls,
39
- get_keypoints_url,
40
- get_keypoints_url_from_env,
41
- get_nng_urls,
42
- get_nng_urls_from_env,
43
- get_segmentation_url,
44
- get_segmentation_url_from_env,
43
+ # SessionId parsing
45
44
  get_session_id_from_env,
46
- has_explicit_nng_urls,
47
45
  parse_session_id,
48
46
  )
49
47
 
@@ -76,10 +74,14 @@ __all__ = [
76
74
  "FRAME_METADATA_SIZE",
77
75
  "KEYPOINTS_SINK_URL_ENV",
78
76
  "SEGMENTATION_SINK_URL_ENV",
77
+ "BinaryFrameReader",
78
+ "BinaryFrameWriter",
79
79
  "BytesSize",
80
80
  "Client",
81
+ "Confidence",
81
82
  "ConnectionMode",
82
83
  "ConnectionString",
84
+ "DeltaFrame",
83
85
  "DuplexShmController",
84
86
  "FrameMetadata",
85
87
  "GstCaps",
@@ -89,6 +91,7 @@ __all__ = [
89
91
  "IKeyPointsSink",
90
92
  "IKeyPointsWriter",
91
93
  "ISegmentationResultSink",
94
+ "ISegmentationResultSource",
92
95
  "ISegmentationResultWriter",
93
96
  "KeyPointsSink",
94
97
  "KeyPointsWriter",
@@ -98,18 +101,11 @@ __all__ = [
98
101
  "PeriodicTimerSync",
99
102
  "Protocol",
100
103
  "RocketWelderClient",
104
+ "SegmentationFrame",
105
+ "SegmentationProtocol",
101
106
  "SegmentationResultSink",
107
+ "SegmentationResultSource",
102
108
  "SegmentationResultWriter",
103
- "get_actions_url",
104
- "get_actions_url_from_env",
105
- "get_configured_nng_urls",
106
- "get_keypoints_url",
107
- "get_keypoints_url_from_env",
108
- "get_nng_urls",
109
- "get_nng_urls_from_env",
110
- "get_segmentation_url",
111
- "get_segmentation_url_from_env",
112
109
  "get_session_id_from_env",
113
- "has_explicit_nng_urls",
114
110
  "parse_session_id",
115
111
  ]
@@ -0,0 +1,222 @@
1
+ """
2
+ Zero-allocation binary reader for parsing streaming protocol data.
3
+ Matches C# BinaryFrameReader ref struct from RocketWelder.SDK.Protocols.
4
+
5
+ Designed for high-performance frame decoding in real-time video processing.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import struct
11
+ from typing import Union
12
+
13
+ from .varint import read_varint, read_zigzag
14
+
15
+
16
+ class BinaryFrameReader:
17
+ """
18
+ Binary reader for parsing streaming protocol data.
19
+
20
+ Reads data from a bytes-like object with position tracking.
21
+ All multi-byte integers are read as little-endian.
22
+
23
+ Attributes:
24
+ position: Current read position in the buffer.
25
+ """
26
+
27
+ __slots__ = ("_data", "_position")
28
+
29
+ def __init__(self, data: Union[bytes, bytearray, memoryview]) -> None:
30
+ """
31
+ Initialize reader with data buffer.
32
+
33
+ Args:
34
+ data: Source data buffer (bytes, bytearray, or memoryview)
35
+ """
36
+ if isinstance(data, memoryview):
37
+ self._data = bytes(data)
38
+ else:
39
+ self._data = bytes(data)
40
+ self._position = 0
41
+
42
+ @property
43
+ def has_more(self) -> bool:
44
+ """Returns True if there is more data to read."""
45
+ return self._position < len(self._data)
46
+
47
+ @property
48
+ def position(self) -> int:
49
+ """Current read position in the buffer."""
50
+ return self._position
51
+
52
+ @position.setter
53
+ def position(self, value: int) -> None:
54
+ """Set the current read position."""
55
+ self._position = value
56
+
57
+ @property
58
+ def remaining(self) -> int:
59
+ """Remaining bytes available to read."""
60
+ return len(self._data) - self._position
61
+
62
+ def read_byte(self) -> int:
63
+ """
64
+ Read a single byte.
65
+
66
+ Returns:
67
+ The byte value (0-255)
68
+
69
+ Raises:
70
+ EOFError: If no more data available
71
+ """
72
+ if self._position >= len(self._data):
73
+ raise EOFError("Unexpected end of data")
74
+ value = self._data[self._position]
75
+ self._position += 1
76
+ return value
77
+
78
+ def read_uint64_le(self) -> int:
79
+ """
80
+ Read an unsigned 64-bit integer (little-endian).
81
+
82
+ Returns:
83
+ The unsigned 64-bit value
84
+
85
+ Raises:
86
+ EOFError: If not enough data available
87
+ """
88
+ if self._position + 8 > len(self._data):
89
+ raise EOFError("Not enough data for UInt64")
90
+ (value,) = struct.unpack_from("<Q", self._data, self._position)
91
+ self._position += 8
92
+ return int(value)
93
+
94
+ def read_int32_le(self) -> int:
95
+ """
96
+ Read a signed 32-bit integer (little-endian).
97
+
98
+ Returns:
99
+ The signed 32-bit value
100
+
101
+ Raises:
102
+ EOFError: If not enough data available
103
+ """
104
+ if self._position + 4 > len(self._data):
105
+ raise EOFError("Not enough data for Int32")
106
+ (value,) = struct.unpack_from("<i", self._data, self._position)
107
+ self._position += 4
108
+ return int(value)
109
+
110
+ def read_uint16_le(self) -> int:
111
+ """
112
+ Read an unsigned 16-bit integer (little-endian).
113
+
114
+ Returns:
115
+ The unsigned 16-bit value
116
+
117
+ Raises:
118
+ EOFError: If not enough data available
119
+ """
120
+ if self._position + 2 > len(self._data):
121
+ raise EOFError("Not enough data for UInt16")
122
+ (value,) = struct.unpack_from("<H", self._data, self._position)
123
+ self._position += 2
124
+ return int(value)
125
+
126
+ def read_single_le(self) -> float:
127
+ """
128
+ Read a 32-bit floating point (little-endian).
129
+
130
+ Returns:
131
+ The 32-bit float value
132
+
133
+ Raises:
134
+ EOFError: If not enough data available
135
+ """
136
+ if self._position + 4 > len(self._data):
137
+ raise EOFError("Not enough data for Single")
138
+ (value,) = struct.unpack_from("<f", self._data, self._position)
139
+ self._position += 4
140
+ return float(value)
141
+
142
+ def read_varint(self) -> int:
143
+ """
144
+ Read a varint-encoded unsigned 32-bit integer.
145
+
146
+ Returns:
147
+ The decoded unsigned value
148
+
149
+ Raises:
150
+ EOFError: If buffer ends before varint completes
151
+ ValueError: If varint is malformed
152
+ """
153
+ value, bytes_read = read_varint(self._data, self._position)
154
+ self._position += bytes_read
155
+ return value
156
+
157
+ def read_zigzag_varint(self) -> int:
158
+ """
159
+ Read a ZigZag-encoded signed integer (varint format).
160
+
161
+ Returns:
162
+ The decoded signed value
163
+
164
+ Raises:
165
+ EOFError: If buffer ends before varint completes
166
+ ValueError: If varint is malformed
167
+ """
168
+ value, bytes_read = read_zigzag(self._data, self._position)
169
+ self._position += bytes_read
170
+ return value
171
+
172
+ def read_string(self, length: int) -> str:
173
+ """
174
+ Read a UTF-8 encoded string of specified length.
175
+
176
+ Args:
177
+ length: Number of bytes to read
178
+
179
+ Returns:
180
+ The decoded UTF-8 string
181
+
182
+ Raises:
183
+ EOFError: If not enough data available
184
+ """
185
+ if self._position + length > len(self._data):
186
+ raise EOFError(f"Not enough data for string of length {length}")
187
+ value = self._data[self._position : self._position + length].decode("utf-8")
188
+ self._position += length
189
+ return value
190
+
191
+ def skip(self, count: int) -> None:
192
+ """
193
+ Skip a specified number of bytes.
194
+
195
+ Args:
196
+ count: Number of bytes to skip
197
+
198
+ Raises:
199
+ EOFError: If not enough data available
200
+ """
201
+ if self._position + count > len(self._data):
202
+ raise EOFError(f"Cannot skip {count} bytes, only {self.remaining} remaining")
203
+ self._position += count
204
+
205
+ def read_bytes(self, length: int) -> bytes:
206
+ """
207
+ Read raw bytes.
208
+
209
+ Args:
210
+ length: Number of bytes to read
211
+
212
+ Returns:
213
+ The raw bytes
214
+
215
+ Raises:
216
+ EOFError: If not enough data available
217
+ """
218
+ if self._position + length > len(self._data):
219
+ raise EOFError(f"Not enough data for {length} bytes")
220
+ value = self._data[self._position : self._position + length]
221
+ self._position += length
222
+ return value
@@ -0,0 +1,213 @@
1
+ """
2
+ Zero-allocation binary writer for encoding streaming protocol data.
3
+ Matches C# BinaryFrameWriter ref struct from RocketWelder.SDK.Protocols.
4
+
5
+ Symmetric counterpart to BinaryFrameReader for round-trip testing.
6
+ Designed for high-performance frame encoding in real-time video processing.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import struct
12
+ from typing import Union
13
+
14
+ from .varint import MAX_BYTES_UINT32, get_byte_count, write_varint, write_zigzag
15
+
16
+
17
+ class BinaryFrameWriter:
18
+ """
19
+ Binary writer for encoding streaming protocol data.
20
+
21
+ Writes data to a pre-allocated bytearray buffer with position tracking.
22
+ All multi-byte integers are written as little-endian.
23
+
24
+ Attributes:
25
+ position: Current write position in the buffer.
26
+ """
27
+
28
+ __slots__ = ("_buffer", "_position")
29
+
30
+ def __init__(self, buffer: bytearray) -> None:
31
+ """
32
+ Initialize writer with a buffer.
33
+
34
+ Args:
35
+ buffer: Destination buffer (bytearray)
36
+ """
37
+ self._buffer = buffer
38
+ self._position = 0
39
+
40
+ @classmethod
41
+ def with_capacity(cls, capacity: int) -> BinaryFrameWriter:
42
+ """
43
+ Create a writer with a new buffer of specified capacity.
44
+
45
+ Args:
46
+ capacity: Size of buffer to create
47
+
48
+ Returns:
49
+ New BinaryFrameWriter instance
50
+ """
51
+ return cls(bytearray(capacity))
52
+
53
+ @property
54
+ def position(self) -> int:
55
+ """Current write position in the buffer."""
56
+ return self._position
57
+
58
+ @property
59
+ def remaining(self) -> int:
60
+ """Remaining bytes available to write."""
61
+ return len(self._buffer) - self._position
62
+
63
+ @property
64
+ def written_bytes(self) -> bytes:
65
+ """Returns the portion of the buffer that has been written to."""
66
+ return bytes(self._buffer[: self._position])
67
+
68
+ @property
69
+ def written_view(self) -> memoryview:
70
+ """Returns a memoryview of the written portion of the buffer."""
71
+ return memoryview(self._buffer)[: self._position]
72
+
73
+ def write_byte(self, value: int) -> None:
74
+ """
75
+ Write a single byte.
76
+
77
+ Args:
78
+ value: Byte value (0-255)
79
+
80
+ Raises:
81
+ OverflowError: If buffer is full
82
+ """
83
+ if self._position >= len(self._buffer):
84
+ raise OverflowError("Buffer overflow: not enough space for byte")
85
+ self._buffer[self._position] = value
86
+ self._position += 1
87
+
88
+ def write_uint64_le(self, value: int) -> None:
89
+ """
90
+ Write an unsigned 64-bit integer (little-endian).
91
+
92
+ Args:
93
+ value: Unsigned 64-bit value
94
+
95
+ Raises:
96
+ OverflowError: If not enough space in buffer
97
+ """
98
+ if self._position + 8 > len(self._buffer):
99
+ raise OverflowError("Buffer overflow: not enough space for UInt64")
100
+ struct.pack_into("<Q", self._buffer, self._position, value)
101
+ self._position += 8
102
+
103
+ def write_int32_le(self, value: int) -> None:
104
+ """
105
+ Write a signed 32-bit integer (little-endian).
106
+
107
+ Args:
108
+ value: Signed 32-bit value
109
+
110
+ Raises:
111
+ OverflowError: If not enough space in buffer
112
+ """
113
+ if self._position + 4 > len(self._buffer):
114
+ raise OverflowError("Buffer overflow: not enough space for Int32")
115
+ struct.pack_into("<i", self._buffer, self._position, value)
116
+ self._position += 4
117
+
118
+ def write_uint16_le(self, value: int) -> None:
119
+ """
120
+ Write an unsigned 16-bit integer (little-endian).
121
+
122
+ Args:
123
+ value: Unsigned 16-bit value
124
+
125
+ Raises:
126
+ OverflowError: If not enough space in buffer
127
+ """
128
+ if self._position + 2 > len(self._buffer):
129
+ raise OverflowError("Buffer overflow: not enough space for UInt16")
130
+ struct.pack_into("<H", self._buffer, self._position, value)
131
+ self._position += 2
132
+
133
+ def write_single_le(self, value: float) -> None:
134
+ """
135
+ Write a 32-bit floating point (little-endian).
136
+
137
+ Args:
138
+ value: 32-bit float value
139
+
140
+ Raises:
141
+ OverflowError: If not enough space in buffer
142
+ """
143
+ if self._position + 4 > len(self._buffer):
144
+ raise OverflowError("Buffer overflow: not enough space for Single")
145
+ struct.pack_into("<f", self._buffer, self._position, value)
146
+ self._position += 4
147
+
148
+ def write_varint(self, value: int) -> None:
149
+ """
150
+ Write a varint-encoded unsigned 32-bit integer.
151
+
152
+ Args:
153
+ value: Unsigned value to encode
154
+
155
+ Raises:
156
+ OverflowError: If not enough space in buffer
157
+ """
158
+ # Check if we have worst-case space or exact space needed
159
+ # (intentionally nested for optimization - fast check first)
160
+ if self._position + MAX_BYTES_UINT32 > len(self._buffer): # noqa: SIM102
161
+ if self._position + get_byte_count(value) > len(self._buffer):
162
+ raise OverflowError("Buffer overflow: not enough space for varint")
163
+
164
+ bytes_written = write_varint(self._buffer, self._position, value)
165
+ self._position += bytes_written
166
+
167
+ def write_zigzag_varint(self, value: int) -> None:
168
+ """
169
+ Write a ZigZag-encoded signed integer (varint format).
170
+
171
+ Args:
172
+ value: Signed value to encode
173
+
174
+ Raises:
175
+ OverflowError: If not enough space in buffer
176
+ """
177
+ if self._position + MAX_BYTES_UINT32 > len(self._buffer):
178
+ raise OverflowError("Buffer overflow: not enough space for varint")
179
+
180
+ bytes_written = write_zigzag(self._buffer, self._position, value)
181
+ self._position += bytes_written
182
+
183
+ def write_bytes(self, source: Union[bytes, bytearray, memoryview]) -> None:
184
+ """
185
+ Write raw bytes from a bytes-like object.
186
+
187
+ Args:
188
+ source: Source bytes to write
189
+
190
+ Raises:
191
+ OverflowError: If not enough space in buffer
192
+ """
193
+ if self._position + len(source) > len(self._buffer):
194
+ raise OverflowError(f"Buffer overflow: not enough space for {len(source)} bytes")
195
+ self._buffer[self._position : self._position + len(source)] = source
196
+ self._position += len(source)
197
+
198
+ def write_string(self, value: str) -> None:
199
+ """
200
+ Write a UTF-8 encoded string.
201
+
202
+ Args:
203
+ value: String to encode and write
204
+
205
+ Raises:
206
+ OverflowError: If not enough space in buffer
207
+ """
208
+ encoded = value.encode("utf-8")
209
+ self.write_bytes(encoded)
210
+
211
+ def reset(self) -> None:
212
+ """Reset position to beginning of buffer."""
213
+ self._position = 0