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_showcaseworks (no creator on-screen) → usecreate-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.mdvoice (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_urlsto 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 structuredscene-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 aDELIVERY_STYLES.mdvoice (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— firesbytedance/seedance-2.0/reference-to-videovia FAL withimage_urlsonly (NEVERvideo_urlsof prior AI-gen scenes — content-policy hard fail),generate_audio: truefor native lip-sync,durationas 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 viacreate-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: truebecause Seedance lip-synced VO occupies the vocal space. Mix at ~22% / -13 dB withamix=normalize=0(per memoryfeedback_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
- Parse input (URL / brief / explicit product set)
- WebFetch the brand site briefly. Pull: company name, brand voice cues, palette, top product candidates with hero images
- 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)
- 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
- 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). - 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".
- Generate 1 hero portrait. Show to user.
- If user wants 1-2 variations (different angles or wardrobe) → fire 1-2 more NB2 calls.
- User locks the hero portrait. Save as
assets/avatar/portrait-hero.png. Save variations asassets/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
- 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]"
- User picks one.
- 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
- 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)
- 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:
- (0-3s) Wide hook — establishes setting + creator + product
- (3-7s) Product hero — rotates product, brand label readable, focus pull
- (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)
- (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-referencefor 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:
- Does the image show ONLY the hero product? Or does it have accessories / secondary products / score badges / lifestyle elements / overlay graphics?
- If clean (product-only): proceed to Phase 5.
- 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
- Endpoint:
- 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:
- Use
fal-ai/nano-banana-pro(text-to-image, $0.15/1K, supports 9:16) for empty-room background generation. - Prompt must include: "NO people, NO faces, NO body parts" + "Deep infinite focus — everything sharp, NO bokeh, NO shallow DOF" + composition details + aesthetic reference.
- Output:
<video-folder>/assets/setting/<setting-name>-hero.png - Pass as
image_urls[2]to Seedance in Phase 5 (Image Refs block: "@Image3 = locked setting"). - 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
- 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. - Build
assets/hyperframes/endcard.htmlwith:- 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
- 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)
-
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)
-
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
-
Concat order:
hook → product1 → product2 → ... → [last product with L-cut endcard] -
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=lanczosbefore concat. Quality loss is invisible in motion. -
ffmpeg concat: use
concat=n=N:v=1:a=1filter (re-encode-safe) rather than demuxer-based concat (which fails if any input differs).
Phase 9 — Captions (DIY karaoke)
- Whisper-transcribe each scene mp4 with
word_timestamps=Trueand brand-nameinitial_promptbias. - Offset each scene's word timestamps to its master timeline position.
- Build ASS with karaoke
\kfhighlighting (yellow active word, white inactive). - Style: SF Pro Display Bold, 60pt, ALL CAPS, white→yellow karaoke, MarginV=400 (bottom-third).
- Burn via
ffmpeg -vf "subtitles='X.ass'".
See atoms/captions/add-captions-klap for the proven recipe.
Phase 10 — Music bed
- 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 } - 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
- 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.mp4normalize=0is critical — defaultnormalize=1silently eats the volume filter (per memoryfeedback_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 point1/0.88 - Lighter touch:
noise=alls=3,saturation=0.94, top curve point1/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
- Run
/watch:watch(or equivalent frame extract + audio check) on the final master - 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
- Upload to gooseworks for share link
- Move to
finals/master-v[N]-captions-music.mp4
Output
finals/master-v[N]-captions-music.mp4— shippable 9:16 1080p adassets/clips/scene-{hook,product1,product2,...}.mp4— individual scenesassets/hyperframes/endcard.png+endcard.html— reusable endcardassets/avatar/portrait-hero.png— locked avatarassets/music/grwm-bed-v1.mp3— music bedworking/master-words.json— Whisper transcript with master-timeline word timestamps- Optional:
storyboard.htmlwith embedded scenes for review
Decision Rules
-
Identity continuity uses
image_urls, NEVERvideo_urlswith AI-gen scenes. Passing a prior AI-generated Seedance scene asvideo_urlstriggers FALcontent_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. -
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.
-
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).
-
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).
-
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.
-
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.
-
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.
-
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.
-
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. -
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.
-
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.
-
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. -
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. -
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. -
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)'sseedfrom its meta and pass via--seed <prior_seed>with the sameimage_urls. Do NOT pass the prior call's mp4 asvideo_urls— it HURTS continuity (hair/wardrobe/product drift increases, not decreases).video_urlsis for STYLE TRANSFER from a real human reference video only. Memory:feedback_seedance_video_urls_vs_seed.md. -
video_urlsstyle transfer takes longer — use 30+ min client timeout. AI-gen video asvideo_urlsinput renders in ~18 min (vs ~10 min for image-only). The defaultfal_client.subscribe()10-min timeout fails. Usefal_client.submit()+ manual polling for these long-poll cases. Memory:feedback_seedance_video_urls_vs_seed.md. -
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.mp40:00-0:02. See Phase 3.7 for the workflow. Memory:feedback_ugc_scroll_stopper_cold_open.md. -
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.mp4Closes ~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. -
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. -
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. -
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.
- 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:
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:watchof 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-falskills/atoms/planning/create-shot-listskills/atoms/voiceover/format-script-to-styleskills/atoms/video-generation/create-video-seedance-2-falskills/atoms/end-cards/create-end-card-from-referenceskills/atoms/assembly/stitch-videos-ffmpegskills/atoms/captions/add-captions-klapskills/atoms/music/create-music-elevenlabs