Write talking-head scripts and produce Instagram reels and YouTube shorts
94
97%
Does it follow best practices?
Impact
85%
2.36xAverage score across 3 eval scenarios
Low
Low-risk findings worth noting
#!/usr/bin/env python3
"""Render headline / logo cards for talking-head b-roll, via Remotion.
Talking-head b-roll is usually cards and screen recordings rather than footage,
and a static card held for three seconds reads as dead air. These render as
real motion clips at mezzanine spec, so the cut plan treats them like any other
segment.
Requires Node 18+. The first run installs Remotion into the project (a few
hundred MB including a headless Chrome), so it is slow once and fast after.
The template is copied out of the plugin into <outdir>/_project first —
node_modules never lands inside the installed skill.
Input JSON (a list, or {"cards": [...]}):
[
{"name": "acquisition", "title": "Garmin bought TrainingPeaks",
"kicker": "July 2026", "subtitle": "The neutral ground changed hands",
"duration": 3.0, "bg": "#0F1D2B", "accent": "#F26B21", "logo": "garmin.png"}
]
Only "title" is required. "logo" is a filename inside <outdir>/_project/public.
Usage:
make_cards.py cards.json --outdir work/cards
make_cards.py cards.json --outdir work/cards --resolution 4k
"""
import argparse, json, shutil, subprocess, sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent))
from render_reel import RESOLUTIONS # noqa: E402
import card_sources # noqa: E402
TEMPLATE = Path(__file__).resolve().parent.parent / "assets/remotion-cards"
FPS = 30
DEFAULTS = {"kicker": "", "subtitle": "", "duration": 3.0,
"bg": "#0F1D2B", "accent": "#F26B21", "fg": "#FFFFFF", "logo": ""}
def require_node():
if not shutil.which("node"):
sys.exit("Node not found — install Node 18+ (brew install node) or "
"build cards as stills and let normalize_clips.py apply Ken "
"Burns motion instead.")
def restore_components(proj):
"""Write any carried component the packaged template is missing.
The registry ships .ts and drops .tsx, so Card.tsx and Root.tsx are absent
from every installed copy and `npx remotion render Card` has no Card
composition to render. card_sources.py carries them as data — a .py file
ships — and they are written back here.
Returns the names restored; empty when running from a repo checkout, where
the real .tsx files are present and win.
"""
written = []
for name, text in card_sources.FILES.items():
dst = proj / "src" / name
if dst.exists():
continue
dst.parent.mkdir(parents=True, exist_ok=True)
dst.write_text(text)
written.append(name)
return written
def ensure_project(outdir):
"""Copy the template out of the plugin and install deps once."""
proj = outdir / "_project"
if not proj.exists():
shutil.copytree(TEMPLATE, proj)
(proj / "public").mkdir(exist_ok=True)
restored = restore_components(proj)
if restored:
print(f"Restored {', '.join(restored)} (dropped by packaging)",
file=sys.stderr)
if not (proj / "node_modules").exists():
print("Installing Remotion (first run only, several minutes)...",
flush=True)
r = subprocess.run(["npm", "install", "--no-audit", "--no-fund"],
cwd=proj, capture_output=True, text=True)
if r.returncode != 0:
sys.exit(f"npm install failed:\n{r.stderr[-1200:]}")
return proj
def render_card(proj, card, dims, outdir):
name = card.get("name") or card["title"][:40].strip().replace(" ", "_")
dst = outdir / f"{name}.mp4"
props = {**DEFAULTS, **{k: v for k, v in card.items() if k != "name"}}
duration = float(props.pop("duration"))
frames = max(1, round(duration * FPS))
w, h = dims
cmd = ["npx", "remotion", "render", "Card", str(dst.resolve()),
"--props", json.dumps(props),
"--width", str(w), "--height", str(h),
"--frames", f"0-{frames - 1}",
"--log", "error"]
r = subprocess.run(cmd, cwd=proj, capture_output=True, text=True)
if r.returncode != 0:
print(f" FAILED {name}: {r.stderr.strip()[-500:]}", file=sys.stderr)
return None
return dst
def main():
ap = argparse.ArgumentParser()
ap.add_argument("cards_json")
ap.add_argument("--outdir", default="work/cards")
ap.add_argument("--resolution", choices=sorted(RESOLUTIONS), default="1080",
help="Match the mezzanine the project was normalized at.")
args = ap.parse_args()
require_node()
spec = json.loads(Path(args.cards_json).read_text())
cards = spec["cards"] if isinstance(spec, dict) else spec
if not cards:
sys.exit("No cards in the spec — nothing to render.")
for c in cards:
if not c.get("title"):
sys.exit(f"Every card needs a 'title': {json.dumps(c)[:120]}")
outdir = Path(args.outdir)
outdir.mkdir(parents=True, exist_ok=True)
proj = ensure_project(outdir)
dims = RESOLUTIONS[args.resolution]
done, failed = [], []
for c in cards:
print(f"Rendering card: {c['title'][:60]}...", flush=True)
out = render_card(proj, c, dims, outdir)
(done if out else failed).append(c.get("name") or c["title"])
print(json.dumps({"rendered": len(done), "failed": failed,
"outdir": str(outdir),
"resolution": f"{dims[0]}x{dims[1]}"}, indent=2))
if failed:
sys.exit(1)
print(f"\nAdd these to raw/ (or reference directly) and probe them — they "
f"are ordinary {dims[0]}x{dims[1]} clips from here on.",
file=sys.stderr)
if __name__ == "__main__":
main().tessl-plugin
evals
skills
reel-builder
assets
references
scripts
yap-writer