@aicopycoders/exodus 2026.9.2000 → 2026.10.1200-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -38,6 +38,37 @@ storyboard itself when its checks pass (see "What costs money"). The full line i
38
38
  note cannot contain a colon. "The script file" shows the right form and the
39
39
  wrong one.
40
40
 
41
+ **The kinds of ad.** `--conceit` takes one of the kinds the server offers. The
42
+ server's list is the truth, and nothing prints it on request. Pass a key it
43
+ doesn't offer and the refusal names every key it does: `video start --conceit
44
+ must be one of …`. Nothing is spent on a refused start. Today the server offers
45
+ 15 formats and then Custom:
46
+
47
+ | Key | Kind |
48
+ |---|---|
49
+ | `podcast` | Podcast |
50
+ | `ugc` | UGC talking head |
51
+ | `stage` | Speaking on stage |
52
+ | `street` | Street interview |
53
+ | `personification` | Personification |
54
+ | `news-broadcast` | News Broadcast |
55
+ | `court-tv` | Court TV |
56
+ | `soap-opera` | Soap Opera |
57
+ | `storytime` | Storytime While Doing a Task |
58
+ | `customer-service-call` | Customer Service Call |
59
+ | `debate-circle` | Debate Circle |
60
+ | `talk-show-panel` | Talk Show Panel |
61
+ | `infomercial` | Infomercial |
62
+ | `dating-show` | Dating Show |
63
+ | `game-show` | Game Show |
64
+ | `custom` | Custom |
65
+
66
+ Custom builds the ad from the script itself. A script with no `CAST:` runs as a
67
+ voice-over, and a script with people runs as a scene. Voice-over and Scene are
68
+ not on the menu by name; Custom is the way to them. A narrator-only script fits
69
+ Custom alone. If the refusal lists different keys from this table, the server
70
+ is right and this table is stale.
71
+
41
72
  The Rig box on that workflow is where the run's Conceit and Style come from.
42
73
  Conceit is the family plus Wrapper, Narrator Person, and Delivery. Style is the
43
74
  look. Narrated Story is the voice-over family and Acted Story is the dialogue
@@ -70,7 +101,7 @@ never how it is made. Every answer to them says `"view": "customer"`, and the
70
101
  CLI words it the way the run page does. On their own brands they can:
71
102
 
72
103
  - `video start --script <file> --conceit <key> --style <slug>` with
73
- `--direction`, `--music`, `--review-storyboard` and `--wait`. Any other start
104
+ `--direction`, `--frame`, `--pacing`, `--music`, `--review-storyboard` and `--wait`. Any other start
74
105
  flag is refused with one sentence naming it. The answer is the run id and the
75
106
  link to the video's page.
76
107
  - `video status <runId>`: where the video is, in plain words (waiting in line,
@@ -125,9 +156,12 @@ exodus workflow run <workflowId|name> --input <field>=@script.txt --voices @voic
125
156
  exodus workflow run <workflowId|name> --input <field>=@script.txt --voice-treatment @voice-treatment.json --wait
126
157
  same, with the video model speaking voices you WROTE
127
158
  exodus video status <runId> where it is, per scene
128
- exodus video start --script <file> --conceit <podcast|ugc|stage|street|personification> --style <slug> [--direction "…"] [--voice-path <path>] [--video-model <id>] [--voice native] [--music] [--review-storyboard] [--director] [--wait]
129
- start from a script, a conceit and a look.
159
+ exodus video start --script <file> --conceit <kind> --style <look> [--direction "…"] [--frame pov] [--pacing <jump-cuts|long-take|montage>] [--voice-path <path>] [--video-model <id>] [--voice native] [--music] [--review-storyboard] [--director] [--wait]
160
+ start from a script and the kinds the server offers.
130
161
  No music bed unless you pass --music.
162
+ --frame pov (conversation conceits) and --pacing
163
+ (presenter conceits) change the shots; a conceit
164
+ that can't follow one refuses it.
131
165
  The app approves its own storyboard when its
132
166
  checks pass and goes straight on to the clips,
133
167
  which is the part that costs. It stops at the
@@ -365,6 +399,78 @@ Do not use a long dash in words a character says, or in words shown on screen.
365
399
  Synthesised speech reads a long dash oddly, and so does burned-in text. A spoken
366
400
  line stays `NAME: what they say`.
367
401
 
402
+ ### Directing a line
403
+
404
+ A spoken line can carry two kinds of note. Neither is required.
405
+
406
+ - **How the line is said goes in parentheses.** The voice acts it out.
407
+ - **What happens on camera goes in square brackets.** The storyboard plans the
408
+ shot around it.
409
+
410
+ ```
411
+ MIA: (whispering, excited) I can't believe this actually worked.
412
+ MIA: [holds the jar up to the camera] This is the one.
413
+ MIA: [leans in] (quietly) Don't tell my sister.
414
+ ```
415
+
416
+ A note may sit anywhere on the line. It comes out of the spoken words, so the
417
+ voice never says it. Several notes of one kind on a line are joined. Notes work
418
+ on `CTA:` lines and on narrator lines too. They do not work on `CAST:` lines,
419
+ where anything in brackets is just part of the description.
420
+
421
+ How the voice uses a delivery note. By default a run records each line first,
422
+ on the Eleven v4 voices, which take the note as a short cue in front of the
423
+ line. The cue is the note's first clause, at most six words. A full stop,
424
+ colon, semicolon, question mark, exclamation mark, or a dash with a space on
425
+ each side ends the clause. A comma does not, so `whispering, excited` reaches
426
+ the voice whole. Keep the note short and put the part that matters first. Put
427
+ one delivery note on a line: two are joined with a semicolon, so the voice
428
+ hears only the first.
429
+
430
+ Right.
431
+
432
+ ```
433
+ MIA: (whispering, excited) I can't believe this actually worked.
434
+ ```
435
+
436
+ Wrong.
437
+
438
+ ```
439
+ MIA: (She is whispering. She can barely contain how excited she is) I can't believe this actually worked.
440
+ ```
441
+
442
+ The wrong note reaches the voice as `[she is whispering]`. The excitement is
443
+ lost.
444
+
445
+ In a narrated scene the whole narration is read as one part, so it takes one
446
+ cue: the note on the first narrator line in that scene.
447
+
448
+ What the reader refuses, with the code `malformed-direction`:
449
+
450
+ - an empty note, `()` or `[]`
451
+ - a square bracket that is never closed, or a stray `]`
452
+ - a line with no spoken words left once the notes come out
453
+
454
+ An unclosed `(` is not refused. It is read as ordinary words, and the voice
455
+ says it. Check every `(` has its `)`.
456
+
457
+ Both ways in read the file the same way: `exodus video start --script` and
458
+ `exodus workflow run --input <field>=@script.txt`. The second holds only when
459
+ the script goes straight from a Brief into the Storyboard. In a workflow where a
460
+ bot writes or rewrites the script first, the notes are not read this way, and
461
+ the bot may drop them or leave them in the spoken words.
462
+
463
+ With `--voice native` (admins only) the video model speaks the lines itself and
464
+ nothing is recorded first, so the six-word cue above does not apply.
465
+
466
+ **A line with no note still gets a natural read.** A note is the surest way to
467
+ get a particular delivery. The storyboard planner may add a note of its own to
468
+ a line you left plain. A note you wrote always wins over the planner's.
469
+
470
+ When you help the user write or tidy a script, suggest a delivery note where
471
+ the tone carries the ad: the hook, the turn, the CTA. Do not put a note on every
472
+ line. A note on every line reads as a script nobody trusted.
473
+
368
474
  **How many speakers you may have depends on the rig.** Three by default. A rig
369
475
  that carries saved format rules sets its own number: an ensemble rig seats more,
370
476
  a talking-head rig seats one. Over the number, the launch refuses before
@@ -979,6 +1085,10 @@ Read these out of `storyboard.json` before you build anything, in any tool:
979
1085
  between the two halves.
980
1086
  - **Overlays and SFX.** `overlays[]` and `audio.sfxCues[]` are cued the same
981
1087
  way, by `cueLineId`. Nothing renders them for you.
1088
+ - **Jump cuts.** When `manifest.json` has `shots.pacing: "jump-cuts"`, every
1089
+ scene was filmed from the same framing on purpose. Punch in about 15% on
1090
+ every other cut between one speaker's scenes, so each cut visibly moves in
1091
+ or out. The bundled script does this for you.
982
1092
  - **Captions.** Nothing is burned into any clip. Captions belong in post; the
983
1093
  word timings are what an editor imports to build them.
984
1094
 
@@ -988,19 +1098,28 @@ State this to the user before you run it, so they know what the cut does with
988
1098
  the pieces:
989
1099
 
990
1100
  - **It cuts the picture to the voice, and never the other way round.** A
991
- narrated scene's segment runs exactly as long as that scene's voice track plus
992
- a beat of air. A clip that runs longer is trimmed; a clip that runs shorter
993
- holds its last frame for the difference. The narration is never sped up,
994
- slowed down or cut short, so every scene plays at the speed it was recorded,
995
- and the word times are handed on as they were measured.
1101
+ narrated scene's segment runs to the narrator's last word plus a beat of air:
1102
+ about 0.25 s before the next scene, 0.5 s when it ends the ad. The last word
1103
+ comes from the voice track's own word timings; when the scene has none, the
1104
+ end of the voice file stands in for it. A clip that runs longer is trimmed; a
1105
+ clip that runs shorter holds its last frame for the difference. The narration
1106
+ is never sped up or slowed down, so every scene plays at the speed it was
1107
+ recorded, and the word times are handed on as they were measured. The voice
1108
+ file is cut only in the silence after its last word, never inside a word.
996
1109
  - **It closes the dead air at the joins.** A clip that plays its own dialogue
997
1110
  keeps about 0.15 s after its last word and about 0.1 s before its first (0.25 s
998
1111
  when a different speaker answers), read from its `words.json`, so one clip
999
1112
  runs into the next like one conversation. It only ever shortens a clip's
1000
1113
  silence and never cuts into a word. The ad's first clip also opens about 0.1 s
1001
1114
  before its first word, so a hum a clip makes before anyone speaks is not the
1002
- first thing heard. The ad's last tail, a narrated scene, a still and a
1003
- reaction beat keep their length.
1115
+ first thing heard. The clip that ends the ad keeps about 0.5 s after its last
1116
+ word, the same beat a narrator line gets there, or longer when the storyboard
1117
+ marks an action after the line. A narrated scene, a still and a reaction beat
1118
+ keep their length.
1119
+ - **It shortens long pauses between sentences inside one clip.** A silence over
1120
+ 0.6 s between two sentences is cut down to about 0.5 s, picture and sound
1121
+ together, unless the script writes the pause in (an ellipsis or a dash), a
1122
+ direction asks for one, or a reaction's laugh follows the line.
1004
1123
  - **It fades the audience reaction in and out.** A reaction beat fades in
1005
1124
  across any quiet start before its laugh (a generated laugh clip can hum
1006
1125
  there). When a line follows, the picture moves on early and the laugh's own
@@ -1017,11 +1136,17 @@ the pieces:
1017
1136
  proportional estimate when the scene has no word times, and saying which
1018
1137
  method it used), a narrated scene's voice
1019
1138
  replacing whatever the clip recorded, a dialogue scene keeping its performed
1020
- line, a clip-less scene becoming a still, everything normalized to 1080×1920
1139
+ line, everything normalized to 1080×1920
1021
1140
  at 24 fps and −16 LUFS per segment, a generated room-tone bed under the whole
1022
1141
  ad, and the music bed underneath at low level. Output is H.264 + AAC in an MP4
1023
1142
  with `-movflags +faststart`.
1024
- - Flags: `--out <file>`, `--skip 2,5`, `--no-music`. It prints what went in,
1143
+ - **It stops when a scene has no clip.** A scene the storyboard plans that is
1144
+ still being made, never started, or failed stops the cut, which names each
1145
+ one. Wait for the clip and pull again, leave the scene out with `--skip`, or
1146
+ pass `--still-fill` to hold that scene's keyframe on screen instead: under its
1147
+ voice track if it has one, else silent for its planned length. The still is
1148
+ only ever made when asked for, because a run of them reads as a broken ad.
1149
+ - Flags: `--out <file>`, `--skip 2,5`, `--no-music`, `--still-fill`. It prints what went in,
1025
1150
  what was left out and why, and the upload command. When a clip hums or
1026
1151
  clicks where nobody speaks at an edge the cut plays, it lists each one under
1027
1152
  "Sound at the edges" with its time in the cut. That is a warning to listen
@@ -1032,8 +1157,12 @@ the pieces:
1032
1157
  timeline, each scene's own word times offset by where that scene starts in the
1033
1158
  cut, less any lead-in trimmed at the join, and otherwise unaltered. Scenes with no word times leave
1034
1159
  gaps and are named in the report; when no scene delivered any, neither file is
1035
- written. These sidecars are the script's own output — another tool will not
1036
- produce them.
1160
+ written. It always writes `cut.scenes.json` too: where each scene plays in the
1161
+ cut, one `{scene, startSec, seconds}` entry per placement. `exodus video
1162
+ upload` sends `cut.scenes.json` and `cut.words.json` with the cut when they
1163
+ sit beside it, so the customer's edit list shows each scene's time on screen
1164
+ and their editor folder gets the captions. These sidecars are the script's own
1165
+ output — another tool will not produce them.
1037
1166
 
1038
1167
  Before uploading anything, from any tool, check what you made:
1039
1168
  `ffprobe -v error -show_entries format=duration -of default=nw=1:nk=1 cut.mp4`
@@ -1,4 +1,4 @@
1
- // #2988: the types of the one export the Trigger worker calls. The script is
1
+ // #2988: the types of the exports the Trigger worker calls. The script is
2
2
  // plain JS so the CLI can run it with `node` and no build.
3
3
 
4
4
  export class FirstCutError extends Error {
@@ -14,6 +14,8 @@ export interface FirstCutOptions {
14
14
  skip?: Set<number>;
15
15
  /** The music bed, when the pull has one. Defaults to on, as the CLI does. */
16
16
  music?: boolean;
17
+ /** #3365: hold a scene's keyframe where it has no clip, instead of refusing. Defaults to off. */
18
+ stillFill?: boolean;
17
19
  }
18
20
 
19
21
  export interface FirstCut {
@@ -25,3 +27,18 @@ export interface FirstCut {
25
27
  }
26
28
 
27
29
  export function firstCut(options: FirstCutOptions): FirstCut;
30
+
31
+ /** #3365: the scenes the cut cannot be made without, by number. */
32
+ export function scenesWithNoClip(options: {
33
+ manifest: { scenes?: Array<{ sceneIndex: number; clip: string | null }> } | null;
34
+ storyboard: unknown;
35
+ skip: Set<number>;
36
+ exists?: (file: string | null) => boolean;
37
+ }): number[];
38
+
39
+ /** #3424: the sentence-gap cut, which scout's scribe.ts sentenceGapCuts mirrors. */
40
+ export function sentenceGapCuts(
41
+ words: Array<{ w: string; s: number; e: number }>,
42
+ lines: Array<{ text: string; asksPause: boolean; cuesBeat: boolean }>,
43
+ levels?: { windowSec: number; db: number[] } | null,
44
+ ): Array<{ s: number; e: number; after: string; gap: number; kept: number; sound: boolean }>;