#!/usr/bin/env python3 """ Seed a scenario from a video chunk. Creates a Timeline (frames in MinIO) + Branch + Checkpoint marked as a scenario. No pipeline, no Redis, no SSE. Prerequisites: - Postgres reachable (Kind NodePort or local) - MinIO reachable (Kind NodePort or local) Usage: python tests/detect/manual/seed_scenario.py python tests/detect/manual/seed_scenario.py --video media/mpr/out/chunks/.../chunk_0001.mp4 Then open: http://mpr.local.ar/detection/?job=#/editor/detect_edges """ from __future__ import annotations import argparse import logging import os import sys import uuid parser = argparse.ArgumentParser(description="Seed a scenario") parser.add_argument("--video", default="media/mpr/out/chunks/95043d50-4df6-4ac8-bbd5-2ba873117c6e/chunk_0000.mp4") parser.add_argument("--label", default="chelsea_edges_default", help="Scenario label for bookmarking") parser.add_argument("--fps", type=float, default=2.0, help="Frames per second to extract") parser.add_argument("--max-frames", type=int, default=20, help="Max frames to extract") parser.add_argument("--db-url", default=os.environ.get("DATABASE_URL", "postgresql://mpr:mpr@localhost:5432/mpr")) parser.add_argument("--s3-url", default=os.environ.get("S3_ENDPOINT_URL", "http://localhost:9000")) args = parser.parse_args() os.environ["DATABASE_URL"] = args.db_url os.environ["S3_ENDPOINT_URL"] = args.s3_url os.environ.setdefault("AWS_ACCESS_KEY_ID", "minioadmin") os.environ.setdefault("AWS_SECRET_ACCESS_KEY", "minioadmin") sys.path.insert(0, ".") logging.basicConfig(level=logging.INFO, format="%(levelname)-7s %(name)s — %(message)s") logger = logging.getLogger(__name__) def extract_frames_ffmpeg(video_path: str, fps: float, max_frames: int): """Extract frames using ffmpeg — no pipeline dependencies.""" import subprocess import tempfile from pathlib import Path import numpy as np from PIL import Image from core.detect.models import Frame tmpdir = tempfile.mkdtemp(prefix="scenario_") pattern = os.path.join(tmpdir, "frame_%04d.jpg") cmd = [ "ffmpeg", "-i", video_path, "-vf", f"fps={fps}", "-frames:v", str(max_frames), "-q:v", "2", pattern, "-y", "-loglevel", "error", ] subprocess.run(cmd, check=True) frames = [] for jpg in sorted(Path(tmpdir).glob("frame_*.jpg")): seq = int(jpg.stem.split("_")[1]) - 1 img = Image.open(jpg).convert("RGB") image_array = np.array(img) frame = Frame( sequence=seq, chunk_id=0, timestamp=seq / fps, image=image_array, ) frames.append(frame) jpg.unlink() Path(tmpdir).rmdir() return frames def main(): video_path = args.video if not os.path.exists(video_path): logger.error("Video not found: %s", video_path) sys.exit(1) logger.info("Video: %s", video_path) logger.info("Label: %s", args.label) # Ensure DB tables exist from core.db.connection import create_tables create_tables() # Extract frames logger.info("Extracting frames (fps=%.1f, max=%d)...", args.fps, args.max_frames) frames = extract_frames_ffmpeg(video_path, args.fps, args.max_frames) logger.info("Extracted %d frames", len(frames)) # Create timeline + branch + checkpoint from core.detect.checkpoint.storage import create_timeline, save_stage_output timeline_id, branch_id = create_timeline( source_video=video_path, profile_name="soccer_broadcast", frames=frames, fps=args.fps, ) # Mark as scenario from core.db import get_latest_checkpoint from core.db.connection import get_session with get_session() as session: checkpoint = get_latest_checkpoint(session, branch_id) if checkpoint: checkpoint.is_scenario = True checkpoint.scenario_label = args.label session.commit() logger.info("") logger.info("Scenario created:") logger.info(" Timeline: %s", timeline_id) logger.info(" Branch: %s", branch_id) logger.info(" Label: %s", args.label) logger.info(" Frames: %d", len(frames)) logger.info("") logger.info("Open in editor:") logger.info(" http://mpr.local.ar/detection/?job=%s#/editor/detect_edges", timeline_id) if __name__ == "__main__": main()