SuperAPI AI Directory

create-ugc-style-video

Design & creation

View repository

Updated 10-15 21:54

About this skill

create-ugc-style-video

Validated end-to-end on beauty-by-earth/video-01-three-product-grwm. Final master: finals/master-v12-captions-music.mp4 (~56s, 9:16 1080p, 3 product scenes + hook + endcard, total spend ~$30 for hero-quality 1080p output).

Purpose

Produce a 15-60s vertical 9:16 UGC review ad where an AI avatar talks to camera about 1-N products, using Seedance 2.0 reference-to-video on FAL. Avatar generation via NB2 (fal-ai/nano-banana-2/edit). Scenes are stitched with an L-cut HTML endcard, then sweetened with DIY karaoke captions + ElevenLabs music bed.

The architectural win: Seedance 2.0 reference-to-video generates lip-synced dialogue natively when generate_audio: true. No separate ElevenLabs VO + Whisper-locked beat manifest + Soul ID needed. Multi-shot internal cuts (4 sub-scenes per 15s call) are handled by the model in a single render.

Use when:

  • 1-4 products per ad
  • Creator talks directly to camera (review / GRWM / how-I-use-it format)
  • Body application of the product is OK to SHOW HOLDING / smell-test, but actual on-body rubbing is optional (often better to sidestep — see NSFW rules)
  • 9:16 vertical, Meta/IG/TikTok delivery
  • Avatar identity must hold across multiple scenes
  • Per-product budget ~$5-10 per 15s scene

Do NOT use for:

  • Real actor / real creator footage → recast in post or hire a creator
  • Long-form explainer videos (>1min) → use create-vo-anchored-beat-video
  • Brands where MS Hyper Motion product_showcase works (no creator on-screen) → use create-product-stopmotion-video

Inputs

Required:

  • One of:
    • Company website URL (skill scrapes top-3 product candidates from site)
    • Product URLs (1-N direct product page URLs)
    • Pre-prepared product brief (markdown with company name, brand voice, product list + images already downloaded)
  • Brand voice notes OR explicit reference to DELIVERY_STYLES.md voice (defaults to UGC-Creator-Confiding: relaxed, first-person, em-dash hesitations, softeners like "honestly" / "like" / "just")

Optional:

  • Reference video URL of a real human creator (NOT AI-generated) — passed as video_urls to Seedance for style/voice anchoring. CRITICAL: must be footage of a real person; AI-gen scenes will trigger FAL content_policy_violation.
  • Avatar portrait (if user already has one they want to lock)
  • Target duration (default: 15s per product scene + 8s hook + 4s endcard)
  • Resolution (default: 1080p; budget mode: 720p)
  • Number of products (default: 3; range 1-4)

Pre-requisites the USER must do before invoking this skill:

  • Research the brand themselves (visit website, watch 2-3 brand reference ads on Meta/TikTok, note tone)
  • Have product hero images either downloaded or accessible via direct URLs
  • Have a rough storyline preference in mind ("morning routine GRWM", "shower-to-bed routine", "weekly favorites", etc.) — skill will propose options but user picks

If the user shows up with just a website URL and no prep, skill does light scrape only. Heavier research belongs to the separate legacy/new-client-video-setup-research skill.

