Write talking-head scripts and produce Instagram reels and YouTube shorts
93
94%
Does it follow best practices?
Impact
88%
1.23xAverage score across 3 eval scenarios
Low
Low-risk findings worth noting
#!/usr/bin/env python3
"""Render a local Hyperframes project into a full-frame motion b-roll clip.
Input: project directory with index.html, one standalone composition root with
explicit data-width/data-height/data-duration, and npm-installed Hyperframes.
Run npm ci in a copy of assets/hyperframes-motion first. No automatic installs.
Requires Node >=22, ffmpeg, ffprobe, and Hyperframes' browser runtime.
Runs Hyperframes check before rendering, then conforms to reel-builder's frame
size, 30fps, H.264/yuv420p/BT.709, and silent stereo 48kHz AAC. Composition audio
is deliberately discarded: music and VO belong to the reel's audio spine.
The final MP4 is replaced atomically only after probing validates the result.
stdout is a JSON result; progress and failures go to stderr; exit 1 on failure.
Usage:
render_hyperframes.py work/motion/title --out work/motion/title.mp4
render_hyperframes.py work/motion/title --preview --out preview/title.mp4
render_hyperframes.py work/motion/title --aspect 16:9 --resolution 4k --out title.mp4
"""
import argparse
import json
import math
import shutil
import subprocess
import sys
import tempfile
from fractions import Fraction
from html.parser import HTMLParser
from pathlib import Path
from render_reel import ASPECTS, dims_for
FPS = 30
class CompositionParser(HTMLParser):
def __init__(self):
super().__init__()
self.roots = []
def handle_starttag(self, tag, attrs):
attributes = dict(attrs)
if (
"data-composition-id" in attributes
and "data-composition-src" not in attributes
):
self.roots.append(attributes)
def composition_info(project: Path, aspect: str) -> tuple[int, int, float]:
entry = project / "index.html"
if not entry.is_file():
raise ValueError(f"Missing {entry} — create the composition before rendering.")
parser = CompositionParser()
parser.feed(entry.read_text(encoding="utf-8"))
if len(parser.roots) != 1:
raise ValueError("index.html needs one standalone data-composition-id root.")
root = parser.roots[0]
try:
width = int(root["data-width"])
height = int(root["data-height"])
duration = float(root["data-duration"])
except (KeyError, TypeError, ValueError) as exc:
raise ValueError(
"Set numeric data-width, data-height and data-duration on the root."
) from exc
if width <= 0 or height <= 0 or not math.isfinite(duration) or duration < 1 / FPS:
raise ValueError(
"Composition dimensions must be positive; duration must cover at least one frame."
)
aw, ah = map(int, aspect.split(":"))
if width * ah != height * aw:
raise ValueError(
f"Composition is {width}x{height}, not {aspect} — adjust its layout or --aspect."
)
return width, height, duration
def run(command: list[str], cwd: Path | None = None) -> str:
result = subprocess.run(
command, cwd=cwd, capture_output=True, text=True, check=False
)
if result.returncode:
detail = (result.stderr + "\n" + result.stdout).strip()
raise RuntimeError(
f"{Path(command[0]).name} failed (exit {result.returncode}):\n{detail[-4000:]}"
)
if result.stderr.strip():
print(result.stderr.strip(), file=sys.stderr)
return result.stdout
def require_tools(project: Path) -> Path:
for tool in ("node", "ffmpeg", "ffprobe"):
if not shutil.which(tool):
raise ValueError(
f"{tool} is missing — install the motion-graphics prerequisites in references/hyperframes.md."
)
version = run(["node", "--version"]).strip().lstrip("v")
if int(version.split(".")[0]) < 22:
raise ValueError(
f"Hyperframes requires Node 22+; found {version} — select a supported Node version."
)
cli = project / "node_modules" / ".bin" / "hyperframes"
if not cli.is_file():
raise ValueError(
f"Hyperframes is missing in {project} — run npm ci in that project first."
)
return cli
def probe(path: Path) -> dict:
return json.loads(
run(
[
"ffprobe",
"-v",
"error",
"-show_streams",
"-show_format",
"-of",
"json",
str(path),
]
)
)
def verify_media(
info: dict, dims: tuple[int, int], duration: float, *, conformed: bool
) -> None:
streams = info.get("streams", [])
video = next((s for s in streams if s.get("codec_type") == "video"), {})
actual_duration = float(
video.get("duration", info.get("format", {}).get("duration", 0))
)
if (
not video
or not math.isfinite(actual_duration)
or abs(actual_duration - duration) > 1 / FPS + 0.001
):
raise ValueError(
"Rendered video duration does not match data-duration — inspect the Hyperframes composition."
)
if video.get("width", 0) * dims[1] != video.get("height", 0) * dims[0]:
raise ValueError(
"Rendered video aspect does not match the composition — inspect the Hyperframes render."
)
if not conformed:
return
audio = next((s for s in streams if s.get("codec_type") == "audio"), {})
try:
frame_rate = Fraction(video.get("avg_frame_rate", "0"))
except (ValueError, ZeroDivisionError, TypeError) as exc:
raise ValueError(
"Output frame rate is unreadable — inspect ffprobe diagnostics."
) from exc
if (
(video.get("width"), video.get("height")) != dims
or video.get("codec_name") != "h264"
or video.get("pix_fmt") != "yuv420p"
or frame_rate != FPS
or video.get("color_space") != "bt709"
or audio.get("codec_name") != "aac"
or audio.get("sample_rate") != "48000"
or audio.get("channels") != 2
):
raise ValueError(
"Output does not match the reel mezzanine spec — inspect ffmpeg diagnostics."
)
def render(
project: Path, out: Path, aspect: str, resolution: str, preview: bool
) -> dict:
project, out = project.resolve(), out.resolve()
if out.suffix.lower() != ".mp4":
raise ValueError("--out must name an .mp4 file.")
width, height, duration = composition_info(project, aspect)
cli = require_tools(project)
dims = dims_for(aspect, resolution, preview)
target = dims_for(aspect, resolution)
# Hyperframes supersamples only at integer scales; reject a crop or stretch.
if target[0] % width or target[1] % height or target[0] < width:
raise ValueError(
f"Set the composition canvas to {target[0]}x{target[1]} or an integer subdivision for this resolution."
)
print("Checking Hyperframes composition...", file=sys.stderr, flush=True)
print(run([str(cli), "check"], cwd=project), file=sys.stderr)
out.parent.mkdir(parents=True, exist_ok=True)
preset = {"9:16": "portrait", "16:9": "landscape", "1:1": "square"}[aspect]
if resolution == "4k":
preset += "-4k"
with tempfile.TemporaryDirectory(
prefix=".hyperframes-", dir=out.parent
) as temporary:
raw = Path(temporary) / "render.mp4"
conformed = Path(temporary) / "clip.mp4"
print("Rendering Hyperframes motion clip...", file=sys.stderr, flush=True)
print(
run(
[
str(cli),
"render",
"--output",
str(raw),
"--fps",
str(FPS),
"--format",
"mp4",
"--quality",
"draft" if preview else "delivery",
*([] if preview else ["--resolution", preset]),
"--sdr",
"--strict",
"--quiet",
],
cwd=project,
),
file=sys.stderr,
)
verify_media(probe(raw), dims, duration, conformed=False)
run(
[
"ffmpeg",
"-v",
"error",
"-y",
"-i",
str(raw),
"-f",
"lavfi",
"-i",
"anullsrc=r=48000:cl=stereo",
"-map",
"0:v:0",
"-map",
"1:a:0",
"-t",
str(duration),
"-vf",
f"scale={dims[0]}:{dims[1]},fps={FPS},setsar=1,format=yuv420p",
"-c:v",
"libx264",
"-preset",
"fast" if preview else "medium",
"-crf",
"23" if preview else "16",
"-colorspace",
"bt709",
"-color_primaries",
"bt709",
"-color_trc",
"bt709",
"-c:a",
"aac",
"-b:a",
"192k",
"-ar",
"48000",
"-ac",
"2",
"-movflags",
"+faststart",
str(conformed),
]
)
verify_media(probe(conformed), dims, duration, conformed=True)
conformed.replace(out)
return {
"engine": "hyperframes",
"project": str(project),
"output": str(out),
"width": dims[0],
"height": dims[1],
"fps": FPS,
"duration": duration,
"audio": "silent",
"preview": preview,
}
def main() -> None:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("project", type=Path)
parser.add_argument("--out", type=Path, required=True)
parser.add_argument("--aspect", choices=sorted(ASPECTS), default="9:16")
parser.add_argument("--resolution", choices=("1080", "4k"), default="1080")
parser.add_argument("--preview", action="store_true")
args = parser.parse_args()
try:
result = render(
args.project, args.out, args.aspect, args.resolution, args.preview
)
except (OSError, ValueError, RuntimeError) as exc:
parser.exit(1, f"{exc}\n")
print(json.dumps(result, indent=2))
if __name__ == "__main__":
main().tessl-plugin
skills
reel-builder
assets
references
scripts
yap-writer