Drop in one talking-head video and get it finished — transcribe it, search the web for supporting b-roll (article screenshots, memes, images, video clips), present the asset list for approval, then hand off to reel-builder to normalize audio, burn subtitles, cut the b-roll in, and export for Instagram and LinkedIn. Use when the user drops a video and wants illustrations/screenshots/memes added, asks to "edit this like CapCut", wants b-roll found for them, or wants a talking-head take prepped for Instagram/LinkedIn. Sourcing and approval only — all rendering belongs to the reel-builder skill.
74
93%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Low
Low-risk findings worth noting
#!/usr/bin/env python3
"""Cut plan + VO -> three captioned, loudness-checked platform exports.
Collapses the whole back half of the pipeline, which is ~15 manual ffmpeg and
gen_captions invocations, into one command. Every step that went wrong at least
once in practice is now a gate that fails loudly instead of a silent default:
- exports read the CAPTIONED file, not the raw master (shipped uncaptioned once)
- loudness measured on every output and rejected outside the band (shipped -42 LUFS once)
- b-roll is padded to the target aspect, never cropped (clipped headlines once)
- --font is always passed (macOS ImageMagick has no Helvetica-Bold)
- picture and audio durations reconciled before muxing (loudnorm shifts length)
Usage:
finish_reel.py plan/cut_plan.json --vo work/vo_music.m4a --outdir exports
finish_reel.py plan/cut_plan.json --vo work/vo.m4a --formats 16:9,4:5
finish_reel.py --selftest
"""
import argparse, json, os, subprocess, sys, re
from pathlib import Path
HERE = Path(__file__).resolve().parent
REEL = HERE.parent.parent / "tessl__reel-builder" / "scripts"
FONT = "/System/Library/Fonts/SFNS.ttf"
FONT_I = "/System/Library/Fonts/SFNSItalic.ttf"
BG = "#0F1D2B"
LUFS_TARGET, LUFS_TOL = -14.0, 2.0
SYNTH_TAG = "contains-synthetic-performer"
SYNTH_NOTE = "AI-generated synthetic performer (likeness and voice)"
FORMATS = { # name: (W, H, crop_from_1920x1080, caption_position, output)
"16:9": (1920, 1080, None, 0.80, "youtube_16x9.mp4"),
"4:5": (1080, 1350, "864:1080:{x}:0", 0.80, "linkedin_feed.mp4"),
"9:16": (1080, 1920, "608:1080:{x}:0", 0.72, "ig_reels.mp4"),
}
def run(cmd, what):
r = subprocess.run(cmd, capture_output=True, text=True)
if r.returncode != 0:
sys.exit(f"{what} failed:\n{r.stderr[-1500:]}")
return r
def probe(path, entries, stream=True):
cmd = ["ffprobe", "-v", "error"]
if stream:
cmd += ["-select_streams", "v:0"]
cmd += ["-show_entries", entries, "-of", "csv=p=0", str(path)]
out = run(cmd, f"ffprobe {entries}").stdout.strip().splitlines()
return out[0] if out else ""
def lufs(path):
r = subprocess.run(["ffmpeg", "-hide_banner", "-nostats", "-i", str(path),
"-af", "ebur128", "-f", "null", "-"],
capture_output=True, text=True)
m = re.findall(r"^\s+I:\s+(-?\d+\.\d+) LUFS", r.stderr, re.M)
return float(m[-1]) if m else None
def is_synthetic(path):
"""Does the source carry the synthetic-performer marking?
Disclosure applied to the source does NOT survive this pipeline: the badge
sat top-right of a 1920-wide frame and the 9:16 window (620..1228) cropped
it clean off, while the re-encode dropped the container tags. Both were
silently lost on exactly the files that go to Instagram. So the exporter
detects the marking and re-applies it per format, after cropping.
"""
r = subprocess.run(["ffprobe", "-v", "error", "-show_entries",
"format_tags=description,comment", "-of", "default=nw=1",
str(path)], capture_output=True, text=True)
return SYNTH_TAG in r.stdout
def disclose(src, dst, W, H, crf):
"""Burn the badge into THIS format's frame and restore the metadata."""
import tempfile
pt = max(18, round(H / 42))
with tempfile.TemporaryDirectory() as td:
png = Path(td) / "b.png"
run(["magick", "-background", "#000000A6", "-fill", "#FFFFFFEB",
"-font", FONT, "-pointsize", str(pt), "label: AI-generated likeness ",
"-bordercolor", "#000000A6", "-border", "10x8", str(png)], "badge render")
run(["ffmpeg", "-v", "error", "-y", "-i", str(src), "-i", str(png),
"-filter_complex", "[0:v][1:v]overlay=W-w-(W/40):H/40",
"-c:v", "libx264", "-preset", "medium", "-crf", str(crf),
"-pix_fmt", "yuv420p", "-movflags", "+faststart", "-c:a", "copy",
"-metadata", f"comment={SYNTH_NOTE}",
"-metadata", f"description={SYNTH_TAG}", str(dst)], "disclosure burn")
return dst
def face_x(master, width):
"""Horizontal crop offset centred on the speaker; falls back to centre."""
try:
sys.path.insert(0, str(REEL))
from detect_subjects import detections, _vision
if not _vision():
raise ImportError
import tempfile, statistics
dur = float(probe(master, "format=duration", stream=False))
xs = []
with tempfile.TemporaryDirectory() as td:
for i in range(5):
p = Path(td) / f"{i}.png"
run(["ffmpeg", "-v", "error", "-ss", str(dur*(i+0.5)/5), "-i", str(master),
"-frames:v", "1", "-y", str(p)], "frame grab")
xs += [cx for k, c, cx, _ in detections(p) if k == "face" and c >= 0.6]
if xs:
return int(max(0, min(1920 - width, statistics.median(xs)*1920 - width/2))) & ~1
except Exception as e:
print(f" WARNING: face detect unavailable ({type(e).__name__}: {e or 'no Vision'}) "
f"— falling back to a CENTRE crop. Run this with .venv/bin/python so "
f"Apple Vision is importable; an off-centre speaker will be cropped out.",
file=sys.stderr)
return ((1920 - width) // 2) & ~1
def pad_broll(src_png, W, H, out):
"""Fit inside 84% and pad — never crop, so a Ken Burns push cannot eat text."""
run(["magick", str(src_png), "-resize", f"{int(W*0.84)}x{int(H*0.84)}",
"-background", BG, "-gravity", "center", "-extent", f"{W}x{H}", str(out)], "pad b-roll")
def selftest():
assert set(FORMATS) == {"16:9", "4:5", "9:16"}
for name, (w, h, crop, pos, out) in FORMATS.items():
assert 0.5 < pos < 1.0 and out.endswith(".mp4")
assert (crop is None) == (name == "16:9")
# the loudness gate must reject exactly what shipped broken before
band = lambda v: abs(v - LUFS_TARGET) <= LUFS_TOL
assert not band(-42.4) and not band(-20.6) and band(-14.8) and band(-15.6)
# The crop offset must be computed against the CROP width, never the output
# width. Using W=1080 for a 608 crop put a face at cx=0.482 outside the
# window and shipped it cut.
def offset_for(crop_w, cx):
return int(max(0, min(1920 - crop_w, cx * 1920 - crop_w / 2))) & ~1
# A badge placed on a 1920-wide source lands outside the 9:16 crop window.
# Assert the geometry that made that happen, so it cannot silently return.
crop_w_916 = int(FORMATS["9:16"][2].split(":")[0])
x916 = int(max(0, min(1920 - crop_w_916, 0.482 * 1920 - crop_w_916 / 2))) & ~1
badge_x = 1920 - 260 - 40 # right-aligned on the source
assert not (x916 <= badge_x <= x916 + crop_w_916), (
"a source-applied badge would fall inside the 9:16 window — "
"this test is no longer meaningful")
for tmpl, out_w in ((FORMATS["9:16"][2], FORMATS["9:16"][0]),
(FORMATS["4:5"][2], FORMATS["4:5"][0])):
crop_w = int(tmpl.split(":")[0])
assert crop_w < out_w, "crop is narrower than output; they are not interchangeable"
cx = 0.482
face_px = cx * 1920
# Membership is the wrong test: the buggy window still *contained* the
# face centre, 67px from its edge, and a ~400px-wide face was cut. What
# matters is how far the face sits from the window's centre.
def off_centre(x):
return abs(face_px - (x + crop_w / 2)) / crop_w
assert off_centre(offset_for(crop_w, cx)) < 0.02, f"{tmpl}: correct offset not centred"
# 9:16 lands 39% off-centre under the bug (badly cut); 4:5 lands 12.7%
# (visibly off). 10% catches both.
assert off_centre(offset_for(out_w, cx)) > 0.10, (
f"{tmpl}: the wrong-width bug would slip past this test")
print("ok")
def main():
ap = argparse.ArgumentParser(description=__doc__,
formatter_class=argparse.RawDescriptionHelpFormatter)
ap.add_argument("plan", nargs="?")
ap.add_argument("--vo", help="continuous VO track")
ap.add_argument("--music", help="music bed; mixed under the VO at --music-db")
ap.add_argument("--music-db", default="-22",
help="bed level under the voice, dB (default -22)")
ap.add_argument("--captions", default="work/captions/phrases_karaoke.json")
ap.add_argument("--broll-src", default="raw/broll", help="original screenshots")
ap.add_argument("--outdir", default="exports")
ap.add_argument("--grade", default="clean")
ap.add_argument("--formats", default="16:9,4:5,9:16")
ap.add_argument("--crf", default="18")
ap.add_argument("--selftest", action="store_true")
a = ap.parse_args()
if a.selftest:
return selftest()
if not a.plan or not a.vo:
ap.error("need a cut plan and --vo")
plan = json.loads(Path(a.plan).read_text())
# Mix the bed in here rather than expecting a pre-mixed file. Doing it
# upstream meant it could simply be forgotten — and was, once.
if a.music:
if not Path(a.music).exists():
sys.exit(f"--music {a.music} not found")
total = sum(s["duration"] for s in plan["segments"])
mixed = "work/vo_music.m4a"
Path("work").mkdir(exist_ok=True)
run(["ffmpeg", "-v", "error", "-y", "-i", a.vo, "-stream_loop", "-1", "-i", a.music,
"-filter_complex",
f"[1:a]volume={a.music_db}dB,afade=t=in:d=2,"
f"afade=t=out:st={max(0,total-3):.2f}:d=3[m];"
f"[0:a][m]amix=inputs=2:duration=first:normalize=0,"
f"loudnorm=I={LUFS_TARGET}:TP=-1.5:LRA=11[a]",
"-map", "[a]", "-t", f"{total:.3f}",
"-c:a", "aac", "-b:a", "192k", mixed], "music mix")
print(f" bed mixed at {a.music_db}dB -> {mixed}", file=sys.stderr)
a.vo = mixed
want = [f.strip() for f in a.formats.split(",")]
Path(a.outdir).mkdir(parents=True, exist_ok=True)
Path("work/broll_fin").mkdir(parents=True, exist_ok=True)
results = []
for fmt in want:
W, H, crop, pos, outname = FORMATS[fmt]
print(f"\n=== {fmt} ({W}x{H}) ===", file=sys.stderr)
# The offset belongs to the CROP width, not the output width. Passing W
# (1080) for a 608-wide crop put the window at 384..992 while the face
# sat at 925 — hard against the right edge, and cut. Parse it from the
# crop template so the two can never disagree again.
crop_w = int(crop.split(":")[0]) if crop else 0
x = face_x(plan["segments"][0]["clip"], crop_w) if crop else 0
parts, tmp = [], Path(f"work/fin_{fmt.replace(':','x')}")
tmp.mkdir(parents=True, exist_ok=True)
for i, s in enumerate(plan["segments"]):
seg = tmp / f"{i:03d}.mp4"
if s["note"].startswith("b-roll"):
stem = Path(s["clip"]).stem
png = Path(a.broll_src) / f"{stem}.png"
if not png.exists():
sys.exit(f"b-roll source missing: {png}")
padded = Path("work/broll_fin") / f"{stem}_{fmt.replace(':','x')}.png"
pad_broll(png, W, H, padded)
vf = (f"scale={W}:{H}:force_original_aspect_ratio=decrease,"
f"pad={W}:{H}:(ow-iw)/2:(oh-ih)/2:color={BG},fps=30,format=yuv420p")
cmd = ["ffmpeg", "-v", "error", "-y", "-loop", "1", "-i", str(padded),
"-t", str(s["duration"]), "-vf", vf]
else:
geom = (f"crop={crop.format(x=x)},scale={W}:{H}:flags=lanczos,"
f"fps=30,format=yuv420p") if crop else \
f"scale={W}:{H}:flags=lanczos,fps=30,format=yuv420p"
cmd = ["ffmpeg", "-v", "error", "-y", "-ss", str(s["in"]), "-i", s["clip"],
"-t", str(s["duration"]), "-vf", geom]
run(cmd + ["-an", "-c:v", "libx264", "-preset", "veryfast", "-crf", "16", str(seg)],
f"segment {i}")
parts.append(seg)
(tmp / "list.txt").write_text("".join(f"file '{p.name}'\n" for p in parts))
pic = tmp / "picture.mp4"
total = sum(s["duration"] for s in plan["segments"])
run(["ffmpeg", "-v", "error", "-y", "-f", "concat", "-safe", "0",
"-i", str(tmp/"list.txt"), "-i", a.vo, "-t", f"{total:.3f}",
"-c:v", "libx264", "-preset", "slow", "-crf", a.crf, "-pix_fmt", "yuv420p",
"-movflags", "+faststart", "-c:a", "aac", "-b:a", "192k", str(pic)], "concat+mux")
out = Path(a.outdir) / outname
run(["python3", str(REEL/"gen_captions.py"), "burn", a.captions,
"--video", str(pic), "--out", str(out), "--crf", a.crf,
"--position", str(pos), "--accent", "#F26B21",
"--font", FONT, "--font-italic", FONT_I], "caption burn")
# If the take was synthetic, every export carries the disclosure —
# re-applied here so cropping cannot remove it.
if is_synthetic(plan["segments"][0]["clip"]) or is_synthetic(a.vo):
tmp_d = out.with_name(out.stem + ".undisclosed" + out.suffix)
out.rename(tmp_d)
disclose(tmp_d, out, W, H, a.crf)
tmp_d.unlink()
if not is_synthetic(out):
sys.exit(f"{outname}: disclosure did not survive the export — refusing to ship")
print(" disclosure re-applied for this format", file=sys.stderr)
d = float(probe(out, "format=duration", stream=False))
L = lufs(out)
wh = probe(out, "stream=width,height")
ok = L is not None and abs(L - LUFS_TARGET) <= LUFS_TOL
results.append((outname, wh, d, L, ok))
print(f" {outname} {wh} {d:.2f}s {L} LUFS {'OK' if ok else 'OUT OF BAND'}",
file=sys.stderr)
print("\n=== exports ===")
for n, wh, d, L, ok in results:
print(f" {n:22} {wh:12} {d:8.2f}s {L:7.1f} LUFS {'ok' if ok else 'FAIL'}")
if any(not ok for *_, ok in results):
sys.exit("one or more exports outside the loudness band — not shippable")
durs = {round(d, 1) for *_, d, _, _ in [(r[0], r[1], r[2], r[3], r[4]) for r in results]}
if len(durs) > 1:
sys.exit(f"exports disagree on duration: {durs} — a stale file is present")
if __name__ == "__main__":
main()