@pika-labs/founder-product-video
>-
| name | founder-product-video |
| description | >- |
| argument-hint | <product-url> --founder "<name, role>" [--photo <path|url|generate>] [brand-kit=<path>] [aspect=16:9|9:16|1:1] [--quick] [--config <path>] |
| required-capabilities | add_captions, analyze_brief, analyze_media, capture_website, edit_audio_mix, edit_concat, edit_video_compose, edit_text_overlay, extract_frame, generate_image, generate_music, generate_reference_video, generate_slide_animation, html_to_png, identity_balance, render_html_animation, task_status, task_cancel, upload_asset |
founder-product-video
You generate a 65-second founder-style product video from a product URL plus user-provided imagery: 60 seconds of talking-founder body video plus a 5-second branded end card. The user's images (product photos / website screenshots / app screenshots) flow into the SeeDance acts as visual references, and digital product screens / brand wordmarks are composited after generation so readable UI is deterministic instead of model-rendered.
No cutaways. No website CSS extraction. AI generation, deterministic render, captions, concat, and music mix go through Pika MCP tools by default. Lower-third overlays are opt-in and use MCP compose by default, with local ffmpeg only as an emergency fallback.
Cost transparency gate
Before any paid MCP call, call identity_balance({verbose: true}) once. Surface the current balance, recent burn rate, and remaining runway, then gate the run with this exact message:
Estimated cost: about 4,000 credits (~$40) for a typical four-act Seedance founder video plus supporting assets. This exceeds $5, so Reply
proceedto continue orcancelto stop.
Do not call any paid MCP tool until the user replies proceed. If the user replies cancel, stop without generating. For non-interactive --quick or --config callers, require cost_ack=proceed in the config; if it is absent, stop with the estimate instead of spending credits.
[0] Intake — run first, before any pipeline step
If invoked with empty args, print this menu verbatim and stop — wait for the user to paste inputs:
What founder video do you want to make? Required:
- Product URL —
https://...(anything with a real homepage)- Founder — name + role, e.g. "Eli Kim, CEO"
- Founder photo — local path, https URL, OR
generate(I'll create a portrait)Optional (sensible defaults if omitted): brand kit path · custom on-phone screenshots · music · aspect (16:9 / 9:16 / 1:1) · location image · voice style · product type
Example:
/founder-product-video https://example.com --founder "Eli Kim, CEO" --photo ~/Pictures/eli.jpg
If args carry partial input in interactive mode, skip the menu and gather the missing required fields by asking one at a time — ask, wait, ask the next. Don't bundle questions into one block. If the user supplies a field unprompted (e.g. they pasted a URL in the trigger message), skip that question and confirm the value back to them once at the end. Don't start the pipeline until all required fields are answered. If the non-interactive fast lane applies, use step [0.5] instead.
[0.5] Non-interactive fast lane
Use this path when the caller passes --quick or --config <path>, or when the
caller states they are running from CI, a subagent, a batch job, or any other
non-interactive harness.
This section has precedence over the interactive ask/wait instructions below.
When it applies, use this fast lane and do not fall through to the multi-turn
intake unless url or founder name/role is truly missing.
--config <path>points to a JSON file with pre-baked values for the canonical input contract:url,brand_kit_pathorbuild_brand,founder_name,founder_role,founder_photo,assets,music_url,aspect_ratio,location_image_url,voice_style,product_type, andlower_third.--quickmeans use defaults for optional extras, auto-build the brand kit withbuild-a-brand --quickifbrand-kitis omitted, and usefounder_photo = "generate"when no photo is supplied.- For
--quickor--config, do not stop for confirmation at the brand-kit branch, founder-photo generation prompt, optional-extras prompt, script choices, or end-card/caption defaults. Record assumptions inline and continue. - If
urlor founder name/role cannot be found in args or config, stop once with a single compact missing-fields list instead of starting a multi-turn Q&A loop.
1. Product URL (required) — https://.... Used to (a) derive the brief in step [1] and (b) feed the brand-kit branch below.
2. Brand kit (required) — interactive mode: ask "Do you already have a brand kit folder, or should I build one first?"
- If a path -> use it (
state.brand_kit_path = <path>). Accept eitherbrand.jsonor an exportedbuild-a-brandkit containingbrand.md,tokens/tokens.json, and logo assets. - If "build" -> invoke the
build-a-brandskill on the URL/brief and wait for the exported brand kit. This is a full identity workflow and may pause for user choices; surface those prompts in interactive mode. - Fast lane: if config provides
brand_kit_path, use it. If config setsbuild_brandor--quickomitsbrand-kit, invokebuild-a-brand --quickon the URL/brief and wait for the exported brand kit; do not surface build-a-brand prompts or stop for identity choices. After either branch, setstate.brand_kit_path. Only stop with a single compact missing-fields list if there is no path and the brand kit cannot be built.
3. Founder identity (required) — interactive mode: ask all three together:
founder_name— e.g. "Avery"founder_role— e.g. "CEO, ExampleCo"founder_photo— local path / https URL / OR the literal stringgenerateto auto-create a portrait. Ifgenerate, prompt the user for a 1-line vibe ("warm, casual smart attire" / "Pixar-style 3D animation" / etc.) — this becomes the seed prompt forgenerate_imagein step [4].- Fast lane: use founder values from args/config. If
founder_photois omitted, setfounder_photo = "generate"and use a neutral founder-portrait vibe derived from the product tone; do not stop for a separate photo-vibe prompt.
Default to no lower-third so the happy path stays lean. If the user explicitly asks for a lower-third, record state.lower_third = true; in interactive mode confirm that edit_video_compose will add the transparent overlay after rendering.
4. Optional extras — interactive mode: offer these once as a single message, then proceed without waiting if no answer comes back in the same turn. Fast lane: use the defaults below without asking.
- Custom imagery — list of
assets(product photos / app screenshots) shown on the founder's phone. Default if omitted: use screenshots frombrand.json.screenshotswhen present, otherwise look for obvious screenshots or product images inside the brand kit, otherwise capture the product URL withcapture_website(mode:"screenshot")before step [2]. Do not proceed to script or SeeDance without real product UI / product imagery unlessproduct_typeis explicitlyserviceand the user accepts an environment-only video. In the fast lane, if no supplied/brand-kit/captured asset exists, stop once with a compact missing-assets error instead of silently shipping a generic talking-head video. - Music — local path / https URL / OR
generate(instrumental, ~60s). Default:generatevia Kling background mode. - Lower-third — optional. Default: off. If enabled, render the transparent
.movthrough MCP and overlay it onto the body withedit_video_compose. - Aspect ratio —
16:9(default),9:16,1:1. - Location — defaults to a flat seamless backdrop in
state.brand.colors.accent(clean studio-shoot look, character against a single brand color, whatever the brand's accent is). Override with a path / URL / text description if the user wants office, outdoor, etc. - Voice style — VO direction string for SeeDance, e.g. "warm authentic founder energy, conversational". Default: derived from
brief.tone. - Product type —
digital | physical_apparel | physical_object | consumable | service. Default: auto-derived in step [2] from asset analyses.
After Stage 0 completes, store all gathered values in state.inputs. If you already created a local work directory for this run, optionally persist the same object as <workdir>/inputs.json; do not require a predefined work-directory environment variable. Then enter the pipeline at step [1].
[0.6] Avatar-type probe for founder photos
Before any paid generate_reference_video call, run this Avatar-type probe on the resolved founder photo/avatar URL after local upload or user-supplied URL normalization. This applies to any founder photo used as the character reference — whether supplied via --photo or generated.
Call analyze_media once:
query: "Classify this image for paid video generation. Is it a photograph of a real human face, an AI-generated realistic portrait, a stylized / illustrated character, or a recognizable trademarked / copyrighted character such as Batman, Pikachu, or Mickey Mouse? Return strict JSON only: { \"avatar_type\": \"real_human\" | \"ai_realistic\" | \"stylized_illustrated\" | \"recognized_ip\", \"recognized_character\": string | null, \"moderation_risk\": \"low\" | \"medium\" | \"high\", \"recommendation\": \"proceed\" | \"warn\" | \"reject\" }. Use null for `recognized_character` when no specific character is recognized; never write \"none\", \"unknown\", or explanatory prose in that field."
Route from the result:
- recognized IP / copyright risk -> STOP only when
avatar_typeis"recognized_ip", orrecognized_characternames a specific character (for example"Batman"), or when bothmoderation_riskis"high"andrecommendationis"reject". Treatrecognized_character: null, empty string,"none","unknown","n/a", and low/mediummoderation_riskas not enough to stop by themselves. Run this check before the real/stylized routes. A chibi Batman is still Batman even whenavatar_typeis stylized / illustrated. - real human / AI-generated realistic -> proceed normally.
- stylized / illustrated -> proceed with a visible warning that stylized avatars may be less reliable for Seedance likeness and moderation, then continue only if the user supplied or accepted that avatar.
- trademarked / copyrighted -> STOP before generation. Surface this message:
Your founder photo appears to be a trademarked character ([X]). Most video providers will moderate this and refuse to generate. Pass --photo <real-looking-photo-url> to override.For this skill,--photo <real-looking-photo-url>is the accepted concrete flag; you may also mention the cross-skill--avatar <real-looking-photo-url>wording because users may know that convention.
Required inputs (canonical contract)
After Stage 0, these are the fields downstream steps consume:
url— the product website (https://). Drives step [1] brief.brand_kit_path— brand kit folder. Required. End card AND lower-third consumebrand.jsonwhen present, otherwisebrand.md,tokens/tokens.json, and logo assets from abuild-a-brandexport. See step [4.5].founder_name+founder_role+founder_photo— required from intake. Step [4] normalizesfounder_photointofounder_photo_urlandcharacter_urlbefore any SeeDance call.assets— optional array of{ url, role?, caption? }. Defaults to the screenshots captured by the brand kit; when the brand kit has no screenshots, capture the product URL before step [2] and store the returnedimage_urlas a real product UI asset.roleis a hint string mapping the asset to a script beat (hero,feature_a,cta, etc.).location_image_url— optional. Defaults to a generated solid-color backdrop instate.brand.colors.accent.music_url— optional. Defaults togenerate(Kling 60s background bed in step [7]).aspect_ratio— default16:9.voice_style— optional, defaults tobrief.tone.product_type— optional, auto-derived in step [2].
State
Keep a simple state object as you work and save every CDN URL there so a partial run can be resumed. Treat task_status value completed as the successful terminal state (failed and cancelled are the failure terminals), then unwrap result.structuredContent when present. The final video lives on Pika's CDN; no local workspace is required unless MCP compose is unavailable and you explicitly trigger the local lower-third fallback in step [8b].
Long-running task_status polling
When any long-running generation or edit call returns a task_id with or without an initial status, including {task_id}, {task_id, status: "queued"}, or an initial queued, running, or processing status, record the task id and start time immediately in state.
- Call
task_status({task_id})in a tight loop until terminal (completed | failed | cancelled). No manual sleep and no Bash polling; the worker holds each status call open. - Emit ONE visible progress line every 60s while status is
queued,running, orprocessing:Seedance i2v queued for {N}m {S}s... still processing. Replace the provider/stage label when polling music, captions, render, concat, mix, or edit tasks. - On
completed, unwrap the returned result URL and save it intostate. - On
failedorcancelled, surface failure to the user withtask_id, status, and the last status message. - After 15 min total from the original submit, call
task_cancel({task_id})if the task is still non-terminal, then surface failure to the user. If cancel reports the task is already terminal, call status once more and report that terminal result. - Do not submit a duplicate request while the original task is still
queued,running, orprocessing.
Pipeline overview
[Stage 0] intake you (Claude): ask user for url + brand-kit (path or build) + founder (name/role/photo) + optional extras
→ [0.5] brand-kit auto-build (only if user said "build")
invoke `build-a-brand`; in non-interactive mode use `build-a-brand --quick`
→ [1] analyze_brief pika MCP: product name + tagline + features + tone + CTA
→ [2] resolve product UI assets pika MCP: use supplied/brand-kit screenshots, or capture_website(product URL)
→ [2] analyze_media × N pika MCP: understand what each real asset shows
→ [3] write script you (Claude): 4 acts × 15s; map assets to acts
→ [4] founder/location refs pika MCP: upload or generate founder ref; carry supplied custom location
→ [4.5] brand-kit ingestion parse brand.json OR brand.md + tokens → state.brand; generate default brand-accent location if needed
→ [5] generate_reference_video × 4 IN PARALLEL pika MCP: SeeDance acts, asset images as refs
→ [5.5] digital UI overlays pika MCP: composite real product UI / wordmark onto digital reveal acts + OCR QA
→ [6] edit_concat acts pika MCP: 60s stitched base (dialogue-only audio)
→ [7] generate_music pika MCP: Kling 60s soft instrumental background bed
→ [8] captions / lower-third pika MCP: add_captions for subtitles; render lower-third as transparent .mov, then overlay it with edit_video_compose when lower-third is enabled.
→ [9] render_html_animation pika MCP: 5s end card — author inline HTML, brand-kit fonts inlined, aspect matches body, no corner clutter, CSS @keyframes (NOT GSAP)
→ [10] edit_concat + audio_mix pika MCP: concat body + end card, then mix music over the full ~65s
→ [10.5] final duration probe pika MCP: analyze final_url and enforce the 55s duration floor before delivery
→ [11] final_url save the MCP returned final_url; upload only if a local fallback created the final MP4
→ [12] deliver
Operational notes
Keep the main workflow focused on sequencing. Historical server validation details live in references/ops-notes.md; only the active constraints stay here:
- Use a unique
seedper SeeDance act (101, 202, 303, 404). Identical generation params can replay cached failures. - Kling music bed generation uses
provider: "kling-audio",mode: "text_to_audio",background: true, andduration_seconds: 60; the MCP worker generates one 10s Kling seed and extends it locally. - If SeeDance rejects a real-person founder photo, re-roll the founder ref with stronger stylization rather than retrying the same rejected reference.
- For local brand-kit logos, upload only logo-appropriate raster assets (
image/png,image/jpeg, orimage/webp). Do not send SVGs toupload_asset; choose the PNG export frombuild-a-brandor rasterize first. - Use CSS
background-image: url(...)for CDN-hosted logo/photo assets in end-card HTML;<img crossorigin>is blocked by CDN CORS. - Use server-side deterministic tools for captions, lower-third compose, concat, and mix. Local ffmpeg is only the fallback if
edit_video_composeis unavailable whilestate.lower_third = true. - Decompose every 15s act into 3 time-coded sub-shots. Single-shot acts look static.
- Open each act with the style-match location framing and repeat the same
WARDROBE LOCK:sentence across all 4 act prompts.
[1] Analyze brief
analyze_brief(
sources=[{ type: "url", url: <product_url> }],
context: "Founder-style 60-second product video. Need: product name, one-line tagline, 3-5 key features, target audience, brand tone, and a call-to-action."
)
Save the result as brief. You'll reference brief.product_name, brief.tagline, brief.key_features, brief.tone, brief.call_to_action throughout.
[2] Resolve product UI assets, then analyze each asset + derive product_type
Before analyzing assets, normalize assets so product reveal shots have a real visual reference:
- Use any caller-provided
assetsfirst. - If none were supplied, read screenshots from
brand.json.screenshotswhen present. - If
brand.jsonhas no screenshots, look for obvious raster screenshots or product images inside the brand kit (screenshots/,assets/,product/, or image files named likehero,screen,app,dashboard,product). - When
assetsis empty after those checks, callcapture_websiteon the product URL:
capture_website(
url: <product_url>,
mode: "screenshot",
mobile: false
)
# Save result.image_url as assets[0].url with role="website_capture".
If the product is likely mobile-first, also run a second capture with mobile: true and keep both URLs when available. Save these as real product UI assets before continuing.
Do not proceed to script writing, generate_reference_video, or any paid SeeDance call without at least one real product UI / product imagery asset, unless the caller explicitly set product_type: "service" and accepted an environment-only video. If capture fails or returns no image_url, surface: Could not capture <url>. Please provide screenshots or hosted product assets; founder-product-video will not silently ship without real product UI.
For each entry in assets, run analyze_media to extract content + visual style + asset type. Run all in parallel in one tool batch:
analyze_media(
media: <asset.url>,
query: 'Describe this product image briefly. Return STRICT JSON:
{
"content_description": "1-line summary of what is visible",
"asset_type": "digital_screen | physical_apparel | physical_object | consumable | infographic | other",
"key_elements": ["3-5 specific UI elements / features / objects in the image"],
"visible_copy": "any text visible — heading, button label, tagline, t-shirt graphic text (or empty string)",
"primary_colors": ["#hex", "#hex", "#hex"],
"vibe": "1-line visual feeling",
"best_for_act": "hook | problem | solution | proof"
}
Return ONLY the JSON.'
)
asset_type decoder:
digital_screen— app UI / website screenshot / SaaS dashboard / mobile app capturephysical_apparel— t-shirts, hoodies, hats, anything wearable (model + garment)physical_object— gadgets, accessories, packaged goods, anything held in handconsumable— food, beverages, supplements (something used/eaten/drunk)infographic— chart, diagram, data viz, illustrationother— anything else; describe and pick best fit
Save as asset_analyses[i].
After analysis, build:
usable_product_assets = asset_analyses
.map((analysis, i) => ({
asset_index: i, # original assets[] index to use in script.shots[].asset_index
asset_url: assets[i].url, # public URL passed to reference_images later
asset_type: analysis.asset_type,
analysis
}))
.filter(entry.asset_type in [
"digital_screen",
"physical_apparel",
"physical_object",
"consumable"
])
If usable_product_assets is empty and you have not already tried a URL capture, call capture_website(mode:"screenshot") on the product URL, append the returned image_url to assets, run analyze_media on that capture, save the analysis at the same new index, and rebuild usable_product_assets. The captured screenshot must therefore have its own asset_index; do not reuse a logo/hero/infographic index for a product reveal.
Do not auto-derive product_type = "service" from logo-only, hero-only, infographic, abstract brand, or other assets. Those are not real product reveal anchors. If usable_product_assets is empty after the capture attempt, stop unless the caller explicitly set product_type: "service" and accepted an environment-only video. Surface the missing-assets error instead of falling through to the service reveal pattern.
Derive product_type
Look at the dominant asset_type across usable_product_assets:
product_type = mode(usable_product_assets[i].asset_type) → mapped to {
digital_screen → "digital"
physical_apparel → "physical_apparel"
physical_object → "physical_object"
consumable → "consumable"
}
If user passed product_type explicitly, use that and skip auto-derivation; service is valid only when explicitly chosen/accepted. The product_type value drives which shots are picked in step [3] and how the founder reveals the product in step [5]. Get this right or the video shows the wrong thing on screen.
Product type → reveal pattern (the most important table in this skill)
product_type (set in step [2]) controls which shots to pick AND how the asset is revealed in each shot. The reveal beat in the SeeDance prompt is product-specific; using the wrong one makes the founder hold a phone for a t-shirt brand.
For digital products, the SeeDance prompt is only responsible for the founder, camera move, phone gesture, and a blank / neutral screen placeholder. Do not ask Seedance or the video model to render readable brand wordmark or product UI text. The real screenshot, product UI, and exact brand spelling are composited in step [5.5].
| product_type | Reveal shots | Reveal beat (used in SeeDance prompt) | Which acts get assets |
|---|---|---|---|
digital |
C-phone, E-phone | "founder lifts her phone toward camera; the phone has a blank neutral screen placeholder reserved for the real UI overlay; do not render readable UI text or brand wordmark" | shots C and E only |
physical_apparel |
G-hold, G-wear, E-detail | "founder lifts a charcoal-washed graphic tee toward camera; the shirt design exactly matches @ImageN — match the print/graphic exactly, do not invent" OR "founder is wearing the t-shirt from @ImageN — match the print exactly" | EVERY shot where a t-shirt is visible (C/E/F + the "wearing" variants) |
physical_object |
C-hold, E-detail, F-twoshot | "founder holds the [product name] up toward camera; the product exactly matches @ImageN — match shape, color, branding" | shots that show the product |
consumable |
C-hold, E-detail, H-using | "founder holds/uses the [product]; the packaging/product matches @ImageN exactly" | shots that show the product |
service |
A, B, D, F (environment) | no specific product reveal — focus on founder + environment | none of the shots reference assets |
For physical products, every shot where the product appears in frame should pass that asset as a reference image; otherwise SeeDance tends to invent a generic-looking product. For apparel, if the founder is wearing a t-shirt and the script says "we make t-shirts", the founder's t-shirt needs to reference one of the assets even in shots that are not reveal moments. Pass the asset URLs in reference_images for those acts and write prompt language like "the founder is wearing the t-shirt from @Image3 — print matches exactly".
[3] Write script + character voice + per-shot asset + per-line beats (you do this — no model call)
Three sub-products, all written by you (Claude) in one inline JSON:
character_voice_profile— 3-4 lines describing the character's DEFAULT delivery (carries through every act for consistency)- Per-shot
asset_index+reveal_beat— what asset is visible in this shot and how it's revealed - Per-shot
beats[]— line-by-line acting direction withemotion+physical+ silence beats between sentences
This is what separates a generic AI-talking-head from a character that actually feels intentional. Read all four sub-sections below ([3.0] founder voice, [3a] character voice profile, [3b] beats, [3c] transitions, [3c.1] acting energy, [3d] full JSON) before writing.
[3.0] Founder voice — write a PITCH, not a feature list
The single most common failure mode in this skill is dialogue that reads like a marketing-page bullet list ("It can reason. Code. Even write your emails. No proxies. No selectors. No maintenance. Plug it into LangChain. LlamaIndex. MCP. Twenty-four thousand stars on GitHub. MIT licensed. Production-grade.") — clean copy, but it's not how a founder talks. Field feedback: "the script sounds like a list of features, not like a founder would sell their product on camera."
Real founders pitching their own product on camera use:
- First-person ownership — "I built", "we shipped", "we use it ourselves", "honestly we just want this everywhere"
- A personal stake or origin moment — Act 1 should reference a frustration the founder lived through, NOT the product abstractly. "Every time I tried building X, I hit the same wall" beats "X is hard."
- Conversational connectives — "look", "honestly", "the thing is", "so", "actually", trailing "..." for thinking. These are throwaway words in writing but the breath of natural speech.
- A "bet" framing for the product — "what if X just worked?", "we asked ourselves", "the whole idea was". Founders frame their product as an answer to a question they asked themselves, not as a list of capabilities.
- One concrete anchor — a specific number, a specific time, a specific scenario. "24 thousand devs starred it last year" beats "it's popular." "At 3 AM the layout breaks" beats "scrapers are unreliable."
- Invitation-energy CTA — "come try us", "go play with it", "we just want it everywhere". NOT "stop scraping. start extracting." (that's a Don Draper tagline, not a founder).
Banned patterns (each was empirically called out by the user, do not repeat):
| ❌ Banned pattern | Example | Why |
|---|---|---|
| Triple-negation chant | "No proxies. No selectors. No maintenance." | Feels like a marketing chant, not human speech |
| Capability staccato | "It can reason. Code. Even write your emails." | Reads as a feature checklist |
| Integration-list-as-pitch | "Plug it into LangChain. LlamaIndex. MCP." | Listing integrations is fine ONCE in passing — never as a 3-beat hook |
| Tagline closer | "Stop scraping. Start extracting." | Pure ad-copy. Founders close with invitation, not a slogan |
| Specs-as-pitch | "MIT licensed. Production-grade." | Specs go in the README, not the founder's mouth on camera |
| "Just" as filler in a list | "Just one API call. Just any URL. Just structured JSON." | "Just" repeated reads as marketing emphasis, not natural speech |
Allowed patterns (use these instead):
| ✅ Pattern | Example |
|---|---|
| Personal-stake hook | "Honestly — every time I tried building X, same thing happened. ..." |
| "What if" framing | "So we built Y. The whole idea was: what if Z just worked?" |
| One concrete claim | "Last year we hit 24 thousand stars. People are plugging us in everywhere." |
| Casual aside on pain | "Hand it a URL. Get clean structured data. The layout changes? Doesn't matter." |
| Invitation closer | "If your agent needs to actually see the live web — come try us." |
Structure (4 acts, ~30-40 words per act = 120-160 words total, ~50-60s spoken):
- Act 1: Personal stake / pain. First person. Reference a specific frustration the founder lived through. Land on the problem named cleanly.
- Act 2: The bet. "So we built X. The idea was — what if [pain] just worked?" One sentence on what it actually does (URL → data, prompt → image, etc).
- Act 3: Proof + community. ONE specific number (stars, customers, ARR). One casual mention of integrations or where it's used. Tone: quiet confidence, not bragging.
- Act 4: Invitation. "If [reader's situation] — come try us. [URL]. [One inviting line]." End on warmth, not a tagline.
Self-test before approving the script. Read each act's dialogue out loud. If you'd be embarrassed to say it on camera as the founder, rewrite it. If it sounds like a 30-second commercial voiceover, rewrite it. If a paragraph has more than two punctuation periods in a row of short fragments, rewrite it.
[3a] Derive character_voice_profile + wardrobe_lock
Two separate fields, both required:
character_voice_profile (3-4 lines) — how the character delivers EVERYTHING: cadence, default expression, signature gestures, hand habits, pause behavior, when smiles arrive. Look at brief.tone + the character reference image (character_image_url or your generated founder ref) + product_type. This is an actor's "circumstance" — not what they're saying, but who they are. It carries through all 4 acts so consistency feels intentional, not accidental.
wardrobe_lock (1 sentence) — what the character is wearing in every act. SeeDance reads @Image1 fresh for each 15s generation and may interpret different clothing between acts. The wardrobe_lock sentence is repeated verbatim in every act's prompt to keep clothing consistent. Read what the founder is wearing in the reference photo and describe it explicitly. Example: "wearing the same charcoal hoodie over a dark band tee throughout all 4 acts, black-framed glasses on". Without an explicit wardrobe lock, later acts can invent different clothing even when the first act matches @Image1.
Tonal-template starters (orchestrator picks/customizes from the brief tone):
brief.tone |
Default cadence | Face | Hands | Pauses |
|---|---|---|---|---|
casual |
conversational, like explaining to a friend at coffee | slight smirk default, eyebrow flicks on reveals | open out flat on big claims, hand to chin when thinking | held eye contact instead of filling silence |
playful |
light staccato, expressive | mischief lives near the eyes, frequent eyebrow flicks, smiles arrive a beat after the punchline | light shoulder bounces, animated count-on-fingers | brief pauses with knowing looks |
professional |
measured pace, deliberate | soft direct eye contact, restrained smile | hand positions deliberate not constant, single open palm gesture | confident silences, doesn't fill |
technical |
analytical, slightly slower | analytical default, eyes cycle to think then return on landing | hand-to-chin thinking gesture, points to imaginary diagrams | thinking-pauses, eyes go up-left |
disruptive / edgy |
staccato, clipped sentences with sudden pauses | dry deadpan default, mischievous grin breaks through then disappears | body stays still, the FACE does the work | sharp pauses, slight head tilts |
Worked example — developer-tool founder (casual tone, 3D Pixar 20s woman):
"Casual confidence, like explaining the product to a friend at coffee. Slight smirk default. Eyebrow flicks on key reveals. Hand goes to chin when thinking, opens out flat on the big 'meet the A P I' claim. Pauses with held eye contact rather than filling silence. Lands punchlines deadpan and lets a small smile arrive a beat after."
Worked example — streetwear founder (playful/edgy tone):
"Sharp dry wit. Talks fast in clipped sentences with sudden pauses. Default slight smirk with one raised eyebrow. Eye-rolls on the pain points ('boring', 'generic'). Mischievous grin breaks through on punchlines but disappears immediately. Hands stay mostly still — the FACE does the work."
[3b] Per-line beats[] — line-level direction, not act-level
Each shot's dialogue is broken into beats. Each beat is one short sentence (or a deliberate silence) with its own emotion + physical direction. Silence between beats is part of the performance — fill it with held looks, micro-expressions, gesture transitions.
A beat with text: "(beat)" is silent (no spoken text) — it just describes what happens visually during the natural pause between sentences. Use these between dialogue beats that need a held moment for emphasis.
When the SeeDance prompt is built in step [5], beats become the per-shot acting direction (the dialogue text without (beat) markers becomes the <<<voice_1>>> payload).
[3c] transition_from_prev — choreograph continuous camera motion between shots in the same clip
The fundamental SeeDance limitation: each 15s SeeDance generation renders ONE virtual environment with ONE virtual camera. When a multi-shot prompt declares "Shot C, then Shot A" without specifying a continuous camera move between them, SeeDance defaults to re-framing the same camera position (zoom or crop). The result reads as a jump zoom, not a real cut — same background, character at different sizes.
The fix: every shot beyond the first in an act must declare a transition_from_prev field — a one-line description of the continuous camera motion that takes us from the previous shot's framing to this one. SeeDance then has to render an actual move-through-space, which means different parts of the room appear behind the character across the clip.
Pattern: name the camera's start position, name where it ends up, name the move that connects them. Movement verbs that work: dolly, pull back, push in, orbit, arc, glide, crane up, crane down, tilt up, tilt down, drift left/right.
Examples:
transition_from_prev |
Effect |
|---|---|
| "Camera pulls back and arcs left, revealing the brick wall and standing desk behind her now in frame" | Real spatial change — different background portion |
| "Push past the phone screen into a closer framing of her face — the room blurs behind her" | Continuous motion using rack focus + dolly |
| "Camera glides clockwise around her at a steady distance, picking up the whiteboard and plants on the new side" | Orbit reveals new background |
| "Pull back from her hands holding the phone to a medium shot, then drift right toward the window light" | Two-step continuous move |
| ❌ "Cut to medium shot" / ❌ "Now we see her in a medium shot" | These don't describe motion — SeeDance falls back to same-position re-frame |
The first shot in an act has no transition_from_prev — it establishes the framing. Every subsequent shot in that act gets one.
SeeDance can do hard cuts within a single 15s clip when prompted explicitly. Write Hard cut: between time-coded sub-shots (instead of Transition:) for distinct framing changes — SeeDance honors this and renders a real cut, not a re-frame. Reserve Transition: for continuous-motion handoffs where you want the camera to glide between framings. Pattern: hard cuts feel like a real edited piece (different framings, different camera angles, different acting energy); transitions feel like a single moving long take.
[3c.1] Acting energy floor — every beat needs explicit body movement
A frequent failure mode: beats are written with only facial micro-expressions ("slight nod", "eyebrow flick", "eyes hold camera"). SeeDance renders this as a near-frozen founder — eyes barely move, no presence. Result reads as "static, frozen, no excitement."
Rule: every beat's physical field needs at least one of:
- A hand or arm gesture (open palm, count on fingers, dismissive flick, point at self/camera, hand to chest, wider arm sweep, hand-to-temple thinking)
- A torso shift (lean forward, lean back, slight body turn, shoulder shift)
- A head action LARGER than a micro-flick (turn left/right and back, tilt 8°+, slow head shake, head bob on rhythm)
- A directional eye flick combined with eyebrow movement (look down then snap up to camera, etc.)
Facial-only beats are acceptable only for:
- Silent
(beat)markers between spoken sentences (those are meant to be still — the held look is the point) - Final landing beat at end of an act when the camera is already moving (camera does the work)
When you write the SeeDance prompt, make sure the assembled "Acting beats" block reads physically dense — if you scan it and see five beats in a row that all say "slight nod" or "small smirk" with no other movement, the founder will look frozen. Rewrite with bigger movement.
[3d] Script JSON
{
"product_type": "<from step [2]>",
"character_voice_profile": "Casual confidence, like explaining to a friend at coffee. Slight smirk default. Eyebrow flicks on key reveals. Hand goes to chin when thinking, opens out flat on big claims. Pauses with held eye contact rather than filling silence. Lands punchlines deadpan, lets a small smile arrive a beat after.",
"segments": [
{
"act": 1,
"shots": [
{
"type": "A",
"asset_index": null,
"beats": [
{ "text": "Assistants are brilliant.", "emotion": "declarative respect — say it like she means it", "physical": "soft direct eye contact, slight nod" },
{ "text": "(beat)", "physical": "subtle smirk arrives, eyes hold camera" },
{ "text": "But it's also kind of...", "emotion": "playful pivot, ellipsis hangs", "physical": "slight head tilt right, eyes drift up briefly on the ellipsis" },
{ "text": "shapeless.", "emotion": "deadpan landing", "physical": "eyes return to camera, single dismissive shrug" }
]
},
{
"type": "B",
"asset_index": null,
"transition_from_prev": "Camera pushes in slowly from the medium framing into a tighter close-up, drifting slightly off-axis to her right so a different slice of the brick wall and window light is visible behind her",
"beats": [
{ "text": "No face. No voice. No personality of its own.", "emotion": "staccato dismissal", "physical": "small head shake on each, eyebrow flick on 'personality'" },
{ "text": "(beat)", "physical": "held look, eyes lock camera, soft smile starts to arrive" },
{ "text": "Just an empty assistant waiting for orders.", "emotion": "flat, slightly resigned deadpan", "physical": "neutral face" },
{ "text": "(beat)", "physical": "soft confident smile arrives, lean forward begins" },
{ "text": "That's about to change.", "emotion": "grounded conviction, the turn", "physical": "lock eyes, single confident nod on 'change'" }
]
}
]
},
{
"act": 2,
"shots": [
{
"type": "C",
"asset_index": 0,
"reveal_beat": "The character holds her phone up toward camera at chest height, screen facing the viewer. The screen is a clean blank neutral placeholder reserved for the real product UI overlay in step [5.5]; do not render readable UI text, brand wordmark, or fake app chrome.",
"beats": [
{ "text": "Meet the A P I.", "emotion": "introduction with quiet pride", "physical": "phone lifts to camera, eyes flick from screen to lens" },
{ "text": "One workflow that gives your product a face, a voice, and a story.", "emotion": "warm steady build", "physical": "free hand counts the three on fingers — face, voice, story" }
]
},
{
"type": "A",
"asset_index": null,
"transition_from_prev": "Camera pulls back from the phone and arcs slightly left, the phone lowers out of frame as we end on a medium shot of the character with the desk and whiteboard now visible behind her",
"beats": [
{ "text": "And the ability to make videos, images, audio.", "emotion": "expanding the promise", "physical": "open-palm gesture sweeps wider on each item" },
{ "text": "Right inside the chat.", "emotion": "the grounding kicker", "physical": "hand lands flat, eyebrows up, slight smile arrives" }
]
}
]
},
{
"act": 3,
"shots": [
{
"type": "D",
"asset_index": null,
"beats": [
{ "text": "Setup takes thirty seconds.", "emotion": "matter-of-fact reassurance", "physical": "walks past a desk, glances at a laptop briefly" },
{ "text": "Open the dashboard, paste the URL, sign in.", "emotion": "quick rhythmic checklist", "physical": "counts three on fingers as she walks" }
]
},
{
"type": "E",
"asset_index": 1,
"reveal_beat": "Close-up of hands holding phone. The screen is a clean blank neutral placeholder reserved for the real product UI overlay in step [5.5]; do not render readable UI text, brand wordmark, or fake app chrome.",
"transition_from_prev": "Camera dollies in fast past her shoulder to land on a tight close-up of her hands and the phone, the loft background drops fully out of focus",
"beats": [
{ "text": "Your app becomes a guide.", "emotion": "the soft surprise reveal", "physical": "small smile at the phone, then up to camera, eyes warm" }
]
},
{
"type": "A",
"asset_index": null,
"transition_from_prev": "Camera pulls back and tilts up from the phone to find her face in a medium shot, the brick wall and afternoon light now visible behind her on a different side of the loft than Shot D",
"beats": [
{ "text": "Or whoever you build.", "emotion": "casual aside", "physical": "small shrug, slight smirk" },
{ "text": "(beat)", "physical": "held look, smirk fades into warm sincerity" },
{ "text": "Now talk to her like a person.", "emotion": "the real point — quiet conviction", "physical": "single nod on 'person', eyes hold" }
]
}
]
},
{
"act": 4,
"shots": [
{
"type": "F",
"asset_index": null,
"beats": [
{ "text": "Skills bundled in.", "emotion": "casual confidence, intro to a list", "physical": "slight tilt of the chin, knowing look" },
{ "text": "Podcasts. Explainer videos. U G C ads.", "emotion": "rhythmic three-beat list", "physical": "small nod on each, eyebrow flick on 'U G C'" },
{ "text": "All from chat.", "emotion": "the grounding tag", "physical": "open-palm gesture lands flat, slight smile arrives" }
]
},
{
"type": "B",
"asset_index": null,
"transition_from_prev": "Camera pushes in slowly from the wider brand-context framing into an intimate medium close-up; the environment recedes into soft bokeh, the character fills more of the frame",
"beats": [
{ "text": "So stop wrestling with generic A I.", "emotion": "direct address, low-key challenge", "physical": "raised eyebrow, slight head tilt" },
{ "text": "(beat)", "physical": "held look, smirk grows" },
{ "text": "Give it a real presence.", "emotion": "the brand line, said with certainty", "physical": "lean slightly into camera, lock eyes" },
{ "text": "Start at example dot com slash demo.", "emotion": "warm CTA, the invitation", "physical": "soft confident smile, single closing nod on 'demo'" }
]
}
]
}
]
}
Per-shot asset assignment rules:
- For digital products: only shots
CandEget anasset_index(phone-reveal moments). These are overlay pointers for step [5.5] and are excluded from the Seedance reference image array. Other shots show the founder without a specific UI reference. - For physical products: any shot where the product is visible in frame gets an
asset_index. The asset is the source of truth for what that product looks like. Acts can reuse the same asset across multiple shots, OR show a different asset per shot to demo product variety. - For service products: no
asset_indexanywhere — the script relies on dialogue + environment.
Every non-null asset_index must come from usable_product_assets[*].asset_index. Never assign a logo-only, hero-only, infographic, abstract brand, or other asset index to a product reveal shot. When using product details in dialogue, prefer usable_product_assets[*].analysis.key_elements and usable_product_assets[*].analysis.visible_copy so the spoken pitch tracks the same real product artifact shown on screen.
Reference image array per act = the union of Seedance-safe asset URLs across that act's shots. Digital digital_screen assets are not Seedance-safe; they stay overlay-only for step [5.5]. Within the SeeDance prompt, refer to non-digital assets by their position in the array as @Image3, @Image4 (positions 3+ — positions 1 and 2 are always character + location refs). The orchestrator computes this mapping when building the prompt in step [5].
Dialogue rules — TOTAL across all 4 acts must read aloud in 55–60s (~150 wpm = ~150 words total, ~37 per act). Short, punchy, speakable. Avoid em-dashes (founders don't speak them). Use natural contractions. Reference the user's actual product features (drawn from usable_product_assets[*].analysis.key_elements and usable_product_assets[*].analysis.visible_copy), not invented ones.
TTS pronunciation rewrites
SeeDance's native lip-sync TTS reads <<<voice_1>>> text literally — it has no semantic awareness that "Ari" is a name, "Vercel" is pronounced "ver-SELL", "UGC" is an acronym to spell, or "example.com" is a URL. Rewrite the dialogue text the way you want it pronounced, then submit. Apply these substitutions:
| Pattern | Wrong (literal) | Correct (rewrite for TTS) |
|---|---|---|
Names ending in -i |
"Ari" → "ah-REE" | "Airy" (or "Tess" / "Mae" / "Sam" — phonetic) |
| Names with unfamiliar spellings | "Aoife" → confused | "Eefa" (phonetic) |
Non-phonetic product / brand names from state.brand.name or brief.product_name |
"Vercel" → "Verkon"; "Linear" → "Lineer" | Create a TTS-only phonetic alias, e.g. "ver-SELL", and use it whenever the brand is spoken inside dialogue / <<<voice_1>>>. |
| Acronyms meant to be spelled out | "UGC" → "uhg" / "ugg"; "MCP" → "mehp" / silent | "U G C" / "M C P" (single spaces between letters) |
| Acronyms spoken as words | "NASA" → "nasa" ✅ (already correct); "IKEA" → "ikea" ✅ | leave alone |
| Domain dots | "example.com" → "examplecom" | "example dot com" |
| URL slashes / paths | "example.com/API" → "examplecom-api" | "example dot com slash A P I" |
| Symbols | "$50" → silent; "@user" → "at user" or silent | "fifty bucks" / "at-sign user" |
| Numbers in weird formats | "2026" → ambiguous | "twenty twenty-six" for years; "two thousand" for round numbers |
When in doubt about how a brand pronounces an acronym (NASA vs N.A.S.A.) or non-obvious product name, check the brand's own website / videos. Default to spelled-out letters for unclear acronyms and a simple phonetic alias for non-phonetic names. For domains that include the brand, combine both rewrites: Vercel.com becomes ver-SELL dot com in the <<<voice_1>>> payload.
Worked example — original script vs TTS-safe rewrite:
Original: "UGC ads. All from chat. Launch the API. Start at example.com/API."
Loading...
Select a file to preview
Analyzing security...
Checking scan reports and verification data.
Bill of Materials
Everything this skill can do — files, network, commands, and more.