venice-video-harness 2.21.0 → 2.22.0
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.
- package/.agents/skills/venice-agent-guide/SKILL.md +1 -0
- package/AGENTS.md +1 -1
- package/CHANGELOG.md +107 -0
- package/README.md +139 -8
- package/capabilities.json +2 -2
- package/dist/agent/guide.d.ts.map +1 -1
- package/dist/agent/guide.js +1 -0
- package/dist/agent/guide.js.map +1 -1
- package/dist/mini-drama/cli.d.ts.map +1 -1
- package/dist/mini-drama/cli.js +72 -14
- package/dist/mini-drama/cli.js.map +1 -1
- package/dist/mini-drama/stream-choices.d.ts +46 -0
- package/dist/mini-drama/stream-choices.d.ts.map +1 -0
- package/dist/mini-drama/stream-choices.js +125 -0
- package/dist/mini-drama/stream-choices.js.map +1 -0
- package/dist/mini-drama/stream-engine.d.ts +81 -6
- package/dist/mini-drama/stream-engine.d.ts.map +1 -1
- package/dist/mini-drama/stream-engine.js +209 -40
- package/dist/mini-drama/stream-engine.js.map +1 -1
- package/dist/mini-drama/video-generator.d.ts +6 -1
- package/dist/mini-drama/video-generator.d.ts.map +1 -1
- package/dist/mini-drama/video-generator.js +8 -3
- package/dist/mini-drama/video-generator.js.map +1 -1
- package/dist/venice/client.d.ts +8 -0
- package/dist/venice/client.d.ts.map +1 -1
- package/dist/venice/client.js +7 -1
- package/dist/venice/client.js.map +1 -1
- package/dist/web/server.d.ts.map +1 -1
- package/dist/web/server.js +13 -1
- package/dist/web/server.js.map +1 -1
- package/dist/web/ui/dist/assets/index-W5lm3DN3.js +41 -0
- package/dist/web/ui/dist/index.html +1 -1
- package/package.json +4 -3
- package/dist/web/ui/dist/assets/index-_MI6v9ev.js +0 -41
|
@@ -43,6 +43,7 @@ in the other.
|
|
|
43
43
|
- Loop mode (`venice-video loop -p <project> -e <n> --mode <looping|production>`): once a shot script exists, render the whole plan continuously and watch it as a live browser loop that hot-swaps fresh takes. It SKIPS the storyboard/QA gates and writes only under `episodes/episode-NNN/loop/`, so it never touches the canonical cut. `looping` = creative flow, lower quality (Turbo 480P, disposable); `production` = gather usable, identity-locked takes (Max R2V @768P). `--mode` is REQUIRED non-interactively; `--budget` caps spend (default $2). This is NOT in the `pipeline` stage list — it is under `branches` in `pipeline --json`.
|
|
44
44
|
- **Loop mode plays AND renders at the same time — it does NOT pre-generate everything.** The browser loops the takes that already exist while the worker keeps generating new ones, swapping each shot's newer take in on the loop's **next pass** (never mid-clip). Two things make a running loop *look* pre-generated when it isn't: (a) it **pauses** when it hits `--budget` or you click Pause, then just replays what's on disk — raise `--budget` or pass `--unbounded` to keep it generating; (b) `--max-takes` is a per-shot ring buffer (default 3, older takes pruned), not a total.
|
|
45
45
|
- **Which mode evolves while you watch:** `looping` (Turbo, 480P) renders faster than it plays, so it visibly changes as you watch. `production`/create renders each shot slowly on Max R2V — it's for **gathering keeper takes**, not a continuously-evolving watch, and on a short/few-shot plan it can look static even while running (one take takes longer to render than a full loop cycle takes to play). Want "watch it keep changing"? Use `looping` with a higher/unbounded budget.
|
|
46
|
+
- **Stream mode** (`venice-video stream -p <project> -e <n> --writer <model> [--direction "..."]`): an INFINITE, live-authored story — not a loop. The writer model authors one beat at a time; beat 1 renders t2v, every later beat i2v off the previous last frame. Needs only `series.json`. Two things to do before starting: (1) **ASK the operator which model writes the beats** — it is the voice of the whole story, bills from beat 1, and its speed sets the lag behind playback; a non-interactive new stream with no `--writer` is a hard error (`--writer default` = `deepseek-v4-flash-0731-fast`, the fastest reliable writer in the bakeoff; `kimi-k3` is 3x slower). Both the writer and the video family are dropdowns in the Stream tab; every family other than MiniMax H3 Max Turbo renders slower than playback. (2) If the show has human faces, put the camera rule in `--direction`: end every beat wide, never on a face close-up (MiniMax i2v dies on a face-filled start frame; the engine also enforces this in the writer prompt and falls back to a t2v soft reset after 2 chained failures). The command registers its episode in `series.json` so the Stream tab can show it.
|
|
46
47
|
- Three video lanes, chosen per shot by the router (see the `venice-video-model-routing` skill): **t2v** (prompt only), **i2v** (animate a supplied START image via `image_url` — establishing/atmosphere shots, or chaining off a previous last frame), **R2V** (identity anchored to a reference stack — the default for character shots). "i2v" means a supplied first frame; "R2V" means `reference_image_urls`, not a start frame — do not conflate them.
|
|
47
48
|
- To animate a single image you already have (plain i2v, no project), the routing skill's bundled `scripts/venice-video.py --image <file> --model <...-image-to-video>` is the standalone path; the project pipeline is for multi-shot, consistency-first work.
|
|
48
49
|
|
package/AGENTS.md
CHANGED
|
@@ -482,7 +482,7 @@ Use `POST /video/quote` (via `quoteVideo()`) to estimate costs before committing
|
|
|
482
482
|
|
|
483
483
|
59. **MiniMax H3 Max (simple-prompt) models IMPROVISE dialogue — the scripted line is intent, not a script (2026-09-04).** Unlike Seedance/Wan, which render the exact line you give them, the H3 Max family (`promptStyle: 'simple'`) performs markedly better carrying natural, continuous speech across a whole generation than reciting a verbatim quote — a fixed line fights the model the same way the directorial blocks do. So in **native-dialogue mode** the prompt builders render a speaker's line as INTENT — `[@ImageN, voice, delivery] conveys: "…"` plus a one-line "improvise naturally in character, keep the meaning and tone, don't recite word for word" note — instead of the directorial `[…]: "exact line"` quote. This is automatic via `shouldImproviseDialogue(modelId, series)` in `prompt-builder.ts` (`modelWantsSimplePrompt(modelId)` AND `videoDefaults.audioStrategy !== 'lip-sync'`), applied in both `buildVideoPrompt` (singles) and `buildMontagePrompt` (montage) — the two paths the H3 Max family renders through. Directorial models keep the exact quote; the legacy Seedance-native / Kling multi-shot builders (`buildMultiShotPrompt`) are untouched because those lanes are never simple-prompt. **The exception is exact-lip-sync**: there the `audio_url` drives the exact spoken words, so the line stays verbatim (the improv gate excludes `audioStrategy === 'lip-sync'`). **Consequence:** when a simple-prompt model improvises, burned/exported captions must be derived by transcribing the rendered audio (the editing pipeline's `silencedetect`/whisper path), NOT from `script.json` — the model will not say it word for word. When changing this, keep `shouldImproviseDialogue` / `formatDialogueLine` / `IMPROV_DIALOGUE_NOTE` in `prompt-builder.ts` and this rule in sync.
|
|
484
484
|
|
|
485
|
-
60. **`stream` is not `loop` — "infinite" means the STORY never ends, not that the playback cycles (2026-09-04).** When the operator asks for an infinite / never-ending / always-on story, use `venice-video stream -p <project> [--direction "…"]`, not `loop`. The two are different products: `loop` takes a fixed shot plan and re-renders the same N shots forever so the *playback* cycles (`--max-takes` is a ring buffer of candidate renders per shot); `stream` (`src/mini-drama/stream-engine.ts`) never repeats and never re-renders. It authors the story live: the intelligence model (`series.intelligence`, or `--writer`) writes ONE beat at a time from the series bible + `story-so-far.md` (one summary line per prior beat) + the last `STREAM_RECENT_BEATS` (6) beats verbatim, with a hard continuity rule ("begins EXACTLY where the previous beat ended, the camera does not cut"). Beat 1 renders t2v on MiniMax H3 Max Turbo; every later beat renders i2v off the previous beat's last frame (`extractLastFrame`). Invariants: (a) **no re-anchoring, ever** — every frame descends from the frame before it, identity drifts slowly by design, and there is no t2v reset cadence; (b) **no ring buffer** — every beat stays on disk in order (`stream/beat-NNNNN.mp4` + `.json`), disk is the only limit; (c) **a stream cannot skip a beat** — after `MAX_CONSECUTIVE_ERRORS` (3) failures at write, chain-frame, or render the engine STOPS (a skipped beat would be a hidden cut), where the loop would give up on one shot and move on; (d) it needs only `series.json` — no script, storyboard, QA, or references; a locked aesthetic and a cast (`--skip-images` is fine) make the writer much better; (e) `--direction` is standing direction folded into every writer prompt (the place for "laugh track after every joke"), and `openingBeat` lets a caller pin beat 1 verbatim; (f) budget/resume semantics match the loop (billed at queue time, `--budget` stops, Start/Continue authorizes another, `--unbounded` lifts the cap; `stream-manifest.json` resumes from the longest unbroken prefix of beats whose files exist and chains off the last one). The writer is `AuthorFn` and the renderer `RenderFn`, both injectable (`tests/stream-engine.test.mjs`). Output for the browser is the **Stream** tab (`StreamView.tsx`, `stream-updated` SSE, `/api/projects/:slug/stream/{state,start,stop}`), which plays forward from beat 1 and holds on the newest beat until the next lands. When changing stream behavior, keep `stream-engine.ts`, the `stream` command in `cli.ts`, `server.ts` (`WebServerOptions.stream`), `state.ts`, `StreamView.tsx`, `api.ts` (`names` list), `types.ts`, and this rule in sync. Note the `-e` option on `stream` is a string parsed by hand — passing Commander's `parseInt` collided with the `applyContextDefaults` hook and yielded `episode-NaN`.
|
|
485
|
+
60. **`stream` is not `loop` — "infinite" means the STORY never ends, not that the playback cycles (2026-09-04).** When the operator asks for an infinite / never-ending / always-on story, use `venice-video stream -p <project> [--direction "…"]`, not `loop`. The two are different products: `loop` takes a fixed shot plan and re-renders the same N shots forever so the *playback* cycles (`--max-takes` is a ring buffer of candidate renders per shot); `stream` (`src/mini-drama/stream-engine.ts`) never repeats and never re-renders. It authors the story live: the intelligence model (`series.intelligence`, or `--writer`) writes ONE beat at a time from the series bible + `story-so-far.md` (one summary line per prior beat) + the last `STREAM_RECENT_BEATS` (6) beats verbatim, with a hard continuity rule ("begins EXACTLY where the previous beat ended, the camera does not cut"). Beat 1 renders t2v on MiniMax H3 Max Turbo; every later beat renders i2v off the previous beat's last frame (`extractLastFrame`). Invariants: (a) **no re-anchoring, ever** — every frame descends from the frame before it, identity drifts slowly by design, and there is no t2v reset cadence; (b) **no ring buffer** — every beat stays on disk in order (`stream/beat-NNNNN.mp4` + `.json`), disk is the only limit; (c) **a stream cannot skip a beat** — after `MAX_CONSECUTIVE_ERRORS` (3) failures at write, chain-frame, or render the engine STOPS (a skipped beat would be a hidden cut), where the loop would give up on one shot and move on; (d) it needs only `series.json` — no script, storyboard, QA, or references; a locked aesthetic and a cast (`--skip-images` is fine) make the writer much better; the command registers its episode in `series.json` when missing (the browser builds its episode list from there — an unregistered episode rendered beats the Stream tab could not show, and hid the Start button behind "No episodes yet."); (e) `--direction` is standing direction folded into every writer prompt (the place for "laugh track after every joke"), and `openingBeat` lets a caller pin beat 1 verbatim; (f) budget/resume semantics match the loop (billed at queue time, `--budget` stops, Start/Continue authorizes another, `--unbounded` lifts the cap; `stream-manifest.json` resumes from the longest unbroken prefix of beats whose files exist and chains off the last one); (g) **the writer is the session's first decision — ASK the operator which model writes the beats before starting a new stream.** It is the voice of the whole story and it bills from beat 1. Interactive runs get a `promptChoice` over `selectableTextModels()`; a non-interactive new stream with no `--writer` is a hard error (pass `--writer <model>` or `--writer default`), mirroring `loop --mode`. A resumed stream keeps the project default. The writer and the per-beat cost print before beat 1 bills. Do not treat `series.intelligence` as the operator's choice for the stream — it was chosen for QA and scripting, not latency. The stream's own default is `STREAM_DEFAULT_WRITER` (`deepseek-v4-flash-0731-fast`, 3.8s median, 9/9 valid, thinking off) from the 2026-09-05 bakeoff (`scripts/bakeoff-stream-writer.ts`; results in `src/mini-drama/stream-choices.ts` and the README). `chatJson` takes `disableThinking`; the stream writer sets it per choice — with thinking on, the same models are 3-10x slower and reasoning-only models burn the token budget and return nothing. Both the writer and the video family are dropdowns in the Stream tab (`POST /stream/config`, `engine.configure()`); a change applies to the NEXT beat, the i2v chain survives a family switch (the start frame is a PNG), and a resumed stream keeps the models in its manifest. Every video family other than Turbo renders slower than playback; the UI says so beside the selector. Per-beat cost comes from the family's quoted `usdPer15s` (Turbo is $0.11 at 480P, not the $0.18 the old constant assumed). (h) **A face-ending beat must not kill the stream.** MiniMax i2v dies server-side on a face-filled start frame after billing (anti-pattern 31), and the chain makes one bad frame poison every retry. Three defenses, in order: the writer's system prompt carries a MANDATORY camera rule — end every beat wide, never on a human face close-up; a failed chained render steps the start frame back (`STREAM_CHAIN_STEP_BACK_SEC`); and after `STREAM_CHAIN_FAILURES_BEFORE_RESET` (2) chained failures on one beat, the engine renders that beat **t2v as a soft reset** (`lane: 't2v-reset'`), with the previous beat's summary prepended so the prompt re-establishes the scene. Identity drifts for one beat; a skipped beat or a dead stream is worse. The Stream tab shows `lastError` while a retry is in flight and marks reset beats. (i) **The Stream tab merges, never replaces.** The on-disk manifest is re-read on every `state-changed` (the workspace watcher fires on each beat's files); it is a fallback. `stream-updated` SSE is the truth. The view merges beats by number so a stale disk snapshot can never remove a beat the SSE already delivered — that race is what made new beats appear only after a reload. Only the `<video>` element swaps source (keyed by file); the page never reloads, and playback is kicked explicitly after each swap. The writer is `AuthorFn` and the renderer `RenderFn`, both injectable (`tests/stream-engine.test.mjs`). Output for the browser is the **Stream** tab (`StreamView.tsx`, `stream-updated` SSE, `/api/projects/:slug/stream/{state,start,stop}`), which plays forward from beat 1 and holds on the newest beat until the next lands. When changing stream behavior, keep `stream-engine.ts`, the `stream` command in `cli.ts`, `server.ts` (`WebServerOptions.stream`), `state.ts`, `StreamView.tsx`, `api.ts` (`names` list), `types.ts`, and this rule in sync. Note the `-e` option on `stream` is a string parsed by hand — passing Commander's `parseInt` collided with the `applyContextDefaults` hook and yielded `episode-NaN`.
|
|
486
486
|
|
|
487
487
|
## Learned Anti-Patterns (Production Issues Log)
|
|
488
488
|
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,112 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 2.22.0 — 2026-09-05
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- **Stream model selectors.** The Stream tab has two dropdowns: the writer
|
|
8
|
+
(which model authors each beat) and the video model family (which t2v/i2v
|
|
9
|
+
pair renders it), each with speed and cost in the label and a plain verdict
|
|
10
|
+
beside it ("keeps up" / "falls behind" / "much slower"). A change applies to
|
|
11
|
+
the next beat; the beat in flight finishes on the models it started with.
|
|
12
|
+
`POST /api/projects/:slug/stream/config`, `StreamEngine.configure()`,
|
|
13
|
+
`--video-family` on the CLI, and `choices` in the manifest. The i2v chain
|
|
14
|
+
survives a family switch. A resumed stream keeps the models in its manifest.
|
|
15
|
+
- **Stream writer bakeoff** (`scripts/bakeoff-stream-writer.ts`): times each
|
|
16
|
+
candidate on the real stream prompt against a real project and checks the
|
|
17
|
+
beat parses. Results are in `src/mini-drama/stream-choices.ts` and the README.
|
|
18
|
+
- `VeniceClient.chatJson` takes `disableThinking`, which sends
|
|
19
|
+
`venice_parameters.disable_thinking`. The stream writer sets it per choice.
|
|
20
|
+
- README "Choosing Stream Models": why the stream has its own writer default,
|
|
21
|
+
why thinking is off, how the numbers were measured, and two decision
|
|
22
|
+
matrices (writer, video family) scored on privacy, speed, cost, and quality,
|
|
23
|
+
plus a pick-by-goal table.
|
|
24
|
+
|
|
25
|
+
### Changed
|
|
26
|
+
|
|
27
|
+
- **Default stream writer is `deepseek-v4-flash-0731-fast`** (3.8 s median,
|
|
28
|
+
9/9 valid beats, private), not the project's intelligence model. Kimi K3
|
|
29
|
+
took 35 s per beat with thinking on and ~10 s without, dropping 1 in 9. With
|
|
30
|
+
Turbo's ~30 s render, the fast writer brings a beat to ~35 s wall time for
|
|
31
|
+
15 s of video. `--writer default` means this model now.
|
|
32
|
+
- **Per-beat cost is quote-derived.** Turbo at 480P is $0.11 per 15 s
|
|
33
|
+
(`POST /video/quote`), not the $0.18 the old `TURBO_USD_PER_SEC` constant
|
|
34
|
+
assumed. Budgets buy more beats than before.
|
|
35
|
+
- `--resolution` on `stream` defaults to the family's draft tier instead of a
|
|
36
|
+
hardcoded `480P`; values are validated against the family.
|
|
37
|
+
- **The stream writer is an explicit decision.** A new stream asks which model
|
|
38
|
+
writes the beats in a terminal; a non-interactive new stream with no
|
|
39
|
+
`--writer` is a hard error (pass `--writer <model>` or `--writer default`),
|
|
40
|
+
mirroring `loop --mode`. A resumed stream keeps the project default. The
|
|
41
|
+
writer and the per-beat cost print before beat 1 bills. AGENTS.md rule 60
|
|
42
|
+
(g)-(i) records the operating rules.
|
|
43
|
+
|
|
44
|
+
### Fixed
|
|
45
|
+
|
|
46
|
+
- **Stream tab showed no Start button on a fresh project.** `stream` needs only
|
|
47
|
+
`series.json`, but the browser builds its episode list from
|
|
48
|
+
`series.episodes`, and `stream` never registered its episode. The view fell
|
|
49
|
+
through to "No episodes yet." with the engine attached and beat 1 billed.
|
|
50
|
+
`stream` now registers the episode in `series.json` before anything bills,
|
|
51
|
+
and the view renders with a synthetic episode if the list is still empty.
|
|
52
|
+
- **New beats appeared only after a page reload.** Every beat's files fire the
|
|
53
|
+
workspace watcher, which pushes `state-changed`; `App` re-fetches the project
|
|
54
|
+
state, and `StreamView` REPLACED its stream with the on-disk manifest — which
|
|
55
|
+
can lag the `stream-updated` SSE that already delivered the beat. The view
|
|
56
|
+
now merges by beat number (SSE is the truth, disk is a fallback), fetches the
|
|
57
|
+
full state if an event arrives before the initial load, and kicks `play()`
|
|
58
|
+
after each `<video>` source swap.
|
|
59
|
+
- **A face-ending beat stalled the whole stream.** MiniMax i2v dies
|
|
60
|
+
server-side on a face-filled start frame after billing (anti-pattern 31); the
|
|
61
|
+
chain made that one frame poison every retry, and the engine stopped after
|
|
62
|
+
three. Now: the writer's system prompt carries a MANDATORY camera rule (end
|
|
63
|
+
every beat wide, never on a human face close-up); and after
|
|
64
|
+
`STREAM_CHAIN_FAILURES_BEFORE_RESET` (2) chained failures on one beat the
|
|
65
|
+
engine renders that beat t2v as a soft reset (`lane: "t2v-reset"`) with the
|
|
66
|
+
previous beat's summary prepended. The Stream tab shows the retry error and
|
|
67
|
+
marks reset beats.
|
|
68
|
+
- `renderVideoFile` no longer warns "No start image available" on
|
|
69
|
+
text-to-video models, where no start image is expected.
|
|
70
|
+
- **The stream manifest is written atomically** (temp file + rename), so a
|
|
71
|
+
concurrent reader — a resuming engine, or the web server serving `/stream/state`
|
|
72
|
+
— can never parse a half-written manifest and silently fall back to defaults
|
|
73
|
+
(it would otherwise drop the configured writer/family on a restart under load).
|
|
74
|
+
|
|
75
|
+
### Known gap (feature request)
|
|
76
|
+
|
|
77
|
+
- **No path for the driving agent to author beats.** An operator running the
|
|
78
|
+
agent on a model outside the Venice text registry (e.g. Claude Fable 5.1)
|
|
79
|
+
cannot make that agent the writer. `AuthorFn` is injectable in code only.
|
|
80
|
+
Proposed: `--writer external`, where the engine writes
|
|
81
|
+
`stream/next-beat-request.json` and waits; `venice-video stream-beat -p <dir>
|
|
82
|
+
-e <n> --file beat.json` (or `POST /api/projects/:slug/stream/beat`) accepts
|
|
83
|
+
an `AuthoredBeat`, runs `normalizeBeat`, and renders it. Same shape for
|
|
84
|
+
pinning beat 1 from the CLI.
|
|
85
|
+
|
|
86
|
+
## 2.21.1 — 2026-09-04
|
|
87
|
+
|
|
88
|
+
### Changed
|
|
89
|
+
|
|
90
|
+
- **`stream` starts paused after the opening beat.** `prime()` writes and renders
|
|
91
|
+
beat 1, then the engine waits — the browser opens with one beat ready and the
|
|
92
|
+
stream paused. Start in the UI renders the next beat immediately and keeps
|
|
93
|
+
going. A resumed session (beats already on disk) opens paused at once.
|
|
94
|
+
- **Stream tab UI.** The Loop tab is hidden (the `loop` command still exists),
|
|
95
|
+
beat rows are clickable and load that beat, and the error banner is replaced
|
|
96
|
+
with a `paused — ready to continue` badge.
|
|
97
|
+
|
|
98
|
+
### Fixed
|
|
99
|
+
|
|
100
|
+
- **Chained render failure now steps the start frame back through the previous
|
|
101
|
+
clip instead of retrying the doomed frame.** A chained render that dies
|
|
102
|
+
server-side is almost always the start frame (MiniMax i2v rejects some frames
|
|
103
|
+
after billing — anti-pattern 31); retrying the same frame is a guaranteed
|
|
104
|
+
repeat and re-writing the text is wasted. Each retry keeps the written beat and
|
|
105
|
+
steps the start frame back through the previous clip (0, 0.5, 1.5, 3.0s from
|
|
106
|
+
the end). `extractLastFrame` gains an optional `secondsFromEnd` (default 0 — no
|
|
107
|
+
behavior change for existing callers). Verified live: a beat that died twice on
|
|
108
|
+
the true last frame landed 1.5s back, and the next beat chained off it normally.
|
|
109
|
+
|
|
3
110
|
## 2.21.0 — 2026-09-04
|
|
4
111
|
|
|
5
112
|
### Added
|
package/README.md
CHANGED
|
@@ -1053,13 +1053,19 @@ venice-video stream -p ~/VeniceVideos/my-show \
|
|
|
1053
1053
|
|
|
1054
1054
|
How it works:
|
|
1055
1055
|
|
|
1056
|
-
1.
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1056
|
+
1. You pick the writer. A new stream asks which model writes the beats (it is
|
|
1057
|
+
the voice of the whole story, and its speed sets how far the stream lags
|
|
1058
|
+
playback). Non-interactive runs must pass `--writer <model>` (or `--writer
|
|
1059
|
+
default` = `deepseek-v4-flash-0731-fast`, the fastest reliable writer in
|
|
1060
|
+
the bakeoff); a resumed stream keeps the writer it last ran with. The
|
|
1061
|
+
writer and the per-beat cost print before beat 1 bills.
|
|
1062
|
+
2. The writer writes beat 1 from the series bible: concept, setting, aesthetic,
|
|
1063
|
+
and cast.
|
|
1064
|
+
3. Beat 1 renders text-to-video on MiniMax H3 Max Turbo.
|
|
1065
|
+
4. The writer reads `story-so-far.md` (one line per prior beat) plus the last
|
|
1060
1066
|
6 beats verbatim, and writes beat 2 so it begins exactly where beat 1 ended.
|
|
1061
|
-
|
|
1062
|
-
|
|
1067
|
+
5. Beat 2 renders image-to-video off beat 1's last frame.
|
|
1068
|
+
6. Repeat forever, until Pause or the budget.
|
|
1063
1069
|
|
|
1064
1070
|
There is no re-anchoring and no ring buffer. Every beat descends from the frame
|
|
1065
1071
|
before it, and every beat stays on disk in order under
|
|
@@ -1074,8 +1080,9 @@ a cast (`add-character`, `--skip-images` is fine) make the writer much better.
|
|
|
1074
1080
|
venice-video stream -p <dir> \
|
|
1075
1081
|
-e 1 \ # episode the stream lives under (default 1)
|
|
1076
1082
|
--direction "<text>" \ # standing direction folded into every beat's writer prompt
|
|
1077
|
-
--writer
|
|
1078
|
-
--
|
|
1083
|
+
--writer <model> \ # writer; asked for a new stream, required non-interactively (see the bakeoff table)
|
|
1084
|
+
--video-family <family> \ # minimax-h3-max-turbo (default) | minimax-h3-max | wan-3-0 | grok-imagine | seedance-2-0 | seedance-2-5 | kling-o3-standard
|
|
1085
|
+
--resolution 480P \ # default: the family's draft tier
|
|
1079
1086
|
--duration 15s \ # per-beat length, snapped to the 5-15s ladder
|
|
1080
1087
|
--budget 2 # stop after ~$2; Continue authorizes another budget
|
|
1081
1088
|
# --unbounded # no cap (streams until Ctrl-C)
|
|
@@ -1087,6 +1094,130 @@ the engine stops rather than skip a beat — a stream cannot have a hidden cut.
|
|
|
1087
1094
|
Identity drifts slowly over many hops, by design; that is the trade for a
|
|
1088
1095
|
continuous, unbroken picture.
|
|
1089
1096
|
|
|
1097
|
+
**Faces and the chain.** MiniMax i2v accepts a start frame that is filled by a
|
|
1098
|
+
human face, bills it, and then fails server-side (anti-pattern 31). Because
|
|
1099
|
+
every beat chains off the previous last frame, one face-ending beat could stall
|
|
1100
|
+
the whole stream. Three things keep it alive: the writer is told to end every
|
|
1101
|
+
beat on a wide shot with no face close-up; a failed chained render first steps
|
|
1102
|
+
the start frame back into the previous clip (0.5s, then 1.5s); and after
|
|
1103
|
+
`STREAM_CHAIN_FAILURES_BEFORE_RESET` (2) chained failures on one beat, that beat
|
|
1104
|
+
renders text-to-video as a **soft reset** (`lane: "t2v-reset"`) — the prompt
|
|
1105
|
+
restates the scene from the previous beat's summary, identity drifts for one
|
|
1106
|
+
beat, and the story keeps going. The Stream tab shows the retry error while it
|
|
1107
|
+
happens and marks reset beats in the story list.
|
|
1108
|
+
|
|
1109
|
+
#### Choosing Stream Models
|
|
1110
|
+
|
|
1111
|
+
A stream is a live broadcast whose producer must keep pace with the viewer.
|
|
1112
|
+
Each beat costs two latencies: the **writer** authors the beat, then the
|
|
1113
|
+
**video model** renders it. The viewer watches 15 s of video per beat. Nothing
|
|
1114
|
+
on Venice renders 15 s of video in under 15 s, so every stream eventually
|
|
1115
|
+
catches up to its newest beat and holds on the last frame. The two model
|
|
1116
|
+
choices decide how long that hold is. Both are dropdowns in the **Stream** tab;
|
|
1117
|
+
a change applies to the next beat, and the i2v chain survives a switch.
|
|
1118
|
+
|
|
1119
|
+
**Why the stream does not use the project's intelligence model.** The
|
|
1120
|
+
intelligence model (`series.intelligence`, default `kimi-k3`) is picked for the
|
|
1121
|
+
workshop, the shot script, and vision QA. Those tasks reward depth and reading
|
|
1122
|
+
panels; a 35 s think is fine. A stream beat is a 100-word paragraph in
|
|
1123
|
+
character, once every 30 s, forever. Latency is the whole game, so the stream
|
|
1124
|
+
has its own default and its own list.
|
|
1125
|
+
|
|
1126
|
+
**Why thinking is off.** The harness never sent `disable_thinking`. On the same
|
|
1127
|
+
model that flag is the difference between 35 s and 4 s per beat, and reasoning-
|
|
1128
|
+
only models spend the entire token budget thinking and return nothing.
|
|
1129
|
+
`chatJson` now takes `disableThinking`; the stream sets it per writer.
|
|
1130
|
+
|
|
1131
|
+
**How the numbers were measured.** `scripts/bakeoff-stream-writer.ts` sends the
|
|
1132
|
+
real stream prompt (system + user, from `stream-engine.ts`) for a real project
|
|
1133
|
+
at beat ~28, 3-6 rounds per model, and checks that the reply parses and
|
|
1134
|
+
normalizes to a beat. Video prices are `POST /video/quote` for a 15 s render at
|
|
1135
|
+
the family's draft resolution. Render times are observed on drop-the-beat
|
|
1136
|
+
(MiniMax) or vendor-typical (others). All measured 2026-09-05. Re-run:
|
|
1137
|
+
|
|
1138
|
+
```bash
|
|
1139
|
+
npx tsx scripts/bakeoff-stream-writer.ts -p <project> --no-thinking [--rounds 6] [--show]
|
|
1140
|
+
```
|
|
1141
|
+
|
|
1142
|
+
##### Writer Matrix
|
|
1143
|
+
|
|
1144
|
+
Scores: ●●● best, ●●○ fine, ●○○ weak, ○○○ not usable. Speed is the bakeoff
|
|
1145
|
+
median per beat with thinking off (unless noted). Cost is USD per million
|
|
1146
|
+
input / output tokens; a beat is ~1.5 k in, ~0.4 k out, so even the priciest
|
|
1147
|
+
writer here is under a cent per beat. Quality is a judgment from reading the
|
|
1148
|
+
beats: does it follow the camera rule, keep one joke per beat, stay in voice.
|
|
1149
|
+
|
|
1150
|
+
| Writer | Privacy | Speed | Cost | Quality | Valid | Verdict |
|
|
1151
|
+
|---|---|---|---|---|---|---|
|
|
1152
|
+
| `deepseek-v4-flash-0731-fast` **(default)** | ●●● private | ●●● 3.8 s | ●●● $0.35 / $0.70 | ●●○ tight, follows rules, drier humor | 9/9 | Use this unless you have a reason not to. |
|
|
1153
|
+
| `mistral-small-2603` | ●●● private | ●●● 5.2 s | ●●● $0.19 / $0.75 | ●●● warmest, most physical comedy | 9/9 | Best prose of the fast tier. Sometimes puts the robot's display name in `characters` (normalized away). |
|
|
1154
|
+
| `seed-2-1-turbo` | ●○○ anonymized | ●●○ 9.2 s | ●●○ $0.63 / $3.13 | ●●○ | 9/9 | Reliable, but slower and not private. Pick only if the first two misbehave on your bible. |
|
|
1155
|
+
| `kimi-k3` | ●●● private | ●●○ 9.8 s (35 s thinking on) | ●○○ $3.75 / $18.75 | ●●● richest callbacks and structure | 8/9 | The intelligence default. Best writer; too slow and 1 in 9 beats fails. Use for a stream you will curate, not one you watch live. |
|
|
1156
|
+
| `deepseek-v4-flash` | ●●● private | ●○○ 11.0 s | ●●● $0.14 / $0.28 | ●●○ | 6/6 | Cheapest. 3x slower than its `-fast` sibling for no quality gain. |
|
|
1157
|
+
| `minimax-m27` | ●●● private | ●○○ 10.1 s (thinking on) | ●●● $0.38 / $1.50 | ●●○ | 5/6 | Needs thinking on to be reliable, which caps its speed. |
|
|
1158
|
+
| `gemini-3-8-flash` | ●○○ anonymized | ○○○ 19.1 s (thinking on) | ●●○ $0.94 / $4.69 | ●●● vivid prose | 4/6 | Good writer, wrong tool: slow, breaks JSON with thinking off, not private. |
|
|
1159
|
+
|
|
1160
|
+
Not offered, with the reason:
|
|
1161
|
+
|
|
1162
|
+
| Model | Why not |
|
|
1163
|
+
|---|---|
|
|
1164
|
+
| `z-ai-glm-5-3` | Thinking-only (400 on `disable_thinking`). With thinking on, 0/3 valid: it spent the whole 1500-token budget reasoning. |
|
|
1165
|
+
| `z-ai-glm-5-3-flash` | 2/3 at 21 s with thinking; 0/3 without: answers with prose ("Let me look..."), not JSON. |
|
|
1166
|
+
| `grok-4-6` | 3/3 valid but 67 s median. |
|
|
1167
|
+
| `qwen3-235b-a22b-instruct-2507` | 3/3 valid but 39 s median. |
|
|
1168
|
+
| `qwen3-6-35b-a3b` | 2 s, but truncates the JSON 1 in 3. |
|
|
1169
|
+
| `kimi-k3-fast` | HTTP 500 "Inference processing failed" on every call. |
|
|
1170
|
+
|
|
1171
|
+
##### Video Family Matrix
|
|
1172
|
+
|
|
1173
|
+
Speed is the wall time to render one 15 s beat. "Lag" is what the viewer
|
|
1174
|
+
feels: with the default writer (~4 s) added, Turbo makes a 15 s beat in ~35 s,
|
|
1175
|
+
so the player holds ~20 s between beats once it has caught up. Every other
|
|
1176
|
+
family holds for a minute or more. Cost is the quote for 15 s at the family's
|
|
1177
|
+
draft resolution. Quality is relative to what the harness knows about each
|
|
1178
|
+
family (see the model registry and AGENTS.md).
|
|
1179
|
+
|
|
1180
|
+
| Family | Privacy | Speed (15 s beat) | Cost / 15 s | Quality | Faces on start frame | Verdict |
|
|
1181
|
+
|---|---|---|---|---|---|---|
|
|
1182
|
+
| `minimax-h3-max-turbo` **(default)** | ●●● private | ●●● ~30 s | ●●● $0.11 | ●●○ good motion, native audio, improvises dialogue | ✗ dies after billing; engine soft-resets | The only lane that nearly keeps pace. Draft look at 480P; 768P selectable. |
|
|
1183
|
+
| `minimax-h3-max` | ●●● private | ●●○ ~60 s | ●●● $0.22 | ●●● sharper than Turbo, same model family | ✗ same limit | Pick when you want the Turbo look at finish quality and will accept a 1-minute hold. |
|
|
1184
|
+
| `wan-3-0` | ●○○ anonymized | ●○○ ~120 s | ●●○ $0.68 | ●●● strong, up to 1080p, 30 s ladder | ✓ accepts faces | Best choice if the show is face-heavy and the camera rule is not enough. Slow. |
|
|
1185
|
+
| `grok-imagine` | ●○○ anonymized | ●○○ ~90 s | ●○○ $0.95 | ●●○ | ✓ | Faster than Wan, pricier, lower ceiling. |
|
|
1186
|
+
| `seedance-2-0` | ●○○ anonymized | ○○○ ~180 s | ●○○ $1.32 | ●●● the harness production look, native lip-synced dialogue | ✓ | Production fidelity. The viewer waits ~3 min per beat. Use for a stream you export, not one you watch. |
|
|
1187
|
+
| `seedance-2-5` | ●○○ anonymized | ○○○ ~180 s | ○○○ $1.93 | ●●● newest Seedance, up to 30 s beats | ✓ | Same trade as 2.0 at a higher price. |
|
|
1188
|
+
| `kling-o3-standard` | ●○○ anonymized | ○○○ ~150 s | ○○○ $1.84 | ●●● | ✓ | No resolution parameter. Expensive for a chain that re-renders forever. |
|
|
1189
|
+
|
|
1190
|
+
##### Pick By Goal
|
|
1191
|
+
|
|
1192
|
+
| You want | Writer | Video | Expect |
|
|
1193
|
+
|---|---|---|---|
|
|
1194
|
+
| Watch it live, cheapest, private | `deepseek-v4-flash-0731-fast` | `minimax-h3-max-turbo` @ 480P | ~35 s per beat, ~20 s hold, ~$0.11/beat, ~$13/hour of story |
|
|
1195
|
+
| Watch it live, best sitcom writing | `mistral-small-2603` | `minimax-h3-max-turbo` | Same lag, warmer beats |
|
|
1196
|
+
| Sharper picture, still private | `deepseek-v4-flash-0731-fast` | `minimax-h3-max` @ 768P | ~65 s per beat, ~50 s hold, $0.22/beat |
|
|
1197
|
+
| Human faces fill the frame often | any fast writer | `wan-3-0` | Faces never kill the chain; ~2 min per beat |
|
|
1198
|
+
| Production look to export later | `kimi-k3` | `seedance-2-0` or `-2-5` | ~3.5 min per beat, $1.32-1.93/beat; run it overnight, do not watch it live |
|
|
1199
|
+
| Strict privacy for both text and pixels | `deepseek-v4-flash-0731-fast` or `mistral-small-2603` | `minimax-h3-max-turbo` or `minimax-h3-max` | The only fully private pairing; MiniMax is the sole private video family here |
|
|
1200
|
+
|
|
1201
|
+
Rules of thumb:
|
|
1202
|
+
|
|
1203
|
+
- The render is the floor. A faster writer cannot make the stream faster than
|
|
1204
|
+
the video model. Below ~5 s of writer time, nothing more is gained.
|
|
1205
|
+
- Switching the video family mid-stream is safe. The start frame is a PNG; any
|
|
1206
|
+
i2v lane can continue from it. Identity will shift a little across the
|
|
1207
|
+
switch, as it does across every hop.
|
|
1208
|
+
- Switching the writer mid-stream is free and instant. The new writer reads the
|
|
1209
|
+
same `story-so-far.md` and the same last six beats.
|
|
1210
|
+
- Do not chase 768P on Turbo for a live watch. It roughly doubles the render
|
|
1211
|
+
time for the same model; pick `minimax-h3-max` at 768P instead if fidelity
|
|
1212
|
+
is the goal, since you are paying the time either way.
|
|
1213
|
+
- Budget arithmetic is per beat, from the quote. Turbo at 480P is $0.11 for
|
|
1214
|
+
15 s. The old `TURBO_USD_PER_SEC` constant assumed $0.18; budgets now buy
|
|
1215
|
+
more beats than they used to.
|
|
1216
|
+
|
|
1217
|
+
The `stream` command registers its episode in `series.json` if it is missing, so
|
|
1218
|
+
the Stream tab always has an episode to show. (Before 2.21.1 a stream under an
|
|
1219
|
+
unregistered episode rendered beats the browser could not display.)
|
|
1220
|
+
|
|
1090
1221
|
### Interrupted renders are resumable
|
|
1091
1222
|
|
|
1092
1223
|
The harness records each Venice `queue_id` to disk *before* it starts polling, so
|
package/capabilities.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"guide.d.ts","sourceRoot":"","sources":["../../src/agent/guide.ts"],"names":[],"mappings":"AAkBA,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED,eAAO,MAAM,WAAW,EAAE,SAAS,YAAY,
|
|
1
|
+
{"version":3,"file":"guide.d.ts","sourceRoot":"","sources":["../../src/agent/guide.ts"],"names":[],"mappings":"AAkBA,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED,eAAO,MAAM,WAAW,EAAE,SAAS,YAAY,EA2E9C,CAAC;AAEF,wBAAgB,WAAW,IAAI;IAAE,OAAO,EAAE,CAAC,CAAC;IAAC,QAAQ,EAAE,SAAS,YAAY,EAAE,CAAA;CAAE,CAE/E;AAED,wBAAgB,WAAW,IAAI,MAAM,CAWpC"}
|
package/dist/agent/guide.js
CHANGED
|
@@ -76,6 +76,7 @@ export const AGENT_GUIDE = [
|
|
|
76
76
|
'Loop mode (`venice-video loop -p <project> -e <n> --mode <looping|production>`): once a shot script exists, render the whole plan continuously and watch it as a live browser loop that hot-swaps fresh takes. It SKIPS the storyboard/QA gates and writes only under episodes/episode-NNN/loop/, so it never touches the canonical cut. LOOPING = creative flow, lower quality (Turbo 480P, disposable); PRODUCTION = gather usable, identity-locked takes (Max R2V @768P). --mode is REQUIRED non-interactively; --budget caps spend (default $2). This is not in the `pipeline` stage list — it is under `branches`.',
|
|
77
77
|
'Loop mode plays AND renders at the same time — it does NOT pre-generate everything. The browser loops the takes that already exist while the worker keeps generating new ones, swapping each shot\'s newer take in on the loop\'s NEXT pass (never mid-clip). Two things make a running loop LOOK pre-generated when it is not: (a) it PAUSES when it hits --budget or you click Pause in the UI, and then just replays what is on disk — raise --budget or pass --unbounded to keep it generating; (b) --max-takes is a per-shot ring buffer (default 3, older takes pruned), not a total, so once every shot has its cap the change per cycle is subtle.',
|
|
78
78
|
'Which mode evolves while you watch: LOOPING (Turbo, 480P) renders faster than it plays, so the video visibly changes as you watch. PRODUCTION/create renders each shot slowly on Max R2V — it is for GATHERING keeper takes, not a continuously-evolving watch, and on a short or few-shot plan it can look static even while running because one take takes longer to render than a full loop cycle takes to play. If the user wants to "watch it keep changing", pick LOOPING and a higher/unbounded budget.',
|
|
79
|
+
'Stream mode (`venice-video stream -p <project> -e <n> --writer <model> [--direction "..."]`): an INFINITE, live-authored story — not a loop. The writer model authors one beat at a time; beat 1 renders t2v, every later beat i2v off the previous last frame. Needs only series.json. TWO things to do before starting: (1) ASK the operator which model writes the beats — it is the voice of the whole story, bills from beat 1, and its speed sets the lag behind playback; a non-interactive new stream with no --writer is a hard error (--writer default = deepseek-v4-flash-0731-fast, the fastest reliable writer in the bakeoff; kimi-k3 is 3x slower). Both the writer and the video family are dropdowns in the Stream tab; every family other than MiniMax H3 Max Turbo renders slower than playback. (2) Put the camera rule in --direction if the show has human faces: end every beat wide, never on a face close-up (MiniMax i2v dies on a face-filled start frame; the engine also enforces this in the writer prompt and falls back to a t2v soft reset after 2 chained failures). The command registers its episode in series.json so the Stream tab can show it.',
|
|
79
80
|
'Three video lanes, chosen per shot by the router (see the venice-video-model-routing skill): t2v (prompt only), i2v (animate a supplied START image via image_url — establishing/atmosphere shots, or chaining off a previous last frame), R2V (identity anchored to a reference stack — the default for character shots). "i2v" means a supplied first frame; "R2V" means reference_image_urls, not a start frame — do not conflate them.',
|
|
80
81
|
'To animate a single image you already have (plain i2v, no project), the routing skill\'s bundled `scripts/venice-video.py --image <file> --model <...-image-to-video>` is the standalone path; the project pipeline is for multi-shot, consistency-first work.',
|
|
81
82
|
],
|
package/dist/agent/guide.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"guide.js","sourceRoot":"","sources":["../../src/agent/guide.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,uDAAuD;AACvD,EAAE;AACF,2EAA2E;AAC3E,8EAA8E;AAC9E,sEAAsE;AACtE,8EAA8E;AAC9E,sDAAsD;AACtD,EAAE;AACF,6EAA6E;AAC7E,8EAA8E;AAC9E,8EAA8E;AAC9E,4EAA4E;AAC5E,EAAE;AACF,gFAAgF;AAChF,8DAA8D;AAC9D,8EAA8E;AAO9E,MAAM,CAAC,MAAM,WAAW,GAA4B;IAClD;QACE,KAAK,EAAE,kCAAkC;QACzC,MAAM,EAAE;YACN,2GAA2G;YAC3G,sGAAsG;YACtG,0GAA0G;SAC3G;KACF;IACD;QACE,KAAK,EAAE,8BAA8B;QACrC,MAAM,EAAE;YACN,sGAAsG;YACtG,wNAAwN;YACxN,kJAAkJ;YAClJ,wGAAwG;SACzG;KACF;IACD;QACE,KAAK,EAAE,yCAAyC;QAChD,MAAM,EAAE;YACN,2FAA2F;YAC3F,4JAA4J;YAC5J,6FAA6F;SAC9F;KACF;IACD;QACE,KAAK,EAAE,2BAA2B;QAClC,MAAM,EAAE;YACN,mGAAmG;YACnG,8JAA8J;YAC9J,kJAAkJ;SACnJ;KACF;IACD;QACE,KAAK,EAAE,4CAA4C;QACnD,MAAM,EAAE;YACN,4PAA4P;YAC5P,kNAAkN;YAClN,kKAAkK;YAClK,6MAA6M;SAC9M;KACF;IACD;QACE,KAAK,EAAE,8BAA8B;QACrC,MAAM,EAAE;YACN,oHAAoH;YACpH,+JAA+J;YAC/J,gKAAgK;YAChK,8JAA8J;YAC9J,6SAA6S;YAC7S,kHAAkH;SACnH;KACF;IACD;QACE,KAAK,EAAE,+DAA+D;QACtE,MAAM,EAAE;YACN,+LAA+L;YAC/L,ylBAAylB;YACzlB,4nBAA4nB;YAC5nB,gfAAgf;YAChf,4aAA4a;YAC5a,gQAAgQ;SACjQ;KACF;IACD;QACE,KAAK,EAAE,gCAAgC;QACvC,MAAM,EAAE;YACN,+EAA+E;YAC/E,gJAAgJ;YAChJ,sFAAsF;YACtF,oIAAoI;SACrI;KACF;CACF,CAAC;AAEF,MAAM,UAAU,WAAW;IACzB,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,QAAQ,EAAE,WAAW,EAAE,CAAC;AAC/C,CAAC;AAED,MAAM,UAAU,WAAW;IACzB,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,KAAK,CAAC,IAAI,CAAC,sEAAsE,CAAC,CAAC;IACnF,KAAK,CAAC,IAAI,CAAC,mFAAmF,CAAC,CAAC;IAChG,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACf,KAAK,MAAM,OAAO,IAAI,WAAW,EAAE,CAAC;QAClC,KAAK,CAAC,IAAI,CAAC,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC;QAClC,KAAK,MAAM,KAAK,IAAI,OAAO,CAAC,MAAM;YAAE,KAAK,CAAC,IAAI,CAAC,OAAO,KAAK,EAAE,CAAC,CAAC;QAC/D,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACjB,CAAC;IACD,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,CAAC;AACpC,CAAC"}
|
|
1
|
+
{"version":3,"file":"guide.js","sourceRoot":"","sources":["../../src/agent/guide.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,uDAAuD;AACvD,EAAE;AACF,2EAA2E;AAC3E,8EAA8E;AAC9E,sEAAsE;AACtE,8EAA8E;AAC9E,sDAAsD;AACtD,EAAE;AACF,6EAA6E;AAC7E,8EAA8E;AAC9E,8EAA8E;AAC9E,4EAA4E;AAC5E,EAAE;AACF,gFAAgF;AAChF,8DAA8D;AAC9D,8EAA8E;AAO9E,MAAM,CAAC,MAAM,WAAW,GAA4B;IAClD;QACE,KAAK,EAAE,kCAAkC;QACzC,MAAM,EAAE;YACN,2GAA2G;YAC3G,sGAAsG;YACtG,0GAA0G;SAC3G;KACF;IACD;QACE,KAAK,EAAE,8BAA8B;QACrC,MAAM,EAAE;YACN,sGAAsG;YACtG,wNAAwN;YACxN,kJAAkJ;YAClJ,wGAAwG;SACzG;KACF;IACD;QACE,KAAK,EAAE,yCAAyC;QAChD,MAAM,EAAE;YACN,2FAA2F;YAC3F,4JAA4J;YAC5J,6FAA6F;SAC9F;KACF;IACD;QACE,KAAK,EAAE,2BAA2B;QAClC,MAAM,EAAE;YACN,mGAAmG;YACnG,8JAA8J;YAC9J,kJAAkJ;SACnJ;KACF;IACD;QACE,KAAK,EAAE,4CAA4C;QACnD,MAAM,EAAE;YACN,4PAA4P;YAC5P,kNAAkN;YAClN,kKAAkK;YAClK,6MAA6M;SAC9M;KACF;IACD;QACE,KAAK,EAAE,8BAA8B;QACrC,MAAM,EAAE;YACN,oHAAoH;YACpH,+JAA+J;YAC/J,gKAAgK;YAChK,8JAA8J;YAC9J,6SAA6S;YAC7S,kHAAkH;SACnH;KACF;IACD;QACE,KAAK,EAAE,+DAA+D;QACtE,MAAM,EAAE;YACN,+LAA+L;YAC/L,ylBAAylB;YACzlB,4nBAA4nB;YAC5nB,gfAAgf;YAChf,wnCAAwnC;YACxnC,4aAA4a;YAC5a,gQAAgQ;SACjQ;KACF;IACD;QACE,KAAK,EAAE,gCAAgC;QACvC,MAAM,EAAE;YACN,+EAA+E;YAC/E,gJAAgJ;YAChJ,sFAAsF;YACtF,oIAAoI;SACrI;KACF;CACF,CAAC;AAEF,MAAM,UAAU,WAAW;IACzB,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,QAAQ,EAAE,WAAW,EAAE,CAAC;AAC/C,CAAC;AAED,MAAM,UAAU,WAAW;IACzB,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,KAAK,CAAC,IAAI,CAAC,sEAAsE,CAAC,CAAC;IACnF,KAAK,CAAC,IAAI,CAAC,mFAAmF,CAAC,CAAC;IAChG,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACf,KAAK,MAAM,OAAO,IAAI,WAAW,EAAE,CAAC;QAClC,KAAK,CAAC,IAAI,CAAC,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC;QAClC,KAAK,MAAM,KAAK,IAAI,OAAO,CAAC,MAAM;YAAE,KAAK,CAAC,IAAI,CAAC,OAAO,KAAK,EAAE,CAAC,CAAC;QAC/D,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACjB,CAAC;IACD,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,CAAC;AACpC,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../src/mini-drama/cli.ts"],"names":[],"mappings":";AAEA,OAAO,eAAe,CAAC;AACvB,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../src/mini-drama/cli.ts"],"names":[],"mappings":";AAEA,OAAO,eAAe,CAAC;AACvB,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA2JpC,QAAA,MAAM,OAAO,SAAgB,CAAC;AA67J9B,OAAO,EAAE,OAAO,EAAE,CAAC"}
|
package/dist/mini-drama/cli.js
CHANGED
|
@@ -37,7 +37,7 @@ import { multiEditImage, loadImageAsDataUri } from '../venice/multi-edit.js';
|
|
|
37
37
|
import { assembleEpisode, collectShotVideos } from './assembler.js';
|
|
38
38
|
import { buildGenerationPlan, saveGenerationPlan } from './generation-planner.js';
|
|
39
39
|
import { AUDIO_STRATEGY_CHOICES, INTELLIGENCE_CHOICES, RENDER_ROUTE_CHOICES, VIDEO_FAMILY_CHOICES, } from './choices.js';
|
|
40
|
-
import { DEFAULT_INTELLIGENCE_MODEL, describeIntelligence, resolveIntelligence, } from '../venice/text-models.js';
|
|
40
|
+
import { DEFAULT_INTELLIGENCE_MODEL, describeIntelligence, getTextModel, resolveIntelligence, } from '../venice/text-models.js';
|
|
41
41
|
import { getProjectLanguage } from '../series/project-language.js';
|
|
42
42
|
import { approveWorkshop, generateWorkshop, getWorkshopPath, inventoryReferencePath, loadWorkshop, saveWorkshop, } from './workshop.js';
|
|
43
43
|
import { hasTreatment, refreshTreatment } from './treatment.js';
|
|
@@ -82,6 +82,10 @@ function montageModeForRoute(route) {
|
|
|
82
82
|
* then the harness default. Projects created before 2.9.0 have no saved
|
|
83
83
|
* choice and land on the default.
|
|
84
84
|
*/
|
|
85
|
+
function describeStreamWriter(id) {
|
|
86
|
+
const spec = getTextModel(id);
|
|
87
|
+
return spec ? `${spec.label} (${spec.privacy})` : id;
|
|
88
|
+
}
|
|
85
89
|
function intelligenceFor(series, override) {
|
|
86
90
|
if (override)
|
|
87
91
|
return resolveIntelligence(override);
|
|
@@ -4560,10 +4564,11 @@ program
|
|
|
4560
4564
|
.requiredOption('-p, --project <dir>', 'Project output directory')
|
|
4561
4565
|
.option('-e, --episode <number>', 'Episode number the stream lives under', '1')
|
|
4562
4566
|
.option('--direction <text>', 'Standing direction folded into every beat (e.g. "live studio audience laugh track after every joke")')
|
|
4563
|
-
.option('--writer <model>', '
|
|
4567
|
+
.option('--writer <model>', 'Model that writes beats. Asked interactively for a new stream; required (or "default" = deepseek-v4-flash-0731-fast, the fastest reliable writer in the bakeoff) in a non-interactive run. A resumed stream keeps the writer it last ran with. Switchable from the Stream tab.')
|
|
4568
|
+
.option('--video-family <family>', 'Video family for the beats: minimax-h3-max-turbo (default; the only one that keeps pace with playback) | minimax-h3-max | wan-3-0 | grok-imagine | seedance-2-0 | seedance-2-5 | kling-o3-standard. Switchable from the Stream tab.')
|
|
4564
4569
|
.option('--port <port>', 'Port to listen on', '3000')
|
|
4565
4570
|
.option('--host <host>', 'Host to bind (localhost only by default)', '127.0.0.1')
|
|
4566
|
-
.option('--resolution <res>', 'Render resolution (
|
|
4571
|
+
.option('--resolution <res>', 'Render resolution (default: the video family\'s draft tier, 480P on MiniMax)')
|
|
4567
4572
|
.option('--duration <dur>', 'Per-beat duration, snapped to the 5-15s ladder', '15s')
|
|
4568
4573
|
.option('--budget <usd>', 'Stop after this much estimated spend (each Start authorizes another budget)', '2')
|
|
4569
4574
|
.option('--unbounded', 'No budget cap — stream until stopped', false)
|
|
@@ -4580,10 +4585,6 @@ program
|
|
|
4580
4585
|
failJson(json, `--episode must be a positive integer. Got "${opts.episode}".`);
|
|
4581
4586
|
process.exit(1);
|
|
4582
4587
|
}
|
|
4583
|
-
if (!['480P', '768P'].includes(opts.resolution.toUpperCase())) {
|
|
4584
|
-
failJson(json, `--resolution must be 480P or 768P. Got "${opts.resolution}".`);
|
|
4585
|
-
process.exit(1);
|
|
4586
|
-
}
|
|
4587
4588
|
const projectDir = resolve(opts.project);
|
|
4588
4589
|
const series = await loadSeries(projectDir);
|
|
4589
4590
|
if (!series) {
|
|
@@ -4603,9 +4604,53 @@ program
|
|
|
4603
4604
|
if (basename(projectDir) !== slug) {
|
|
4604
4605
|
console.warn(`⚠ Project directory basename (${basename(projectDir)}) does not match the series slug (${slug}); browser media/deeplink may not resolve.`);
|
|
4605
4606
|
}
|
|
4607
|
+
// The writer is the session's first decision, like `loop --mode`: it is
|
|
4608
|
+
// the voice of the whole story, it costs money from beat 1, and its speed
|
|
4609
|
+
// sets how far the stream falls behind playback. Ask in a terminal; in a
|
|
4610
|
+
// non-interactive run require --writer so no agent silently commits the
|
|
4611
|
+
// operator to a model they did not choose. The list is the bakeoff list
|
|
4612
|
+
// (stream-choices.ts), fastest reliable first — NOT the project's
|
|
4613
|
+
// intelligence model, which is tuned for QA and scripting, not latency.
|
|
4614
|
+
const { STREAM_WRITER_CHOICES, STREAM_DEFAULT_WRITER, STREAM_VIDEO_CHOICES, getStreamVideoChoice } = await import('./stream-choices.js');
|
|
4615
|
+
const resumed = existsSync(join(getEpisodeDir(series, episodeNumber), 'stream', 'stream-manifest.json'));
|
|
4616
|
+
let writer;
|
|
4617
|
+
if (opts.writer) {
|
|
4618
|
+
writer = opts.writer === 'default' ? STREAM_DEFAULT_WRITER : opts.writer;
|
|
4619
|
+
}
|
|
4620
|
+
else if (resumed) {
|
|
4621
|
+
writer = undefined; // the engine keeps the manifest's writer
|
|
4622
|
+
}
|
|
4623
|
+
else if (!json && stdin.isTTY) {
|
|
4624
|
+
writer = await promptChoice('Which model writes the beats? (its speed sets how far the stream lags playback)', STREAM_WRITER_CHOICES.map(w => ({
|
|
4625
|
+
label: `${w.label} — ~${w.medianSec}s/beat, ${w.reliability} valid, ${w.privacy}${w.id === STREAM_DEFAULT_WRITER ? ' (default)' : ''}`,
|
|
4626
|
+
value: w.id,
|
|
4627
|
+
description: w.note,
|
|
4628
|
+
})), 0);
|
|
4629
|
+
}
|
|
4630
|
+
else {
|
|
4631
|
+
failJson(json, `A writer is required for a new stream: pass --writer <model>, or --writer default (${STREAM_DEFAULT_WRITER}). Tested writers, fastest first: ${STREAM_WRITER_CHOICES.map(w => `${w.id} (~${w.medianSec}s)`).join(', ')}.`);
|
|
4632
|
+
process.exit(1);
|
|
4633
|
+
return;
|
|
4634
|
+
}
|
|
4635
|
+
if (opts.videoFamily && !getStreamVideoChoice(opts.videoFamily)) {
|
|
4636
|
+
failJson(json, `Unknown --video-family "${opts.videoFamily}". Choices: ${STREAM_VIDEO_CHOICES.map(v => v.id).join(', ')}.`);
|
|
4637
|
+
process.exit(1);
|
|
4638
|
+
return;
|
|
4639
|
+
}
|
|
4640
|
+
// The stream needs only series.json, but the browser builds its episode
|
|
4641
|
+
// list FROM series.json. A stream under an unregistered episode rendered
|
|
4642
|
+
// beats the Stream tab could not show (it fell through to "No episodes
|
|
4643
|
+
// yet." with no Start button). Register the episode before anything bills.
|
|
4644
|
+
if (!series.episodes.some(ep => ep.number === episodeNumber)) {
|
|
4645
|
+
while (series.episodes.length < episodeNumber) {
|
|
4646
|
+
addEpisode(series, series.episodes.length + 1 === episodeNumber ? 'Stream' : `Episode ${series.episodes.length + 1}`);
|
|
4647
|
+
}
|
|
4648
|
+
await saveSeries(series);
|
|
4649
|
+
if (!json)
|
|
4650
|
+
console.log(`Registered episode ${episodeNumber} in series.json so the browser can show the stream.`);
|
|
4651
|
+
}
|
|
4606
4652
|
const apiKey = await getVeniceApiKey();
|
|
4607
4653
|
const client = new VeniceClient(apiKey);
|
|
4608
|
-
const writer = intelligenceFor(series, opts.writer).model;
|
|
4609
4654
|
const { startWebServer } = await import('../web/server.js');
|
|
4610
4655
|
const { EventHub } = await import('../web/events.js');
|
|
4611
4656
|
const { StreamEngine } = await import('./stream-engine.js');
|
|
@@ -4618,7 +4663,8 @@ program
|
|
|
4618
4663
|
episodeDir: getEpisodeDir(series, episodeNumber),
|
|
4619
4664
|
slug,
|
|
4620
4665
|
writerModel: writer,
|
|
4621
|
-
|
|
4666
|
+
videoFamily: opts.videoFamily,
|
|
4667
|
+
resolution: opts.resolution,
|
|
4622
4668
|
duration: opts.duration,
|
|
4623
4669
|
budgetUsd: Number.isFinite(budgetUsd) ? budgetUsd : undefined,
|
|
4624
4670
|
unbounded: opts.unbounded,
|
|
@@ -4633,14 +4679,25 @@ program
|
|
|
4633
4679
|
hub,
|
|
4634
4680
|
stream: { slug, episode: episodeNumber, engine },
|
|
4635
4681
|
});
|
|
4636
|
-
const status = await engine.start();
|
|
4637
4682
|
const url = `http://${opts.host}:${server.port}/?project=${encodeURIComponent(slug)}&tab=Stream`;
|
|
4683
|
+
// A new session renders the opening beat and then WAITS. The browser opens
|
|
4684
|
+
// with one beat ready and the stream paused; Start in the UI continues the
|
|
4685
|
+
// story. A resumed session (beats on disk) opens at once, also paused.
|
|
4686
|
+
const before = engine.state();
|
|
4687
|
+
if (!json && before.beats.length === 0) {
|
|
4688
|
+
const perBeat = (getStreamVideoChoice(before.videoFamily)?.usdPer15s ?? 0.11) * (Number.parseInt(before.duration, 10) / 15);
|
|
4689
|
+
const fam = getStreamVideoChoice(before.videoFamily);
|
|
4690
|
+
console.log(`Writer: ${describeStreamWriter(before.model.writer)}. Video: ${before.model.t2v} @ ${before.resolution || 'default'}, ${before.duration}/beat, about $${perBeat.toFixed(2)} per beat (billed at queue time)${fam && fam.speed !== 'keeps up' ? ` — ${fam.label} renders slower than playback (~${fam.renderSecApprox}s per beat)` : ''}.`);
|
|
4691
|
+
console.log('Rendering the opening beat before opening the browser…');
|
|
4692
|
+
}
|
|
4693
|
+
const status = await engine.prime();
|
|
4638
4694
|
if (json) {
|
|
4639
4695
|
emitJson({
|
|
4640
4696
|
ok: true,
|
|
4641
4697
|
url,
|
|
4642
4698
|
project: slug,
|
|
4643
4699
|
episode: episodeNumber,
|
|
4700
|
+
paused: true,
|
|
4644
4701
|
model: status.model,
|
|
4645
4702
|
resolution: status.resolution,
|
|
4646
4703
|
duration: status.duration,
|
|
@@ -4651,12 +4708,13 @@ program
|
|
|
4651
4708
|
else {
|
|
4652
4709
|
console.log(`venice-video stream running at ${url}`);
|
|
4653
4710
|
console.log(` project: ${slug} (episode ${episodeNumber})`);
|
|
4654
|
-
console.log(` writer: ${
|
|
4655
|
-
console.log(` video: ${status.model.t2v} (beat 1) then ${status.model.i2v} chained off each last frame @ ${status.resolution}, ${status.duration}/beat`);
|
|
4711
|
+
console.log(` writer: ${describeStreamWriter(status.model.writer)}`);
|
|
4712
|
+
console.log(` video: ${status.model.t2v} (beat 1) then ${status.model.i2v} chained off each last frame @ ${status.resolution || 'default'}, ${status.duration}/beat`);
|
|
4713
|
+
console.log(' models: switch the writer or the video model any time from the Stream tab; changes apply to the next beat.');
|
|
4656
4714
|
console.log(` direction: ${opts.direction ?? '(none)'}`);
|
|
4657
4715
|
console.log(` budget: ${opts.unbounded ? 'unbounded (streams until you stop it)' : `$${budgetUsd.toFixed(2)} (Start authorizes another budget)`}`);
|
|
4658
|
-
|
|
4659
|
-
|
|
4716
|
+
console.log(` beats: ${status.beats.length} on disk`);
|
|
4717
|
+
console.log(' state: PAUSED — click Start in the browser to continue the story. New beats then render back to back.');
|
|
4660
4718
|
console.log(' The story never repeats and never re-renders. Every beat is kept under the episode\'s stream/ directory.');
|
|
4661
4719
|
console.log(' Ctrl-C to stop.');
|
|
4662
4720
|
if (opts.open)
|