CtrlK
BlogDocsLog inGet started
Tessl Logo

gamussa/reels-producer-skill

help quicky produce instagram reels and youtube shorts

80

1.95x
Quality

93%

Does it follow best practices?

Impact

47%

1.95x

Average score across 1 eval scenario

SecuritybySnyk

Passed

No known issues

Overview
Quality
Evals
Security
Files

export_variants.pyskills/reel-builder/scripts/

#!/usr/bin/env python3
"""Export platform variants from the master render.

All platforms want 1080x1920 H.264 + AAC, but tuned:
  tiktok    - CRF 19, loudness -14 LUFS, <=287s
  ig_reels  - CRF 18, loudness -14 LUFS (IG recompresses hard; feed it quality)
  yt_shorts - CRF 18, loudness -14 LUFS, BT.709 tags explicit

Also grabs a thumbnail JPEG at --thumb-at seconds, and — when a Gemini API
key is present (GEMINI_API_KEY / GOOGLE_AI_API_KEY / GOOGLE_API_KEY) — asks
stylize_thumbnail.py for sport-magazine variants (modern + 80s) so the human
can pick a cover from all three. Styled thumbs are a bonus: any failure there
warns and continues, never blocks the export.

Usage: export_variants.py work/master.mp4 --outdir exports/ --thumb-at 1.5 \
           [--thumb-text "HOOK LINE"] [--no-stylize]
"""
import argparse, os, subprocess, sys
from pathlib import Path

PLATFORMS = {
    "tiktok":    {"crf": "19"},
    "ig_reels":  {"crf": "18"},
    "yt_shorts": {"crf": "18"},
}


def run(cmd, label):
    r = subprocess.run(cmd, capture_output=True, text=True)
    if r.returncode != 0:
        sys.exit(f"{label} failed:\n{r.stderr[-800:]}")


def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("master")
    ap.add_argument("--outdir", default="exports")
    ap.add_argument("--thumb-at", type=float, default=1.5)
    ap.add_argument("--thumb-text", help="Cover headline for the stylized "
                                         "thumbnails (magazine-cover mode)")
    ap.add_argument("--no-stylize", action="store_true",
                    help="Skip Nano Banana thumbnail styling even if an API "
                         "key is present")
    args = ap.parse_args()

    outdir = Path(args.outdir)
    outdir.mkdir(parents=True, exist_ok=True)

    # write to .tmp.mp4 and rename on success so a partial export can never
    # be mistaken for a deliverable
    for name, cfg in PLATFORMS.items():
        dst = outdir / f"{name}.mp4"
        tmp = outdir / f"{name}.tmp.mp4"
        print(f"Exporting {name}...", flush=True)
        run(["ffmpeg", "-y", "-i", args.master,
             "-c:v", "libx264", "-preset", "medium", "-crf", cfg["crf"],
             "-profile:v", "high", "-level", "4.1", "-pix_fmt", "yuv420p",
             "-colorspace", "bt709", "-color_primaries", "bt709", "-color_trc", "bt709",
             "-c:a", "aac", "-b:a", "192k", "-ar", "48000",
             "-af", "loudnorm=I=-14:TP=-1.0:LRA=11",
             "-movflags", "+faststart", str(tmp)], name)
        tmp.rename(dst)

    thumb = outdir / "thumbnail.jpg"
    thumb_tmp = outdir / "thumbnail.tmp.jpg"
    run(["ffmpeg", "-y", "-ss", str(args.thumb_at), "-i", args.master,
         "-frames:v", "1", "-q:v", "2", str(thumb_tmp)], "thumbnail")
    thumb_tmp.rename(thumb)

    # optional: sport-magazine styled variants of the same frame
    has_key = any(os.environ.get(v) for v in
                  ("GEMINI_API_KEY", "GOOGLE_AI_API_KEY", "GOOGLE_API_KEY"))
    if args.no_stylize:
        pass
    elif not has_key:
        print("(no Gemini API key — skipping stylized thumbnails; plain "
              "frame grab only)")
    else:
        cmd = [sys.executable, str(Path(__file__).parent / "stylize_thumbnail.py"),
               str(thumb), "--style", "all", "--outdir", str(outdir)]
        if args.thumb_text:
            cmd += ["--text", args.thumb_text]
        r = subprocess.run(cmd)
        if r.returncode != 0:
            print("WARNING: thumbnail styling failed — continuing with the "
                  "plain frame grab", file=sys.stderr)

    print(f"\nDeliverables in {outdir}/:")
    for f in sorted(outdir.iterdir()):
        print(f"  {f.name} ({f.stat().st_size/1e6:.1f} MB)")


if __name__ == "__main__":
    main()

tile.json