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.
- {rocket_welder_sdk-1.1.42 → rocket_welder_sdk-1.1.44}/PKG-INFO +1 -4
- rocket_welder_sdk-1.1.44/VERSION +1 -0
- {rocket_welder_sdk-1.1.42 → rocket_welder_sdk-1.1.44}/pyproject.toml +3 -7
- {rocket_welder_sdk-1.1.42 → rocket_welder_sdk-1.1.44}/rocket_welder_sdk/__init__.py +18 -22
- rocket_welder_sdk-1.1.44/rocket_welder_sdk/binary_frame_reader.py +222 -0
- rocket_welder_sdk-1.1.44/rocket_welder_sdk/binary_frame_writer.py +213 -0
- rocket_welder_sdk-1.1.44/rocket_welder_sdk/confidence.py +206 -0
- rocket_welder_sdk-1.1.44/rocket_welder_sdk/delta_frame.py +150 -0
- {rocket_welder_sdk-1.1.42 → rocket_welder_sdk-1.1.44}/rocket_welder_sdk/high_level/__init__.py +8 -1
- {rocket_welder_sdk-1.1.42 → rocket_welder_sdk-1.1.44}/rocket_welder_sdk/high_level/client.py +114 -3
- {rocket_welder_sdk-1.1.42 → rocket_welder_sdk-1.1.44}/rocket_welder_sdk/high_level/connection_strings.py +3 -15
- {rocket_welder_sdk-1.1.42 → rocket_welder_sdk-1.1.44}/rocket_welder_sdk/high_level/frame_sink_factory.py +2 -15
- rocket_welder_sdk-1.1.44/rocket_welder_sdk/high_level/transport_protocol.py +112 -0
- {rocket_welder_sdk-1.1.42 → rocket_welder_sdk-1.1.44}/rocket_welder_sdk/keypoints_protocol.py +520 -55
- {rocket_welder_sdk-1.1.42 → rocket_welder_sdk-1.1.44}/rocket_welder_sdk/rocket_welder_client.py +0 -77
- {rocket_welder_sdk-1.1.42 → rocket_welder_sdk-1.1.44}/rocket_welder_sdk/segmentation_result.py +387 -2
- rocket_welder_sdk-1.1.44/rocket_welder_sdk/session_id.py +62 -0
- {rocket_welder_sdk-1.1.42 → rocket_welder_sdk-1.1.44}/rocket_welder_sdk/transport/__init__.py +10 -3
- {rocket_welder_sdk-1.1.42 → rocket_welder_sdk-1.1.44}/rocket_welder_sdk/transport/frame_sink.py +3 -3
- {rocket_welder_sdk-1.1.42 → rocket_welder_sdk-1.1.44}/rocket_welder_sdk/transport/frame_source.py +2 -2
- rocket_welder_sdk-1.1.44/rocket_welder_sdk/transport/websocket_transport.py +316 -0
- rocket_welder_sdk-1.1.44/rocket_welder_sdk/varint.py +213 -0
- {rocket_welder_sdk-1.1.42 → rocket_welder_sdk-1.1.44}/rocket_welder_sdk.egg-info/PKG-INFO +1 -4
- {rocket_welder_sdk-1.1.42 → rocket_welder_sdk-1.1.44}/rocket_welder_sdk.egg-info/SOURCES.txt +11 -2
- {rocket_welder_sdk-1.1.42 → rocket_welder_sdk-1.1.44}/rocket_welder_sdk.egg-info/requires.txt +0 -4
- rocket_welder_sdk-1.1.44/tests/test_binary_frame.py +431 -0
- rocket_welder_sdk-1.1.44/tests/test_confidence.py +257 -0
- rocket_welder_sdk-1.1.44/tests/test_delta_frame.py +215 -0
- {rocket_welder_sdk-1.1.42 → rocket_welder_sdk-1.1.44}/tests/test_high_level_api.py +140 -105
- {rocket_welder_sdk-1.1.42 → rocket_welder_sdk-1.1.44}/tests/test_keypoints_protocol.py +255 -18
- {rocket_welder_sdk-1.1.42 → rocket_welder_sdk-1.1.44}/tests/test_segmentation_result.py +245 -1
- {rocket_welder_sdk-1.1.42 → rocket_welder_sdk-1.1.44}/tests/test_session_id.py +0 -59
- rocket_welder_sdk-1.1.44/tests/test_transport_cross_platform.py +475 -0
- rocket_welder_sdk-1.1.44/tests/test_websocket_transport.py +376 -0
- rocket_welder_sdk-1.1.42/VERSION +0 -1
- rocket_welder_sdk-1.1.42/rocket_welder_sdk/high_level/transport_protocol.py +0 -238
- rocket_welder_sdk-1.1.42/rocket_welder_sdk/session_id.py +0 -238
- rocket_welder_sdk-1.1.42/rocket_welder_sdk/transport/nng_transport.py +0 -197
- rocket_welder_sdk-1.1.42/tests/test_transport_cross_platform.py +0 -1207
- {rocket_welder_sdk-1.1.42 → rocket_welder_sdk-1.1.44}/MANIFEST.in +0 -0
- {rocket_welder_sdk-1.1.42 → rocket_welder_sdk-1.1.44}/README.md +0 -0
- {rocket_welder_sdk-1.1.42 → rocket_welder_sdk-1.1.44}/logo.png +0 -0
- {rocket_welder_sdk-1.1.42 → rocket_welder_sdk-1.1.44}/rocket_welder_sdk/bytes_size.py +0 -0
- {rocket_welder_sdk-1.1.42 → rocket_welder_sdk-1.1.44}/rocket_welder_sdk/connection_string.py +0 -0
- {rocket_welder_sdk-1.1.42 → rocket_welder_sdk-1.1.44}/rocket_welder_sdk/controllers.py +0 -0
- {rocket_welder_sdk-1.1.42 → rocket_welder_sdk-1.1.44}/rocket_welder_sdk/external_controls/__init__.py +0 -0
- {rocket_welder_sdk-1.1.42 → rocket_welder_sdk-1.1.44}/rocket_welder_sdk/external_controls/contracts.py +0 -0
- {rocket_welder_sdk-1.1.42 → rocket_welder_sdk-1.1.44}/rocket_welder_sdk/external_controls/contracts_old.py +0 -0
- {rocket_welder_sdk-1.1.42 → rocket_welder_sdk-1.1.44}/rocket_welder_sdk/frame_metadata.py +0 -0
- {rocket_welder_sdk-1.1.42 → rocket_welder_sdk-1.1.44}/rocket_welder_sdk/gst_metadata.py +0 -0
- {rocket_welder_sdk-1.1.42 → rocket_welder_sdk-1.1.44}/rocket_welder_sdk/high_level/data_context.py +0 -0
- {rocket_welder_sdk-1.1.42 → rocket_welder_sdk-1.1.44}/rocket_welder_sdk/high_level/schema.py +0 -0
- {rocket_welder_sdk-1.1.42 → rocket_welder_sdk-1.1.44}/rocket_welder_sdk/opencv_controller.py +0 -0
- {rocket_welder_sdk-1.1.42 → rocket_welder_sdk-1.1.44}/rocket_welder_sdk/periodic_timer.py +0 -0
- {rocket_welder_sdk-1.1.42 → rocket_welder_sdk-1.1.44}/rocket_welder_sdk/py.typed +0 -0
- {rocket_welder_sdk-1.1.42 → rocket_welder_sdk-1.1.44}/rocket_welder_sdk/transport/stream_transport.py +0 -0
- {rocket_welder_sdk-1.1.42 → rocket_welder_sdk-1.1.44}/rocket_welder_sdk/transport/tcp_transport.py +0 -0
- {rocket_welder_sdk-1.1.42 → rocket_welder_sdk-1.1.44}/rocket_welder_sdk/transport/unix_socket_transport.py +0 -0
- {rocket_welder_sdk-1.1.42 → rocket_welder_sdk-1.1.44}/rocket_welder_sdk/ui/__init__.py +0 -0
- {rocket_welder_sdk-1.1.42 → rocket_welder_sdk-1.1.44}/rocket_welder_sdk/ui/controls.py +0 -0
- {rocket_welder_sdk-1.1.42 → rocket_welder_sdk-1.1.44}/rocket_welder_sdk/ui/icons.py +0 -0
- {rocket_welder_sdk-1.1.42 → rocket_welder_sdk-1.1.44}/rocket_welder_sdk/ui/ui_events_projection.py +0 -0
- {rocket_welder_sdk-1.1.42 → rocket_welder_sdk-1.1.44}/rocket_welder_sdk/ui/ui_service.py +0 -0
- {rocket_welder_sdk-1.1.42 → rocket_welder_sdk-1.1.44}/rocket_welder_sdk/ui/value_types.py +0 -0
- {rocket_welder_sdk-1.1.42 → rocket_welder_sdk-1.1.44}/rocket_welder_sdk.egg-info/dependency_links.txt +0 -0
- {rocket_welder_sdk-1.1.42 → rocket_welder_sdk-1.1.44}/rocket_welder_sdk.egg-info/top_level.txt +0 -0
- {rocket_welder_sdk-1.1.42 → rocket_welder_sdk-1.1.44}/setup.cfg +0 -0
- {rocket_welder_sdk-1.1.42 → rocket_welder_sdk-1.1.44}/setup.py +0 -0
- {rocket_welder_sdk-1.1.42 → rocket_welder_sdk-1.1.44}/tests/test_bytes_size.py +0 -0
- {rocket_welder_sdk-1.1.42 → rocket_welder_sdk-1.1.44}/tests/test_connection_string.py +0 -0
- {rocket_welder_sdk-1.1.42 → rocket_welder_sdk-1.1.44}/tests/test_controllers.py +0 -0
- {rocket_welder_sdk-1.1.42 → rocket_welder_sdk-1.1.44}/tests/test_external_controls_serialization.py +0 -0
- {rocket_welder_sdk-1.1.42 → rocket_welder_sdk-1.1.44}/tests/test_external_controls_serialization_v2.py +0 -0
- {rocket_welder_sdk-1.1.42 → rocket_welder_sdk-1.1.44}/tests/test_frame_metadata.py +0 -0
- {rocket_welder_sdk-1.1.42 → rocket_welder_sdk-1.1.44}/tests/test_gst_metadata.py +0 -0
- {rocket_welder_sdk-1.1.42 → rocket_welder_sdk-1.1.44}/tests/test_icons.py +0 -0
- {rocket_welder_sdk-1.1.42 → rocket_welder_sdk-1.1.44}/tests/test_keypoints_cross_platform.py +0 -0
- {rocket_welder_sdk-1.1.42 → rocket_welder_sdk-1.1.44}/tests/test_rocket_welder_client.py +0 -0
- {rocket_welder_sdk-1.1.42 → rocket_welder_sdk-1.1.44}/tests/test_segmentation_cross_platform.py +0 -0
- {rocket_welder_sdk-1.1.42 → rocket_welder_sdk-1.1.44}/tests/test_ui_controls.py +0 -0
- {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.
|
|
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
|
-
"
|
|
100
|
-
"
|
|
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
|
|
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
|
|
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
|
|
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
|