Claude 5e2094f2ad
feat(media): local inference sidecar for Media Studio
FastAPI service in infra/media-worker implementing every
MediaInferenceProvider operation behind HTTP API v1, plus capabilities.
Bearer auth, MEDIA_ROOT realpath enforcement, strict request schemas,
cancellation on disconnect, GPU/CPU gates with 503 overflow, input
size/duration limits, lazy SHA256-pinned model downloads, fail-closed
redaction with atomic output, offline pytest suite with fake backends,
CPU/CUDA/ROCm Dockerfile and a license review of every model.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0138YqDj3ttfayRwuMKKrqVj
2026-10-03 18:19:47 +00:00

55 lines
2.1 KiB
Python

"""Per-frame mask directories (`<tMs>.png`), as written by segmentPrompted."""
from __future__ import annotations
import bisect
import os
import re
import cv2
import numpy as np
from ..errors import BadRequest, MediaError
MASK_NAME = re.compile(r"^(\d+)\.png$")
class MaskIndex:
def __init__(self, directory: str, frame_ms: float) -> None:
entries = []
for name in os.listdir(directory):
match = MASK_NAME.match(name)
if match:
entries.append((int(match.group(1)), os.path.join(directory, name)))
if not entries:
raise BadRequest("mask directory contains no <tMs>.png masks")
entries.sort()
self.times = [t for t, _ in entries]
self.files = [p for _, p in entries]
gaps = np.diff(self.times) if len(self.times) > 1 else np.array([frame_ms])
# A mask covers frames up to 1.5 sampling intervals away (at least one frame).
self.tolerance = max(float(np.median(gaps)) * 1.5, frame_ms)
self._cache: tuple[int, np.ndarray] | None = None
def lookup(self, t_ms: int, width: int, height: int) -> np.ndarray | None:
"""Binary mask (uint8 0/255) for the frame at t_ms, resized to the frame, or None."""
i = bisect.bisect_left(self.times, t_ms)
best = None
for j in (i - 1, i):
if not 0 <= j < len(self.times) or abs(self.times[j] - t_ms) > self.tolerance:
continue
if best is None or abs(self.times[j] - t_ms) < abs(self.times[best] - t_ms):
best = j
if best is None:
return None
if self._cache is not None and self._cache[0] == best:
return self._cache[1]
mask = cv2.imread(self.files[best], cv2.IMREAD_GRAYSCALE)
if mask is None:
raise MediaError("internal", f"unreadable mask {os.path.basename(self.files[best])}")
if mask.shape != (height, width):
mask = cv2.resize(mask, (width, height), interpolation=cv2.INTER_NEAREST)
mask = np.where(mask > 127, 255, 0).astype(np.uint8)
self._cache = (best, mask)
return mask