Hypermotion + Kinetic Typography intercut = one virtuoso AI-gen product clip, diced into segments and interleaved with PIL-rendered text cards for spec callouts, ending with a brand-asset end card. Music-led, silent VO, 9:16 Reels-native. Validated on Soundboks (industrial/party voice) 2026-05-28.
Purpose
Produce a 20-30s 9:16 vertical product ad by:
- Gathering brand assets (real product photos + brand logo PNG + brand color palette)
- Generating ONE 12-15s hypermotion i2v via Seedance 2.0 using a 5-block prompt recipe
- Rendering 5-7 kinetic typography text cards via PIL (italic skew, outline echo, 3D extrusion, slam-with-shake, inversion flash) for spec callouts + CTA
- Rendering 1 brand-asset end card via PIL (real logo PNG + brand-coded BG + slam motion blur + cascade reveal of subtitle + CTA + inversion flash)
- Cutting the hypermotion into 5-6 segments, intercutting with text cards in a fixed beat structure, ffmpeg concat + crop to 9:16
- Generating a 124 BPM bass-driven music bed via ElevenLabs Music (fal proxy)
- Mux + self-QC + ship
Use when:
- Brand voice is industrial / sport / party / utility (Soundboks-class)
- Hero product photographs well (real PDP shots available, not AI-gen ex-nihilo)
- Brand has 4-6 hard spec callouts (dB, hours, IP rating, count, etc.)
- Brand has a logo SVG/PNG that can be extracted (most brand SVGs are base64 PNG wrappers)
- Target placement: TikTok / Reels / Stories (9:16 vertical) — works muted
Do NOT use for:
- UGC ads with creator → use
skills/molecules/ugc-ad/* - VO-driven explainer → use
skills/molecules/explainer-video/* - Editorial / luxury / wellness brand voices → use
create-vignette-ad(calmer) - Single static product hero → use
skills/atoms/product-images/* -
30s long-form — format breaks past 30s (the hypermotion clip is the ceiling)
Inputs
Required:
brand_url— e.g.https://soundboks.comproduct_url— PDP URL for the hero product (Shopify or equivalent)spec_callouts[]— 4-6 short spec strings (e.g.["126 dB SPL", "40-HOUR BATTERY", "IP65 RATED", "SWAPPABLE BATTERY", "PAIR 5 SPEAKERS"])brand_voice— one ofindustrial | sport | party | utility | luxury | editorial | playful- The first 4 share the same aesthetic palette (black + Space Grotesk + utility orange)
editorialuses cream paper + EB Garamond (Hope-style) — better suited forcreate-vignette-adluxuryandplayfulneed different palettes — currently OUT OF SCOPE (single-brand validation)
Optional (with autonomous defaults):
duration_s— default 25.0 (range 20-30)cta_text— default"Own the party."or"Own the {category}."intro_text— default"INTRODUCING"hypermotion_brief— free-text. If omitted, derived from brand category + product (Soundboks → "beach festival crash-zoom on speaker, lateral orbit, slow-mo dust"). Seereferences/hypermotion-prompt-recipe.md.music_brief— default"{BPM} BPM bass-driven electronic banger, no vocals or with a single rallying lyric for end-card sync". BPM default 124 for industrial/party, 100 for sport/utility.accent_color_hex— default#FF6B1A(utility orange). Override via brand palette scrape.bg_color_hex— default#0C0C0C(industrial black). Override only if brand requires.
Prerequisites:
FAL_API_KEYin.envat repo root — for Seedance + ElevenLabs Musicffmpeg+ffprobeinstalled- Python 3.10+ with
Pillowandfal_client - TTF fonts in project
source/fonts/:SpaceGrotesk-Bold.ttf(static, NOT variable — variable renders as Regular in PIL)EBGaramond-Italic.ttf(only required for editorial voice)
- Brand logo as PNG (extract from SVG via base64 decode — see
references/brand-asset-extraction.md)
Composed Atoms
source-brand-assets— scrape PDP product photo, brand logo SVG, brand color hex from homepage CSSremove-backgrounds— strip product PNG to alpha viafal-ai/birefnet/v2(only needed if hypermotion uses cutout-on-clean-BG style; for in-environment hypermotion, skip — Seedance handles the BG)create-video-seedance-2-fal— single 12-15s i2v hypermotion call viafal-ai/bytedance/seedance/v2/pro/image-to-video($0.30/s, supports start_image + audio_off)create-music-elevenlabs— 25-30s music bed viafal-ai/elevenlabs/musicendpointadd-music-track— ffmpeg mux pass with-map 0:v -map 1:aexplicitstitch-videos-ffmpeg— concat the 12-14 segments (hypermotion cuts + kinetic cards)- Inline (not yet an atom — see "Back-propagation to atom"): PIL-based
kinetic typography pipeline (11 techniques, see
references/kinetic-typography-pipeline.md). The existingskills/atoms/motion-graphics/create-kinetic-typographyatom outputs transparent overlays (qtrle .mov); this molecule needs full-frame text cards. watch—/watch:watchexternal skill for end-to-end QC on the final mp4
Do NOT use: Klap, captions, Whisper, Soul ID, UGC-creator atoms, Higgsfield Marketing Studio (validated to not match Hyper Motion brief — Seedance 2.0 beats it for product-as-hero hypermotion).
Workflow
Phase 0 — Brand asset gathering (autonomous, $0)
-
Scrape product hero photo from brand PDP:
curl -sL "${product_url}.json" | jq -r '.product.images[0].src'Download highest-res (PDP shots typically 1500-2000px). Save to
source/scraped-product-images/. -
Extract brand logo PNG from SVG. Most brand SVGs are wrappers around base64-encoded PNGs:
import re, base64 svg = open('brand_logo.svg').read() m = re.search(r'data:img/png;base64,([A-Za-z0-9+/=]+)', svg) or \ re.search(r'data:image/png;base64,([A-Za-z0-9+/=]+)', svg) open('brand_logo.png', 'wb').write(base64.b64decode(m.group(1)))Validated on Soundboks (decoded to 865×135 PNG of the actual wordmark). If SVG is pure vector (rare), use
rsvg-convert -w 2400 brand_logo.svg > brand_logo.png. -
Scrape brand palette from homepage CSS — count
#xxxxxxpatterns, take top 5 frequencies. Map dominant accent color →accent_color_hex. -
Download fonts to
source/fonts/:curl -sSL -o source/fonts/SpaceGrotesk-Bold.ttf \ "https://raw.githubusercontent.com/floriankarsten/space-grotesk/master/fonts/ttf/static/SpaceGrotesk-Bold.ttf" -
Write brief.md with: PDP photo preview, brand palette swatches, proposed hypermotion concept + Seedance prompt + spec callouts + estimated spend (~$5).
🚦 HUMAN GATE 1 — Brief approval
Show user: brief.md + hypermotion prompt + 5-7 spec callouts + estimated spend.
User approves OR edits brief.md.
Phase 1 — Hypermotion + music in parallel (~$4.99, ~10 min wallclock)
Run both in parallel via ThreadPoolExecutor or two Bash processes:
-
Fire Seedance i2v hypermotion ($4.54 for 15s):
- Endpoint:
fal-ai/bytedance/seedance/v2/pro/image-to-video - Input: hero product PNG as
image_url(uploaded viafal_client.upload_file) - Duration: 15s (max for Seedance Pro)
- Generate audio: false (we use external music bed)
- Resolution: 1080p, aspect 1:1 (will be center-cropped to 9:16 later)
- Prompt: 5-block recipe (see
references/hypermotion-prompt-recipe.md):- Block 1: STYLE ANCHOR ("hypermotion product film, 124 BPM cuts, ...")
- Block 2: CINEMATOGRAPHY ("ARRI Alexa, 50mm anamorphic, ...")
- Block 3: SUBJECT + ENVIRONMENT ("Soundboks 4 on beach festival deck, ...")
- Block 4: MULTI-SHOT TIMECODES ("0-3s crash zoom, 3-6s lateral orbit, ...")
- Block 5: ABSOLUTE CONSTRAINTS ("no character contact, no multi-product, no text overlays, label-locked geometry, physics effects in environment only NOT on product")
- Skip-if-exists guard: if
assets/hypermotion-raw.mp4exists, skip the call
- Endpoint:
-
Fire ElevenLabs Music ($0.45 for 30s):
- Endpoint:
fal-ai/elevenlabs/music - Duration:
duration_s * 1000 ms(generate exact target — Soundboks 25s validated) - Prompt:
{BPM} BPM bass-driven {brand_voice} banger, with rallying single-lyric ending for end-card sync output_format: "mp3_44100_192"- Skip-if-exists guard
- Endpoint:
Phase 2 — Render kinetic typography cards (autonomous, $0)
Render all text clips at 1080×1920 via PIL frame-by-frame to working/kinetic-frames/<label>/f%04d.png, then ffmpeg-encode each to working/kinetic-movs/<label>.mp4.
Cards to render (defaults — adjust per spec_callouts list):
intro.mp4(1.0s) — "INTRODUCING" italic + outline echo, BG: brand bg + grainspec_1.mp4(1.5s) — Killer-stat slam: 3D extrusion in accent color (the hero spec, e.g. "126 dB SPL")spec_2.mp4(1.5s) — italic slam on dark BG (e.g. "40-HOUR BATTERY")spec_3.mp4(1.5s) — italic + outline echo on light BG (e.g. "SWAPPABLE BATTERY")spec_4.mp4(1.5s) — italic + sparkle accent (e.g. "IP65 RATED")spec_5.mp4(1.5s) — italic + shadow stack (e.g. "PAIR 5 SPEAKERS")cta.mp4(1.5s) — "OWN THE PARTY" 3D extrusion + accent→black flashendcard.mp4(3.5s) — REAL LOGO with slam motion blur, settle, micro-motion, inversion flash at 60% mark, cascade reveal of subtitle ("126 dB · 40 HRS · IP65") + CTA
Use the 11 reusable PIL helpers from references/kinetic-typography-pipeline.md (italic skew, outline echo, 3D extrusion, repetition stack, slam-with-shake, color flash, inversion flash, paper/dark grain BG, rotated text, motion blur, micro-motion).
Reference scripts from Soundboks (copy + adapt):
clients/soundboks/video-02-hypermotion/working/scripts/gen_kinetic_v6.pyclients/soundboks/video-02-hypermotion/working/scripts/gen_endcard_v10.py
Phase 3 — Assemble + mux (autonomous, $0)
-
Center-crop hypermotion from 1:1 (1440×1440 or 1080×1080) to 9:16 (1080×1920):
ffmpeg -i hypermotion-raw.mp4 -vf "scale=-1:1920,crop=1080:1920:(iw-1080)/2:0,fps=30" -c:v libx264 -crf 18 hypermotion-9x16.mp4Loses ~25% horizontal context but feels Reels-native.
-
Cut hypermotion into 5-6 segments at fixed timecodes via ffmpeg
-ss/-t: Default segments (for 25s ad with 15s hypermotion source):- Seg A: t=[0.0, 2.5] (2.5s — opener crash-zoom)
- Seg B: t=[2.5, 5.0] (2.5s — lateral orbit)
- Seg C: t=[5.0, 7.0] (2.0s — first slow-mo dust)
- Seg D: t=[7.0, 8.5] (1.5s — second slow-mo dust)
- Seg E: t=[8.5, 10.0] (1.5s — pull-back)
- Seg F: t=[10.0, 11.5] (1.5s — final hero)
-
Concat segments + cards per beat structure (see
references/beat-structure.md):intro → segA → spec_1 → segB → spec_2 → segC → spec_3 → segD → spec_4 → segE → spec_5 → segF → cta → endcardUse ffmpeg concat demuxer with a
concat.txtlisting all segment files in order. -
Mux music as SEPARATE ffmpeg pass per memory
feedback_ffmpeg_lcut_endcard_recipe.md:ffmpeg -y -i master-silent.mp4 -i music.mp3 \ -c:v copy -c:a aac -b:a 192k -ar 44100 \ -map 0:v -map 1:a -shortest \ -movflags +faststart finals/master-v1-9x16.mp4Verify audio bit_rate ≈ 192000 via ffprobe (NOT 1000 — that's the default-mapping silent bug per
feedback_ffmpeg_map_directive.md).
Phase 4 — Self-QC + ship (autonomous, $0)
-
Run
/watch:watch finals/master-v1-9x16.mp4to verify:- Duration ± 0.5s of target
- Aspect 1080×1920
- All text cards readable, no frame bleed (outline echoes at 1.08x sweet spot)
- Hypermotion segments hold subject identity (no geometry drift)
- End card real logo visible + inversion flash hits at right beat
- Music holds throughout, no decay tail visible
-
Upload to gooseworks for share URL via
mcp__gooseworks__get_upload_url+ curl PUT.
Output
<project>/
├── brief.md
├── HOW_TO_MAKE_THIS_VIDEO.md
├── source/
│ ├── scraped-product-images/ # raw PDP PNGs
│ ├── logo/
│ │ ├── brand_logo.svg # original
│ │ └── brand_logo_white.png # extracted from SVG
│ ├── fonts/
│ │ ├── SpaceGrotesk-Bold.ttf # static (NOT variable)
│ │ └── EBGaramond-Italic.ttf # only if editorial voice
│ └── brand-palette.json # scraped color hex
├── assets/
│ ├── hypermotion-raw.mp4 # Seedance i2v output (12-15s, 1:1)
│ ├── hypermotion-9x16.mp4 # center-cropped to vertical
│ └── music/
│ └── music_bed.mp3 # 20-30s ElevenLabs Music
├── working/
│ ├── kinetic-frames/ # PIL PNG sequences (intermediate)
│ ├── kinetic-movs/ # encoded kinetic clips
│ ├── segments/ # hypermotion cuts
│ └── scripts/
│ ├── fire_hypermotion.py
│ ├── fire_music.py
│ ├── gen_kinetic_cards.py
│ ├── gen_endcard.py
│ └── assemble.py
└── finals/
└── master-v1-9x16.mp4 # shipping master, ~25s, 1080×1920
Decision Rules
-
One hypermotion call, not many. Generate ONE 12-15s clip and CUT into 5-6 segments via ffmpeg, rather than firing 5-6 separate Seedance calls. Cheaper ($4.54 vs $22) AND maintains visual consistency across segments (same camera/grade/subject — what Seedance does well in one call).
-
Center-crop, don't regen for 9:16. Generate Seedance at 1:1 and center-crop to 9:16 in post. Generating natively 9:16 in Seedance is more expensive and quality-equivalent. ~25% horizontal context loss is acceptable for the product-centric framing.
-
Real brand logo > typeset wordmark on end card. Always use the actual brand SVG/PNG, not typeset text. Per memory
feedback_brand_asset_end_card.md— typeset wordmarks miss distinctive letterforms (Soundboks has custom italic-slanted glyphs that Space Grotesk Bold can't approximate). See also LEARNINGS.md #138. -
Brand voice drives aesthetic palette. Don't apply Hope-style cream paper + EB Garamond italic to industrial/sport/party brands. Soundboks-class = black BG + Space Grotesk Bold + utility orange accent. Editorial/literary brands belong in
create-vignette-ad, not here. Per LEARNINGS.md #139. -
Outline echo at 1.08x is the bleed-safe sweet spot. Larger (1.2x+) reads as accidental clipping; smaller defeats the ambient-shadow purpose. Validated on Soundboks V8→V9 bleed audit. Per LEARNINGS.md #140.
-
Never freeze after settle on the wordmark. Continuous ±1% scale pulse + ±3px horizontal drift for the entire 3.5s end card hold. 3+ seconds of frozen text feels like a JPEG. Per LEARNINGS.md #141.
-
Hypermotion prompt must include ABSOLUTE CONSTRAINTS block. Without it, Seedance lets geometry drift mid-clip (V2 broke to cowbell shape). Block must include: "no character contact, no multi-product, label-locked geometry, physics effects (heat-shimmer, dust) in environment only NOT on product." Per memory
feedback_hypermotion_prompt_recipe.md. -
ffmpeg mux: ALWAYS explicit
-map 0:v -map 1:a+ SEPARATE pass from concat. Default mapping silently produces 1 kbps garbage audio. Per memoryfeedback_ffmpeg_map_directive.md+feedback_ffmpeg_lcut_endcard_recipe.md. -
Use STATIC font TTFs, never variable. PIL doesn't respect variable font axes —
SpaceGrotesk[wght].ttfrenders as Regular weight regardless of the weight axis. Always download single-weight static TTFs (/static/subfolder on most font repos). -
Skip-if-exists guards on all paid calls. Re-running the orchestrator must NEVER re-bill if outputs already exist. Wrap each
fal_client.subscribeinif not output_path.exists(): .... -
Music with vocals is fine for industrial/party voice. Per memory
feedback_music_with_vocals_default.md— instrumental is the wrong default for hypermotion product ads (no VO competes). Bake a single rallying lyric into the end-card beat for AV-sync hook. -
Cut hypermotion at beat boundaries, not arbitrary timecodes. Seedance naturally has 2-3 internal "beats" (crash-zoom → orbit → settle). Cut on these boundaries via /watch:watch frame-by-frame inspection, not at uniform intervals. Uniform cuts break visual rhythm.
Quality Checks
Before declaring done, verify the final mp4:
- Duration within target ± 0.5s (e.g. 25.0s ± 0.5s)
- Aspect 1080×1920 (9:16 vertical)
- All N text cards readable, no accidental frame bleed (outline echoes ≤1.08x)
- Hypermotion segments hold subject identity (label/logo on product stays consistent across cuts)
- End card real logo visible + sharp (no PIL upscale artifact)
- Inversion flash at end card hits at correct beat (~60% of card duration)
- Continuous micro-motion visible on end card wordmark (NOT frozen)
- Music holds throughout, no decay tail in last 5s
- Music aligns to cuts on the strongest beats (intro slam, mid-bridge, end-card flash)
- Audio bit_rate ≈ 192kbps AAC (verify via
ffprobe -show_entries stream=bit_rate) - No people, no hands, no text overlays inside the hypermotion clip itself
- Final mp4 plays cleanly in QuickTime + browser
Failure Modes
| Failure | Cause | Recovery |
|---|---|---|
| Hypermotion geometry drifts mid-clip (product morphs shape) | Prompt lacks ABSOLUTE CONSTRAINTS block | Add the 5th block per references/hypermotion-prompt-recipe.md. Regen ($4.54). |
| Hypermotion shows character touching product | "Character" entered prompt at all | Strip all human/hand references from prompt. Add "no character contact" to constraints. |
| Text cards bleed off frame | Outline echo scale > 1.08x | Tighten echo to 1.08x. Re-render PIL (free). |
| Fonts render as Regular weight despite Bold being specified | Downloaded variable font TTF | Replace with static TTF from /static/ subfolder of font repo |
| End card wordmark looks generic/wrong | Used typeset font instead of real logo | Extract real logo PNG from brand SVG via base64 decode. Re-render. |
| End card feels frozen after 1s | No micro-motion phase added | Add scale = 1.0 + 0.010*sin(t*1.4); x_off = 3*sin(t*1.1) after settle phase |
| Final mp4 audio is 1 kbps | ffmpeg mux without explicit map | Add -map 0:v -map 1:a per memory feedback_ffmpeg_map_directive.md |
| Music tapers in 2nd half | Known ElevenLabs Music behavior | Generate 20% longer than target + trim. OR loop-and-flatten with acrossfade + acompressor + loudnorm per feedback_elevenlabs_music_decay.md |
| 9:16 crop loses critical product detail | Hypermotion framed product at horizontal edges | Re-generate Seedance with explicit "subject centered, vertical-safe composition" in prompt |
gen_kinetic_cards.py script fails on font load |
Wrong font path or font not downloaded | Verify source/fonts/SpaceGrotesk-Bold.ttf exists; re-download from raw.githubusercontent.com/floriankarsten/space-grotesk/master/fonts/ttf/static/ |
| Brand logo SVG isn't a base64 PNG wrapper | Pure vector SVG (rare) | Use rsvg-convert -w 2400 logo.svg > logo.png instead |
| Hypermotion clip is silent but final has music too quiet | Music loudnorm not applied | Add acompressor=threshold=-12dB:ratio=2:attack=20:release=200,loudnorm=I=-14:TP=-1.5:LRA=9 to music before mux |
Spend Reference (validated on Soundboks V10, 2026-05-28)
| Item | Count | Per-unit | Subtotal |
|---|---|---|---|
| Seedance 2.0 i2v 15s (1080p, audio off) | 1 | $4.54 | $4.54 |
| ElevenLabs Music 25s | 1 | $0.45 | $0.45 |
| Brand logo PNG extraction (base64 decode) | 1 | $0.00 | $0.00 |
| PIL kinetic typography rendering (8 clips) | 8 | $0.00 | $0.00 |
| ffmpeg concat + mux + crop | 1 | $0.00 | $0.00 |
| V1 marginal cost (no iteration) | ~$4.99 |
For full multi-iteration runs (Soundboks did V1→V10): ~$17.43 total. V10 alone is $5 to reproduce. Most "iteration cost" is PIL re-renders + ffmpeg = $0.
Sub-Archetypes (none yet)
This molecule has 1 validated sub-archetype: the Soundboks pattern (industrial voice, single-product hero, 5-7 spec callouts, brand-asset end card).
Future sub-archetypes pending multi-brand validation:
- PH-LUXURY — luxury brand voice (gold + monochrome typography, slower BPM, longer holds)
- PH-SPORT — sport/fitness voice (high-saturation accent, faster BPM ~140, athletic motion verbs)
- PH-PLAYFUL — pop/retail voice (bright color blocks, bold display sans, exaggerated 3D extrusion)
Until validated on N=2+ brands per sub-archetype, treat them as open questions, not Decision Rules.
Tests
See tests/:
smoke-test.md— structural pass (all required files exist, frontmatter valid)sample-input.md— Soundboks worked exampleexpected-output.md— what successful output looks likehuman-test.md— manual acceptance criteriaverifier.md— ffmpeg + ffprobe assertions
Related
Originally validated in content-goose at
clients/soundboks/video-02-hypermotion/. Adapt paths to your project.
- Reference project:
clients/soundboks/video-02-hypermotion/— full worked example withHOW_TO_MAKE_THIS_VIDEO.md(V1→V10 iteration history) - Reference scripts:
clients/soundboks/video-02-hypermotion/working/scripts/gen_kinetic_v6.py— 8 spec/filler cardsclients/soundboks/video-02-hypermotion/working/scripts/gen_endcard_v10.py— brand-asset end cardclients/soundboks/video-02-hypermotion/working/scripts/assemble_v10.py— concat + mux template
- Related molecules:
skills/molecules/ad-format/create-vignette-ad— for editorial/luxury brand voicesskills/molecules/ad-format/create-value-prop-ad— for VO-led value prop adsskills/molecules/product-video/create-product-stopmotion-video— for tabletop stop-motionskills/molecules/ugc-ad/*— for UGC creator ads
- Memory rules to read before running:
reference_kinetic_typography_pipeline.md— full PIL pipeline reference (11 techniques + code)feedback_brand_asset_end_card.md— real logo > typesetfeedback_hypermotion_prompt_recipe.md— 5-block Seedance prompt templatefeedback_ffmpeg_map_directive.md—-map 0:v -map 1:aalwaysfeedback_ffmpeg_lcut_endcard_recipe.md— separate concat + mux passesfeedback_music_with_vocals_default.md— vocals OK for product adsfeedback_elevenlabs_music_decay.md— generate longer + trim
- LEARNINGS.md rules: #137 (PIL kinetic typography supersets atom), #138 (real brand assets on end cards), #139 (brand aesthetic per voice register), #140 (outline echo 1.08x sweet spot), #141 (never freeze after settle).
Back-propagation to skill atom (future work)
The existing skills/atoms/motion-graphics/create-kinetic-typography atom
exposes 5 animation_type options. This molecule's PIL pipeline adds 6 more:
italic skew, outline echo, 3D extrusion, repetition stack, mask wipe,
letterbox slide, color flash, inversion flash. Worth extracting these into
the atom as new animation_type options once a 2nd brand validates the
pipeline.
Open Questions (not yet Decision Rules)
These need N=2+ project validations before promoting:
- Optimal segment count per hypermotion clip. Soundboks used 6 segments from a 15s clip. Would 4 (longer holds) or 8 (faster cuts) be better defaults?
- Spec callout count. Soundboks used 5 hard specs + 1 intro + 1 CTA + 1 endcard. Is 5 specs the right ceiling? Below 3 may feel sparse; above 6 may feel like a deck.
- Music BPM auto-selection per brand voice. Soundboks 124 BPM worked. Does sport/luxury need different BPM tables?
- 9:16 vs 1:1 vs 16:9 portability. Soundboks validated 9:16. Does this molecule produce equally-good 1:1 (Instagram feed) and 16:9 (YouTube) variants with just a different center-crop?
- Cost of iteration. Soundboks went V1→V10 ($17.43 total). What does the 60th-percentile brand spend look like for a "first ship"?