#!/usr/bin/env python3 """Turn chosen clips (and stills) into footage plates the page can show frame-exactly. python3 prep-plates.py plates.json [--out plates/] [--fps 60] [keys...] plates.json: {"key": {"kind": "clip", "src": "clips/x.mp4", "in": 1.2, "dur": 0.5, "inset": 0.0}, "key2": {"kind": "still", "src": "stills/y.jpg", "inset": 0.03}} Clips become plates//001.jpg… at the render frame rate (dur + 0.25 s of frames, from `in`); stills become plates/.jpg, 10% larger than the frame so the page can push in without softness. Everything gets the same grade, so footage from different generators reads as one film. The page swaps sources per frame inside window.seek (see references/launch-film.md). """ import argparse, json, os, pathlib, subprocess from common import ffmpeg ap = argparse.ArgumentParser() ap.add_argument("config"); ap.add_argument("keys", nargs="*") ap.add_argument("--out", default="plates"); ap.add_argument("--fps", type=int, default=60) ap.add_argument("--grade", default="eq=contrast=1.06:saturation=0.86:gamma=0.97,colorbalance=rs=-0.02:bs=0.02:rh=0.03:bh=-0.03") a = ap.parse_args() FF = ffmpeg(); out = pathlib.Path(a.out); out.mkdir(exist_ok=True) for k, p in json.load(open(a.config)).items(): if k.startswith("_") or (a.keys and k not in a.keys): continue i = p.get("inset", 0) crop = f"crop=iw*{1 - 2 * i}:ih*{1 - 2 * i}," if i else "" if p["kind"] == "clip": d = out / k; d.mkdir(exist_ok=True) for f in d.glob("*.jpg"): f.unlink() vf = f"{crop}scale=1920:1080:force_original_aspect_ratio=increase:flags=lanczos,crop=1920:1080,{a.grade},fps={a.fps}" subprocess.run([FF, "-v", "error", "-y", "-ss", str(p["in"]), "-t", str(p["dur"] + 0.25), "-i", p["src"], "-vf", vf, "-q:v", "3", str(d / "%03d.jpg")], check=True) print(k, len(list(d.glob("*.jpg"))), "frames") else: vf = f"{crop}scale=2112:1188:force_original_aspect_ratio=increase:flags=lanczos,crop=2112:1188,{a.grade}" subprocess.run([FF, "-v", "error", "-y", "-i", p["src"], "-vf", vf, "-q:v", "2", str(out / f"{k}.jpg")], check=True) print(k, "still")