AI Film Production
Build a narrative short from a premise. Two stages: generation (paid, slow) and
post-production (free, fast, endlessly re-runnable). Keeping them in separate
scripts is the most important structural decision here — transitions, grading,
loudness, subtitles and delivery get re-run dozens of times without spending again.
shots_<project>.py scenes: shot design, identity, props, audio rules, dialogue
check_script.py lints the script; --script prints the dialogue alone free
generate.py → out_<project>/*.mp4 via pika_client.py (stdlib only) paid
audit_faces.py → audit_<project>/*.png face contact sheets free
finish.py → concat → grade → (subtitles) → (score) → cut free
verify_film.py measures the cut: runtime, colour, music leak, banding free
deliver.py → MASTER / full / share, two-pass linear loudness free
Copy templates/ into a working directory, then cp shots_template.py shots_<project>.py and edit that. Every other script is generic and routes by
--project <name>. Run check_script.py before spending anything.
The one rule everything else follows from
The model executes descriptions. It does not execute negations. And it fills
every space you leave it.
Nine times out of ten a defect is fixed by changing what you described, not by
adding another "NO …". Measured, repeatedly:
| Defect |
Adding a negative |
Changing the description |
| Extras appearing on a small boat |
failed twice — "NO other passengers, NO crowd, NO boatman" |
fixed first try — "shoot the whole boat bow to stern so the people can be counted" |
| A santa hat from the reference photo |
failed |
fixed — supply a clean reference photo |
| Calligraphy laid out wrong |
failed twice |
fixed — describe the physical sheet, not the forbidden layout |
| Two characters swapped for the whole film |
n/a |
fixed — remove the body-type adjectives that were fighting the photos |
| A second pistol appeared in a stand-off |
n/a |
fixed — "EXACTLY ONE revolver, the one in her two hands" instead of placing it "low in the corner of frame" |
| A destroyed room came back barely scuffed |
"comprehensively destroyed" |
better — "the room is ALREADY a wreck in the first frame" |
Negatives still earn their place for iconography ("no opera makeup", "no fur on
the face") — they suppress a strong prior. They are near-useless for composition,
staging and headcount.
Order of work
- Story first. Premise → 8–19 scenes, four acts, uneven durations. Pick the
recurring object and the spoken promise, and write the causal chain as a list
of facts before any shot design. →
references/story.md
- Check the subject for identity risk (see below). This decides how much of
your budget goes to reruns.
- Reference photos. Real photos, clean background, face large. Two per person
at different angles is best; one clean frontal photo can hold. →
references/identity.md
- Write
shots_*.py. Six design elements per shot; dialogue carries all plot.
→ references/prompting.md
- Audit it as a stranger.
check_script.py --script prints only what the
audience hears. Read it top to bottom as someone who has never heard of the film.
- Generate ONE shot with each new face and look at it — cropped, front-on.
A profile test frame cannot verify anything.
- Generate the rest, then iterate freely in post.
- Verify by measurement, not by glance. Face crops beside the reference;
verify_film.py for everything else. Exit code 0 means nothing here.
Choose the subject knowing what it costs
Subject choice is the single largest driver of identity cost. A character with
a famous "standard face" will overwrite the actor's face, and no amount of prompt
work fully removes it.
| Subject |
Result |
| Idiom fables, original stories, generic-period drama |
12/12 first pass, zero identity reruns |
| Dream of the Red Chamber — famous but no face paint |
13/13 first pass |
| Mulan — famous but no fixed face |
11/13, 2 unrelated failures |
| A 1942 Hollywood romance remade with two leads in their seventies |
2 of 17 first-pass shots rerun for face drift (small face; a prop holding focus); every later rerun was story, staging or wardrobe |
| Journey to the West — Monkey King |
3 full rounds of reruns |
| Three Kingdoms — Guan Yu's red face and beard |
8 of 12 shots reshot |
If the brief demands a high-prior character, budget the reruns up front and say so.
Then strip the entire iconography — not just the name. → references/identity.md
Non-negotiable rules
Each of these cost a generation cycle to learn.
- Write shot design, not scene description. LENS, FOREGROUND, FRAME, BLOCKING,
EYELINE, MOVE. Without them: subject-centred, symmetrical, facing camera — a
stock photo. Camera-body jargon buys texture, not cinema.
- The photograph wins. Never write a physical attribute that contradicts the
reference photo. Body-type adjectives override the
@ImageN tag and the model
will silently swap two characters for an entire film.
- Never age a face. No de-aging, no aging-up, no child/teen variants. The era
is carried by wardrobe, props, hair and set. A flashback to the leads' youth is
shot without faces — hands, objects, weather — rather than de-aged.
- Lock identity with hardware that exists in the photo. Glasses shape (square
vs round vs oval) is the most reliable discriminator found. Build, height and face
shape are not — they fool you in profile and in low light.
- A recurring prop needs a fixed description block, exactly like a character.
"A small notebook" yields a different notebook every scene and the object's
three-beat silently breaks.
- Three simultaneous demands per shot, maximum. Ask for five and the model
does the cheapest one.
- Never let the video model score the film. It composes music, the music trips
copyright moderation, and the job fails after rendering — you pay, you get
nothing. Ban it in every prompt; score in post, if at all.
- Title and end cards must be declared
ABSOLUTELY SILENT. With no dialogue
the model invents ambience or music and trips the same copyright filter.
- Max three speakers and five lines per shot. Beyond that the model loses the
turn-taking and has two characters say the same line in unison. Add an explicit
one-speaker-per-line rule to the global audio block.
- Adding to a prompt weakens what is already in it. A long new prop block let
Christmas wardrobe back in; a focal-plane block added to a stand-off dropped
both leads' costumes. After lengthening a prompt, re-verify what previously
passed, and restate wardrobe inside the shot as continuity.
provider_timeout is not a content problem. A ~20-minute server ceiling,
driven by queue load, not shot complexity. It bills nothing. Retry unchanged.
- Never use a negative for headcount or framing. Define the frame so the wrong
answer has nowhere to live — and that applies to props: place a weapon by whose
hands hold it, never by its position in frame.
- One generation is one image. A drift across four subjects ("a series of
tight framings… up the sleeve… out to the platform") came back as a dark blur,
twice, in two films. Rewrite as one sustained image with one move.
- Transitions are picture-only. A dip to black fades the image; sound carries
straight through. A line was measured starting at 0.00s of the shot after a dip;
in a controlled test the old 0.35s audio fade took 17 dB off its first syllable.
A first-time viewer is the only audience that matters
A remake passed every check — faces, colour, sync, lint — and still lost its
audience. "If I'm watching this for the first time I don't know what's going on."
Every break was missing spoken information:
| Break |
Fix |
| A flashback's city was first named five shots after the flashback |
name it in the scene that leads into it |
| The heroine's name was never spoken in 3.5 minutes; the hero's, four times |
name every lead aloud, early |
| The rule of the world (no one leaves without papers) was never stated |
say it — it is why the whole plot matters |
| The ending moved three people on two travel permits |
count the resource out loud at the climax; plant the exception in the first half |
| A time jump (night to dawn, one city to another) happened on a hard cut |
dip to black at every jump in time or place |
The method, all free:
1. List the facts a stranger needs, each with the line that says it:
CAUSAL_CHAIN = [("nobody leaves without papers", "nobody leaves this city"), …]
check_script.py errors on any fact nobody speaks.
2. check_script.py --script → read only the dialogue, top to bottom, as a stranger.
3. Count every countable thing: tickets, seats, doses, bullets, letters.
Viewers do this arithmetic whether you meant them to or not.
When a viewer says they are confused, it is a script defect, not a taste note.
Do not re-grade or re-time it; find the fact nobody said. → references/story.md
Verification is a procedure, not a glance
Three separate defects shipped past a "verified" wide frame — a soft out-of-focus
face, a face that had drifted to a stranger, and two leads swapped for the entire
film. All three were found by the user, not by the check.
For every shot with a face:
1. Crop the face region and scale it up
2. Put it beside the reference photograph
3. Judge by glasses shape / hardware — not by face shape
4. Confirm WHICH person is wearing WHICH costume
audit_faces.py does the contact sheets. Look at three frames per shot (15%,
50%, 85%), never one: a single mid-frame once read as "side by side" for a shot
that was correctly back to back, and "no fight" for a shot where the table went over.
Then measure the cut — verify_film.py:
| Check |
How |
What it has caught |
| Runtime |
cut vs sum of real clip durations |
concat truncation that exits 0 |
| Colour |
95th-percentile chroma, never the mean |
"black and white" shots in full colour; the mean hid them |
| Music |
spectral concentration per clip |
a model-composed score under a music ban |
| Banding |
flat runs on gradients, skipping black frames |
a false 1,663 px "band" that was a dip to black |
| Wardrobe |
mean brightness of a costume region per shot |
a white dinner jacket that went dark (15 vs 128) |
A measurement that cannot be computed is a failure, not a pass. A probe once
returned −1 for every clip because its parser matched nothing, and the table read
like a clean bill of health.
Subtitles: time them from the audio
Generated dialogue often sits at −37 dBFS under ambience. Fed raw to a
transcription model it returns timings 10+ seconds out.
1. Normalise hard first highpass + compand + loudnorm ← this is the fix
2. ElevenLabs Scribe, word-level timestamps USD 0.004/min
3. Align script lines to word timings sequentially (script order is authoritative)
4. Never sort cues by timestamp — it reorders dialogue
Tokenise by language. Scribe returns per-character tokens for Chinese and
per-word tokens for English; comparing letters to words matches nothing and yields
an empty subtitle file with no error.
→ references/postproduction.md
Look, sound and delivery
- Black and white is demanded in the prompt AND forced in post (
MONOCHROME = True): panchromatic channel mix plus a silver-gelatin curve, not hue=s=0.
- Grain goes on in post (
GRAIN = 5), uniform across every cut, and every
grainy master encodes with -tune grain — otherwise x264 smooths the grain
and the banding it hid comes back.
- Loudness is two-pass linear. Single-pass
loudnorm is a compressor: measured,
it lifted a silent title card from −65 to −16 dBFS and collapsed a 36 dB gap to
4 dB in a controlled test (on a real film: title at −17 dBFS single-pass, −42
linear). deliver.py measures, then applies one constant gain.
- Default to no score for dialogue-driven shorts (
SCORE = False). A viewer
rejected a sidechain-ducked orchestral bed as competing with the lines, even with
speech holding 70–80% of the spectrum. Ask before scoring.
-movflags +faststart or the video looks broken in a browser. Remux, don't
re-encode.
- Three tiers: MASTER (video stream-copied, first generation), full-resolution
CRF 19 for watching, and a 720p share copy stepped down to fit a size cap. Chat
and upload tools cap attachments (25–50 MB is common), so size the share tier to
the channel. Never upscale.
- Name cards must be opt-in. A fallback that returned a default set burned one
film's character names into two unrelated films.
→ references/delivery.md
Setup and access
PIKA_API_KEY in the environment or a .env.local beside the scripts (chmod 600,
never committed). Endpoints and field names come from the published index at
https://dev.pika.art/llms.txt — read the linked per-model spec rather than
guessing; the aspect field is ratio, not aspect_ratio.
When nothing renders, the status says why, and the three look alike from a distance:
| Response |
Meaning |
401 |
key unknown or malformed |
403 "Your API key is not active" |
key exists but was deactivated |
caller_plan_required |
key works; the plan does not include that model |
422 |
payload rejected — often a prompt over 15,000 characters |
An ImportError in the wrapper script hides all four. One pipeline died importing
a client library that had been deleted, which masked both a deactivated key and a
plan that no longer included video models — two separate problems behind one
traceback.
Cost
Read spend from job billing records, never from list prices — a naive estimate once
read 4× high.
| Operation |
Measured |
seedance-2.5 reference- or text-to-video, 1080p, 4:3 and 21:9 |
USD 0.46/s (0.457–0.460 across 60+ renders) |
seedance-2.0-fast/reference-to-video |
~USD 0.83/shot |
minimax/minimax-music-3.0 (is_instrumental: true) |
USD 0.09/call; ~105–170 s, no duration control |
eleven-scribe/transcription |
USD 0.004/min |
A 200-second film is ~USD 92 before reruns. Iteration on two remakes added 24% and
133% — the second because the story was rewritten twice after a viewing. Budget at
least one full second pass. Concurrency is capped at 20 jobs in
flight — exceeding it returns 429 on uploads too.
bytedance/seed-audio-1.0/text-to-audio is a TTS model; instrumental prompts are
rejected by its speech safety audit. Model discovery: GET /catalog/apis.
Variants are cheap
- Alternate ending — override only the shots that change. 3 of 11 ≈ USD 16.
- Another language — reuse every shot design (already in English), swap only
dialogue and the casting names.
- Re-grade, re-time, re-mix, re-deliver — free.
Working practice
- Do not invent constraints the user did not set. Three rounds were burned
forcing horizontal calligraphy to "match the previous film" when vertical is the
correct convention and the real defect was a petal covering a character.
- When a complaint is directional, confirm the direction. "There are several
people on the boat" was a bug report; it was read as a request.
- Show a test before committing a batch. Any new face: one render, cropped,
front-on, approved, then spend.
- Gate every render on the patch having landed. A prompt patch aborted on a
failed assertion, wrote nothing, and three shots were rendered — and paid for —
without the fix. Grep the built prompt for the new text before submitting.
- Keep rejected takes. Move them aside rather than overwriting; the old take is
the evidence for why the prompt changed.
generate.py skips clips that exist.
- Never write a shell wait-loop that greps for your own command.
pgrep -f "generate.py" matches the wrapper shell running the loop, so it waits forever.
Use your harness's background mechanism instead.
- Lint before you spend.
check_script.py catches the music ban, the 15,000-char
prompt cap, casting on empty plates, missing shot-design elements, montages inside
one generation, weapons without a headcount, and unspoken causal facts.
- No dollar-sign figures in this file. When the skill is invoked with arguments,
a dollar sign followed by a digit is treated as a positional placeholder and
replaced by a word of the user's request — every price here once rendered as the
name of the film being made. Write
USD 0.45.
Rights
- Do not use a real public figure's likeness as a reference image. Generate an
original character instead. Real private people from photos they supplied are
fine — say once that you are assuming consent for the photos you were given.
- Genre premises, historical events and plot ideas are not protected; titles and
short phrases are not copyrightable. Extended dialogue and specific written
scenes are. A remake keeps the beats and the iconography, quotes at most a
famous short line or two, and writes every other line fresh. Treat public
distribution of a close remake with more care than a private one.
- Not-showing is a craft tool, not just a constraint. An antagonist as a gloved
hand, a husband as a hand on a shoulder and a hat on a chair, a therapist as a
voice behind the lens — each is stronger than showing the face, and each is one
fewer likeness to hold.
- Do not dramatise live litigation or unproven allegations about named real people.
Swapping the faces does not change what the text is about.
References
references/identity.md — read this first; faces, references, priors, swaps
references/story.md — object, promise, rhyme, the stranger audit, endings
references/prompting.md — prompt anatomy, copy-ready blocks, failure catalogue
references/postproduction.md — transitions, grading, loudness, subtitles, ffmpeg traps
references/delivery.md — tiers, encoding, hosting, faststart
templates/ — shots_template.py, check_script.py, pika_client.py,
generate.py, audit_faces.py, finish.py, verify_film.py, deliver.py
examples/last-run/ — a complete 7-shot worked example
1---2name: ai-film3description: Produce multi-shot narrative short films (60–240s) with AI video generation — story architecture, shot design, locking a real person's face across dozens of shots, dialogue, transitions, black-and-white and period looks, subtitles timed from audio, and multi-language/multi-ending variants. Use when asked to make a short film, movie, remake, cinematic video, trailer, or any multi-scene narrative video with recurring characters. Also use when an existing AI video looks "not cinematic", when a viewer says they can't follow the story, when characters drift or get swapped between shots, when someone's face stops looking like them, when props or wardrobe change between scenes, when a transition swallows a line, when "black and white" comes back in colour, or when a finished video won't play in a browser.4---56# AI Film Production78Build a narrative short from a premise. Two stages: **generation** (paid, slow) and9**post-production** (free, fast, endlessly re-runnable). Keeping them in separate10scripts is the most important structural decision here — transitions, grading,11loudness, subtitles and delivery get re-run dozens of times without spending again.1213```14shots_<project>.py scenes: shot design, identity, props, audio rules, dialogue15check_script.py lints the script; --script prints the dialogue alone free16generate.py → out_<project>/*.mp4 via pika_client.py (stdlib only) paid17audit_faces.py → audit_<project>/*.png face contact sheets free18finish.py → concat → grade → (subtitles) → (score) → cut free19verify_film.py measures the cut: runtime, colour, music leak, banding free20deliver.py → MASTER / full / share, two-pass linear loudness free21```2223Copy `templates/` into a working directory, then `cp shots_template.py24shots_<project>.py` and edit that. Every other script is generic and routes by25`--project <name>`. Run `check_script.py` before spending anything.2627---2829## The one rule everything else follows from3031**The model executes descriptions. It does not execute negations. And it fills32every space you leave it.**3334Nine times out of ten a defect is fixed by *changing what you described*, not by35adding another "NO …". Measured, repeatedly:3637| Defect | Adding a negative | Changing the description |38|---|---|---|39| Extras appearing on a small boat | failed twice — "NO other passengers, NO crowd, NO boatman" | fixed first try — "shoot the whole boat bow to stern so the people can be counted" |40| A santa hat from the reference photo | failed | fixed — supply a **clean reference photo** |41| Calligraphy laid out wrong | failed twice | fixed — describe the physical sheet, not the forbidden layout |42| Two characters swapped for the whole film | n/a | fixed — remove the body-type adjectives that were fighting the photos |43| A second pistol appeared in a stand-off | n/a | fixed — "EXACTLY ONE revolver, the one in her two hands" instead of placing it "low in the corner of frame" |44| A destroyed room came back barely scuffed | "comprehensively destroyed" | better — "the room is ALREADY a wreck in the first frame" |4546Negatives still earn their place for *iconography* ("no opera makeup", "no fur on47the face") — they suppress a strong prior. They are near-useless for composition,48staging and headcount.4950---5152## Order of work53541. **Story first.** Premise → 8–19 scenes, four acts, uneven durations. Pick the55 recurring object and the spoken promise, and **write the causal chain as a list56 of facts** before any shot design. → `references/story.md`572. **Check the subject for identity risk** (see below). This decides how much of58 your budget goes to reruns.593. **Reference photos.** Real photos, clean background, face large. Two per person60 at different angles is best; one clean frontal photo can hold. →61 `references/identity.md`624. **Write `shots_*.py`.** Six design elements per shot; dialogue carries all plot.63 → `references/prompting.md`645. **Audit it as a stranger.** `check_script.py --script` prints only what the65 audience hears. Read it top to bottom as someone who has never heard of the film.666. **Generate ONE shot with each new face and look at it — cropped, front-on.**67 A profile test frame cannot verify anything.687. **Generate the rest**, then iterate freely in post.698. **Verify by measurement, not by glance.** Face crops beside the reference;70 `verify_film.py` for everything else. Exit code 0 means nothing here.7172---7374## Choose the subject knowing what it costs7576**Subject choice is the single largest driver of identity cost.** A character with77a famous "standard face" will overwrite the actor's face, and no amount of prompt78work fully removes it.7980| Subject | Result |81|---|---|82| Idiom fables, original stories, generic-period drama | **12/12 first pass**, zero identity reruns |83| *Dream of the Red Chamber* — famous but no face paint | 13/13 first pass |84| *Mulan* — famous but no fixed face | 11/13, 2 unrelated failures |85| A 1942 Hollywood romance remade with two leads in their seventies | 2 of 17 first-pass shots rerun for face drift (small face; a prop holding focus); every later rerun was story, staging or wardrobe |86| *Journey to the West* — Monkey King | **3 full rounds of reruns** |87| *Three Kingdoms* — Guan Yu's red face and beard | **8 of 12 shots reshot** |8889If the brief demands a high-prior character, budget the reruns up front and say so.90Then strip the entire iconography — not just the name. → `references/identity.md`9192---9394## Non-negotiable rules9596Each of these cost a generation cycle to learn.97981. **Write shot design, not scene description.** LENS, FOREGROUND, FRAME, BLOCKING,99 EYELINE, MOVE. Without them: subject-centred, symmetrical, facing camera — a100 stock photo. Camera-body jargon buys texture, not cinema.1012. **The photograph wins.** Never write a physical attribute that contradicts the102 reference photo. Body-type adjectives override the `@ImageN` tag and the model103 will silently **swap two characters for an entire film**.1043. **Never age a face.** No de-aging, no aging-up, no child/teen variants. The era105 is carried by wardrobe, props, hair and set. A flashback to the leads' youth is106 shot **without faces** — hands, objects, weather — rather than de-aged.1074. **Lock identity with hardware that exists in the photo.** Glasses *shape* (square108 vs round vs oval) is the most reliable discriminator found. Build, height and face109 shape are not — they fool you in profile and in low light.1105. **A recurring prop needs a fixed description block, exactly like a character.**111 "A small notebook" yields a different notebook every scene and the object's112 three-beat silently breaks.1136. **Three simultaneous demands per shot, maximum.** Ask for five and the model114 does the cheapest one.1157. **Never let the video model score the film.** It composes music, the music trips116 copyright moderation, and the job fails *after* rendering — you pay, you get117 nothing. Ban it in every prompt; score in post, if at all.1188. **Title and end cards must be declared `ABSOLUTELY SILENT`.** With no dialogue119 the model invents ambience or music and trips the same copyright filter.1209. **Max three speakers and five lines per shot.** Beyond that the model loses the121 turn-taking and has two characters say the same line in unison. Add an explicit122 one-speaker-per-line rule to the global audio block.12310. **Adding to a prompt weakens what is already in it.** A long new prop block let124 Christmas wardrobe back in; a focal-plane block added to a stand-off dropped125 both leads' costumes. After lengthening a prompt, re-verify what previously126 passed, and restate wardrobe inside the shot as continuity.12711. **`provider_timeout` is not a content problem.** A ~20-minute server ceiling,128 driven by queue load, not shot complexity. It bills nothing. Retry unchanged.12912. **Never use a negative for headcount or framing.** Define the frame so the wrong130 answer has nowhere to live — and that applies to props: place a weapon by whose131 hands hold it, never by its position in frame.13213. **One generation is one image.** A drift across four subjects ("a series of133 tight framings… up the sleeve… out to the platform") came back as a dark blur,134 twice, in two films. Rewrite as one sustained image with one move.13514. **Transitions are picture-only.** A dip to black fades the image; sound carries136 straight through. A line was measured starting at 0.00s of the shot after a dip;137 in a controlled test the old 0.35s audio fade took 17 dB off its first syllable.138139---140141## A first-time viewer is the only audience that matters142143A remake passed every check — faces, colour, sync, lint — and still lost its144audience. "If I'm watching this for the first time I don't know what's going on."145Every break was missing **spoken** information:146147| Break | Fix |148|---|---|149| A flashback's city was first named **five shots after** the flashback | name it in the scene that leads into it |150| The heroine's name was **never spoken** in 3.5 minutes; the hero's, four times | name every lead aloud, early |151| The rule of the world (no one leaves without papers) was never stated | say it — it is why the whole plot matters |152| The ending moved **three people on two travel permits** | count the resource out loud at the climax; plant the exception in the first half |153| A time jump (night to dawn, one city to another) happened on a hard cut | dip to black at every jump in time or place |154155The method, all free:156157```1581. List the facts a stranger needs, each with the line that says it:159 CAUSAL_CHAIN = [("nobody leaves without papers", "nobody leaves this city"), …]160 check_script.py errors on any fact nobody speaks.1612. check_script.py --script → read only the dialogue, top to bottom, as a stranger.1623. Count every countable thing: tickets, seats, doses, bullets, letters.163 Viewers do this arithmetic whether you meant them to or not.164```165166**When a viewer says they are confused, it is a script defect, not a taste note.**167Do not re-grade or re-time it; find the fact nobody said. → `references/story.md`168169---170171## Verification is a procedure, not a glance172173Three separate defects shipped past a "verified" wide frame — a soft out-of-focus174face, a face that had drifted to a stranger, and **two leads swapped for the entire175film**. All three were found by the user, not by the check.176177```178For every shot with a face:179 1. Crop the face region and scale it up180 2. Put it beside the reference photograph181 3. Judge by glasses shape / hardware — not by face shape182 4. Confirm WHICH person is wearing WHICH costume183```184185`audit_faces.py` does the contact sheets. Look at **three frames per shot** (15%,18650%, 85%), never one: a single mid-frame once read as "side by side" for a shot187that was correctly back to back, and "no fight" for a shot where the table went over.188189Then measure the cut — `verify_film.py`:190191| Check | How | What it has caught |192|---|---|---|193| Runtime | cut vs sum of **real** clip durations | concat truncation that exits 0 |194| Colour | 95th-percentile chroma, never the mean | "black and white" shots in full colour; the mean hid them |195| Music | spectral concentration per clip | a model-composed score under a music ban |196| Banding | flat runs on gradients, skipping black frames | a false 1,663 px "band" that was a dip to black |197| Wardrobe | mean brightness of a costume region per shot | a white dinner jacket that went dark (15 vs 128) |198199**A measurement that cannot be computed is a failure, not a pass.** A probe once200returned −1 for every clip because its parser matched nothing, and the table read201like a clean bill of health.202203---204205## Subtitles: time them from the audio206207Generated dialogue often sits at −37 dBFS under ambience. Fed raw to a208transcription model it returns timings 10+ seconds out.209210```2111. Normalise hard first highpass + compand + loudnorm ← this is the fix2122. ElevenLabs Scribe, word-level timestamps USD 0.004/min2133. Align script lines to word timings sequentially (script order is authoritative)2144. Never sort cues by timestamp — it reorders dialogue215```216217**Tokenise by language.** Scribe returns per-character tokens for Chinese and218per-word tokens for English; comparing letters to words matches nothing and yields219an empty subtitle file with no error.220221→ `references/postproduction.md`222223---224225## Look, sound and delivery226227- **Black and white is demanded in the prompt AND forced in post** (`MONOCHROME =228 True`): panchromatic channel mix plus a silver-gelatin curve, not `hue=s=0`.229- **Grain goes on in post** (`GRAIN = 5`), uniform across every cut, and every230 grainy master encodes with **`-tune grain`** — otherwise x264 smooths the grain231 and the banding it hid comes back.232- **Loudness is two-pass linear.** Single-pass `loudnorm` is a compressor: measured,233 it lifted a silent title card from −65 to −16 dBFS and collapsed a 36 dB gap to234 4 dB in a controlled test (on a real film: title at −17 dBFS single-pass, −42235 linear). `deliver.py` measures, then applies one constant gain.236- **Default to no score for dialogue-driven shorts** (`SCORE = False`). A viewer237 rejected a sidechain-ducked orchestral bed as competing with the lines, even with238 speech holding 70–80% of the spectrum. Ask before scoring.239- **`-movflags +faststart` or the video looks broken** in a browser. Remux, don't240 re-encode.241- **Three tiers**: MASTER (video stream-copied, first generation), full-resolution242 CRF 19 for watching, and a 720p share copy stepped down to fit a size cap. Chat243 and upload tools cap attachments (25–50 MB is common), so size the share tier to244 the channel. Never upscale.245- **Name cards must be opt-in.** A fallback that returned a default set burned one246 film's character names into two unrelated films.247248→ `references/delivery.md`249250---251252## Setup and access253254`PIKA_API_KEY` in the environment or a `.env.local` beside the scripts (chmod 600,255never committed). Endpoints and field names come from the published index at256`https://dev.pika.art/llms.txt` — read the linked per-model spec rather than257guessing; the aspect field is `ratio`, not `aspect_ratio`.258259When nothing renders, the status says why, and the three look alike from a distance:260261| Response | Meaning |262|---|---|263| `401` | key unknown or malformed |264| `403 "Your API key is not active"` | key exists but was deactivated |265| `caller_plan_required` | key works; the plan does not include that model |266| `422` | payload rejected — often a prompt over 15,000 characters |267268An `ImportError` in the wrapper script hides all four. One pipeline died importing269a client library that had been deleted, which masked both a deactivated key and a270plan that no longer included video models — two separate problems behind one271traceback.272273---274275## Cost276277Read spend from job billing records, never from list prices — a naive estimate once278read 4× high.279280| Operation | Measured |281|---|---|282| `seedance-2.5` reference- or text-to-video, 1080p, 4:3 and 21:9 | **USD 0.46/s** (0.457–0.460 across 60+ renders) |283| `seedance-2.0-fast/reference-to-video` | ~USD 0.83/shot |284| `minimax/minimax-music-3.0` (`is_instrumental: true`) | USD 0.09/call; ~105–170 s, no duration control |285| `eleven-scribe/transcription` | USD 0.004/min |286287A 200-second film is ~USD 92 before reruns. Iteration on two remakes added 24% and288133% — the second because the story was rewritten twice after a viewing. Budget at289least one full second pass. Concurrency is capped at 20 jobs in290flight — exceeding it returns 429 on *uploads* too.291292`bytedance/seed-audio-1.0/text-to-audio` is a TTS model; instrumental prompts are293rejected by its speech safety audit. Model discovery: `GET /catalog/apis`.294295---296297## Variants are cheap298299- **Alternate ending** — override only the shots that change. 3 of 11 ≈ USD 16.300- **Another language** — reuse every shot design (already in English), swap only301 `dialogue` and the casting names.302- **Re-grade, re-time, re-mix, re-deliver** — free.303304---305306## Working practice307308- **Do not invent constraints the user did not set.** Three rounds were burned309 forcing horizontal calligraphy to "match the previous film" when vertical is the310 correct convention and the real defect was a petal covering a character.311- **When a complaint is directional, confirm the direction.** "There are several312 people on the boat" was a bug report; it was read as a request.313- **Show a test before committing a batch.** Any new face: one render, cropped,314 front-on, approved, then spend.315- **Gate every render on the patch having landed.** A prompt patch aborted on a316 failed assertion, wrote nothing, and three shots were rendered — and paid for —317 without the fix. Grep the built prompt for the new text before submitting.318- **Keep rejected takes.** Move them aside rather than overwriting; the old take is319 the evidence for why the prompt changed. `generate.py` skips clips that exist.320- **Never write a shell wait-loop that greps for your own command.** `pgrep -f321 "generate.py"` matches the wrapper shell running the loop, so it waits forever.322 Use your harness's background mechanism instead.323- **Lint before you spend.** `check_script.py` catches the music ban, the 15,000-char324 prompt cap, casting on empty plates, missing shot-design elements, montages inside325 one generation, weapons without a headcount, and unspoken causal facts.326- **No dollar-sign figures in this file.** When the skill is invoked with arguments,327 a dollar sign followed by a digit is treated as a positional placeholder and328 replaced by a word of the user's request — every price here once rendered as the329 name of the film being made. Write `USD 0.45`.330331---332333## Rights334335- **Do not use a real public figure's likeness** as a reference image. Generate an336 original character instead. Real private people from photos they supplied are337 fine — say once that you are assuming consent for the photos you were given.338- Genre premises, historical events and plot *ideas* are not protected; titles and339 short phrases are not copyrightable. **Extended dialogue and specific written340 scenes are.** A remake keeps the beats and the iconography, quotes at most a341 famous short line or two, and writes every other line fresh. Treat public342 distribution of a close remake with more care than a private one.343- **Not-showing is a craft tool, not just a constraint.** An antagonist as a gloved344 hand, a husband as a hand on a shoulder and a hat on a chair, a therapist as a345 voice behind the lens — each is stronger than showing the face, and each is one346 fewer likeness to hold.347- Do not dramatise live litigation or unproven allegations about named real people.348 Swapping the faces does not change what the text is about.349350---351352## References353354- `references/identity.md` — **read this first**; faces, references, priors, swaps355- `references/story.md` — object, promise, rhyme, the stranger audit, endings356- `references/prompting.md` — prompt anatomy, copy-ready blocks, failure catalogue357- `references/postproduction.md` — transitions, grading, loudness, subtitles, ffmpeg traps358- `references/delivery.md` — tiers, encoding, hosting, faststart359- `templates/` — `shots_template.py`, `check_script.py`, `pika_client.py`,360 `generate.py`, `audit_faces.py`, `finish.py`, `verify_film.py`, `deliver.py`361- `examples/last-run/` — a complete 7-shot worked example