CtrlK
BlogDocsLog inGet started
Tessl Logo

andrebrov/broll-sourcing

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

Quality

93%

Does it follow best practices?

Run evals on this skill

Adds up to 20 points to the overall score

View guide

SecuritybySnyk

Low

Low-risk findings worth noting

Overview
Quality
Evals
Security
Files

finish_reel.pyscripts/

#!/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()

SKILL.md

tile.json