render-creator-pip-listicle
Assemble a creator picture-in-picture product-listicle ad from a config — the creator stays FULL-FRAME the whole beat (voice plus lips generated together per beat, no separate VO, no cut to a full-frame product shot), and on each product beat three persistent overlays ride on top
Install
npx skills add https://github.com/gooseworks-ai/goose-skills/tree/main/skills/ads/capabilities/render-creator-pip-listicle
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-creator-pip-listicle
Assemble a creator picture-in-picture product listicle ad from a config: an AI creator counts down N products in the brand's own voice, and the creator stays FULL-FRAME the whole time — there is NO cut to a full-frame product shot, ever. On each product beat, three persistent overlays ride on top of the full-frame creator for the WHOLE beat: (1) a title pill top-center (persistent, it carries the listicle title), (2) the DEMO in a rounded PiP window top-right — the brand's real UGC clip (MUTED), or for a no-UGC brand the product's own demo (a real screen-recording, or an autocropped high-res product-UI/dashboard still sized to fill the window), and (3) a product card pinned bottom (rounded thumbnail left + "N · CATEGORY" small-caps + product NAME in a serif face like Georgia). Hook + CTA beats are the creator full-frame with the title pill only (no PiP/card). The creator's voice + lips are generated together, natively per beat — there is no separate voiceover. This capability is the FREE, deterministic assembly — build the per-beat overlay PNG, cover-scale the creator clip + composite the overlay, concat all beats, and burn the captions.
scripts/config.example.json is the worked example (DIBS Beauty "5 products that replaced my whole
makeup bag", ~46s 1080×1920 9:16, a hook + 5 product beats + a CTA); scripts/PIPELINE.md maps
every config block to its source step and scripts/README.md documents the free assembly.
Run
This is the FREE, deterministic assembly stage — it spends nothing beyond the caption burn. The
paid inputs are separate capabilities — the creator anchor (create-image-fal, Seedream v5 Pro,
model bytedance/seedream/v5/pro/text-to-image with no fal-ai/ prefix) and one native Seedance
talking clip per beat (create-video-fal, model bytedance/seedance-2.0/reference-to-video,
generate_audio=ON, the SAME seed across beats so the face holds, 720p default). Given those native
clips + the brand's real UGC demo clips (or the product's own autocropped UI stills / screen
recordings) + the real product photos + the brand palette + the title copy,
render-creator-pip-listicle builds ONE full-1080×1920 transparent overlay PNG per beat (title pill
always; + demo PiP top-right + bottom product card + rank number on product beats), cover-scales the
creator clip to 1080×1920 and overlays the beat's overlay PNG for the whole beat while keeping the
native audio, concats all beats, and burns the captions last → the master. Re-cuts reuse the existing
native clips + overlays and cost $0.
Anchor gotcha (Seedream v5 Pro, not gpt-image-2/Seedream-v4). Seedance 2.0's partner-validation
gate REJECTS photoreal faces from gpt-image-2 AND Seedream v4 ("may contain likenesses of real
people"); a Seedream v5 Pro face passes. Generate a FRESH anchor with create-image-fal,
model bytedance/seedream/v5/pro/text-to-image (no fal-ai/ prefix) — reusing an existing photoreal
face from another project also trips the gate.
Clip gotchas (Seedance 2.0). A REJECTED submit STILL bills via the fal-proxy — pre-flight ONE test clip (the hook) and confirm it renders before firing the batch. Presigned anchor URLs expire ~1h → re-host the anchor if a batch runs long (else "Failed to download the file" mid-batch). The intermittent fal "User is locked: Exhausted balance" is the proxy's upstream fal account (not your GooseWorks credits) → retry with backoff.
Contract (the free assembly)
- Creator FULL-FRAME the whole beat — NO cut to a full-frame product shot. Every beat is ONE
continuous full-frame creator clip; the product content lives in overlays, never in a full-frame
cutaway.
per_beat_shots = 1. - Native creator audio carries the reel — no separate VO. Each beat's voice + lips come from ONE
Seedance take (
generate_audio=ON); this stage never adds a VO or a lip-sync pass. The creator clip's native audio plays continuous across the whole beat; the demo PiP's audio is muted (else the voice doubles). - Three persistent overlays on each product beat. For the WHOLE beat, on top of the full-frame
creator: (a) the title pill top-center (persistent,
title_pill.on=true— carries the listicle title, kept short to fit one line); (b) the DEMO PiP top-right; (c) the product card bottom. Hook + CTA carry the title pill only. - The DEMO PiP is autocropped to FILL its window. It's the brand's REAL UGC clip (MUTED, never AI-regenerated) — OR, for a brand with NO UGC (B2B/SaaS), the product's own demo: a real screen-recording, or an AUTOCROPPED high-res product-UI/dashboard still. Autocrop the still (trim transparent/near-white margins) and size the PiP window to the cropped content's aspect ratio so it fills the window — no letterbox whitespace; a WIDE screenshot → a SHORT + WIDE window, a tall/square one → a taller window. Rounded window, white hairline border, soft drop shadow, top-right. Disclose in the review when the demo is a still/mockup rather than a real UGC clip.
- Products are REAL photos — never AI-render the product. The bottom product card (rounded thumbnail + "N · CATEGORY" small-caps + product NAME in a serif face like Georgia) comes from the brand's real product photo / UI thumbnail; a product with no clean photo falls back to a brand-color tile with the wordmark, never an AI product render.
- Persistent title pill, rank number on the card. The title pill is persistent top-center (this format's identity, on by default). A counting rank number (1..N) is rendered on the product card on product beats.
- Captions burned LAST as PIL PNG overlays, brand-accent, deterministic. This ffmpeg has no
libass → render each ~2-word cue as a timed PIL PNG overlay
(
overlay=…:enable='between(t,s,e)'): white words + a brand-accent underline, black stroke for legibility, positioned CLEAR of the PiP (top) and the card (bottom) (mid-to-lower band). Time them DETERMINISTICALLY from the known per-beat script — the fal-ai/whisper proxy is unreliable (900s timeouts); do NOT depend on it. The known script is the brand-correct source, so brand tokens are always spelled right. - FFmpeg composite, deterministic, FREE. Per beat: build ONE full-1080×1920 transparent overlay
PNG (title pill; + PiP + card + rank number on product beats), cover-scale the creator clip to
1080×1920, overlay the PNG for the whole beat, keep the native audio. Concat all beats with the
concat demuxer @ 30fps / yuv420p → a 1080×1920 h264+aac master, then burn captions last. Probe
durations with
ffprobe -of csv=p=0(NOT-of default=nk=1:np=1, which errors on some builds). No paid calls in the composite/stitch, no keys.
Files (goose-skills)
-
scripts
-
config.example.json 11.5 KB
{ "_comment": "DIBS Beauty '5 products that replaced my whole makeup bag' creator-pip-listicle — THE WORKED EXAMPLE. Copy to config.json and edit. A ~46s 9:16 (1080x1920, 30fps) AI-creator talking-head PRODUCT LISTICLE with NATIVE Seedance audio: an AI creator counts down 5 DIBS products, and the creator stays FULL-FRAME the WHOLE time — there is NO cut to a full-frame product shot. On each product beat three persistent overlays ride on top of the full-frame creator for the whole beat: (1) a title pill top-center (carries the listicle title), (2) the DEMO in a rounded PiP window top-right (the brand's real UGC clip MUTED — or, for a no-UGC brand, the product's own autocropped UI still / screen-recording sized to fill the window), (3) a product card pinned bottom (rounded thumbnail + 'N · CATEGORY' small-caps + serif product name). Hook + CTA are the creator full-frame with the title pill only. Voice + lips are generated TOGETHER in one Seedance take per beat (no separate VO, no lip-sync bolt-on); DIBS-pink 2-word captions burned LAST as PIL PNG overlays. Pipeline: (1) lock the conversational beat script -> native-beats.json; (2) lock the creator anchor (Seedream v5 Pro candid portrait, realism-gated); (3) one native Seedance clip per beat (generate_audio=ON, SAME seed); (4) FREE demo-PiP prep (autocrop) + product cards; (5) FREE single-shot full-frame composite (build the per-beat overlay PNG, cover-scale + overlay, native audio kept); (6) FREE stitch + PIL-PNG caption burn. Master 1080x1920 h264+aac, ~46s. See scripts/PIPELINE.md for the source steps.", "brand_name": "DIBS Beauty", "campaign": "5 products that replaced my whole makeup bag", "title": "5 products that replaced my whole makeup bag", "width": 1080, "height": 1920, "fps": 30, "aspect_ratio": "9:16", "duration_sec": 46, "listicle": { "count": 5, "hook_pattern": "N products that replaced my whole [X]" }, "beats": [ {"name": "00-hook", "dialogue": "Okay — five products that genuinely replaced my whole makeup bag.", "duration": 6, "pip": null, "card": null, "badge": null, "show_title": true}, {"name": "01-did", "dialogue": "First, the Desert Island Duo. Blush and bronzer in one stick, I just swipe it on with my fingers.", "duration": 8, "pip": "01-did", "card": "01-did", "badge": 1, "show_title": true}, {"name": "02-lovebeam", "dialogue": "Then the LoveBeam Perfector. I'm obsessed — it's primer, highlight, and skincare in one.", "duration": 7, "pip": "02-lovebeam", "card": "02-lovebeam", "badge": 2, "show_title": true}, {"name": "03-status", "dialogue": "The Status Stick on your collarbones gives this lit-from-within glow that's never greasy.", "duration": 7, "pip": "03-status", "card": "03-status", "badge": 3, "show_title": true}, {"name": "04-glowtour", "dialogue": "Glow Tour Duo is blush and contour together — two swipes and my face looks snatched.", "duration": 7, "pip": "04-glowtour", "card": "04-glowtour", "badge": 4, "show_title": true}, {"name": "05-no-pressure", "dialogue": "And the No Pressure liner with the Go-To balm stays put through everything.", "duration": 6, "pip": "05-no-pressure", "card": "05-no-pressure", "badge": 5, "show_title": true}, {"name": "06-cta", "dialogue": "Fewer products, better face. You're welcome.", "duration": 5, "pip": null, "card": null, "badge": null, "show_title": false} ], "_note_beats": "duration = clamp(ceil(words / 2.3) + 1, 4, 11) s (Seedance native pace ~2.3 words/s). Hook + CTA carry no PiP/card/badge (title pill only). Each of the 5 product beats is ONE continuous full-frame creator shot with three persistent overlays for the whole beat: the title pill (top), the demo PiP (top-right), and the product card (bottom) with a rank number (1..5). Status Stick (03) has no clean e-comm photo -> its card thumbnail falls back to a branded brand-color tile, not an AI product render.", "creator": { "_comment": "ONE creator anchor, generated FRESH with Seedream v5 Pro (create-image-fal, model `bytedance/seedream/v5/pro/text-to-image` — NO `fal-ai/` prefix), locked once and threaded (with the SAME Seedance seed) into every native clip so the face holds. CRITICAL — Seedance 2.0's partner-validation gate REJECTS photoreal faces from gpt-image-2 AND Seedream v4 ('may contain likenesses of real people'); a Seedream v5 Pro face PASSES. Use a FRESHLY generated image (reusing an existing photoreal face also trips the gate). Realism-gated.", "anchor": "assets/characters/anchor.png", "method": "anchor-ref", "image_model": "bytedance/seedream/v5/pro/text-to-image", "realism": "plain, calm, candid head-and-shoulders portrait on a neutral wall — un-retouched skin (visible pores, faint freckles, fine lines, slightly uneven tone), minimal makeup, no perfect teeth, soft even indoor daylight. AVOID 'beautiful/perfect/8k/hyperreal/studio'.", "descriptor": "Woman ~27, warm medium skin, minimal makeup, casual at-home top, relaxed friendly delivery to camera." }, "clip_engine": { "_comment": "ONE Seedance 2.0 reference-to-video call PER BEAT with generate_audio=ON — voice + lips generated TOGETHER (no separate VO, no lip-sync bolt-on). Dialogue INLINE in the prompt. SAME seed across beats off the creator anchor. 720p default deliverable. BILLING: a REJECTED submit STILL bills via the fal-proxy — pre-flight ONE test clip (the hook) before firing the batch. Presigned anchor URLs expire ~1h -> re-host if a batch runs long. Intermittent fal 'User is locked: Exhausted balance' is the proxy's upstream account (not GooseWorks credits) -> retry with backoff.", "provider": "fal", "model": "bytedance/seedance-2.0/reference-to-video", "mode": "reference-to-video", "resolution": "720p", "native_audio": true, "seed_locked": true, "duration_formula": "clamp(ceil(words / 2.3) + 1, 4, 11)" }, "pip": { "_comment": "The DEMO in a rounded window top-right, played picture-in-picture for the whole product beat with audio MUTED (the creator beat's native audio is the only voice). It's the brand's REAL existing UGC ad (one per product beat, NEVER AI-regenerated) — OR, for a brand with NO UGC (B2B/SaaS), the product's own demo: a real screen-recording, or an autocropped high-res product-UI/dashboard still. AUTOCROP the demo (trim transparent/near-white margins) and size the window to the cropped content's aspect ratio so it FILLS the window (no letterbox whitespace); a WIDE screenshot -> a SHORT + WIDE window. Source: source/pip-candidates/pip-<slug>.mp4 (UGC) or working/demo/demo-<slug>.png (product-UI still).", "content_source": "source/pip-candidates/pip-<slug>.mp4 (real UGC clip) OR working/demo/demo-<slug>.png (autocropped product-UI still / screen-recording)", "position": "top-right", "top_y": 175, "right_margin_px": 30, "max_w": 520, "max_h": 600, "fit": "AUTOCROP to remove transparent/near-white margins, then size the window to the cropped content's aspect ratio so it FILLS the window (no letterbox whitespace).", "border": "white hairline ~5px, rounded ~26px, soft drop shadow", "tag": "a small dark '▶ DEMO' pill bottom-left of the window (a real UGC clip may instead show its view-count badge; do NOT fabricate view counts for a product demo)", "audio": "MUTED" }, "product_cards": { "_comment": "FREE bottom-pinned card PNG pinned on the whole product beat: rounded product/UI thumbnail LEFT, then 'N · CATEGORY' (rank + short category, DIBS-pink, small caps) and the product NAME in a serif face (Georgia/Times) below. From the real product photo (or a brand-pink 'dibs.' tile if no photo). Script: working/scripts/render_product_cards.py.", "engine": "pil", "script": "working/scripts/render_product_cards.py", "card_h": 300, "palette": { "pink": "#E27098", "paper": "#FBF6F2", "ink": "#281C20" }, "output": "assets/overlays/product-card-<slug>.png", "no_photo_fallback": "brand-pink tile with 'dibs.' wordmark in place of the thumbnail" }, "title_pill": { "_comment": "PERSISTENT title pill top-center — faithful to the source video, which carries the listicle title the whole reel. ON by default (this format's identity). A DIBS text bar rendered once as a PNG at top-center; keep the copy short so it fits one line.", "on": true, "asset": "assets/overlays/title-pill.png", "position": "top-center", "top_y": 55 }, "rank_badge": { "_comment": "A counting rank number (1..5) rendered on the product CARD (badge circle / 'N ·' prefix) on product beats.", "on": true, "anchored_to": "card" }, "composite": { "_comment": "FREE single-shot full-frame composite: each beat is ONE continuous full-frame creator shot with persistent overlays (NO cut to a full-frame product shot). Per beat: build ONE full-1080x1920 transparent overlay PNG (title pill on all beats; + demo PiP top-right + bottom product card + rank number on product beats), scale the creator clip to 1080x1920 (cover), overlay the beat's overlay PNG for the WHOLE beat, keep the native audio. PiP audio muted. Script: working/scripts/composite_pip.py.", "engine": "ffmpeg", "script": "working/scripts/composite_pip.py", "per_beat_shots": 1, "model": "creator FULL-FRAME the whole beat + persistent overlays (title pill + demo PiP top-right + product card bottom). NO cut to a full-frame product shot.", "overlay": "Build ONE full-1080x1920 transparent overlay PNG per beat (title pill; + PiP + card on product beats; title only on hook/CTA), then overlay it on the creator clip (scaled cover to 1080x1920) for the whole beat. Native audio preserved." }, "stitch": { "_comment": "FREE. Concat the per-beat composites @ 30fps / yuv420p -> the pre-caption master. Probe durations with `ffprobe -of csv=p=0` (NOT `default=nk=1:np=1`, which errors on some builds).", "engine": "ffmpeg-concat-demuxer", "fps": 30, "pix_fmt": "yuv420p", "output": "finals-native/master-pip.mp4" }, "captions": { "_comment": "Burned LAST as timed PIL PNG overlays (this ffmpeg has NO libass — no subtitles/ass filter). White ~2-word chunks with the DIBS-pink accent underline, positioned CLEAR of the top-right PiP and the bottom card. Timed DETERMINISTICALLY from the known per-beat script (the fal-ai/whisper proxy is unreliable, 900s timeouts — do NOT depend on it); the known script is the brand-correct source so brand tokens are always spelled right.", "on": true, "engine": "PIL PNG overlays (overlay=...:enable='between(t,s,e)')", "style": "white ~2-word chunks, DIBS-pink underline bar, black stroke", "position": "mid-to-lower, clear of the PiP (top) and card (bottom)", "source": "the known per-beat script, distributed across each beat's time window (deterministic)", "last": true }, "post_production": { "native_audio": { "default": "on", "note": "the Seedance take's native voice IS the bed (no separate VO); PiP audio muted; the creator clip's native audio plays continuous across the whole beat" }, "captions": { "default": "on", "note": "white ~2-word chunks, DIBS-pink accent, PIL PNG overlays, deterministic from the known script, burned LAST, clear of PiP+card" }, "title_pill": { "default": "on", "note": "persistent DIBS title pill top-center — this format's identity" }, "rank_badge": { "default": "on", "note": "counting rank number (1..5) on the product card on product beats" } } } -
PIPELINE.md 8.2 KB
# Pipeline — creator-pip-listicle How `config.example.json` maps to the real production steps. This capability ships a **config + this map**, not a bundled runner: the worked example (DIBS Beauty "5 products that replaced my whole makeup bag") was produced by the video-orchestrator's per-state steps plus a set of per-project drivers (`render_product_cards.py`, `composite_pip.py`) and the shared native-Seedance driver (`gen_seedance_native.py`). The steps run **in order** because each depends on the last: the beat script sets the per-beat durations, the creator anchor + seed drive the native talking clips, the real demo + product photos drive the PiP + product cards, the composite overlays them on the full-frame creator, and the caption burn lands last on the stitched master. ## Field → source-script map | Config field | Phase | Source step / script (in the run) | Paid? | |---|---|---|---| | `beats[]` (dialogue / duration / pip / card / badge / show_title), `listicle` | 1 Script | concept + conversational script lock → `native-beats.json` | free | | `creator.anchor`, `creator.image_model`, `creator.realism`, `creator.descriptor` | 2 Creator | **Seedream v5 Pro** candid portrait (`bytedance/seedream/v5/pro/text-to-image`, no `fal-ai/` prefix), realism-gated | **PAID** | | `clip_engine`, `beats[].dialogue` (native talking clip) | 3 Clips | Seedance 2.0 reference-to-video (`bytedance/seedance-2.0/reference-to-video`), dialogue inline, `generate_audio=ON`, SAME seed, 720p → `creator-beat-<name>.mp4` | **PAID** (largest spend) | | `pip` (autocropped demo) | 4 Overlays | demo prep: brand's real UGC clip → `pip-<slug>.mp4`, OR autocrop a product-UI still → `demo-<slug>.png` (fill the window) | free | | `product_cards` | 4 Overlays | `render_product_cards.py` (real product photo/UI thumbnail + brand type → bottom-pinned PNG) → `product-card-<slug>.png` | free | | `title_pill`, `rank_badge`, `composite.overlay` | 5 Composite | ONE full-1080×1920 transparent overlay PNG per beat (title pill; + PiP + card + rank number on product beats) | free | | `composite` (single-shot full-frame + overlay), `beats[].duration` | 5 Composite | `composite_pip.py` (cover-scale the creator clip to 1080×1920, overlay the beat's PNG, keep native audio) → `composite-<name>.mp4` | free | | `stitch` | 6 Stitch | ffmpeg concat demuxer @ 30fps / yuv420p → `master-pip.mp4` | free | | `captions` | 6 Captions | PIL PNG caption overlays, timed deterministically from the known script, burned last (`overlay=…:enable='between(t,s,e)'`) | free | ## 1. Script → beat metadata (config: `beats`, `listicle`) [paid stage owner — not this cap] Lock a first-person countdown (a hook + N product beats + a CTA) and convert to `native-beats.json`: per beat `{name, dialogue, duration, pip, card, badge, show_title}`. `duration = clamp(ceil(words / 2.3) + 1, 4, 11)` s (Seedance native pace ≈ 2.3 words/s). Hook + CTA carry no PiP/card/badge (title pill only); each product beat carries a PiP slug + a card slug + a rank number. Keep each sentence whole on its beat. ## 2. Creator → Seedream v5 Pro candid anchor (config: `creator`) [PAID — `create-image-fal`] One **Seedream v5 Pro** candid portrait — `create-image-fal`, model `bytedance/seedream/v5/pro/text-to-image` (**no `fal-ai/` prefix**). A plain, calm, candid head-and-shoulders portrait on a neutral wall — un-retouched (pores, freckles, fine lines, no perfect teeth), soft even indoor daylight. AVOID "beautiful/perfect/8k/hyperreal/studio". Gate on realism → `anchor.png`. **CRITICAL:** Seedance 2.0's partner-validation gate REJECTS photoreal faces from **gpt-image-2 AND Seedream v4** ("may contain likenesses of real people") — Seedream v5 Pro **passes**. Use a **FRESHLY generated** image (reusing an existing photoreal face also trips the gate). The approved anchor + the **same seed** are threaded into every native talking clip so the creator holds beat to beat. ## 3. Native talking clips → Seedance 2.0 reference-to-video (config: `clip_engine`, `beats[].dialogue`) [PAID — `create-video-fal`] `gen_seedance_native.py` reads `native-beats.json`, puts each beat's `dialogue` **inline in the prompt**, and generates one Seedance 2.0 reference-to-video clip per beat off the creator anchor — model `bytedance/seedance-2.0/reference-to-video`, `generate_audio=ON`, the **SAME seed across beats**, 720p default → `creator-beat-<name>.mp4`. The voice + lips are generated **together** — no separate VO, no lip-sync bolt-on. The largest spend, so **GATE it**: a **REJECTED submit STILL bills** via the fal-proxy → **pre-flight the HOOK clip alone** and confirm it renders before firing the rest. Presigned anchor URLs expire ~1h → **re-host** the anchor if a batch runs long (else "Failed to download the file" mid-batch). The intermittent fal "User is locked: Exhausted balance" is the proxy's upstream fal account (not your GooseWorks credits) → retry with backoff. ## 4. Demo PiP prep + product cards → autocrop + PIL (config: `pip`, `product_cards`) [FREE — this cap] - **Demo (PiP):** per product beat, provide the demo shown in the top-right PiP: (a) the brand's **REAL UGC** ad → `pip-<slug>.mp4` (MUTED, never AI-regenerated); OR (b) for a brand with **NO UGC** (B2B/SaaS), the product's **own** demo — a real screen-recording, or an **AUTOCROPPED** high-res product-UI/dashboard still → `demo-<slug>.png`. Autocrop each still (trim transparent/near-white margins) and size the PiP window to the cropped content's aspect ratio so it **FILLS the window** — no letterbox whitespace (a **WIDE screenshot → a SHORT + WIDE window**). Disclose in the review when the demo is a still/mockup rather than a real UGC clip. - **Product cards** (`render_product_cards.py`): a bottom-pinned rounded card PNG — rounded product/UI thumbnail left, then "N · CATEGORY" (rank + short category, brand-accent, small caps) and the product NAME in a serif face (Georgia/Times) below, brand palette — from the real product photo (or a brand-color tile with the wordmark if no photo) → `product-card-<slug>.png`. Pinned bottom on the whole product beat. ## 5. Single-shot full-frame composite → `composite_pip.py` (config: `composite`, `pip`, `rank_badge`, `title_pill`) [FREE — this cap] Each beat is **ONE continuous full-frame creator shot** with persistent overlays — **there is NO cut to a full-frame product shot** (`per_beat_shots = 1`). Per beat: 1. **Build the overlay PNG.** Compose ONE full-1080×1920 transparent PNG: the **title pill** top-center (all beats); + on product beats the **demo PiP** top-right (autocropped, the window sized to the demo's aspect so it fills — no whitespace), the **bottom product card** (thumbnail + "N · CATEGORY" + serif name), and the **rank number** on the card. 2. **Composite.** Scale the creator clip to 1080×1920 (**cover**), overlay the beat's overlay PNG for the **WHOLE beat**, and keep the creator clip's **native audio** (the demo PiP's audio is muted so the voice never doubles) → `composite-<name>.mp4`. Hook + CTA are the full-frame creator with the title pill only (no PiP/card). ## 6. Stitch + captions → ffmpeg concat + PIL PNG overlays (config: `stitch`, `captions`) - **Stitch [FREE]:** concat the per-beat composites with the ffmpeg concat demuxer @ 30fps / yuv420p → `master-pip.mp4`. Probe durations with `ffprobe -of csv=p=0` (NOT `-of default=nk=1:np=1`, which errors on some builds). - **Captions [FREE]:** burn **LAST** as **timed PIL PNG overlays** — this ffmpeg has **no libass** (no `subtitles`/`ass` filter). Render each ~2-word cue as a PIL PNG (white words + brand-accent underline, black stroke) and composite with `overlay=…:enable='between(t,s,e)'`, positioned **CLEAR of the top-right PiP and the bottom card** (mid-to-lower band). Time them **DETERMINISTICALLY** from the known per-beat script — the fal-ai/whisper proxy is unreliable (900s timeouts); do NOT depend on it. The known script is the brand-correct source, so brand tokens are always spelled right. Re-cuts (new beat durations, a swapped card, a re-cropped demo, a caption re-chunk, toggled overlays) reuse the existing native clips + overlays and cost **$0** — only the anchor and the N native clips spend. -
README.md 5.8 KB
# render-creator-pip-listicle scripts — the FREE assembly `render-creator-pip-listicle` is the **deterministic, $0 assembly stage** of the creator-pip-listicle format. The paid stages (the creator anchor, the N per-beat native Seedance talking clips) are separate capabilities — `create-image-fal` (**Seedream v5 Pro**) and `create-video-fal` (**Seedance 2.0**). This capability takes those native clips + the brand's real UGC demo clips (or the product's own autocropped UI stills / screen recordings) + the real product photos + the brand palette + the title copy and stitches the finished master. Re-cuts (new beat durations, a swapped card, a re-cropped demo, a caption re-chunk, toggled overlays) reuse the existing native clips + overlays and cost **$0**. `config.example.json` is the worked example (DIBS Beauty "5 products that replaced my whole makeup bag", ~46s 1080×1920). `PIPELINE.md` maps every config block to its source step. This README documents the FREE assembly pieces that `render-creator-pip-listicle` owns. **The one thing to get right:** the creator stays **FULL-FRAME the whole beat** — there is **NO cut to a full-frame product shot, ever**. The product content lives entirely in overlays that ride on top of the full-frame creator. ## 0. The paid inputs (separate caps) — Seedream v5 Pro anchor + Seedance 2.0 clips - **Anchor** (`create-image-fal`): ONE **Seedream v5 Pro** candid portrait — model `bytedance/seedream/v5/pro/text-to-image` (**no `fal-ai/` prefix**). Seedance 2.0's partner-validation gate REJECTS photoreal faces from **gpt-image-2 AND Seedream v4** ("may contain likenesses of real people"); a **Seedream v5 Pro** face passes. Use a **FRESH** image. - **Clips** (`create-video-fal`): ONE Seedance 2.0 reference-to-video call per beat — model `bytedance/seedance-2.0/reference-to-video`, `generate_audio=ON`, SAME seed across beats, 720p default. A **REJECTED submit STILL bills** via the fal-proxy → **pre-flight ONE test clip (the hook)** first. Presigned anchor URLs expire ~1h → re-host if a batch runs long. The intermittent fal "User is locked: Exhausted balance" is the proxy's upstream account → retry with backoff. ## 1. Demo PiP prep (autocrop) + product cards — REAL demo + REAL product photos, no AI product - **Demo (PiP):** the demo shown in the top-right PiP is the brand's **REAL UGC clip** (MUTED, never AI-regenerated) → `pip-<slug>.mp4`; OR, for a brand with **NO UGC** (B2B/SaaS), the product's own demo — a real screen-recording, or an **AUTOCROPPED** high-res product-UI/dashboard still → `demo-<slug>.png`. Autocrop each still (trim transparent/near-white margins) and size the PiP window to the cropped content's aspect ratio so it **FILLS the window — no letterbox whitespace** (a **WIDE screenshot → a SHORT + WIDE window**). Disclose in the review when the demo is a still/mockup rather than a real UGC clip. - **Product cards** (`render_product_cards.py`): a bottom-pinned rounded card PNG — rounded product/UI thumbnail left, then "N · CATEGORY" (rank + short category, brand-accent, small caps) and the product NAME in a serif face (Georgia/Times) below, brand palette — from the real product photo (or a brand-color tile with the wordmark if no photo). Pinned bottom on the whole product beat. The product is **never** AI-regenerated (i2v mangles the label into gibberish). ## 2. Single-shot full-frame composite + persistent overlays Each beat is **ONE continuous full-frame creator shot** with persistent overlays (`composite_pip.py`) — **there is NO cut to a full-frame product shot** (`per_beat_shots = 1`). On each **product** beat, three overlays ride on top of the full-frame creator for the **whole beat**: (a) the **title pill** top-center, (b) the **demo PiP** top-right (autocropped, window sized to the demo's aspect so it fills), (c) the **product card** pinned bottom with a rank number. Hook + CTA are the full-frame creator with the **title pill only** (no PiP/card). Per beat: build ONE full-1080×1920 transparent overlay PNG (title pill; + PiP + card + rank number on product beats), scale the creator clip to 1080×1920 (**cover**), overlay the PNG for the whole beat. ## 3. Native audio — continuous across the beat, no separate VO The creator beat's native Seedance audio (voice + lips generated together in the take) plays **continuous across the whole beat**, and the demo PiP clip's own audio is **muted** so the voice never doubles. There is **no separate VO** and **no lip-sync bolt-on**. ## 4. Captions — burned LAST as PIL PNG overlays, brand-accent, deterministic This ffmpeg has **no libass** (no `subtitles`/`ass` filter), so captions are rendered as **timed PIL PNG overlays**: each ~2-word cue is a PIL PNG (white words + a brand-accent underline, black stroke) composited with ffmpeg `overlay=…:enable='between(t,s,e)'`, positioned **CLEAR of the top-right PiP and the bottom card** (mid-to-lower band). Time them **DETERMINISTICALLY** from the known per-beat script — the fal-ai/whisper proxy is unreliable (900s timeouts); do **not** depend on it. The known script is the brand-correct source, so brand tokens are always spelled right. Burned **last**, over the stitched master. ## 5. FFmpeg composite FFmpeg stitches the master: build the per-beat overlay PNG (title pill + demo PiP + product card + rank number), cover-scale the creator clip to 1080×1920, overlay the PNG for the whole beat while keeping the native audio, hard-concat all beats with the concat demuxer @ 30fps / yuv420p → a 1080×1920 h264 + aac master, then burn the PIL-PNG captions last. Probe durations with `ffprobe -of csv=p=0` (NOT `-of default=nk=1:np=1`, which errors on some builds). The native creator audio IS the bed — no separate VO. Deterministic, no paid calls in the composite/stitch, no keys.
-
-
tests
-
smoke-test.md 2.5 KB
# Smoke Test Given one native talking-head clip per beat (voice + lips generated together — no separate VO), the brand's real UGC demo clips (or the product's own autocropped UI stills / screen recordings), the real product photos, and the brand palette, `render-creator-pip-listicle` assembles the master: build ONE full-1080×1920 transparent overlay PNG per beat (title pill; + demo PiP top-right + bottom product card + rank number on product beats), cover-scale the creator clip to 1080×1920 and overlay the PNG for the whole beat while keeping the native audio, hard-concat all beats @ 30fps, and burn the captions last as PIL PNG overlays → 1080×1920 h264+aac (~46s). Pass when the assembly runs to a valid MP4 and: - structure is a hook → N product beats → CTA; the creator is **FULL-FRAME the whole beat** — there is **NO cut to a full-frame product shot** (`per_beat_shots = 1`); hard-concat the per-beat composites; - on each product beat, three persistent overlays ride on the full-frame creator for the whole beat: the **title pill** top-center, the **demo PiP** top-right, and the **bottom product card**; hook + CTA carry the title pill only; - the demo PiP is **autocropped to FILL its window** (no letterbox whitespace; a wide UI still → a short + wide window), sits top-right with a hairline border, and is **MUTED**; the product card is pinned bottom and legible (rounded thumbnail + "N · CATEGORY" small-caps + serif name); a counting rank number rides the product card on product beats; the title pill is persistent top-center; - the creator's native audio is continuous across each beat — the spoken voice is the entire audio (no separate VO); the same creator face holds across every beat; - captions are white ~2-word chunks positioned CLEAR of the PiP (top) and card (bottom), rendered as timed PIL PNG overlays (this ffmpeg has no libass), timed deterministically from the known per-beat script, brand tokens spelled correctly; - the products are the REAL product photos / real UGC (or the product's own real UI still) — never AI-regenerated (no mangled labels); - **no paid call is made in the composite/stitch stage** — the creator anchor comes from `create-image-fal` (Seedream v5 Pro, `bytedance/seedream/v5/pro/text-to-image`, no `fal-ai/` prefix) and the N native clips from `create-video-fal` (Seedance 2.0, `bytedance/seedance-2.0/reference-to-video`); this assembly is $0, and a re-cut reuses the existing native clips + overlays.
-
-
SKILL.md 8.1 KB
--- name: render-creator-pip-listicle description: Assemble a creator picture-in-picture product-listicle ad from a config — the creator stays FULL-FRAME the whole beat (voice plus lips generated together per beat, no separate VO, no cut to a full-frame product shot), and on each product beat three persistent overlays ride on top for the WHOLE beat — a title pill top-center, the DEMO in a rounded PiP window top-right (the brand's real UGC clip MUTED, or for a no-UGC brand the product's own autocropped UI still / screen-recording sized to fill the window), and a bottom product card (rounded thumbnail plus 'N · CATEGORY' small-caps plus product NAME in a serif face). Hook plus CTA beats are the creator full-frame with the title pill only. Assembly builds ONE full-1080x1920 transparent overlay PNG per beat, overlays it on the creator clip (cover-scaled to 1080x1920) for the whole beat keeping the native audio, concats all beats, then burns captions LAST as timed PIL PNG overlays (this ffmpeg has no libass) timed deterministically from the known per-beat script. This is the FREE deterministic assembly stage (overlay-PNG build plus cover-scale composite plus concat plus PIL-PNG caption burn); the creator anchor and the N native talking clips come from create-image-fal (Seedream v5 Pro) and create-video-fal (Seedance 2.0). Use for the creator-pip-listicle format. status: active --- # render-creator-pip-listicle Assemble a **creator picture-in-picture product listicle** ad from a config: an AI creator counts down N products in the brand's own voice, and **the creator stays FULL-FRAME the whole time — there is NO cut to a full-frame product shot, ever.** On each product beat, three persistent overlays ride on top of the full-frame creator for the WHOLE beat: (1) a **title pill** top-center (persistent, it carries the listicle title), (2) the **DEMO** in a rounded **PiP window top-right** — the brand's real UGC clip (MUTED), or for a no-UGC brand the product's own demo (a real screen-recording, or an autocropped high-res product-UI/dashboard still sized to fill the window), and (3) a **product card** pinned bottom (rounded thumbnail left + "N · CATEGORY" small-caps + product NAME in a serif face like Georgia). Hook + CTA beats are the creator full-frame with the **title pill only** (no PiP/card). The creator's voice + lips are generated **together, natively** per beat — there is no separate voiceover. This capability is the **FREE, deterministic assembly** — build the per-beat overlay PNG, cover-scale the creator clip + composite the overlay, concat all beats, and burn the captions. `scripts/config.example.json` is the worked example (DIBS Beauty "5 products that replaced my whole makeup bag", ~46s 1080×1920 9:16, a hook + 5 product beats + a CTA); `scripts/PIPELINE.md` maps every config block to its source step and `scripts/README.md` documents the free assembly. ## Run This is the **FREE, deterministic** assembly stage — it spends nothing beyond the caption burn. The paid inputs are separate capabilities — the creator anchor (`create-image-fal`, **Seedream v5 Pro**, model `bytedance/seedream/v5/pro/text-to-image` with **no `fal-ai/` prefix**) and one native Seedance talking clip per beat (`create-video-fal`, model `bytedance/seedance-2.0/reference-to-video`, `generate_audio=ON`, the SAME seed across beats so the face holds, 720p default). Given those native clips + the brand's real UGC demo clips (or the product's own autocropped UI stills / screen recordings) + the real product photos + the brand palette + the title copy, `render-creator-pip-listicle` builds ONE full-1080×1920 transparent overlay PNG per beat (title pill always; + demo PiP top-right + bottom product card + rank number on product beats), cover-scales the creator clip to 1080×1920 and overlays the beat's overlay PNG for the **whole beat** while keeping the native audio, concats all beats, and burns the captions last → the master. Re-cuts reuse the existing native clips + overlays and cost **$0**. **Anchor gotcha (Seedream v5 Pro, not gpt-image-2/Seedream-v4).** Seedance 2.0's partner-validation gate REJECTS photoreal faces from **gpt-image-2 AND Seedream v4** ("may contain likenesses of real people"); a **Seedream v5 Pro** face passes. Generate a **FRESH** anchor with `create-image-fal`, model `bytedance/seedream/v5/pro/text-to-image` (no `fal-ai/` prefix) — reusing an existing photoreal face from another project also trips the gate. **Clip gotchas (Seedance 2.0).** A **REJECTED submit STILL bills** via the fal-proxy — **pre-flight ONE test clip (the hook)** and confirm it renders before firing the batch. Presigned anchor URLs expire ~1h → re-host the anchor if a batch runs long (else "Failed to download the file" mid-batch). The intermittent fal "User is locked: Exhausted balance" is the proxy's upstream fal account (not your GooseWorks credits) → retry with backoff. ## Contract (the free assembly) - **Creator FULL-FRAME the whole beat — NO cut to a full-frame product shot.** Every beat is ONE continuous full-frame creator clip; the product content lives in overlays, never in a full-frame cutaway. `per_beat_shots = 1`. - **Native creator audio carries the reel — no separate VO.** Each beat's voice + lips come from ONE Seedance take (`generate_audio=ON`); this stage never adds a VO or a lip-sync pass. The creator clip's native audio plays continuous across the whole beat; the demo PiP's audio is **muted** (else the voice doubles). - **Three persistent overlays on each product beat.** For the WHOLE beat, on top of the full-frame creator: (a) the **title pill** top-center (persistent, `title_pill.on=true` — carries the listicle title, kept short to fit one line); (b) the **DEMO PiP** top-right; (c) the **product card** bottom. Hook + CTA carry the **title pill only**. - **The DEMO PiP is autocropped to FILL its window.** It's the brand's **REAL UGC clip** (MUTED, never AI-regenerated) — OR, for a brand with **NO UGC** (B2B/SaaS), the product's own demo: a real screen-recording, or an **AUTOCROPPED** high-res product-UI/dashboard still. Autocrop the still (trim transparent/near-white margins) and size the PiP window to the cropped content's aspect ratio so it **fills the window — no letterbox whitespace**; a **WIDE screenshot → a SHORT + WIDE window**, a tall/square one → a taller window. Rounded window, white hairline border, soft drop shadow, top-right. Disclose in the review when the demo is a still/mockup rather than a real UGC clip. - **Products are REAL photos — never AI-render the product.** The bottom product card (rounded thumbnail + "N · CATEGORY" small-caps + product NAME in a serif face like Georgia) comes from the brand's real product photo / UI thumbnail; a product with no clean photo falls back to a brand-color tile with the wordmark, never an AI product render. - **Persistent title pill, rank number on the card.** The title pill is persistent top-center (this format's identity, on by default). A counting rank number (1..N) is rendered on the product **card** on product beats. - **Captions burned LAST as PIL PNG overlays, brand-accent, deterministic.** This ffmpeg has **no libass** → render each ~2-word cue as a timed PIL PNG overlay (`overlay=…:enable='between(t,s,e)'`): white words + a brand-accent underline, black stroke for legibility, positioned **CLEAR of the PiP (top) and the card (bottom)** (mid-to-lower band). Time them **DETERMINISTICALLY from the known per-beat script** — the fal-ai/whisper proxy is unreliable (900s timeouts); do NOT depend on it. The known script is the brand-correct source, so brand tokens are always spelled right. - **FFmpeg composite, deterministic, FREE.** Per beat: build ONE full-1080×1920 transparent overlay PNG (title pill; + PiP + card + rank number on product beats), cover-scale the creator clip to 1080×1920, overlay the PNG for the whole beat, keep the native audio. Concat all beats with the concat demuxer @ 30fps / yuv420p → a 1080×1920 h264+aac master, then burn captions last. Probe durations with `ffprobe -of csv=p=0` (NOT `-of default=nk=1:np=1`, which errors on some builds). No paid calls in the composite/stitch, no keys. -
skill.meta.json 331 B
{ "slug": "render-creator-pip-listicle", "category": "capabilities", "domain": "ads", "tags": [ "ads" ], "installation": { "base_command": "npx goose-skills install render-creator-pip-listicle", "supports": [ "claude", "cursor", "codex" ] }, "requires_skills": [ "watch" ] }
Comments (0)
Sign in to join the conversation.
Reviews (0)
No reviews yet.
No comments yet.