@aicopycoders/exodus 2026.9.2000 → 2026.10.1200-beta.1

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.
@@ -70,7 +70,7 @@ never how it is made. Every answer to them says `"view": "customer"`, and the
70
70
  CLI words it the way the run page does. On their own brands they can:
71
71
 
72
72
  - `video start --script <file> --conceit <key> --style <slug>` with
73
- `--direction`, `--music`, `--review-storyboard` and `--wait`. Any other start
73
+ `--direction`, `--frame`, `--pacing`, `--music`, `--review-storyboard` and `--wait`. Any other start
74
74
  flag is refused with one sentence naming it. The answer is the run id and the
75
75
  link to the video's page.
76
76
  - `video status <runId>`: where the video is, in plain words (waiting in line,
@@ -125,9 +125,12 @@ exodus workflow run <workflowId|name> --input <field>=@script.txt --voices @voic
125
125
  exodus workflow run <workflowId|name> --input <field>=@script.txt --voice-treatment @voice-treatment.json --wait
126
126
  same, with the video model speaking voices you WROTE
127
127
  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.
128
+ 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]
129
+ start from a script and the kinds the server offers.
130
130
  No music bed unless you pass --music.
131
+ --frame pov (conversation conceits) and --pacing
132
+ (presenter conceits) change the shots; a conceit
133
+ that can't follow one refuses it.
131
134
  The app approves its own storyboard when its
132
135
  checks pass and goes straight on to the clips,
133
136
  which is the part that costs. It stops at the
@@ -979,6 +982,10 @@ Read these out of `storyboard.json` before you build anything, in any tool:
979
982
  between the two halves.
980
983
  - **Overlays and SFX.** `overlays[]` and `audio.sfxCues[]` are cued the same
981
984
  way, by `cueLineId`. Nothing renders them for you.
985
+ - **Jump cuts.** When `manifest.json` has `shots.pacing: "jump-cuts"`, every
986
+ scene was filmed from the same framing on purpose. Punch in about 15% on
987
+ every other cut between one speaker's scenes, so each cut visibly moves in
988
+ or out. The bundled script does this for you.
982
989
  - **Captions.** Nothing is burned into any clip. Captions belong in post; the
983
990
  word timings are what an editor imports to build them.
984
991
 
@@ -988,19 +995,28 @@ State this to the user before you run it, so they know what the cut does with
988
995
  the pieces:
989
996
 
990
997
  - **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.
998
+ narrated scene's segment runs to the narrator's last word plus a beat of air:
999
+ about 0.25 s before the next scene, 0.5 s when it ends the ad. The last word
1000
+ comes from the voice track's own word timings; when the scene has none, the
1001
+ end of the voice file stands in for it. A clip that runs longer is trimmed; a
1002
+ clip that runs shorter holds its last frame for the difference. The narration
1003
+ is never sped up or slowed down, so every scene plays at the speed it was
1004
+ recorded, and the word times are handed on as they were measured. The voice
1005
+ file is cut only in the silence after its last word, never inside a word.
996
1006
  - **It closes the dead air at the joins.** A clip that plays its own dialogue
997
1007
  keeps about 0.15 s after its last word and about 0.1 s before its first (0.25 s
998
1008
  when a different speaker answers), read from its `words.json`, so one clip
999
1009
  runs into the next like one conversation. It only ever shortens a clip's
1000
1010
  silence and never cuts into a word. The ad's first clip also opens about 0.1 s
1001
1011
  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.
1012
+ first thing heard. The clip that ends the ad keeps about 0.5 s after its last
1013
+ word, the same beat a narrator line gets there, or longer when the storyboard
1014
+ marks an action after the line. A narrated scene, a still and a reaction beat
1015
+ keep their length.
1016
+ - **It shortens long pauses between sentences inside one clip.** A silence over
1017
+ 0.6 s between two sentences is cut down to about 0.5 s, picture and sound
1018
+ together, unless the script writes the pause in (an ellipsis or a dash), a
1019
+ direction asks for one, or a reaction's laugh follows the line.
1004
1020
  - **It fades the audience reaction in and out.** A reaction beat fades in
1005
1021
  across any quiet start before its laugh (a generated laugh clip can hum
1006
1022
  there). When a line follows, the picture moves on early and the laugh's own
@@ -1017,11 +1033,17 @@ the pieces:
1017
1033
  proportional estimate when the scene has no word times, and saying which
1018
1034
  method it used), a narrated scene's voice
1019
1035
  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
1036
+ line, everything normalized to 1080×1920
1021
1037
  at 24 fps and −16 LUFS per segment, a generated room-tone bed under the whole
1022
1038
  ad, and the music bed underneath at low level. Output is H.264 + AAC in an MP4
1023
1039
  with `-movflags +faststart`.
1024
- - Flags: `--out <file>`, `--skip 2,5`, `--no-music`. It prints what went in,
1040
+ - **It stops when a scene has no clip.** A scene the storyboard plans that is
1041
+ still being made, never started, or failed stops the cut, which names each
1042
+ one. Wait for the clip and pull again, leave the scene out with `--skip`, or
1043
+ pass `--still-fill` to hold that scene's keyframe on screen instead: under its
1044
+ voice track if it has one, else silent for its planned length. The still is
1045
+ only ever made when asked for, because a run of them reads as a broken ad.
1046
+ - Flags: `--out <file>`, `--skip 2,5`, `--no-music`, `--still-fill`. It prints what went in,
1025
1047
  what was left out and why, and the upload command. When a clip hums or
1026
1048
  clicks where nobody speaks at an edge the cut plays, it lists each one under
1027
1049
  "Sound at the edges" with its time in the cut. That is a warning to listen
@@ -1032,8 +1054,12 @@ the pieces:
1032
1054
  timeline, each scene's own word times offset by where that scene starts in the
1033
1055
  cut, less any lead-in trimmed at the join, and otherwise unaltered. Scenes with no word times leave
1034
1056
  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.
1057
+ written. It always writes `cut.scenes.json` too: where each scene plays in the
1058
+ cut, one `{scene, startSec, seconds}` entry per placement. `exodus video
1059
+ upload` sends `cut.scenes.json` and `cut.words.json` with the cut when they
1060
+ sit beside it, so the customer's edit list shows each scene's time on screen
1061
+ and their editor folder gets the captions. These sidecars are the script's own
1062
+ output — another tool will not produce them.
1037
1063
 
1038
1064
  Before uploading anything, from any tool, check what you made:
1039
1065
  `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 }>;