Turn a film idea into a self-contained films/<slug>/ package the vs CLI can
generate: a stills.json and shots.json that pass validation and follow
Seedance 2.5 craft.
- IS: structuring a story into reference stills and generation units, writing Seedance prompts, and producing schema-valid JSON.
- IS NOT: the story itself (
storycraft), running the generations (vs), Nano Banana still prompts (nano-banana-2), or editing the CLI's TypeScript.
Write for 2.5. The CLI default is dreamina-seedance-2-5-260628 on BytePlus
ModelArk, so a film that omits film.model gets 2.5's envelope and 2.5's rules
on Ark. Ark is the default because it is the only route that returns a usage
block, which is what lets a run reconcile its quote against the real bill. Name
bytedance/seedance-2.5 to route through Vercel AI Gateway instead. Registry
confidence stays inferred: a capability mismatch on 2.5 is reported as a
warning and the request still goes out, rather than being refused.
Two worked examples, the same story either way, both lint-clean in CI:
| File | Shows |
|---|---|
examples/shots-2-5.json |
2.5: the film as two 30s acts, timestamp plans, a pure ordinal-bound pack in act 1 and the mixed mode (first_frame at @Image 1) in act 2 |
films/lighthouse/ |
2.0: the same film as twelve 8s shots, the complete planning ladder, keyframe-per-shot, cards, a real run |
lighthouse is the end-to-end demo of the whole pipeline and stays the model
for structure, anchoring, and planning documents. Its 8s Shot N: clips are
2.0's shape, not a template for a 2.5 act.
Read before writing
| Reference | Read when |
|---|---|
references/shots-schema.md |
Writing or fixing any JSON. Exact fields, the ordinal contract, hard rules, every lint |
references/seedance-prompting.md |
Writing any prompt. Duration, timestamp plans, brackets, reference binding |
references/film-pipeline.md |
Turning planning documents into JSON. Anchoring, blocks, still mapping |
Workflow
- [ ] 1. Lock the idea: logline, throughline, length, look, generation budget
- [ ] 2. Write the planning documents (storycraft)
- [ ] 3. Derive the keyframes and any prop/environment stills -> stills.json,
and the cast -> characters.json
- [ ] 4. Write shots.json: prompts, anchoring, ordinal bindings, seeds, cards
- [ ] 5. Run `vs cast sync` to write the cast blocks and sheet references
- [ ] 6. Dry-run both files, fix every warning, report cost
Step 1. Pin down the logline, the emotional throughline, total runtime,
generation count, look, and audio plan. Each shot's duration is a story unit
within the model envelope (2.5: 4-30s; 2.0: 4-15s). Short inserts, mid-length
multi-cuts, and 30s one-take acts are all valid; never force a house length and
never always max. On 2.5 the natural unit is an act, not a gesture: a 30s
pass holds setup, turn, and resolution. If the user gave a one-liner, propose a
logline and a final image, then confirm. storycraft owns this craft.
Step 3. One still per shot, sharing the shot's id, showing that shot's literal opening composition. Add character sheets (one per age block), hero props, and shared environment plates as needed. On 2.5, a long act usually also wants a per-segment staging plate, so each timestamp range has its own image ordinal to point at.
Step 4. Anchor every shot to at least one image, give it a seed matching
its still's seed, and write the beats as a timestamp plan. Name each
reference's single job by ordinal in the prompt. Shared style goes in
film.promptPreamble.
Step 5.
node dist/cli.js stills films/<slug>/stills.json --dry-run
node dist/cli.js generate films/<slug>/shots.json --dry-run
Same validation, lint, and cost estimate as a real run, with no network calls and no manifest writes. Then report the generation count, the summed clip seconds, and the card seconds separately, because cards add runtime the clip total does not show.
Non-negotiables
- Bind every reference by ordinal, and count ordinals per media type.
@Image 2is the second image inreferences[], not the second entry, and afirst_frameconsumes an image ordinal. Getting this wrong spends a whole 30s generation on the wrong reference for the wrong job. Full contract inshots-schema.md. - Anchor every shot to an image. Mode A: a
first_framekeyframe, the literal opening composition. Mode B: areference_imagepack for likeness, style, staging, and motion. On 2.5 you may combine them, and if you do, the frame role goes first so the packs start at@Image 2. On 2.0-family, mixing frame roles withreference_*is a schema error. A frame role is declared in the provider's exact words as a standalone sentence (Use @Image 1 as the first frame.) and locks the output ratio to that image, so a 9:16 shot needs a 9:16 still. - A film longer than one generation is several independent acts, cut together
by
vs stitch. Each act carries its own reference pack, is retakeable on its own, and generates concurrently up to the model's task limit. Continuing one generation from another's output is a repair tool, not a structure: seeseedance-prompting.md. - Past 20s, a timestamp plan is the carrier.
Shot N:orders the beats but says nothing about rhythm, so the model invents the pacing between them and the gaps stretch. Use0-6s:/7-13s:ranges. Integer seconds only: 2.5's documented unit is one second, and a fractional range like[0.0s-4.0s]is both off-grammar and invisible to the lint. - Segment count follows the event count, not a clock. Under-filling a range lets the model improvise; over-filling one makes it drop beats you paid for, and the documented fix is fewer stages rather than finer ones. Four to seven seconds a segment is where a six-event 30s act lands, not a target to hit. Every segment closes on an observable end state.
- Prompts are fully expanded. No tokens, no "the character from the previous shot". The model has no memory across generations. The exception is choreography, which the provider asks you to describe generally.
- Coverage, not empty time. Every timestamp segment changes camera setup and verb, and carries one principal camera move. Open mid-action. Do not invent a mandatory beat count.
- Per-clip audio is diegetic SFX and ambience only, in
<angle brackets>. Score and narration are mixed atvs stitch. KeepgenerateAudio: true. - Title cards are rendered in post via the
cardsarray. In-model text is unreliable, and any signage that must appear in frame is baked into the keyframe still instead. - Generate at 720p. 2.5 does render 1080p, but it costs 2.25x the tokens for
the same act and is widely reported to weaken prompt adherence, so 1080p is a
delivery upscale (
vs upscale, free, and only on the shots that survive the edit) rather than a generation target. There is no 4K on 2.5 at all. - A 2.5 film sets no
film.draftModel.vs generate --draftvalidates against the draft model, and 2.0-fast is documented at 4-15s, so a 30s film with a 2.0-fast draft model is refused outright. Leave it unset and--draftruns the film's own model at 480p.
Related skills
storycraftfor the treatment, style bible, beat sheet, and shot list.vsto run the pipeline once the JSON is written.nano-banana-2for still prompts when a film's stillsmodelisgemini-*, and for any keyframe that must contain legible text.