Composed Atoms

  • atoms/image-generation/create-image-nano-banana-2-fal — NB2 multi-image edit via FAL (fal-ai/nano-banana-2/edit). Generates the hero avatar portrait + optional wardrobe variations; also generates the endcard BG flatlay plate. Soul ID is NOT used in this architecture — a single portrait reference carries identity across all Seedance scenes.
  • atoms/planning/create-shot-list — produces the structured scene-manifest.yml (text) that drives prompt assembly. The pre-render approval gate. Encodes wardrobe variation, NSFW sidestep, sub-beat structure, continuity links per scene.
  • atoms/voiceover/format-script-to-style — formats a draft script to a DELIVERY_STYLES.md voice (default: UGC-Creator-Confiding) for use inside the Seedance prompt's audio-direction block. Used for the textual VO direction only; Seedance itself synthesizes the audio.
  • atoms/video-generation/create-video-seedance-2-fal — fires bytedance/seedance-2.0/reference-to-video via FAL with image_urls only (NEVER video_urls of prior AI-gen scenes — content-policy hard fail), generate_audio: true for native lip-sync, duration as string. The prompt template lives in this atom's docs.
  • atoms/end-cards/create-end-card-from-reference — reference + NB2 + HTML overlay endcard pattern. NB2 generates a flatlay BG via create-image-nano-banana-2-fal; HTML hyperframe overlays brand wordmark, tagline, CTA, cert footer (NB2 can't render brand text cleanly).
  • atoms/assembly/stitch-videos-ffmpeg — concat the rendered scenes. The L-cut endcard (final scene's closing audio lands over the endcard visual) is implemented inline in this molecule's Phase 8 — uses Whisper word-timestamps to find the natural sentence boundary before the CTA, then maps audio + video separately in ffmpeg.
  • atoms/captions/add-captions-klap — DIY karaoke caption pipeline (Whisper word-timestamps → ASS → ffmpeg subtitles filter; visually matches Klap output).
  • atoms/music/create-music-elevenlabs — instrumental music bed via FAL ElevenLabs Music endpoint (fal-ai/elevenlabs/music). force_instrumental: true because Seedance lip-synced VO occupies the vocal space. Mix at ~22% / -13 dB with amix=normalize=0 (per memory feedback_ffmpeg_amix_normalize.md).

Workflow

Phase 0 — Pre-flight checks

Skill should refuse and ask user to prepare first IF:

  • User has no clear product list and no website URL
  • User can't articulate which 1-4 products they want to feature
  • User has zero reference ads watched and no sense of brand voice (skill will produce generic UGC unless guided)

Skill should proceed IF:

  • Website URL provided → skill will WebFetch the brand page and propose 3 hero products
  • Product URLs provided → skill will WebFetch each and confirm images downloadable
  • Brief markdown provided → skill verifies required fields present

Phase 1 — Intake & confirm

  1. Parse input (URL / brief / explicit product set)
  2. WebFetch the brand site briefly. Pull: company name, brand voice cues, palette, top product candidates with hero images
  3. Confirm with user:
    • Product list (default 3; user can adjust 1-4)
    • Target duration per product scene (default 15s)
    • Storyline theme (skill proposes 2-3 options; e.g. "morning skincare GRWM", "before-bed wind-down routine", "products I'm obsessed with this month")
    • Resolution mode (default 1080p std tier; 720p budget mode if user explicitly asks)
    • Reference video URL of real creator? (optional — anchors style)
  4. Budget gate: show estimated total spend (Avatar NB2 + N×Seedance scenes + hook scene
    • endcard NB2 BG + music + composite). Get user OK before any paid calls.

API limits to surface to user (per FAL docs):

  • Image refs: max 9 per call
  • Video refs: max 3 per call (combined ≤15s, total ≤50 MB)
  • Audio refs: max 3 per call (combined ≤15s)
  • Total files combined: max 12
  • Duration per scene: 4-15s
  • Resolutions: 480p / 720p / 1080p

Phase 2 — Avatar generation

  1. NB2 prompt for the avatar: describe age range (early-to-mid 20s creator typical), hair (length, color, texture), eyes, light makeup, ethnicity if user specified, body framing (head + shoulders + chest), wardrobe (loose neutral top — actual wardrobe will be overridden per scene later). 1a. CRITICAL — Generate on a SEAMLESS NEUTRAL BACKDROP (cream/off-white), NOT in the target environment. The background ref is generated separately in Phase 4.6 and passed to Seedance as image_urls[2]. Baking avatar + environment into one NB2 image produces: (a) bokeh / shallow DOF on the background even with explicit "no bokeh" prompts; (b) studio-portrait priors on the avatar → AI-glossy plastic skin; (c) locked composition (creator distance to camera can't vary across sub-scenes). Memory: feedback_avatar_background_separation.md. Validated Lineage Video 01 (2026-05-26).
  2. Apply the v8 soft-skin language (do NOT use "freckles prominent", "real-person imperfections", "lived-in face"). Instead: "tiny natural imperfections", "subtle peach fuzz", "faint fine lines", "healthy young-adult skin", "light golden tan".
  3. Generate 1 hero portrait. Show to user.
  4. If user wants 1-2 variations (different angles or wardrobe) → fire 1-2 more NB2 calls.
  5. User locks the hero portrait. Save as assets/avatar/portrait-hero.png. Save variations as assets/avatar/portrait-var{N}.png.

Cost: ~$0.08 per NB2 call. Total ≤$0.24 for 1 hero + 2 variations.

Note: Skip the 8-portrait contact-sheet pattern used by older Soul ID architectures. Soul ID is not needed here — a single portrait reference image carries identity across all Seedance scenes.

Phase 3 — Storyline + script

  1. Propose 2-3 storyline options. Examples:
    • "A UGC creator showing 3 products as her one-by-one favorites in a morning routine"
    • "A wind-down evening routine ending with a self-tanner application before bed"
    • "Weekly favorites — what I keep restocking from [brand]"
  2. User picks one.
  3. Generate first-person UGC script using DELIVERY_STYLES.md (default UGC-Creator-Confiding voice). Script structure:
    • Hook line (5-10 words, the opener — "Okay so… top 3 products I'm honestly obsessed with")
    • Per-product: 3-4 lines that fit a ~15s scene with 4 sub-scenes (hook/hero/sidestep/ reaction)
    • Closer: "Highly recommend honestly" or equivalent CTA
  4. Naturalness QC (per memory feedback_script_naturalness.md):
    • Read aloud — must sound like a real creator, NOT ad copy
    • Em-dashes, ellipses, "honestly", "like", "just" as softeners
    • NO bulletpoint-y lists, NO superlatives stacked
    • 1-2 phrases per sub-scene (Seedance dialogue feels natural at ~3-4s per phrase)
  5. User approves the script.

Phase 3.7 — Scroll-stopper cold open (1s teaser) — DEFAULT (skip in recreate-mode when the hook IS the reveal)

Default: every UGC ad opens with a 1-second teaser of the payoff state BEFORE the verbal intro. Without it, the verbal hook has no chance to land — Meta/IG/TikTok viewers swipe before "Today I'm trying…" finishes.

When to SKIP the teaser:

For single-creator recreate-mode briefs where the source ad opens directly on the visual reveal moment (e.g. ad-04-style: open-mouth excited "I have been looking for..." with the surprise expression at second 0), the verbal hook IS the visual scroll-stop. Adding a separate teaser creates audio repetition (the same phrase plays twice) and weakens the natural hook delivery.

Indicators that you should skip:

  • Source/reference ad has no scroll-stopper teaser of its own
  • The first 1-2 seconds of the hook scene already show the payoff expression (excited face, open-mouth reveal, satisfied reaction)
  • The ad is a faithful single-creator recreate, not a slow-build GRWM or multi-product haul

Validated: Lineage Video 01 (2026-05-26) shipped without teaser after the first attempt felt awkward — "I finally found it" played twice (teaser + at 5.75s of Call 1). Final version (master-v3-captioned.mp4) opens directly on Call 1's open-mouth hook moment.

When the teaser still applies (Kosas-style): multi-product GRWM where the hook line is "Okay so… top 3 products I'm honestly obsessed with" (2-3s of setup before any reveal). Without the visual teaser the viewer has no payoff cue in the first second.

What it is: A 0.8-1.2s opening shot that shows the result before the setup. Examples (Kosas Lip Pulse ad-06 reference):

  • Extreme CU of already-plumped glossy lips + VO "Okay yeah, that's plumped" (lifted from the 0:31 callback)
  • Creator's surprised face mid-reaction (lifted from a later tingle beat)
  • Product mid-flick / mid-squeeze / mid-spray onto a finger
  • Mouth slightly open + sharp inhale + freeze
  • "Wait, look —" half-sentence over an off-center extreme close-up

Why this works (rubric): the viewer's brain registers "something already happened here" → curiosity gap → keeps watching to figure out what it was. The full ad then "resolves" the gap by showing the build-up.

How to source it (two options — pick ONE per ad):

Option When Cost Risk
A. Lift from a later scene The payoff beat in Phase 4's product scenes already includes the reaction/result moment (post-application reveal, tingle face, satisfaction shot). Just ffmpeg-trim 0.8-1.2s out of that scene and prepend it. $0 None — same render, same identity, same wardrobe. Default choice.
B. Generate a standalone 1-2s Seedance call The lifted slice would expose context that spoils the body (e.g. you'd see the product name before the reveal beat). Render a dedicated 1-2s Seedance scene matching wardrobe + setting + identity. Keep duration at the FAL minimum ("duration": "4", then ffmpeg-trim to 1s). ~$1.20 at 720p / ~$2.72 at 1080p Identity/wardrobe drift if you forget to seed-reuse from Call 1.

Voice-over treatment:

  • Lift the VO too if you're lifting visual (matched audio).
  • If the lifted visual has no usable VO, drop in an interjection: "Wait —", "Okay so —", "Hold on —", or a sharp inhale. Render via ElevenLabs v3 as a tiny separate clip.
  • Never silent. The audio cue is half the scroll-stop.

Composite order: [1s scroll-stopper] → [hook scene] → [product scenes] → [endcard]

Continuity rule: the scroll-stopper should NOT show a state that contradicts the hook scene's opening state. If the hook starts with the creator holding all products on the dresser, the scroll-stopper showing applied-product lips is FINE (it's a flash-forward, viewer reads it as a tease). But the scroll-stopper showing the creator outdoors when the rest of the ad is indoors is a continuity break.

Memory: feedback_ugc_scroll_stopper_cold_open.md. Reference exemplar: kosas/existing-ads/ad-06.mp4 (0:00-0:02 extreme CU lips + "okay yeah, that's plumped" — verbatim callback of the 0:31 payoff).

Phase 4 — Scene breakdown

For each of: hook + per-product scenes:

  • Wardrobe (STRICT, with negations — e.g. "cream linen tank, NOT white, NOT silk, NOT patterned"). Must vary across scenes to provide visual rhythm. Identity holds via portrait; wardrobe creates rhythm.
  • Setting (specific environment — bathroom counter / bedroom dresser / edge of bed / kitchen / etc.). Vary across scenes.
  • Lighting (morning warm window light / amber bedside lamp / kitchen overhead daylight / etc.)
  • 4 sub-scenes within each 15s product scene:
    1. (0-3s) Wide hook — establishes setting + creator + product
    2. (3-7s) Product hero — rotates product, brand label readable, focus pull
    3. (7-11s) Sidestep "application" — pump/twist/squeeze onto fingertips OR smell test. DOES NOT actually apply to body unless product is clearly safe (eye cream around eye area = OK; face wash water+lather = NOT OK; leg tanner = NOT OK)
    4. (11-15s) Reaction + recommendation — pull back to medium, satisfied closing

For the hook (8s, all products visible):

  • Single continuous shot
  • All N products on a dresser/counter with creator behind/beside
  • Hand sweep gesture to introduce
  • Script: "Okay so… top [N] [brand] products I'm honestly obsessed with"

For the endcard (HTML, 3-4s):

  • Brand wordmark + tagline + leaf/icon + "Shop Now" + brand URL + cert footer
  • Optional NB2 lifestyle BG plate (warm flat-lay style, products on linen with shadow play)
  • See atoms/end-cards/create-end-card-from-reference for the proven recipe

User approves the full scene breakdown before any Seedance calls.

Phase 4.5 — Product image pre-clean check (NEW — validated 2026-05-23)

Before assembling Seedance prompts, inspect each product reference image:

  1. Does the image show ONLY the hero product? Or does it have accessories / secondary products / score badges / lifestyle elements / overlay graphics?
  2. If clean (product-only): proceed to Phase 5.
  3. If accessories present: Seedance will fuse them onto the hero product's face (validated Bristle 2026-05-23 — vials and funnel got composited onto the Bristle box's front face). Pre-process via atoms/image-generation/create-image-nano-banana-2-fal:
    • Endpoint: fal-ai/nano-banana-2/edit
    • Prompt: explicit "remove [accessories], keep ONLY the [hero product], fill with clean background"
    • Cost: $0.08
    • Output: cleaned product reference saved to brands/<brand>/assets/product-refs/<product>-cleaned.png
  4. Pass the cleaned image as image_urls[1] to Seedance instead of the original.

Surface to operator: if pre-clean is needed, flag the issue + cost + show the cleaned result before proceeding.

Memory: feedback_nb2_clean_product_refs.md.

Phase 4.6 — Background reference image (NEW — validated 2026-05-23)

For locked indoor settings, generate a background hero image BEFORE Phase 5:

  1. Use fal-ai/nano-banana-pro (text-to-image, $0.15/1K, supports 9:16) for empty-room background generation.
  2. Prompt must include: "NO people, NO faces, NO body parts" + "Deep infinite focus — everything sharp, NO bokeh, NO shallow DOF" + composition details + aesthetic reference.
  3. Output: <video-folder>/assets/setting/<setting-name>-hero.png
  4. Pass as image_urls[2] to Seedance in Phase 5 (Image Refs block: "@Image3 = locked setting").
  5. Use the SAME background image across all calls of the same ad — different bg = "she moved rooms" continuity break.

Why: Text-only setting description yields generic-rental aesthetic. Background reference image LOCKS the aesthetic visually — far stronger than text. Validated 2026-05-23 (Bristle Video 02).

Memory: feedback_seedance_long_calls_not_short.md.

Phase 5 — Prompt assembly (per scene)

Use the proven template from content-goose/prompt-example.md (sunscreen + lipstick prompts that produced high-quality output). Per-scene prompt must include:

📱 UGC INFLUENCER [PRODUCT] REVIEW REEL (SELFIE-STYLE, NOT A COMMERCIAL)

[BLOCK 0 — anti-pattern opener]
This is NOT a cinematic commercial, NOT a [product category] ad, NOT an editorial campaign.
This is a real Instagram Reel filmed by a 20-something [demographic] creator on her iPhone
front camera, talking directly to her followers in a relaxed, confiding, friendly tone...

[Image reference assignments]
Use @Image1 as the influencer's face/identity. Use @Image2 as the exact [product] she is
reviewing — it is a [VISUAL DESCRIPTION OF THE PRODUCT] (USE THE PRE-CLEANED IMAGE from
Phase 4.5 if the source had accessories). It is NOT a [common confusion], NOT a [common
confusion]. The product and label MUST be clearly identifiable.
Use @Image3 as the LOCKED [setting] — match @Image3 exactly: [list specific visible
elements from the background hero generated in Phase 4.6]. The setting NEVER changes across
sub-scenes.

[FORMAT block]
- Vertical 9:16 phone-shot selfie aesthetic, slight handheld micro-shake
- WARDROBE (STRICT, FULLY SPECIFIED — top + bottom + hair + accessories):
  - Top: [top with negations — "NOT white-white, NOT silk, NOT patterned"]
  - Bottom: [bottom with negations — "NOT jeans, NOT pajama shorts, NOT leggings"]
  - Hair: [exact styling — "loose low ponytail at the nape with face-framing strands" works
    better than vague "messy bun"]
  - Accessories: [e.g. "thin gold hoop earrings, nothing else"]
- Setting: match @Image3 exactly
- Lighting: [lighting]
- Real, slightly imperfect skin (natural barely-any makeup), light golden tan, even skin tone

[IDENTITY block]
Same young woman as @Image1 — [age range, hair, eyes, skin tone]

[DYNAMIC FRAMING block]
The video must vary its framing across scenes like a real Reel — do NOT stay on one focal
length the whole time. Mix wide selfie / medium chest-up / close-ups.

[SCENE 1 (0-3s)] [WIDE HOOK] — [script line] + [emotion + action]
[SCENE 2 (3-7s)] [PRODUCT HERO with rotation] — [script line] + [emotion + action]
[SCENE 3 (7-11s)] [SIDESTEP application or smell test] — [script line] + [emotion + action]
[SCENE 4 (11-15s)] [REACTION + recommendation] — [script line] + [emotion + action]

[AUDIO DIRECTION]
[Voice character: tone, accent, pace. Match DELIVERY_STYLES.md voice.]
NO music in the audio (we add music in post).

[GLOBAL RULES]
- Wardrobe identical across the scene
- Product stays identical and clearly visible in her hand
- Settings consistent within this scene
- Lips remain CLOSED between dialogue beats
- [NSFW sidestep rules — see Decision Rule 2]

[STRICT REALISM RULES — Block F]
- Tiny natural imperfections, subtle peach fuzz, faint fine lines
- NOT plastic, NOT airbrushed, NOT doll-like, NOT porcelain
- NOT freckle-heavy, NOT blotchy, NOT mottled
- Lighting physics: realistic falloff, gentle shadow under jaw and nose, sun catch-light
- Skin highlights ONLY on nose tip / cheekbones / cupid's bow
- Hair: individual flyaway strands, slight frizz
- Image grain matches iPhone front camera, NOT cinema sensor

User reviews the full prompt for each scene before firing.

Phase 6 — Fire scenes (parallel where possible)

Per-product scenes (and the hook):

fal_client.subscribe(
    "bytedance/seedance-2.0/reference-to-video",
    arguments={
        "prompt": prompt,
        "image_urls": [portrait_url, product_url],  # 2 refs for per-product
        # "video_urls": ...  ← DO NOT pass AI-gen scenes here. Will 422.
        "resolution": "1080p",  # or "720p" for budget mode
        "duration": "15",        # str, NOT int — FAL schema
        "aspect_ratio": "9:16",
        "generate_audio": True,  # native lip-synced dialogue + ambient
    },
)

Hook scene (multi-product):

arguments={
    "prompt": hook_prompt,
    "image_urls": [portrait_url, product_url_1, product_url_2, product_url_3],  # up to 9
    "resolution": "1080p",
    "duration": "8",
    "aspect_ratio": "9:16",
    "generate_audio": True,
}

Cost per call (FAL pricing as of 2026-05):

  • 720p std: $0.3024/sec → $4.54 per 15s scene
  • 1080p std: token-formula → ~$10.20 per 15s scene
  • Fast tier ~20% cheaper but caps at 720p

Time per call: 5-15 min depending on FAL queue. Fire in parallel where possible.

Phase 7 — Endcard hyperframe

  1. Optional: fire NB2 multi-image with product PNGs to generate a warm flat-lay BG plate (~$0.08). Save as assets/hyperframes/endcard-bg.png.
  2. Build assets/hyperframes/endcard.html with:
    • Brand wordmark (Cormorant Garamond or brand serif)
    • Tagline (italic serif)
    • Leaf or brand icon (inline SVG)
    • CTA: "Shop Now"
    • URL: [brand].com
    • Cert footer (USDA / Leaping Bunny / EWG / brand-specific)
    • Subtle warm gradient overlays at top/bottom for text legibility against the NB2 BG
  3. Render via Playwright at design dimensions (1080×1920), output PNG.

See atoms/end-cards/create-end-card-from-reference for the proven recipe.

Phase 8 — Composite (L-cut endcard)

  1. Trim alignment:

    • Each per-product scene ends with her saying the closer at ~14-15s
    • Use Whisper word-timestamps to find the natural sentence boundary just before the final CTA word (e.g. "...beach trip." ends at 13.96s, then "Highly recommend." at 14.28-14.96s)
  2. L-cut endcard on the LAST product scene:

    • Tanner VISUAL cuts to endcard at the natural sentence boundary (~13-14s into scene)
    • Tanner AUDIO continues full length (15s) — final words ("highly recommend") play OVER the endcard wordmark
    • Endcard holds silent for an additional 2-3s after audio finishes
  3. Concat order: hook → product1 → product2 → ... → [last product with L-cut endcard]

  4. Resolution match: if scenes are mixed resolution (e.g. one 720p budget scene mixed with 1080p hero scenes), upscale 720p → 1080p with scale=1080:1920:flags=lanczos before concat. Quality loss is invisible in motion.

  5. ffmpeg concat: use concat=n=N:v=1:a=1 filter (re-encode-safe) rather than demuxer-based concat (which fails if any input differs).

Phase 9 — Captions (DIY karaoke)

  1. Whisper-transcribe each scene mp4 with word_timestamps=True and brand-name initial_prompt bias.
  2. Offset each scene's word timestamps to its master timeline position.
  3. Build ASS with karaoke \kf highlighting (yellow active word, white inactive).
  4. Style: SF Pro Display Bold, 60pt, ALL CAPS, white→yellow karaoke, MarginV=400 (bottom-third).
  5. Burn via ffmpeg -vf "subtitles='X.ass'".

See atoms/captions/add-captions-klap for the proven recipe.

Phase 10 — Music bed

  1. Generate via FAL ElevenLabs Music endpoint (fal-ai/elevenlabs/music):
    arguments={
        "prompt": "[genre + tempo + instrumentation + mood + NO vocals]",
        "music_length_ms": master_duration_ms + 500,  # 0.5s tail buffer
        "force_instrumental": True,  # critical — lip-sync VO needs vocal space
    }
    
  2. Prompt guidance:
    • Specify BPM (100-110 for energetic GRWM, 85-95 for chill evening)
    • Specify instruments (warm guitar pluck + syncopated drums + dreamy synth pad is a proven GRWM stack)
    • Mandate Strictly instrumental — NO vocals, NO singing, NO humming, NO ooh-ah
  3. Mix:
    ffmpeg -i master.mp4 -i music.mp3 \
      -filter_complex "
        [1:a]volume=0.22,afade=t=in:st=0:d=1.0,afade=t=out:st=[END-1.3]:d=1.3[music];
        [0:a][music]amix=inputs=2:duration=first:normalize=0[outa]
      " -map 0:v -map "[outa]" -c:v copy -c:a aac -b:a 192k final.mp4
    
    • normalize=0 is critical — default normalize=1 silently eats the volume filter (per memory feedback_ffmpeg_amix_normalize.md)
    • 22% volume ≈ -13 dB — VO clearly audible, music adds vibe

Cost: ~$0.40 per music gen.

Phase 10.5 — De-AI grade pass (MANDATORY for NB2+Seedance plasticity)

NB2+Seedance pipelines bake plasticity into the output: AI-glossy specular highlights on cheekbones / forehead / nose, over-pushed peachy skin saturation, too-smooth skin micro-surface. A 3-filter ffmpeg pass partially fixes this (~50-60% plasticity perception reduction) at $0 cost. Apply as the default final composite pass on every UGC ad this molecule produces.

-vf "curves=all='0/0 0.25/0.27 0.5/0.5 0.75/0.73 0.85/0.82 1/0.93',eq=saturation=0.88,noise=alls=6:allf=t"
  • curves — slight shadow lift (0.25→0.27, gives soft shadow detail) + linear mids + highlight compression 0.85→0.82, 1.0→0.93 (the key plasticity fix — kills AI specular shine on skin)
  • eq=saturation=0.88 — −12% sat (de-pushes peachy AI skin tones toward neutral phone-camera)
  • noise=alls=6:allf=t — subtle luma grain, temporal (varies per frame, adds micro-texture variation)

How to apply:

  • Apply UNIFORMLY across the whole master timeline (avatar clips + endcard) so transitions don't tonally jump
  • Apply at the composite stage as part of the concat filter_complex, not as a separate post-pass (one fewer re-encode)
  • Use CRF 22-23 on final output — temporal grain costs h264 efficiency. Validated Lineage Video 01: CRF 18 = 232 MB → CRF 23 = 24 MB at 30s 1080p with no visible quality loss
  • Burn captions in the same pass — they draw viewer eye away from face (additional plasticity masking)

When NOT to use:

  • Real human creator footage — don't degrade authentic footage
  • Animated / motion-graphics ads — grain hurts vector aesthetics
  • Tabletop product stop-motion — preserve sharp product details

Tunables:

  • Stronger AI-tell removal: noise=alls=10, saturation=0.82, top curve point 1/0.88
  • Lighter touch: noise=alls=3, saturation=0.94, top curve point 1/0.95

Memory: feedback_deai_grade_chain.md. Validated: Lineage Video 01 — recipe in lineage-hb/video-01-variety-pack-kitchen/working/build_master_v2.py.

Phase 11 — QC + delivery

  1. Run /watch:watch (or equivalent frame extract + audio check) on the final master
  2. Verify:
    • Lip-sync within ~100ms across all scenes
    • Captions in sync with spoken word (within ~200ms)
    • Music ducks under VO cleanly — VO intelligible
    • No NSFW visual snuck in (body application, water, lather, etc.)
    • L-cut endcard lands brand CTA audio over endcard wordmark visual
    • Final 9:16 1080p @ 24fps, AAC 44.1kHz stereo
  3. Upload to gooseworks for share link
  4. Move to finals/master-v[N]-captions-music.mp4

Output

  • finals/master-v[N]-captions-music.mp4 — shippable 9:16 1080p ad
  • assets/clips/scene-{hook,product1,product2,...}.mp4 — individual scenes
  • assets/hyperframes/endcard.png + endcard.html — reusable endcard
  • assets/avatar/portrait-hero.png — locked avatar
  • assets/music/grwm-bed-v1.mp3 — music bed
  • working/master-words.json — Whisper transcript with master-timeline word timestamps
  • Optional: storyboard.html with embedded scenes for review

Decision Rules

  1. Identity continuity uses image_urls, NEVER video_urls with AI-gen scenes. Passing a prior AI-generated Seedance scene as video_urls triggers FAL content_policy_violation: partner_validation_failed. The 60% video-input discount is real but only applies to REAL HUMAN reference video. For AI-avatar ads, always use image_urls only. This is the single most important rule of this molecule.

  2. NSFW sidestep for body-application products. Water + lather + face + female avatar = Seedance NSFW reject (validated 6× on BBE early in this project). Reframe ANY scene with body application as: pump-onto-fingertips, twist-up-and-smell-test, hand-only POV placement. Avoid: actual face washing motion, leg/arm body application, underarm application, water visible, lather visible. The prompt must explicitly state these as negations in the Global Rules block.

  3. Default 1080p std tier for hero quality. Skin texture and product label fidelity are meaningfully better at 1080p vs 720p. Use 720p only if user explicitly requests budget mode (saves ~55% per scene).

  4. Single product per Seedance scene. Multi-product scenes within a single Seedance call degrade label fidelity rapidly. The hook scene is the exception (it shows all products together but doesn't go macro on any single label).

  5. Vary wardrobe + setting between scenes; lock identity via portrait. Same portrait reference image across all scenes keeps Clara as Clara. Different wardrobe + setting + lighting per scene gives the ad visual rhythm. Skill must enforce this in the scene breakdown phase.

  6. End card via HTML hyperframe + L-cut. Don't generate the end card via Seedance — typography drifts at the brand level. Use HTML+CSS for crisp wordmark. Then L-cut the endcard 1-2s before the final word so closing CTA audio lands over the brand visual.

  7. Music: force_instrumental=True. Seedance lip-synced VO occupies the vocal space. Vocal music will compete and muddy the mix. Always instrumental for this architecture.

  8. Soft skin language only. Block B language must use "tiny natural imperfections, subtle peach fuzz, faint fine lines" — NOT "freckles prominent", "real-person imperfections", "lived-in face". The aggressive language amplifies into heavy/blotchy texture at NB2 + Seedance stages. Validated on BBE v7 → v8 prompt revision.

  9. NSFW reject = STOP and surface, do not auto-retry. Per memory feedback_hf_moderation_surface.md. If FAL returns content_policy_violation, surface to user with 3 options: rewrite prompt to sidestep, reframe scene as POV (no face), or skip this scene. Do not silently fall back to a different model or retry the same prompt.

  10. Budget gate before any paid calls. Show the user the total estimated cost before firing anything. Typical: 1 portrait NB2 ($0.08) + N×$10.20 product scenes + $5.44 hook

    • $0.08 endcard BG + $0.40 music + captions free = ~$30-50 for a 3-product 1080p ad.
  11. API limit awareness: Surface the 9 image / 3 video / 3 audio / 12 total limit to the user at intake. Most projects need ≤4 image refs (portrait + 3 products in the hook), well within budget.

  12. Pre-clean product reference images via NB2 edit before Seedance. Validated 2026-05-23 (Bristle Video 02). If the source product photo has accessories / secondary products / score badges / lifestyle elements, Seedance will composite them onto the hero product's face. Route through atoms/image-generation/create-image-nano-banana-2-fal ($0.08) to isolate the hero before passing to Seedance. Cheaper than rerolling a $4.50-$10.20 bad render. Memory: feedback_nb2_clean_product_refs.md.

  13. Pass a background reference image as image_urls[2] for locked indoor settings. Validated 2026-05-23. Text-only setting description yields generic-rental aesthetic. Generate via NB2 Pro (fal-ai/nano-banana-pro, $0.15) with "NO people, NO bokeh, deep infinite focus, [aesthetic descriptor]" directives. Use the SAME background image across all calls of the same ad. Memory: feedback_seedance_long_calls_not_short.md.

  14. Lock WARDROBE FULLY: top + bottom + hair + accessories in every prompt. Validated 2026-05-23: when only the top was specified, Seedance hallucinated denim shorts as the bottom. Specify all four components with negations. For confessional / single-sitting UGC, wardrobe is IDENTICAL across all calls of the same ad; vary only for GRWM / transformation formats. Memory: feedback_seedance_long_calls_not_short.md.

  15. Sequential-call continuity via SEED REUSE, not video_urls. Validated 2026-05-23 (Bristle Variant A/B test). For Call N>1 of a multi-call ad, capture Call (N-1)'s seed from its meta and pass via --seed <prior_seed> with the same image_urls. Do NOT pass the prior call's mp4 as video_urls — it HURTS continuity (hair/wardrobe/product drift increases, not decreases). video_urls is for STYLE TRANSFER from a real human reference video only. Memory: feedback_seedance_video_urls_vs_seed.md.

  16. video_urls style transfer takes longer — use 30+ min client timeout. AI-gen video as video_urls input renders in ~18 min (vs ~10 min for image-only). The default fal_client.subscribe() 10-min timeout fails. Use fal_client.submit() + manual polling for these long-poll cases. Memory: feedback_seedance_video_urls_vs_seed.md.

  17. Open with a 1s scroll-stopper teaser (MANDATORY). Composite order is [1s teaser] → [hook] → [product scenes] → [endcard]. The teaser shows the payoff state (post-application reveal, surprised reaction, product mid-squeeze) BEFORE the verbal intro. Default to lifting a 0.8-1.2s slice from a later scene via ffmpeg (free, no drift); only generate a dedicated Seedance scene if the lifted slice spoils the body. The verbal hook in the script is NOT a substitute — the teaser is a visual scroll-stop that buys the verbal hook a chance to land. Reference: kosas/existing-ads/ad-06.mp4 0:00-0:02. See Phase 3.7 for the workflow. Memory: feedback_ugc_scroll_stopper_cold_open.md.

  18. Anti-plastic post-process grade — try BEFORE rerolling on "looks AI" feedback. When a reviewer flags a Seedance render as plastic / glossy / uncanny smooth, the cheap fix is a ffmpeg grade pass (grain + slight desaturation + highlight rolloff). Validated Fuelin Video 02. Recipe:

    ffmpeg -i call.mp4 -vf "noise=alls=14:allf=t+u, eq=saturation=0.92, curves=master='0/0 0.4/0.40 0.85/0.82 1/0.92'" -c:v libx264 -crf 18 -c:a copy call-graded.mp4
    

    Closes ~50-60% of the plastic gap for $0. Limits: can't add physiology that wasn't rendered (no sweat, no damp hair, no flush) — those need a reroll with hardened prompt. Use as the FIRST move in the recovery ladder, escalate to reroll only if grade insufficient. Canonical recipe + tunables (lighter/heavier variants): feedback_deai_grade_chain.md. Fuelin-specific tuning: LEARNINGS.md #127.

  19. Apply post-process grade UNIFORMLY across all clips in a composite. Validated Fuelin Video 02 v2 vs v3: master-v2 (Call 1 raw + Call 2 graded) read as TWO different camera setups; master-v3 (both clips graded with identical filter chain) read as ONE continuous iPhone-front-camera recording. Asymmetric grade is MORE jarring than no grade. Rule: if applying anti-plastic grade or any cosmetic pass, apply the IDENTICAL filter chain to every clip in the composite — even clips that "looked fine on their own." Save the no-grade version too for revert. Memory: feedback_uniform_grade_composite.md. See LEARNINGS.md #128.

  20. Don't de-escalate physiology in continuation prompts. For multi-call ads where Call N>1 continues the same recording session, do NOT write "less out of breath," "sweat fading," "breathing settling," "soft flush fading slightly." Seedance reads de-escalation language as "render polished" → smoother skin, less micro-tension, more AI feel. Validated Fuelin Video 02: Call 1 with "POST-RUN: sheen of sweat, flushed cheeks, damp strands" → raw real-feel face. Call 2 with "CONTINUING POST-RUN: light residual sheen, soft flush fading slightly, settling" → polished AI face. Rule: for continuation calls, keep physiology language at FULL strength or stronger ("STILL out of breath," "MORE damp," "chest heaving between phrases"). Reserve continuation framing for EMOTIONAL tone (less surprised, more reflective), never physiological state. Memory: feedback_continuation_no_deescalate.md. See LEARNINGS.md #129.

  21. Seedance background-ref can be rendered as a B-roll cutaway — freeze-frame fix preserves lip-sync. When image_urls[2] is a clean empty-setting plate, Seedance occasionally inserts a 0.5-1.5s cutaway of that plate into the rendered video (typically near a sentence boundary). Audio continues over it. Validated Fuelin Video 02 Call 2 at 9.0-9.85s. Three fix options ranked:

    • Freeze-frame replacement (preferred): hold the last frame BEFORE the cutaway for the cutaway duration, splice back. Audio uninterrupted, lip-sync preserved, held gesture (peace sign / smile / point) reads as intentional emphasis. Recipe:
      ffmpeg -i in.mp4 -filter_complex "
        [0:v]trim=0:T0,setpts=PTS-STARTPTS[v1];
        [0:v]trim=T0-0.05:T0,setpts=PTS-STARTPTS,loop=loop=-1:size=1,trim=duration=LEN,setpts=N/FRAME_RATE/TB[v2];
        [0:v]trim=T1:DUR,setpts=PTS-STARTPTS[v3];
        [v1][v2][v3]concat=n=3:v=1:a=0[vout]
      " -map "[vout]" -map 0:a -c:a copy out.mp4
      
    • Reroll the call ($4.20 + queue) — clean fix but risks losing other landed gestures.
    • Ship with cutaway — acceptable if cutaway lands precisely on sentence boundary and reads as intentional B-roll. Anti-pattern (do NOT do this): trim video without trimming matching audio — breaks lip-sync from the cut point onward. See LEARNINGS.md #131 + #132. Memory: feedback_seedance_cutaway_freeze_fix.md.

Quality Checks

Before declaring done, verify:

  • 1s scroll-stopper teaser present at 0:00-0:01 showing payoff state (lifted slice or dedicated render) — verified via /watch:watch of the final master
  • Avatar identity consistent across all per-product scenes (face/hair/build matches portrait reference within reasonable drift)
  • Wardrobe locked within each scene (no drift between sub-scenes)
  • Wardrobe varies across scenes (visual rhythm)
  • Product label READABLE in the hero sub-scene of each product (zoom in on the t=5s frame and confirm "Beauty by [Brand]" and product name are crisp)
  • Lips closed between dialogue beats (no idle mouth movement)
  • No NSFW classifier visual leaked (no water, no lather, no actual body application unless explicitly approved)
  • L-cut endcard: tanner's final words ("Highly recommend") land DURING endcard visual
  • Captions sync within ~200ms of spoken word
  • Music audible but doesn't compete with VO (VO clearly intelligible)
  • Music has no vocals / humming / ooh-ahs
  • Final master: 9:16 vertical, 1080×1920, 24fps, h264 + AAC 44.1kHz stereo
  • Total duration matches plan ± 1s
  • No artifact glitches at scene cuts (use ffmpeg -i master.mp4 -filter:v "select='gt(scene,0.4)',showinfo" to find harsh cuts if needed)
  • Gooseworks share link works (1hr expiry on presigned URL)

Failure Modes

Failure Cause Recovery
FAL 422 content_policy_violation: partner_validation_failed on video_urls Passed AI-generated scene as video reference Remove video_urls from payload. Rely on image_urls (portrait) alone for identity continuity.
FAL 422 nsfw on response (status field) Body-application + female + water/lather hit classifier Rewrite scene 3 (application) as smell-test or fingertips-show. Remove water/lather language. Re-fire.
FAL 404 on endpoint path Wrong endpoint slug (e.g. fal-ai/bytedance/seedance-2.0/...) Use bytedance/seedance-2.0/reference-to-video exactly. No fal-ai/ prefix.
Heavy freckles / blotchy skin texture Block B language too aggressive Use v8 soft skin language. Drop "freckles prominent", "real-person imperfections". Replace with "tiny natural imperfections, subtle peach fuzz, faint fine lines".
Product label garbled / wrong NB2 multi-image drifted, or wrong resolution Re-fire at 1080p (label fidelity scales with resolution). Add explicit "product label MUST be sharp and clearly readable" in the scene 2 hero prompt.
Mouth moving when not speaking Default Seedance behavior for portrait + product + camera setup Add to Global Rules: "Lips remain CLOSED between dialogue beats — no mouth movement when not speaking."
Music drowning out VO Default normalize=1 in amix eats the volume filter Use amix=...:normalize=0 (per memory feedback_ffmpeg_amix_normalize.md). Set music volume 0.18-0.22 (-15 to -13 dB).
Scene exceeds 15s requested FAL duration param sent as int not string Send as string: "duration": "15" not "duration": 15.
Cinematic slow pacing despite "real iPhone front camera" prompt Seedance prior leans cinematic Post-process with ffmpeg setpts=0.66*PTS (1.5x speed) on selected scenes for UGC pacing. OR strengthen prompt with "Casual real-time pace, NOT slow-motion, NOT dolly moves, NOT cinematic."
Scenes uniform 15s feel like documentary 4 cuts in 56s is below Meta's 13-15 cuts in 30s benchmark Add 3 bridge b-rolls between scenes (~$4.50 total). Each is a 4s, no-audio Seedance call with portrait + product setup. Insert at scene boundaries.
One scene looks more plastic/AI than another De-escalation language in continuation prompt OR rendered without post-activity physiology First try anti-plastic grade (Decision Rule 18). If insufficient, reroll with hardened physiology language per Decision Rule 20. Apply identical grade to ALL clips (Decision Rule 19) to avoid asymmetric polish.
Seedance inserts B-roll cutaway of background-ref plate mid-clip Model interpreting image_urls[2] (clean setting) as a shot to use Use freeze-frame replacement per Decision Rule 21 — hold last good frame for cutaway duration, splice. Preserves lip-sync. Do NOT trim video without trimming audio.
Lip-sync drifts after a video trim Trimmed video without matching audio cut Revert trim. Either cut video AND audio together (acceptable on natural silence), or use freeze-frame to extend video while audio plays through. Always ffprobe -show_entries stream=duration after any trim to confirm V/A delta < 0.05s.
Brand-name caption corrupted by Whisper post-process (e.g. "under-fueling" → "under Fuelin") Naive find/replace on transcript without context check Implement context-aware rule: brand replacement only when previous word is NOT the compound-word stem. Pattern: if low_text in ('fueling','fuelin') and not prev.endswith('under'): text = 'Fuelin' + punct. See LEARNINGS.md #130.

Spend reference (validated on BBE Video 01, May 2026)

Item Count Per-unit Subtotal
NB2 portrait + 0 variations 1 $0.08 $0.08
Seedance hook scene (1080p, 8s) 1 $5.44 $5.44
Seedance product scenes (1080p, 15s) 3 $10.20 $30.60
Endcard BG via NB2 (optional) 1 $0.08 $0.08
ElevenLabs Music via FAL 1 $0.40 $0.40
DIY captions — $0 $0
Composite (ffmpeg local) — $0 $0
Total ~$36.60

Budget mode (all 720p): ~$17-19. Quality drops noticeably on labels — only use if hard ceiling.

Skill location & related

This molecule: skills/molecules/ugc-ad/create-ugc-style-video/

Related atoms used:

  • skills/atoms/image-generation/create-image-nano-banana-2-fal
  • skills/atoms/planning/create-shot-list
  • skills/atoms/voiceover/format-script-to-style
  • skills/atoms/video-generation/create-video-seedance-2-fal
  • skills/atoms/end-cards/create-end-card-from-reference
  • skills/atoms/assembly/stitch-videos-ffmpeg
  • skills/atoms/captions/add-captions-klap
  • skills/atoms/music/create-music-elevenlabs