Source code for datumaro.plugins.data_formats.datumaro_binary.mapper.media
# Copyright (C) 2024 Intel Corporation
#
# SPDX-License-Identifier: MIT
import os.path as osp
import struct
from typing import Dict, Optional, Tuple
from datumaro.components.errors import DatumaroError
from datumaro.components.media import Image, MediaElement, MediaType, PointCloud, Video, VideoFrame
from datumaro.util.os_util import join_within_base
from .common import Mapper, StringMapper
[docs]
class MediaMapper(Mapper):
[docs]
@classmethod
def forward(cls, obj: Optional[MediaElement]) -> bytes:
if obj is None:
return struct.pack("<I", MediaType.NONE)
if obj._type == MediaType.IMAGE:
return ImageMapper.forward(obj)
if obj._type == MediaType.POINT_CLOUD:
return PointCloudMapper.forward(obj)
if obj._type == MediaType.VIDEO:
return VideoMapper.forward(obj)
if obj._type == MediaType.VIDEO_FRAME:
return VideoFrameMapper.forward(obj)
if obj._type == MediaType.MEDIA_ELEMENT:
return MediaElementMapper.forward(obj)
raise DatumaroError(f"{obj._type} is not allowed for MediaMapper.")
[docs]
@classmethod
def backward(
cls,
_bytes: bytes,
offset: int = 0,
media_path_prefix: Optional[Dict[MediaType, str]] = None,
allow_external_media: bool = False,
) -> Tuple[Optional[MediaElement], int]:
(media_type,) = struct.unpack_from("<I", _bytes, offset)
if media_type == MediaType.NONE:
return None, offset + 4
if media_type == MediaType.IMAGE:
return ImageMapper.backward(_bytes, offset, media_path_prefix, allow_external_media)
if media_type == MediaType.POINT_CLOUD:
return PointCloudMapper.backward(_bytes, offset, media_path_prefix, allow_external_media)
if media_type == MediaType.VIDEO:
return VideoMapper.backward(_bytes, offset, media_path_prefix, allow_external_media)
if media_type == MediaType.VIDEO_FRAME:
return VideoFrameMapper.backward(_bytes, offset, media_path_prefix, allow_external_media)
if media_type == MediaType.MEDIA_ELEMENT:
return MediaElementMapper.backward(_bytes, offset, media_path_prefix, allow_external_media)
raise DatumaroError(f"{media_type} is not allowed for MediaMapper.")
[docs]
class MediaElementMapper(Mapper):
MAGIC_PATH = "/NOT/A/REAL/PATH"
MEDIA_TYPE = MediaType.MEDIA_ELEMENT
[docs]
@classmethod
def forward(cls, obj: MediaElement) -> bytes:
bytes_arr = bytearray()
bytes_arr.extend(struct.pack("<I", obj.type))
path = getattr(obj, "path", cls.MAGIC_PATH)
bytes_arr.extend(StringMapper.forward(path))
return bytes(bytes_arr)
[docs]
@classmethod
def backward_dict(
cls,
_bytes: bytes,
offset: int = 0,
media_path_prefix: Optional[Dict[MediaType, str]] = None,
allow_external_media: bool = False,
) -> Tuple[Dict, int]:
(media_type,) = struct.unpack_from("<I", _bytes, offset)
assert media_type == cls.MEDIA_TYPE, f"Expect {cls.MEDIA_TYPE} but actual is {media_type}."
offset += 4
path, offset = StringMapper.backward(_bytes, offset)
if path == cls.MAGIC_PATH:
path = None
join_media_path = osp.join if allow_external_media else join_within_base
return {
"type": media_type,
"path": path
if path == cls.MAGIC_PATH or media_path_prefix is None
else join_media_path(media_path_prefix[cls.MEDIA_TYPE], path),
}, offset
[docs]
@classmethod
def backward(
cls,
_bytes: bytes,
offset: int = 0,
media_path_prefix: Optional[Dict[MediaType, str]] = None,
allow_external_media: bool = False,
) -> Tuple[MediaElement, int]:
_, offset = cls.backward_dict(_bytes, offset, media_path_prefix, allow_external_media)
return MediaElement(), offset
[docs]
class ImageMapper(MediaElementMapper):
MAGIC_SIZE_FOR_NONE = (-1583, -1597)
MEDIA_TYPE = MediaType.IMAGE
[docs]
@classmethod
def forward(cls, obj: Image) -> bytes:
size = obj.size if obj.has_size else cls.MAGIC_SIZE_FOR_NONE
bytes_arr = bytearray()
bytes_arr.extend(super().forward(obj))
bytes_arr.extend(struct.pack("<ii", size[0], size[1]))
return bytes(bytes_arr)
[docs]
@classmethod
def backward(
cls,
_bytes: bytes,
offset: int = 0,
media_path_prefix: Optional[Dict[MediaType, str]] = None,
allow_external_media: bool = False,
) -> Tuple[Image, int]:
media_dict, offset = cls.backward_dict(_bytes, offset, media_path_prefix, allow_external_media)
height, width = struct.unpack_from("<ii", _bytes, offset)
size = (height, width)
offset += 8
return (
Image.from_file(path=media_dict["path"], size=size if size != cls.MAGIC_SIZE_FOR_NONE else None),
offset,
)
[docs]
class VideoMapper(MediaElementMapper):
MAGIC_END_FRAME_FOR_NONE = 4294967295 # max value of unsigned int32
MEDIA_TYPE = MediaType.VIDEO
[docs]
@classmethod
def forward(cls, obj: Video) -> bytes:
end_frame = obj._end_frame if obj._end_frame else cls.MAGIC_END_FRAME_FOR_NONE
bytes_arr = bytearray()
bytes_arr.extend(super().forward(obj))
bytes_arr.extend(struct.pack("<III", obj._step, obj._start_frame, end_frame))
return bytes(bytes_arr)
[docs]
@classmethod
def backward(
cls,
_bytes: bytes,
offset: int = 0,
media_path_prefix: Optional[Dict[MediaType, str]] = None,
allow_external_media: bool = False,
) -> Tuple[Video, int]:
media_dict, offset = cls.backward_dict(_bytes, offset, media_path_prefix, allow_external_media)
step, start_frame, end_frame = struct.unpack_from("<III", _bytes, offset)
offset += 12
video = Video(
path=media_dict["path"],
step=step,
start_frame=start_frame,
end_frame=end_frame if end_frame != cls.MAGIC_END_FRAME_FOR_NONE else None,
)
return (video, offset)
[docs]
class VideoFrameMapper(MediaElementMapper):
MEDIA_TYPE = MediaType.VIDEO_FRAME
[docs]
@classmethod
def forward(cls, obj: VideoFrame) -> bytes:
bytes_arr = bytearray()
bytes_arr.extend(super().forward(obj))
bytes_arr.extend(struct.pack("<I", obj.index))
return bytes(bytes_arr)
[docs]
@classmethod
def backward(
cls,
_bytes: bytes,
offset: int = 0,
media_path_prefix: Optional[Dict[MediaType, str]] = None,
allow_external_media: bool = False,
) -> Tuple[VideoFrame, int]:
media_dict, offset = cls.backward_dict(_bytes, offset, media_path_prefix, allow_external_media)
(frame_index,) = struct.unpack_from("<I", _bytes, offset)
video = Video(media_dict["path"])
offset += 4
return (
VideoFrame(video, frame_index),
offset,
)
[docs]
class PointCloudMapper(MediaElementMapper):
MEDIA_TYPE = MediaType.POINT_CLOUD
[docs]
@classmethod
def forward(cls, obj: PointCloud) -> bytes:
bytes_arr = bytearray()
bytes_arr.extend(super().forward(obj))
bytes_arr.extend(struct.pack("<I", len(obj.extra_images)))
for img in obj.extra_images:
bytes_arr.extend(ImageMapper.forward(img))
return bytes(bytes_arr)
[docs]
@classmethod
def backward(
cls,
_bytes: bytes,
offset: int = 0,
media_path_prefix: Optional[Dict[MediaType, str]] = None,
allow_external_media: bool = False,
) -> Tuple[PointCloud, int]:
media_dict, offset = cls.backward_dict(_bytes, offset, media_path_prefix, allow_external_media)
(len_extra_images,) = struct.unpack_from("<I", _bytes, offset)
offset += 4
extra_images = []
for _ in range(len_extra_images):
img, offset = ImageMapper.backward(_bytes, offset, media_path_prefix, allow_external_media)
extra_images.append(img)
return PointCloud.from_file(path=media_dict["path"], extra_images=extra_images), offset