fix(m4): retain compact obstacle evidence

This commit is contained in:
DCCONSTRUCTIONS
2026-08-05 22:16:33 +03:00
parent 13ed096f80
commit aacc6dc43b
24 changed files with 981 additions and 410 deletions
+314
View File
@@ -0,0 +1,314 @@
from __future__ import annotations
import hashlib
import os
import struct
import subprocess
import threading
from contextlib import suppress
from dataclasses import dataclass
from pathlib import Path
from .media import RecordedMediaEpoch, RecordedMediaInspector, RecordedMediaManifest
from .models import SessionIntegrityError
from .store import SessionStore
_MAX_KEYFRAME_DISTANCE = 120
_FFMPEG_TIMEOUT_SECONDS = 15.0
@dataclass(frozen=True, slots=True)
class RecordedCameraFrame:
payload: bytes
media_type: str
width: int
height: int
sha256: str
class RecordedCameraFrameService:
"""Decode one exact archived camera frame without preparing the full video.
Canonical camera archives contain one video sample per fMP4 fragment. The
service validates the sealed manifest, walks back only to the preceding IDR
fragment and gives that bounded GOP to ffmpeg. This keeps CAMERA review
independent from the 4489-row VIDEO overlay and full-player preparation.
"""
def __init__(
self,
store: SessionStore,
inspector: RecordedMediaInspector,
*,
ffmpeg_path: Path,
cache_root: Path,
) -> None:
resolved_ffmpeg = ffmpeg_path.expanduser().resolve(strict=True)
if not resolved_ffmpeg.is_file() or not os.access(resolved_ffmpeg, os.X_OK):
raise SessionIntegrityError("ffmpeg is unavailable for camera frame review")
self._store = store
self._inspector = inspector
self._ffmpeg_path = resolved_ffmpeg
self._cache_root = cache_root.expanduser().absolute()
self._cache_root.mkdir(parents=True, exist_ok=True)
if self._cache_root.is_symlink() or not self._cache_root.is_dir():
raise SessionIntegrityError("camera frame cache root is invalid")
self._lock = threading.Lock()
def extract(
self,
session_id: str,
frame_index: int,
*,
expected_source_name: str = "sensor.camera.right",
) -> RecordedCameraFrame:
if frame_index < 0:
raise SessionIntegrityError("camera frame index is invalid")
replay = self._store.prepare_replay(session_id, speed=1.0, loop=False)
matches = tuple(
artifact
for artifact in self._store.list_recorded_media(session_id)
if artifact.source_path.name == expected_source_name
)
if len(matches) != 1:
raise SessionIntegrityError("recorded camera source is unavailable")
artifact = matches[0]
manifest = self._inspector.inspect(artifact, replay)
epoch, sequence = _frame_location(manifest, frame_index)
cache_key = hashlib.sha256(
(
f"{manifest.generation_sha256}\0{artifact.artifact_id}\0"
f"{expected_source_name}\0{frame_index}\0jpeg-q2-v1"
).encode()
).hexdigest()
cache_path = self._cache_root / f"{cache_key}.jpg"
with self._lock:
cached = _read_cached_jpeg(cache_path)
if cached is not None:
return cached
frame = self._decode(manifest, epoch, sequence)
_publish_cached_jpeg(cache_path, frame.payload)
return frame
def _decode(
self,
manifest: RecordedMediaManifest,
epoch: RecordedMediaEpoch,
sequence: int,
) -> RecordedCameraFrame:
target = self._inspector.get_segment(manifest, epoch.ordinal, sequence)
fragments: list[bytes] = [target.payload]
key_sequence = sequence
while not _fragment_is_sync(fragments[0]):
key_sequence -= 1
if key_sequence < 1 or sequence - key_sequence > _MAX_KEYFRAME_DISTANCE:
raise SessionIntegrityError("camera frame has no bounded sync fragment")
previous = self._inspector.get_segment(
manifest,
epoch.ordinal,
key_sequence,
)
fragments.insert(0, previous.payload)
init = self._inspector.get_init(manifest, epoch.ordinal)
select_index = sequence - key_sequence
try:
completed = subprocess.run(
[
str(self._ffmpeg_path),
"-hide_banner",
"-loglevel",
"error",
"-f",
"mp4",
"-i",
"pipe:0",
"-vf",
f"select=eq(n\\,{select_index})",
"-fps_mode",
"passthrough",
"-frames:v",
"1",
"-f",
"image2pipe",
"-c:v",
"mjpeg",
"-q:v",
"2",
"pipe:1",
],
input=b"".join((init.payload, *fragments)),
capture_output=True,
timeout=_FFMPEG_TIMEOUT_SECONDS,
check=False,
)
except (OSError, subprocess.TimeoutExpired) as exc:
raise SessionIntegrityError("camera frame decoder failed") from exc
if completed.returncode != 0:
detail = completed.stderr.decode("utf-8", errors="replace").strip()[-500:]
raise SessionIntegrityError(f"camera frame decoder rejected sealed media: {detail}")
width, height = _jpeg_dimensions(completed.stdout)
digest = hashlib.sha256(completed.stdout).hexdigest()
return RecordedCameraFrame(
payload=completed.stdout,
media_type="image/jpeg",
width=width,
height=height,
sha256=digest,
)
def _frame_location(
manifest: RecordedMediaManifest,
frame_index: int,
) -> tuple[RecordedMediaEpoch, int]:
remaining = frame_index
for epoch in manifest.epochs:
if remaining < len(epoch.segments):
return epoch, remaining + 1
remaining -= len(epoch.segments)
raise SessionIntegrityError("camera frame is outside the recorded media manifest")
def _fragment_is_sync(payload: bytes) -> bool:
tfhd_default_flags: int | None = None
sample_flags: int | None = None
sample_count: int | None = None
for box_type, body in _walk_boxes(payload):
if box_type == b"tfhd":
if len(body) < 8:
raise SessionIntegrityError("camera fragment tfhd is truncated")
flags = int.from_bytes(body[1:4], "big")
offset = 8
for mask, size in ((0x000001, 8), (0x000002, 4), (0x000008, 4), (0x000010, 4)):
if flags & mask:
offset += size
if flags & 0x000020:
if offset + 4 > len(body):
raise SessionIntegrityError("camera fragment default flags are truncated")
tfhd_default_flags = struct.unpack_from(">I", body, offset)[0]
elif box_type == b"trun":
if len(body) < 8:
raise SessionIntegrityError("camera fragment trun is truncated")
flags = int.from_bytes(body[1:4], "big")
sample_count = struct.unpack_from(">I", body, 4)[0]
if sample_count != 1:
raise SessionIntegrityError("camera fragment must contain exactly one sample")
offset = 8
if flags & 0x000001:
offset += 4
if flags & 0x000004:
if offset + 4 > len(body):
raise SessionIntegrityError("camera fragment first flags are truncated")
sample_flags = struct.unpack_from(">I", body, offset)[0]
offset += 4
per_sample_sizes = (
(0x000100, 4),
(0x000200, 4),
(0x000400, 4),
(0x000800, 4),
)
for mask, size in per_sample_sizes:
if flags & mask:
if offset + size > len(body):
raise SessionIntegrityError("camera fragment sample data is truncated")
if mask == 0x000400:
sample_flags = struct.unpack_from(">I", body, offset)[0]
offset += size
if sample_count != 1:
raise SessionIntegrityError("camera fragment has no unique video sample")
effective_flags = sample_flags if sample_flags is not None else tfhd_default_flags
if effective_flags is None:
raise SessionIntegrityError("camera fragment sample flags are unavailable")
return (effective_flags & 0x00010000) == 0
def _walk_boxes(payload: bytes):
containers = {b"moof", b"traf"}
pending = [(0, len(payload))]
boxes = 0
while pending:
start, end = pending.pop()
offset = start
while offset + 8 <= end:
boxes += 1
if boxes > 64:
raise SessionIntegrityError("camera fragment box budget was exceeded")
size = struct.unpack_from(">I", payload, offset)[0]
box_type = payload[offset + 4 : offset + 8]
header = 8
if size == 1:
if offset + 16 > end:
raise SessionIntegrityError("camera fragment extended box is truncated")
size = struct.unpack_from(">Q", payload, offset + 8)[0]
header = 16
elif size == 0:
size = end - offset
if size < header or offset + size > end:
raise SessionIntegrityError("camera fragment box size is invalid")
body_start = offset + header
body_end = offset + size
yield box_type, payload[body_start:body_end]
if box_type in containers:
pending.append((body_start, body_end))
offset = body_end
if offset != end:
raise SessionIntegrityError("camera fragment box boundary is invalid")
def _jpeg_dimensions(payload: bytes) -> tuple[int, int]:
if len(payload) < 4 or payload[:2] != b"\xff\xd8" or payload[-2:] != b"\xff\xd9":
raise SessionIntegrityError("camera frame decoder returned an invalid JPEG")
offset = 2
while offset + 4 <= len(payload):
if payload[offset] != 0xFF:
offset += 1
continue
marker = payload[offset + 1]
offset += 2
if marker in {0xD8, 0xD9} or 0xD0 <= marker <= 0xD7:
continue
if offset + 2 > len(payload):
break
length = struct.unpack_from(">H", payload, offset)[0]
if length < 2 or offset + length > len(payload):
break
if marker in {0xC0, 0xC1, 0xC2} and length >= 7:
height, width = struct.unpack_from(">HH", payload, offset + 3)
if width > 0 and height > 0:
return width, height
offset += length
raise SessionIntegrityError("camera frame JPEG dimensions are unavailable")
def _read_cached_jpeg(path: Path) -> RecordedCameraFrame | None:
try:
if path.is_symlink() or not path.is_file():
return None
payload = path.read_bytes()
width, height = _jpeg_dimensions(payload)
except (OSError, SessionIntegrityError):
return None
return RecordedCameraFrame(
payload=payload,
media_type="image/jpeg",
width=width,
height=height,
sha256=hashlib.sha256(payload).hexdigest(),
)
def _publish_cached_jpeg(path: Path, payload: bytes) -> None:
temporary = path.parent / f".{path.name}.{os.getpid()}.{threading.get_ident()}.tmp"
try:
with temporary.open("xb") as stream:
stream.write(payload)
stream.flush()
os.fsync(stream.fileno())
os.replace(temporary, path)
except OSError as exc:
raise SessionIntegrityError("camera frame cache could not be published") from exc
finally:
with suppress(FileNotFoundError):
temporary.unlink()