mcap-codec-support 0.5.0__tar.gz → 0.6.0__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.
- {mcap_codec_support-0.5.0 → mcap_codec_support-0.6.0}/PKG-INFO +1 -1
- {mcap_codec_support-0.5.0 → mcap_codec_support-0.6.0}/pyproject.toml +1 -1
- {mcap_codec_support-0.5.0 → mcap_codec_support-0.6.0}/src/mcap_codec_support/_protocols.py +24 -11
- {mcap_codec_support-0.5.0 → mcap_codec_support-0.6.0}/src/mcap_codec_support/pointcloud/compression.py +5 -3
- {mcap_codec_support-0.5.0 → mcap_codec_support-0.6.0}/src/mcap_codec_support/pointcloud/factories.py +3 -1
- {mcap_codec_support-0.5.0 → mcap_codec_support-0.6.0}/src/mcap_codec_support/video/__init__.py +2 -1
- {mcap_codec_support-0.5.0 → mcap_codec_support-0.6.0}/src/mcap_codec_support/video/compression.py +19 -12
- {mcap_codec_support-0.5.0 → mcap_codec_support-0.6.0}/README.md +0 -0
- {mcap_codec_support-0.5.0 → mcap_codec_support-0.6.0}/src/mcap_codec_support/__init__.py +0 -0
- {mcap_codec_support-0.5.0 → mcap_codec_support-0.6.0}/src/mcap_codec_support/_messages.py +0 -0
- {mcap_codec_support-0.5.0 → mcap_codec_support-0.6.0}/src/mcap_codec_support/_schemas.py +0 -0
- {mcap_codec_support-0.5.0 → mcap_codec_support-0.6.0}/src/mcap_codec_support/pointcloud/__init__.py +0 -0
- {mcap_codec_support-0.5.0 → mcap_codec_support-0.6.0}/src/mcap_codec_support/pointcloud/_messages.py +0 -0
- {mcap_codec_support-0.5.0 → mcap_codec_support-0.6.0}/src/mcap_codec_support/pointcloud/schemas.py +0 -0
- {mcap_codec_support-0.5.0 → mcap_codec_support-0.6.0}/src/mcap_codec_support/py.typed +0 -0
- {mcap_codec_support-0.5.0 → mcap_codec_support-0.6.0}/src/mcap_codec_support/video/_messages.py +0 -0
- {mcap_codec_support-0.5.0 → mcap_codec_support-0.6.0}/src/mcap_codec_support/video/common.py +0 -0
- {mcap_codec_support-0.5.0 → mcap_codec_support-0.6.0}/src/mcap_codec_support/video/factories.py +0 -0
- {mcap_codec_support-0.5.0 → mcap_codec_support-0.6.0}/src/mcap_codec_support/video/ffmpeg.py +0 -0
- {mcap_codec_support-0.5.0 → mcap_codec_support-0.6.0}/src/mcap_codec_support/video/pyav.py +0 -0
- {mcap_codec_support-0.5.0 → mcap_codec_support-0.6.0}/src/mcap_codec_support/video/schemas.py +0 -0
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
from typing import TYPE_CHECKING, Protocol
|
|
5
|
+
from typing import TYPE_CHECKING, Protocol, TypeAlias, TypeVar
|
|
6
6
|
|
|
7
7
|
if TYPE_CHECKING:
|
|
8
8
|
from av import VideoFrame
|
|
@@ -10,6 +10,11 @@ if TYPE_CHECKING:
|
|
|
10
10
|
|
|
11
11
|
from mcap_codec_support.video.common import DecompressedFrame, EncoderConfig
|
|
12
12
|
|
|
13
|
+
# The frame representation differs by backend: PyAV works on ``av.VideoFrame``,
|
|
14
|
+
# the ffmpeg-CLI backend on raw ``bytes``. Parameterizing keeps decode→encode
|
|
15
|
+
# paired per backend instead of pretending they share one frame type.
|
|
16
|
+
FrameT = TypeVar("FrameT")
|
|
17
|
+
|
|
13
18
|
|
|
14
19
|
class RawImageMessage(Protocol):
|
|
15
20
|
"""Structural shape of a ROS ``sensor_msgs/Image`` message."""
|
|
@@ -28,12 +33,12 @@ class CompressedImageMsg(Protocol):
|
|
|
28
33
|
def data(self) -> bytes | bytearray | memoryview: ...
|
|
29
34
|
|
|
30
35
|
|
|
31
|
-
class VideoEncoderProtocol(Protocol):
|
|
32
|
-
"""
|
|
36
|
+
class VideoEncoderProtocol(Protocol[FrameT]):
|
|
37
|
+
"""Encoder interface; ``FrameT`` is the per-backend frame type."""
|
|
33
38
|
|
|
34
39
|
config: EncoderConfig
|
|
35
40
|
|
|
36
|
-
def encode(self, frame:
|
|
41
|
+
def encode(self, frame: FrameT) -> bytes | None: ...
|
|
37
42
|
|
|
38
43
|
def flush_packets(self) -> list[bytes]: ...
|
|
39
44
|
|
|
@@ -50,8 +55,13 @@ class VideoDecompressorProtocol(Protocol):
|
|
|
50
55
|
...
|
|
51
56
|
|
|
52
57
|
|
|
53
|
-
class VideoCompressionBackend(Protocol):
|
|
54
|
-
"""Backend used by roscompress for CompressedVideo output.
|
|
58
|
+
class VideoCompressionBackend(Protocol[FrameT]):
|
|
59
|
+
"""Backend used by roscompress for CompressedVideo output.
|
|
60
|
+
|
|
61
|
+
``FrameT`` ties ``decode_*`` output to the frame type ``create_encoder``'s
|
|
62
|
+
encoder consumes, so a backend can't decode to one frame type and encode
|
|
63
|
+
another.
|
|
64
|
+
"""
|
|
55
65
|
|
|
56
66
|
label: str
|
|
57
67
|
prefetch_supported: bool
|
|
@@ -60,11 +70,9 @@ class VideoCompressionBackend(Protocol):
|
|
|
60
70
|
|
|
61
71
|
def resolve_encoder(self, codec: str) -> str: ...
|
|
62
72
|
|
|
63
|
-
def decode_compressed(self, data: bytes) -> tuple[
|
|
73
|
+
def decode_compressed(self, data: bytes) -> tuple[FrameT, int, int]: ...
|
|
64
74
|
|
|
65
|
-
def decode_image(
|
|
66
|
-
self, msg: DecodedMessage, schema_name: str
|
|
67
|
-
) -> tuple[VideoFrame, int, int]: ...
|
|
75
|
+
def decode_image(self, msg: DecodedMessage, schema_name: str) -> tuple[FrameT, int, int]: ...
|
|
68
76
|
|
|
69
77
|
def create_encoder(
|
|
70
78
|
self,
|
|
@@ -75,6 +83,11 @@ class VideoCompressionBackend(Protocol):
|
|
|
75
83
|
*,
|
|
76
84
|
input_pix_fmt: str | None = None,
|
|
77
85
|
scale: tuple[int, int] | None = None,
|
|
78
|
-
) -> VideoEncoderProtocol: ...
|
|
86
|
+
) -> VideoEncoderProtocol[FrameT]: ...
|
|
79
87
|
|
|
80
88
|
def get_pix_fmt(self, topic: str) -> str | None: ...
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
# A backend chosen at runtime is either the PyAV (VideoFrame) or ffmpeg-CLI
|
|
92
|
+
# (bytes) flavor; this union is the honest type at that dynamic boundary.
|
|
93
|
+
AnyVideoBackend: TypeAlias = "VideoCompressionBackend[VideoFrame] | VideoCompressionBackend[bytes]"
|
|
@@ -97,10 +97,12 @@ class CloudiniPointCloudCompressor:
|
|
|
97
97
|
info = _build_encoding_info(
|
|
98
98
|
msg, self._encoding_opt, self._compression_opt, self._resolution
|
|
99
99
|
)
|
|
100
|
-
|
|
100
|
+
encoder = self._cached_encoder
|
|
101
|
+
if self._cached_info != info or encoder is None:
|
|
101
102
|
self._cached_info = info
|
|
102
|
-
|
|
103
|
-
|
|
103
|
+
encoder = self._PointcloudEncoder(info)
|
|
104
|
+
self._cached_encoder = encoder
|
|
105
|
+
return encoder.encode(bytes(msg.data))
|
|
104
106
|
|
|
105
107
|
|
|
106
108
|
def _compute_position_quantization(
|
{mcap_codec_support-0.5.0 → mcap_codec_support-0.6.0}/src/mcap_codec_support/pointcloud/factories.py
RENAMED
|
@@ -226,7 +226,9 @@ def _decode_draco_payload(payload: bytes, header: Header) -> Pointcloud2Dict:
|
|
|
226
226
|
dtype = np.dtype([(name, values.dtype) for name, values in columns])
|
|
227
227
|
point_data = np.empty(point_count, dtype=dtype)
|
|
228
228
|
for name, values in columns:
|
|
229
|
-
point_data
|
|
229
|
+
# Structured-array field assignment; numpy's stubs type ``point_data`` as a
|
|
230
|
+
# plain float64 array, so they don't model string-key (field) assignment.
|
|
231
|
+
point_data[name] = values # ty: ignore[invalid-assignment]
|
|
230
232
|
|
|
231
233
|
fields: list[PointFieldDict] = [
|
|
232
234
|
{"name": field.name, "offset": field.offset, "datatype": field.datatype, "count": 1}
|
{mcap_codec_support-0.5.0 → mcap_codec_support-0.6.0}/src/mcap_codec_support/video/__init__.py
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""Video MCAP factories, schema constants, and backend helpers."""
|
|
2
2
|
|
|
3
|
-
from mcap_codec_support._protocols import VideoCompressionBackend
|
|
3
|
+
from mcap_codec_support._protocols import AnyVideoBackend, VideoCompressionBackend
|
|
4
4
|
from mcap_codec_support.video.common import (
|
|
5
5
|
EncoderBackend,
|
|
6
6
|
EncoderConfig,
|
|
@@ -33,6 +33,7 @@ __all__ = [
|
|
|
33
33
|
"IMAGE",
|
|
34
34
|
"IMAGE_SCHEMAS",
|
|
35
35
|
"RAW_SCHEMAS",
|
|
36
|
+
"AnyVideoBackend",
|
|
36
37
|
"EncoderBackend",
|
|
37
38
|
"EncoderConfig",
|
|
38
39
|
"EncoderMode",
|
{mcap_codec_support-0.5.0 → mcap_codec_support-0.6.0}/src/mcap_codec_support/video/compression.py
RENAMED
|
@@ -6,6 +6,8 @@ import io
|
|
|
6
6
|
from collections import deque
|
|
7
7
|
from typing import TYPE_CHECKING, Any
|
|
8
8
|
|
|
9
|
+
import numpy as np
|
|
10
|
+
|
|
9
11
|
from mcap_codec_support._schemas import normalize_schema_name
|
|
10
12
|
from mcap_codec_support.video.common import (
|
|
11
13
|
DEFAULT_FPS,
|
|
@@ -29,13 +31,17 @@ if TYPE_CHECKING:
|
|
|
29
31
|
from collections.abc import Iterable, Iterator
|
|
30
32
|
from concurrent.futures import Future, ThreadPoolExecutor
|
|
31
33
|
|
|
34
|
+
import numpy.typing as npt
|
|
35
|
+
from av import VideoFrame
|
|
32
36
|
from small_mcap import DecodedMessage
|
|
33
37
|
|
|
34
38
|
from mcap_codec_support._protocols import (
|
|
39
|
+
AnyVideoBackend,
|
|
35
40
|
RawImageMessage,
|
|
36
|
-
VideoCompressionBackend,
|
|
37
41
|
VideoDecompressorProtocol,
|
|
38
42
|
)
|
|
43
|
+
from mcap_codec_support.video.ffmpeg import FFmpegVideoEncoder
|
|
44
|
+
from mcap_codec_support.video.pyav import VideoEncoder
|
|
39
45
|
|
|
40
46
|
|
|
41
47
|
class _PyAVCompressionBackend:
|
|
@@ -52,13 +58,13 @@ class _PyAVCompressionBackend:
|
|
|
52
58
|
|
|
53
59
|
return resolve_encoder(codec)
|
|
54
60
|
|
|
55
|
-
def decode_compressed(self, data: bytes) -> tuple[
|
|
61
|
+
def decode_compressed(self, data: bytes) -> tuple[VideoFrame, int, int]:
|
|
56
62
|
from mcap_codec_support.video.pyav import decode_compressed_frame # noqa: PLC0415
|
|
57
63
|
|
|
58
64
|
frame = decode_compressed_frame(data)
|
|
59
65
|
return frame, frame.width, frame.height
|
|
60
66
|
|
|
61
|
-
def decode_image(self, msg: DecodedMessage, schema_name: str) -> tuple[
|
|
67
|
+
def decode_image(self, msg: DecodedMessage, schema_name: str) -> tuple[VideoFrame, int, int]:
|
|
62
68
|
if schema_name in COMPRESSED_SCHEMAS:
|
|
63
69
|
return self.decode_compressed(bytes(msg.decoded_message.data))
|
|
64
70
|
|
|
@@ -77,7 +83,7 @@ class _PyAVCompressionBackend:
|
|
|
77
83
|
*,
|
|
78
84
|
input_pix_fmt: str | None = None,
|
|
79
85
|
scale: tuple[int, int] | None = None,
|
|
80
|
-
) ->
|
|
86
|
+
) -> VideoEncoder:
|
|
81
87
|
# PyAV reformats input frames per-frame inside VideoEncoder.encode, so
|
|
82
88
|
# the protocol's pix-fmt / scale knobs are FFmpeg-CLI-only.
|
|
83
89
|
del input_pix_fmt, scale
|
|
@@ -117,13 +123,13 @@ class _FfmpegCliCompressionBackend:
|
|
|
117
123
|
|
|
118
124
|
return resolve_encoder(codec)
|
|
119
125
|
|
|
120
|
-
def decode_compressed(self, data: bytes) -> tuple[
|
|
126
|
+
def decode_compressed(self, data: bytes) -> tuple[bytes, int, int]:
|
|
121
127
|
from mcap_codec_support.video.ffmpeg import probe_image_dimensions # noqa: PLC0415
|
|
122
128
|
|
|
123
129
|
width, height = probe_image_dimensions(data)
|
|
124
130
|
return data, width, height
|
|
125
131
|
|
|
126
|
-
def decode_image(self, msg: DecodedMessage, schema_name: str) -> tuple[
|
|
132
|
+
def decode_image(self, msg: DecodedMessage, schema_name: str) -> tuple[bytes, int, int]:
|
|
127
133
|
data = bytes(msg.decoded_message.data)
|
|
128
134
|
topic = msg.channel.topic
|
|
129
135
|
|
|
@@ -150,7 +156,7 @@ class _FfmpegCliCompressionBackend:
|
|
|
150
156
|
*,
|
|
151
157
|
input_pix_fmt: str | None = None,
|
|
152
158
|
scale: tuple[int, int] | None = None,
|
|
153
|
-
) ->
|
|
159
|
+
) -> FFmpegVideoEncoder:
|
|
154
160
|
from mcap_codec_support.video.ffmpeg import FFmpegVideoEncoder # noqa: PLC0415
|
|
155
161
|
|
|
156
162
|
return FFmpegVideoEncoder(
|
|
@@ -167,7 +173,7 @@ class _FfmpegCliCompressionBackend:
|
|
|
167
173
|
|
|
168
174
|
def create_video_compression_backend(
|
|
169
175
|
mode: EncoderMode, codec: str, *, do_video: bool
|
|
170
|
-
) ->
|
|
176
|
+
) -> AnyVideoBackend:
|
|
171
177
|
"""Select the roscompress video backend."""
|
|
172
178
|
if mode is EncoderMode.FFMPEG_CLI:
|
|
173
179
|
return _FfmpegCliCompressionBackend()
|
|
@@ -183,7 +189,7 @@ def create_video_compression_backend(
|
|
|
183
189
|
|
|
184
190
|
def prefetch_image_decodes(
|
|
185
191
|
messages: Iterable[DecodedMessage],
|
|
186
|
-
backend:
|
|
192
|
+
backend: AnyVideoBackend,
|
|
187
193
|
pool: ThreadPoolExecutor,
|
|
188
194
|
prefetch: int = 8,
|
|
189
195
|
) -> Iterator[tuple[DecodedMessage, Future[Any] | None]]:
|
|
@@ -230,11 +236,12 @@ def encode_raw_image_to_jpeg(
|
|
|
230
236
|
return buf.getvalue(), target_w, target_h
|
|
231
237
|
|
|
232
238
|
|
|
233
|
-
def decode_compressed_image_to_rgb_array(data: bytes) ->
|
|
234
|
-
"""Decode JPEG/PNG compressed image bytes to an RGB numpy array."""
|
|
239
|
+
def decode_compressed_image_to_rgb_array(data: bytes) -> npt.NDArray[np.uint8]:
|
|
240
|
+
"""Decode JPEG/PNG compressed image bytes to an RGB (uint8) numpy array."""
|
|
235
241
|
from mcap_codec_support.video.pyav import decode_compressed_frame # noqa: PLC0415
|
|
236
242
|
|
|
237
|
-
|
|
243
|
+
rgb = decode_compressed_frame(data).to_ndarray(format="rgb24")
|
|
244
|
+
return np.asarray(rgb, dtype=np.uint8)
|
|
238
245
|
|
|
239
246
|
|
|
240
247
|
def create_video_decompressor(
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mcap_codec_support-0.5.0 → mcap_codec_support-0.6.0}/src/mcap_codec_support/pointcloud/__init__.py
RENAMED
|
File without changes
|
{mcap_codec_support-0.5.0 → mcap_codec_support-0.6.0}/src/mcap_codec_support/pointcloud/_messages.py
RENAMED
|
File without changes
|
{mcap_codec_support-0.5.0 → mcap_codec_support-0.6.0}/src/mcap_codec_support/pointcloud/schemas.py
RENAMED
|
File without changes
|
|
File without changes
|
{mcap_codec_support-0.5.0 → mcap_codec_support-0.6.0}/src/mcap_codec_support/video/_messages.py
RENAMED
|
File without changes
|
{mcap_codec_support-0.5.0 → mcap_codec_support-0.6.0}/src/mcap_codec_support/video/common.py
RENAMED
|
File without changes
|
{mcap_codec_support-0.5.0 → mcap_codec_support-0.6.0}/src/mcap_codec_support/video/factories.py
RENAMED
|
File without changes
|
{mcap_codec_support-0.5.0 → mcap_codec_support-0.6.0}/src/mcap_codec_support/video/ffmpeg.py
RENAMED
|
File without changes
|
|
File without changes
|
{mcap_codec_support-0.5.0 → mcap_codec_support-0.6.0}/src/mcap_codec_support/video/schemas.py
RENAMED
|
File without changes
|