mcap-codec-support 0.3.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.3.0 → mcap_codec_support-0.6.0}/PKG-INFO +1 -1
- {mcap_codec_support-0.3.0 → mcap_codec_support-0.6.0}/pyproject.toml +1 -1
- {mcap_codec_support-0.3.0 → mcap_codec_support-0.6.0}/src/mcap_codec_support/_protocols.py +25 -11
- {mcap_codec_support-0.3.0 → mcap_codec_support-0.6.0}/src/mcap_codec_support/pointcloud/compression.py +5 -3
- {mcap_codec_support-0.3.0 → mcap_codec_support-0.6.0}/src/mcap_codec_support/pointcloud/factories.py +3 -1
- {mcap_codec_support-0.3.0 → mcap_codec_support-0.6.0}/src/mcap_codec_support/video/__init__.py +2 -1
- {mcap_codec_support-0.3.0 → mcap_codec_support-0.6.0}/src/mcap_codec_support/video/compression.py +24 -14
- {mcap_codec_support-0.3.0 → mcap_codec_support-0.6.0}/README.md +0 -0
- {mcap_codec_support-0.3.0 → mcap_codec_support-0.6.0}/src/mcap_codec_support/__init__.py +0 -0
- {mcap_codec_support-0.3.0 → mcap_codec_support-0.6.0}/src/mcap_codec_support/_messages.py +0 -0
- {mcap_codec_support-0.3.0 → mcap_codec_support-0.6.0}/src/mcap_codec_support/_schemas.py +0 -0
- {mcap_codec_support-0.3.0 → mcap_codec_support-0.6.0}/src/mcap_codec_support/pointcloud/__init__.py +0 -0
- {mcap_codec_support-0.3.0 → mcap_codec_support-0.6.0}/src/mcap_codec_support/pointcloud/_messages.py +0 -0
- {mcap_codec_support-0.3.0 → mcap_codec_support-0.6.0}/src/mcap_codec_support/pointcloud/schemas.py +0 -0
- {mcap_codec_support-0.3.0 → mcap_codec_support-0.6.0}/src/mcap_codec_support/py.typed +0 -0
- {mcap_codec_support-0.3.0 → mcap_codec_support-0.6.0}/src/mcap_codec_support/video/_messages.py +0 -0
- {mcap_codec_support-0.3.0 → mcap_codec_support-0.6.0}/src/mcap_codec_support/video/common.py +0 -0
- {mcap_codec_support-0.3.0 → mcap_codec_support-0.6.0}/src/mcap_codec_support/video/factories.py +0 -0
- {mcap_codec_support-0.3.0 → mcap_codec_support-0.6.0}/src/mcap_codec_support/video/ffmpeg.py +0 -0
- {mcap_codec_support-0.3.0 → mcap_codec_support-0.6.0}/src/mcap_codec_support/video/pyav.py +0 -0
- {mcap_codec_support-0.3.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."""
|
|
@@ -17,6 +22,7 @@ class RawImageMessage(Protocol):
|
|
|
17
22
|
width: int
|
|
18
23
|
height: int
|
|
19
24
|
encoding: str
|
|
25
|
+
step: int
|
|
20
26
|
data: bytes
|
|
21
27
|
|
|
22
28
|
|
|
@@ -27,12 +33,12 @@ class CompressedImageMsg(Protocol):
|
|
|
27
33
|
def data(self) -> bytes | bytearray | memoryview: ...
|
|
28
34
|
|
|
29
35
|
|
|
30
|
-
class VideoEncoderProtocol(Protocol):
|
|
31
|
-
"""
|
|
36
|
+
class VideoEncoderProtocol(Protocol[FrameT]):
|
|
37
|
+
"""Encoder interface; ``FrameT`` is the per-backend frame type."""
|
|
32
38
|
|
|
33
39
|
config: EncoderConfig
|
|
34
40
|
|
|
35
|
-
def encode(self, frame:
|
|
41
|
+
def encode(self, frame: FrameT) -> bytes | None: ...
|
|
36
42
|
|
|
37
43
|
def flush_packets(self) -> list[bytes]: ...
|
|
38
44
|
|
|
@@ -49,8 +55,13 @@ class VideoDecompressorProtocol(Protocol):
|
|
|
49
55
|
...
|
|
50
56
|
|
|
51
57
|
|
|
52
|
-
class VideoCompressionBackend(Protocol):
|
|
53
|
-
"""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
|
+
"""
|
|
54
65
|
|
|
55
66
|
label: str
|
|
56
67
|
prefetch_supported: bool
|
|
@@ -59,11 +70,9 @@ class VideoCompressionBackend(Protocol):
|
|
|
59
70
|
|
|
60
71
|
def resolve_encoder(self, codec: str) -> str: ...
|
|
61
72
|
|
|
62
|
-
def decode_compressed(self, data: bytes) -> tuple[
|
|
73
|
+
def decode_compressed(self, data: bytes) -> tuple[FrameT, int, int]: ...
|
|
63
74
|
|
|
64
|
-
def decode_image(
|
|
65
|
-
self, msg: DecodedMessage, schema_name: str
|
|
66
|
-
) -> tuple[VideoFrame, int, int]: ...
|
|
75
|
+
def decode_image(self, msg: DecodedMessage, schema_name: str) -> tuple[FrameT, int, int]: ...
|
|
67
76
|
|
|
68
77
|
def create_encoder(
|
|
69
78
|
self,
|
|
@@ -74,6 +83,11 @@ class VideoCompressionBackend(Protocol):
|
|
|
74
83
|
*,
|
|
75
84
|
input_pix_fmt: str | None = None,
|
|
76
85
|
scale: tuple[int, int] | None = None,
|
|
77
|
-
) -> VideoEncoderProtocol: ...
|
|
86
|
+
) -> VideoEncoderProtocol[FrameT]: ...
|
|
78
87
|
|
|
79
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.3.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.3.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.3.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
|
|
|
@@ -75,9 +81,12 @@ class _PyAVCompressionBackend:
|
|
|
75
81
|
codec_name: str,
|
|
76
82
|
quality: int,
|
|
77
83
|
*,
|
|
78
|
-
input_pix_fmt: str | None = None,
|
|
79
|
-
scale: tuple[int, int] | None = None,
|
|
80
|
-
) ->
|
|
84
|
+
input_pix_fmt: str | None = None,
|
|
85
|
+
scale: tuple[int, int] | None = None,
|
|
86
|
+
) -> VideoEncoder:
|
|
87
|
+
# PyAV reformats input frames per-frame inside VideoEncoder.encode, so
|
|
88
|
+
# the protocol's pix-fmt / scale knobs are FFmpeg-CLI-only.
|
|
89
|
+
del input_pix_fmt, scale
|
|
81
90
|
from mcap_codec_support.video.pyav import VideoEncoder # noqa: PLC0415
|
|
82
91
|
|
|
83
92
|
return VideoEncoder(
|
|
@@ -114,13 +123,13 @@ class _FfmpegCliCompressionBackend:
|
|
|
114
123
|
|
|
115
124
|
return resolve_encoder(codec)
|
|
116
125
|
|
|
117
|
-
def decode_compressed(self, data: bytes) -> tuple[
|
|
126
|
+
def decode_compressed(self, data: bytes) -> tuple[bytes, int, int]:
|
|
118
127
|
from mcap_codec_support.video.ffmpeg import probe_image_dimensions # noqa: PLC0415
|
|
119
128
|
|
|
120
129
|
width, height = probe_image_dimensions(data)
|
|
121
130
|
return data, width, height
|
|
122
131
|
|
|
123
|
-
def decode_image(self, msg: DecodedMessage, schema_name: str) -> tuple[
|
|
132
|
+
def decode_image(self, msg: DecodedMessage, schema_name: str) -> tuple[bytes, int, int]:
|
|
124
133
|
data = bytes(msg.decoded_message.data)
|
|
125
134
|
topic = msg.channel.topic
|
|
126
135
|
|
|
@@ -147,7 +156,7 @@ class _FfmpegCliCompressionBackend:
|
|
|
147
156
|
*,
|
|
148
157
|
input_pix_fmt: str | None = None,
|
|
149
158
|
scale: tuple[int, int] | None = None,
|
|
150
|
-
) ->
|
|
159
|
+
) -> FFmpegVideoEncoder:
|
|
151
160
|
from mcap_codec_support.video.ffmpeg import FFmpegVideoEncoder # noqa: PLC0415
|
|
152
161
|
|
|
153
162
|
return FFmpegVideoEncoder(
|
|
@@ -164,7 +173,7 @@ class _FfmpegCliCompressionBackend:
|
|
|
164
173
|
|
|
165
174
|
def create_video_compression_backend(
|
|
166
175
|
mode: EncoderMode, codec: str, *, do_video: bool
|
|
167
|
-
) ->
|
|
176
|
+
) -> AnyVideoBackend:
|
|
168
177
|
"""Select the roscompress video backend."""
|
|
169
178
|
if mode is EncoderMode.FFMPEG_CLI:
|
|
170
179
|
return _FfmpegCliCompressionBackend()
|
|
@@ -180,7 +189,7 @@ def create_video_compression_backend(
|
|
|
180
189
|
|
|
181
190
|
def prefetch_image_decodes(
|
|
182
191
|
messages: Iterable[DecodedMessage],
|
|
183
|
-
backend:
|
|
192
|
+
backend: AnyVideoBackend,
|
|
184
193
|
pool: ThreadPoolExecutor,
|
|
185
194
|
prefetch: int = 8,
|
|
186
195
|
) -> Iterator[tuple[DecodedMessage, Future[Any] | None]]:
|
|
@@ -227,11 +236,12 @@ def encode_raw_image_to_jpeg(
|
|
|
227
236
|
return buf.getvalue(), target_w, target_h
|
|
228
237
|
|
|
229
238
|
|
|
230
|
-
def decode_compressed_image_to_rgb_array(data: bytes) ->
|
|
231
|
-
"""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."""
|
|
232
241
|
from mcap_codec_support.video.pyav import decode_compressed_frame # noqa: PLC0415
|
|
233
242
|
|
|
234
|
-
|
|
243
|
+
rgb = decode_compressed_frame(data).to_ndarray(format="rgb24")
|
|
244
|
+
return np.asarray(rgb, dtype=np.uint8)
|
|
235
245
|
|
|
236
246
|
|
|
237
247
|
def create_video_decompressor(
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mcap_codec_support-0.3.0 → mcap_codec_support-0.6.0}/src/mcap_codec_support/pointcloud/__init__.py
RENAMED
|
File without changes
|
{mcap_codec_support-0.3.0 → mcap_codec_support-0.6.0}/src/mcap_codec_support/pointcloud/_messages.py
RENAMED
|
File without changes
|
{mcap_codec_support-0.3.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.3.0 → mcap_codec_support-0.6.0}/src/mcap_codec_support/video/_messages.py
RENAMED
|
File without changes
|
{mcap_codec_support-0.3.0 → mcap_codec_support-0.6.0}/src/mcap_codec_support/video/common.py
RENAMED
|
File without changes
|
{mcap_codec_support-0.3.0 → mcap_codec_support-0.6.0}/src/mcap_codec_support/video/factories.py
RENAMED
|
File without changes
|
{mcap_codec_support-0.3.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.3.0 → mcap_codec_support-0.6.0}/src/mcap_codec_support/video/schemas.py
RENAMED
|
File without changes
|