112 lines
3.0 KiB
Python
112 lines
3.0 KiB
Python
"""Frame image storage — save/load to S3/MinIO as JPEGs."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
import os
|
|
import tempfile
|
|
|
|
import numpy as np
|
|
from PIL import Image
|
|
|
|
from detect.models import Frame
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
BUCKET = os.environ.get("S3_BUCKET", "mpr")
|
|
CHECKPOINT_PREFIX = "checkpoints"
|
|
|
|
|
|
def save_frames(job_id: str, frames: list[Frame]) -> dict[int, str]:
|
|
"""
|
|
Save frame images to S3 as JPEGs.
|
|
|
|
Returns manifest: {sequence: s3_key}
|
|
"""
|
|
from core.storage.s3 import upload_file
|
|
|
|
manifest = {}
|
|
|
|
for frame in frames:
|
|
key = f"{CHECKPOINT_PREFIX}/{job_id}/frames/{frame.sequence}.jpg"
|
|
|
|
with tempfile.NamedTemporaryFile(suffix=".jpg", delete=False) as tmp:
|
|
img = Image.fromarray(frame.image)
|
|
img.save(tmp, format="JPEG", quality=85)
|
|
tmp_path = tmp.name
|
|
|
|
try:
|
|
upload_file(tmp_path, BUCKET, key)
|
|
finally:
|
|
os.unlink(tmp_path)
|
|
|
|
manifest[frame.sequence] = key
|
|
|
|
logger.info("Saved %d frames to s3://%s/%s/%s/frames/",
|
|
len(frames), BUCKET, CHECKPOINT_PREFIX, job_id)
|
|
return manifest
|
|
|
|
|
|
def load_frames(manifest: dict[int, str], frame_metadata: list[dict]) -> list[Frame]:
|
|
"""
|
|
Load frame images from S3 and reconstitute Frame objects.
|
|
|
|
frame_metadata: list of dicts with sequence, chunk_id, timestamp, perceptual_hash.
|
|
"""
|
|
from core.storage.s3 import download_to_temp
|
|
|
|
meta_map = {m["sequence"]: m for m in frame_metadata}
|
|
frames = []
|
|
|
|
for seq, key in manifest.items():
|
|
tmp_path = download_to_temp(BUCKET, key)
|
|
try:
|
|
img = Image.open(tmp_path).convert("RGB")
|
|
image_array = np.array(img)
|
|
finally:
|
|
os.unlink(tmp_path)
|
|
|
|
meta = meta_map.get(seq, {})
|
|
frame = Frame(
|
|
sequence=seq,
|
|
chunk_id=meta.get("chunk_id", 0),
|
|
timestamp=meta.get("timestamp", 0.0),
|
|
image=image_array,
|
|
perceptual_hash=meta.get("perceptual_hash", ""),
|
|
)
|
|
frames.append(frame)
|
|
|
|
frames.sort(key=lambda f: f.sequence)
|
|
return frames
|
|
|
|
|
|
def load_frames_b64(manifest: dict[int, str], frame_metadata: list[dict]) -> list[dict]:
|
|
"""
|
|
Load frame images from S3 as base64 JPEG — lightweight, no numpy.
|
|
|
|
Returns list of dicts: {seq, timestamp, jpeg_b64}
|
|
"""
|
|
import base64
|
|
from core.storage.s3 import download_to_temp
|
|
|
|
meta_map = {m["sequence"]: m for m in frame_metadata}
|
|
frames = []
|
|
|
|
for seq, key in manifest.items():
|
|
tmp_path = download_to_temp(BUCKET, key)
|
|
try:
|
|
with open(tmp_path, "rb") as f:
|
|
jpeg_bytes = f.read()
|
|
finally:
|
|
os.unlink(tmp_path)
|
|
|
|
meta = meta_map.get(seq, {})
|
|
frames.append({
|
|
"seq": seq,
|
|
"timestamp": meta.get("timestamp", 0.0),
|
|
"jpeg_b64": base64.b64encode(jpeg_bytes).decode(),
|
|
})
|
|
|
|
frames.sort(key=lambda f: f["seq"])
|
|
return frames
|