148 lines
4.4 KiB
Python
148 lines
4.4 KiB
Python
#!/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=<TIMELINE_ID>#/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 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 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.detect import get_latest_checkpoint
|
|
from core.db.connection import get_session
|
|
|
|
checkpoint = get_latest_checkpoint(branch_id)
|
|
if checkpoint:
|
|
checkpoint.is_scenario = True
|
|
checkpoint.scenario_label = args.label
|
|
with get_session() as session:
|
|
session.add(checkpoint)
|
|
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()
|