render-proof-points-overlay
Build the deterministic PIL pill overlays for a 'perfect-score + proof-points' UGC video ad (white 10/10 score header with a medal + orange sub with a finger-down + 3-4 green-check proof pills) and composite them onto a base clip in a diagonal L->R->L->R cascade, then mux the mus
Install
npx skills add https://github.com/gooseworks-ai/goose-skills/tree/main/skills/ads/capabilities/render-proof-points-overlay
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install gooseworks-ai-goose-skills@llmmart
git clone https://github.com/gooseworks-ai/goose-skills.git
The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole gooseworks-ai/goose-skills collection as a plugin from our marketplace. Git is the plain clone.
Skill manifest
render-proof-points-overlay
Build the deterministic PIL/FFmpeg overlays for the "Instagram comparison-tool reviewer" UGC ad — a white "we got a perfect 10/10 score" headline pill (trailing medal), an orange "but here's also why you'll love us" sub pill (trailing finger-down, width-matched to the header), and 3-4 green-check proof pills — then composite them onto a base clip in the format's signature diagonal cascade and mux the music into the master. FREE and deterministic: the pills are PIL-rendered so the score, checks, and wordmark stay pixel-crisp (a video model would smear type). The base clip (create-image-fal keyframe -> create-video-fal i2v) and the music bed (create-music-elevenlabs) come from separate paid capabilities; this one only does the free rendering.
Run
fetch_icons.py --run-dir
Scripts
fetch_icons.py— downloads the three Twemoji PNGs (medal 1f3c5, finger-down 1f447, check 2705) to<run>/assets/icons. PIL cannot render Apple Color Emoji, so pills paste Twemoji PNGs. Free, local.build_overlays.py— PIL: renders the white score header (trailing medal), the orange subhead (trailing finger-down, width-matched to the header), and N green-check proof pills auto-sized to their copy. Bold weight and icon-centered-on-pill-middle are load-bearing.compose_master.py— FFmpeg: scale/crop the base clip to 1080x1920@30, composite the always-on headers, cascade the proof pills (eachenable='gte(t,T)'on its own alternating LEFT/RIGHT row), mux the music, apply the anti-AI grain pass, re-encode crf23/maxrate12M -> master-final.mp4.
Contract
- Deterministic + FREE (PIL + FFmpeg); no paid calls, no AI-rendered text — the score, checks, and wordmark are composited, never generated.
- Config-driven off one
config.json(overlays,layout,duration_sec, optionalmusic/post_production); the template recipe supplies the config fromrecipe.config. - Always re-run
build_overlays.pybeforecompose_master.py— the compositor reads pre-rendered PNGs and silently reuses stale ones on a copy change. - Headers stay on 0-duration and must not cover the bottle face; proof pills cascade one-per-beat down the diagonal (NOT four-corners) — the cascade is the format's signature.
- Requires
Pillow+ffmpeg. No API keys.
Files (goose-skills)
-
scripts
-
build_overlays.py 6.7 KB
#!/usr/bin/env python3 """Build the pill PNG overlays for a "perfect-score + proof-points" ad. Config-driven (reads config.json) so the same engine renders any brand: - one persistent white SCORE header pill (trailing medal), - one persistent orange SUBHEAD pill (trailing finger-down), - 3-4 green-CHECK proof pills (leading check), each auto-sized to its copy. The reference look (Origins Nutra / SpoiledChild E27) is: bold rounded pills, Twemoji icons pasted as PNGs (PIL cannot render Apple Color Emoji), icons vertically centered on the pill middle. See SKILL.md Phase 3 for the rules. Usage: build_overlays.py --config config.json --out-dir <run>/generated/overlays """ import argparse import json import pathlib from PIL import Image, ImageDraw, ImageFont # ---- font resolution (bold is load-bearing: regular reads as a generic UI card) ---- FONT_CANDIDATES = [ "/System/Library/Fonts/Supplemental/Arial Bold.ttf", "/System/Library/Fonts/Supplemental/Arial Rounded Bold.ttf", "/System/Library/Fonts/HelveticaNeue.ttc", "/System/Library/Fonts/SFNS.ttf", "/System/Library/Fonts/Supplemental/Arial.ttf", ] def load_font(size): for p in FONT_CANDIDATES: try: return ImageFont.truetype(p, size) except Exception: continue return ImageFont.load_default() def measure(draw, text, font): b = draw.textbbox((0, 0), text, font=font) return b[2] - b[0], b[3] - b[1] class Icons: """Loads Twemoji PNGs from an icons dir (check.png / medal.png / finger-down.png).""" def __init__(self, icons_dir): self.dir = pathlib.Path(icons_dir) self.cache = {} def get(self, name): if name not in self.cache: self.cache[name] = Image.open(self.dir / name).convert("RGBA") return self.cache[name] def painter(self, name): def _paint(target, x, y, size): icon = self.get(name).resize((size, size), Image.LANCZOS) target.alpha_composite(icon, (x, y)) return _paint def rounded_pill(lines, font, pad_x=36, pad_y=14, bg=(255, 255, 255, 255), fg=(0, 0, 0, 255), radius=28, trailing_icon=None, leading_icon=None, icon_scale=0.95, icon_line=-1, min_width=0, min_height=0): """Render one rounded-rect pill. Icon is sized off single-line cap-height and pasted inline (trailing = right end of `icon_line`; leading = left of line 0). Both icons vertically center on the pill's geometric middle.""" dummy = Image.new("RGBA", (10, 10)) d = ImageDraw.Draw(dummy) cap = d.textbbox((0, 0), "Hg", font=font) cap_h = cap[3] - cap[1] widths, heights = [], [] for ln in lines: w, h = measure(d, ln, font) widths.append(w) heights.append(h) line_gap = 6 text_h = sum(heights) + line_gap * (len(lines) - 1) icon_pad = max(8, int(cap_h * 0.15)) icon_size = int(cap_h * icon_scale) target = icon_line if icon_line >= 0 else len(lines) + icon_line extra = (icon_pad + icon_size) if trailing_icon else 0 text_w = max(max(widths), widths[target] + extra) lead_extra = (icon_pad + icon_size) if leading_icon else 0 box_w = max(min_width, text_w + 2 * pad_x + lead_extra) box_h = max(min_height, text_h + 2 * pad_y) img = Image.new("RGBA", (box_w, box_h), (0, 0, 0, 0)) d = ImageDraw.Draw(img) d.rounded_rectangle([0, 0, box_w - 1, box_h - 1], radius=radius, fill=bg) text_x = pad_x + lead_extra line_h = max(heights) if heights else 0 block_h = len(lines) * line_h + line_gap * (len(lines) - 1) block_top = (box_h - block_h) // 2 icon_y = (box_h - icon_size) // 2 if leading_icon: leading_icon(img, pad_x, icon_y, icon_size) for i, ln in enumerate(lines): cy = block_top + i * (line_h + line_gap) + line_h // 2 d.text((text_x, cy), ln, font=font, fill=fg, anchor="lm") if trailing_icon and i == target: trailing_icon(img, text_x + widths[i] + icon_pad, icon_y, icon_size) return img def tup(v): return tuple(v) if isinstance(v, list) else v def main(): ap = argparse.ArgumentParser() ap.add_argument("--config", required=True) ap.add_argument("--icons-dir", default=None, help="defaults to <run>/assets/icons") ap.add_argument("--out-dir", required=True) args = ap.parse_args() cfg = json.loads(pathlib.Path(args.config).read_text()) out = pathlib.Path(args.out_dir) out.mkdir(parents=True, exist_ok=True) icons_dir = args.icons_dir or (pathlib.Path(args.config).resolve().parent / "assets" / "icons") icons = Icons(icons_dir) ov = cfg["overlays"] # 1. white SCORE header (trailing medal on line index icon_line, default 0) h = ov["header"] hdr1 = rounded_pill(h["lines"], load_font(h.get("font_size", 54)), pad_x=h.get("pad_x", 38), pad_y=h.get("pad_y", 14), bg=tup(h.get("bg", [255, 255, 255, 245])), fg=tup(h.get("fg", [15, 15, 15, 255])), radius=h.get("radius", 34), trailing_icon=icons.painter(h.get("icon", "medal.png")), icon_scale=h.get("icon_scale", 1.35), icon_line=h.get("icon_line", 0)) hdr1.save(out / "01-header-white.png") print(f"[ov] 01 white header {hdr1.size}") # 2. orange SUBHEAD (width-matched to header so the two stack cleanly) s = ov["subhead"] hdr2 = rounded_pill(s["lines"], load_font(s.get("font_size", 50)), pad_x=s.get("pad_x", 34), pad_y=s.get("pad_y", 10), bg=tup(s.get("bg", [244, 92, 50, 245])), fg=tup(s.get("fg", [255, 255, 255, 255])), radius=s.get("radius", 30), trailing_icon=icons.painter(s.get("icon", "finger-down.png")), icon_scale=s.get("icon_scale", 1.30), min_width=hdr1.size[0]) hdr2.save(out / "02-header-orange.png") print(f"[ov] 02 orange sub {hdr2.size}") # 3-N. green-check proof pills (auto-sized to content — NO min_width) pf = ov["proof_points"] font_check = load_font(ov.get("proof_font_size", 40)) for i, pp in enumerate(pf, start=3): p = rounded_pill([pp["line_a"], pp["line_b"]], font_check, pad_x=24, pad_y=14, radius=24, bg=(255, 255, 255, 248), fg=(15, 15, 15, 255), leading_icon=icons.painter("check.png"), icon_scale=1.0) name = f"{i:02d}-check-{pp.get('slug', i)}.png" p.save(out / name) print(f"[ov] {name} {p.size}") print(f"[ov] built {2 + len(pf)} overlays -> {out}") if __name__ == "__main__": main() -
compose_master.py 4.3 KB
#!/usr/bin/env python3 """Composite the pill overlays onto the base clip in a diagonal cascade, mux the music bed, apply the anti-AI grain pass -> master-final.mp4. Config-driven so it handles any 3-4 proof-point count. Assumes overlays already built into <run>/generated/overlays by build_overlays.py (the one_shot.py driver runs that first). Positions + reveal times from config.layout. Cascade signature: pills alternate LEFT / RIGHT down the frame, each revealed at its own time via overlay enable='gte(t,T)' — eye follows L->R->L->R on the beat. Usage: compose_master.py --config config.json --run-dir <run> Output: <run>/master-final.mp4 """ import argparse import json import pathlib import subprocess def main(): ap = argparse.ArgumentParser() ap.add_argument("--config", required=True) ap.add_argument("--run-dir", required=True) ap.add_argument("--no-music", action="store_true", help="skip music mux (silent master)") args = ap.parse_args() cfg = json.loads(pathlib.Path(args.config).read_text()) run = pathlib.Path(args.run_dir) gen = run / "generated" ov = gen / "overlays" lay = cfg["layout"] dur = cfg.get("duration_sec", 10) clip = gen / "clip-handheld.mp4" header = ov / "01-header-white.png" subhead = ov / "02-header-orange.png" pills = sorted(ov.glob("[0-9][0-9]-check-*.png")) if not pills: raise SystemExit("no proof pills found in generated/overlays — run build_overlays.py first") rows = lay["pill_rows_y"] times = lay["reveal_times"] left_x = lay.get("pill_left_x", 40) right_margin = lay.get("pill_right_margin", 40) inputs = ["-i", str(clip), "-i", str(header), "-i", str(subhead)] for p in pills: inputs += ["-i", str(p)] # base scale/crop to 1080x1920 @ 30fps fc = [ "[0:v]scale=1080:1920:force_original_aspect_ratio=increase," "crop=1080:1920,setsar=1,fps=30[base]", f"[base][1]overlay={lay['header_x']}:{lay['header_y']}[v1]", f"[v1][2]overlay={lay['header_x']}:{lay['subhead_y']}[v2]", ] prev = "v2" for i, _p in enumerate(pills): idx = 3 + i # ffmpeg input index for this pill y = rows[i % len(rows)] t = times[i % len(times)] x = str(left_x) if i % 2 == 0 else f"(W-w-{right_margin})" out = f"vp{i}" fc.append(f"[{prev}][{idx}]overlay={x}:{y}:enable='gte(t,{t})'[{out}]") prev = out composite = gen / "composite-no-audio.mp4" subprocess.run([ "ffmpeg", "-y", "-hide_banner", "-loglevel", "error", *inputs, "-filter_complex", ";".join(fc), "-map", f"[{prev}]", "-c:v", "libx264", "-pix_fmt", "yuv420p", "-crf", "18", "-preset", "medium", "-movflags", "+faststart", "-t", str(dur), str(composite), ], check=True) print(f"[composite] {composite}") # grain master + music mux. grain: eq + hqdn3d + noise; re-encode crf23/maxrate12M # (noise inflates bitrate — see memory feedback_grain_pass_inflates_bitrate). out = run / "master-final.mp4" grain = "[0:v]eq=contrast=1.06:saturation=0.93,hqdn3d=1.5:1.5:3:3,noise=alls=9:allf=t+u[v]" music = gen / "music-bed.m4a" if args.no_music or not music.exists(): subprocess.run([ "ffmpeg", "-y", "-hide_banner", "-loglevel", "error", "-i", str(composite), "-filter_complex", grain, "-map", "[v]", "-c:v", "libx264", "-pix_fmt", "yuv420p", "-crf", "23", "-maxrate", "12M", "-bufsize", "24M", "-preset", "medium", "-movflags", "+faststart", "-t", str(dur), str(out), ], check=True) else: subprocess.run([ "ffmpeg", "-y", "-hide_banner", "-loglevel", "error", "-i", str(composite), "-i", str(music), "-filter_complex", grain, "-map", "[v]", "-map", "1:a:0", "-c:v", "libx264", "-pix_fmt", "yuv420p", "-crf", "23", "-maxrate", "12M", "-bufsize", "24M", "-preset", "medium", "-c:a", "aac", "-b:a", "192k", "-movflags", "+faststart", "-t", str(dur), str(out), ], check=True) print(f"[master] {out}") subprocess.run([ "ffprobe", "-v", "error", "-show_entries", "stream=codec_name,width,height,r_frame_rate,duration", "-show_entries", "format=duration,bit_rate", "-of", "default=nw=1", str(out), ]) if __name__ == "__main__": main() -
fetch_icons.py 1.1 KB
#!/usr/bin/env python3 """Download the three Twemoji PNGs the pills need into <run>/assets/icons. PIL cannot render Apple Color Emoji (horizontal-bar artifacts) — we paste 72x72 Twemoji PNGs instead (see memory feedback_pil_emoji_use_twemoji_png). Free, local. Usage: fetch_icons.py --run-dir <run> """ import argparse import pathlib import urllib.request CDN = "https://cdn.jsdelivr.net/gh/jdecked/twemoji@latest/assets/72x72" ICONS = { "medal.png": "1f3c5", # 🏅 score header "finger-down.png": "1f447", # 👇 subhead "check.png": "2705", # ✅ proof pills } def main(): ap = argparse.ArgumentParser() ap.add_argument("--run-dir", required=True) args = ap.parse_args() out = pathlib.Path(args.run_dir) / "assets" / "icons" out.mkdir(parents=True, exist_ok=True) for name, cp in ICONS.items(): dst = out / name if dst.exists(): print(f"[icons] have {name}") continue urllib.request.urlretrieve(f"{CDN}/{cp}.png", dst) print(f"[icons] {name} <- {cp}.png") if __name__ == "__main__": main()
-
-
tests
-
smoke-test.md 686 B
# Smoke Test fetch_icons.py --run-dir <run> ; build_overlays.py --config config.json --out-dir <run>/generated/overlays ; compose_master.py --config config.json --run-dir <run> --no-music — deterministic, $0. Pass when: - `fetch_icons.py` lands medal.png / finger-down.png / check.png in `<run>/assets/icons`. - `build_overlays.py` emits `01-header-white.png`, `02-header-orange.png`, and one `NN-check-*.png` per proof point into `generated/overlays`. - `compose_master.py` produces a valid 1080x1920 `master-final.mp4` with the headers always on and the proof pills revealing in the L->R->L->R cascade. - No paid provider call is made (this capability is FREE and deterministic).
-
-
SKILL.md 3.2 KB
--- name: render-proof-points-overlay description: Build the deterministic PIL pill overlays for a 'perfect-score + proof-points' UGC video ad (white 10/10 score header with a medal + orange sub with a finger-down + 3-4 green-check proof pills) and composite them onto a base clip in a diagonal L->R->L->R cascade, then mux the music bed into master-final.mp4. Config-driven (config.json), 1080x1920 9:16, FREE and deterministic (no paid calls, text stays pixel-crisp). Use for the overlay-proof-points format; the base clip + music come from separate paid capabilities. status: active --- # render-proof-points-overlay Build the deterministic PIL/FFmpeg overlays for the "Instagram comparison-tool reviewer" UGC ad — a white "we got a perfect 10/10 score" headline pill (trailing medal), an orange "but here's also why you'll love us" sub pill (trailing finger-down, width-matched to the header), and 3-4 green-check proof pills — then composite them onto a base clip in the format's signature diagonal cascade and mux the music into the master. FREE and deterministic: the pills are PIL-rendered so the score, checks, and wordmark stay pixel-crisp (a video model would smear type). The base clip (create-image-fal keyframe -> create-video-fal i2v) and the music bed (create-music-elevenlabs) come from separate paid capabilities; this one only does the free rendering. ## Run fetch_icons.py --run-dir <run> ; build_overlays.py --config config.json --out-dir <run>/generated/overlays ; compose_master.py --config config.json --run-dir <run> — reads <run>/generated/clip-handheld.mp4 + generated/music-bed.m4a, writes <run>/master-final.mp4. 1080x1920, deterministic, $0. (Add --no-music to compose for a silent design preview.) ## Scripts - `fetch_icons.py` — downloads the three Twemoji PNGs (medal 1f3c5, finger-down 1f447, check 2705) to `<run>/assets/icons`. PIL cannot render Apple Color Emoji, so pills paste Twemoji PNGs. Free, local. - `build_overlays.py` — PIL: renders the white score header (trailing medal), the orange subhead (trailing finger-down, width-matched to the header), and N green-check proof pills auto-sized to their copy. Bold weight and icon-centered-on-pill-middle are load-bearing. - `compose_master.py` — FFmpeg: scale/crop the base clip to 1080x1920@30, composite the always-on headers, cascade the proof pills (each `enable='gte(t,T)'` on its own alternating LEFT/RIGHT row), mux the music, apply the anti-AI grain pass, re-encode crf23/maxrate12M -> master-final.mp4. ## Contract - Deterministic + FREE (PIL + FFmpeg); no paid calls, no AI-rendered text — the score, checks, and wordmark are composited, never generated. - Config-driven off one `config.json` (`overlays`, `layout`, `duration_sec`, optional `music`/`post_production`); the template recipe supplies the config from `recipe.config`. - Always re-run `build_overlays.py` before `compose_master.py` — the compositor reads pre-rendered PNGs and silently reuses stale ones on a copy change. - Headers stay on 0-duration and must not cover the bottle face; proof pills cascade one-per-beat down the diagonal (NOT four-corners) — the cascade is the format's signature. - Requires `Pillow` + `ffmpeg`. No API keys. -
skill.meta.json 331 B
{ "slug": "render-proof-points-overlay", "category": "capabilities", "domain": "ads", "tags": [ "ads" ], "installation": { "base_command": "npx goose-skills install render-proof-points-overlay", "supports": [ "claude", "cursor", "codex" ] }, "requires_skills": [ "watch" ] }
Comments (0)
Sign in to join the conversation.
Reviews (0)
No reviews yet.
No comments yet.