- Cuts inside one generation: write the shots into the prompt and the model executes real cuts, with one soundtrack binding them.
- A shot pipeline: script a film as a shot list, generate the shots concurrently (rate limits are per concurrent generation, so a film’s shots can run in parallel), and cut them together with ffmpeg.
1. Setup
Setup: the API client from the quickstart (run this first)
Setup: the API client from the quickstart (run this first)
2. Cuts inside one generation
Write the shots explicitly and mark the cuts. Two things make in-prompt cuts land:- Consecutive shots must differ strongly, and scale is the strongest kind of difference: macro to wide to aerial reads as three deliberate cuts, where three similar angles blend into one drifting take.
- One audio idea across all shots. Name a continuous bed and the cuts read as editing instead of channel-surfing. Here the bed has two layers: a cello drone that never breaks, and one substance (fire) whose sound is scaled per shot rather than restarted.
3. A film as a shot list
Past two or three shots, generate each shot as its own clip and edit. This is how you get films of any length, and it has real advantages over one long generation: shots run in parallel, a miss costs one shot instead of the film, and you keep editorial control of timing. The craft that holds a multi-generation film together:- A world bible. One paragraph describing the world (place, palette, light, film grade) pasted into every shot prompt word for word. Consistency across generations comes from consistent text, so copy it, don’t paraphrase it per shot.
- An audio motif. Name the same musical idea in every shot, staged for where the shot sits in the film (enters, warmer, builds, resolves). You cut the picture; the motif carries the through-line.
- One beat per shot. Each shot does one thing. The film is the sequence, not any single clip.
4. Generate the shots concurrently
Rate limits are on concurrent generations (5 per org on the API), not requests per second. Going over returns429 (too many active tasks): a signal to wait for a slot,
never something to retry in a loop.
So the pattern for a film is a thread pool. run_shots is the whole thing: three
workers keeps two tasks of headroom under the cap. If a shot fails or gets moderated,
the cell stops with that shot’s name in the traceback; finished shots are already on
disk, so reword the miss and re-run the cell - only the missing shots cost anything.
First job for the pool: one line, three languages
Before the film, a controlled experiment: three payloads, identical except for one templated block, run through the same pool. Hold everything else fixed and any difference in the output traces to the one lever you moved. The lever here is the spoken language, and it rides on two prompt rules that make dialogue reliable anywhere:- The exactly-once direction. “Speaks on camera exactly once, the complete sentence, no repetition and no other words in the entire shot.” Without it, lines repeat or pick up filler.
- The language lock. “Every word in French, no English words at all.” Without it, the model tends to translate the line back to English and speak that.
Now the film
Same pool, real workload: the four shots of the shot list, with the retry policy from the quickstart. AnError is worth one resubmit; a moderated result means reword, never
resubmit unchanged.
5. The edit
Straight cuts in script order, one re-encode for uniform codec parameters. The dissolve-free cut is the right default: the shots were written to contrast, and the motif carries the continuity.Scaling it up
Everything past four shots is the same loop with a longer shot list.- Length: a 12-shot film is one dict and the same
run_shotscall. Duration 5 suits action and single beats; 10 to 20 suits scenes that breathe or carry dialogue. - A recurring character: hold identity with
reference_imageson every shot the character appears in. Build the reference sheet once, as in Start from images; a world bible holds the world together, references hold a face. - A shot that ended too soon: extend it with
start_video(Edit, recast, continue) instead of re-rolling. - Webhooks over polling: for long shot lists, set
webhook_urlon each request and let the results come to you.

