voxflow 1.15.2 → 1.15.4
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/dist/index.js +1 -1
- package/lib/commands/slice-render.js +71 -7
- package/lib/commands/slice-stage.js +97 -0
- package/lib/internal/deck-validator.js +47 -0
- package/lib/stage-core/local-render.js +324 -0
- package/lib/stage-core/server.js +228 -2
- package/lib/stage-core/tts-audition.js +0 -0
- package/lib/stage-core/voiceover-mux.js +183 -0
- package/lib/stage-ui/slice/template.js +660 -7
- package/package.json +1 -1
- package/skills/voxflow-slice/SKILL.md +75 -2
package/package.json
CHANGED
|
@@ -145,6 +145,31 @@ All cards require a non-empty `narration` string (TTS reads this; 30–60 zh cha
|
|
|
145
145
|
}
|
|
146
146
|
```
|
|
147
147
|
|
|
148
|
+
### Optional per-card `voiceover` override
|
|
149
|
+
|
|
150
|
+
Any card kind may carry a nested `voiceover` object to tune its audio
|
|
151
|
+
track. All four sub-fields are optional inside an optional object —
|
|
152
|
+
absent ⇒ the renderer uses the job-level default voice with
|
|
153
|
+
`card.narration` at 1× speed (back-compat with Phase 0 silent decks).
|
|
154
|
+
|
|
155
|
+
```jsonc
|
|
156
|
+
{
|
|
157
|
+
"kind": "body",
|
|
158
|
+
"caption": "短字幕",
|
|
159
|
+
"narration": "默认是 TTS 朗读的文本",
|
|
160
|
+
"voiceover": {
|
|
161
|
+
"enabled": true, // false → this card is silent in the mp4
|
|
162
|
+
"voiceId": "v-female-R2s4N9qJ", // overrides the job-level default voice
|
|
163
|
+
"text": "口播稿可以跟字幕不一样", // overrides narration for TTS only (visible caption unaffected)
|
|
164
|
+
"rate": 1.1 // [0.5, 2.0], default 1.0
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Validator caps: `voiceover.text` ≤ 500 chars; `rate ∈ [0.5, 2.0]`.
|
|
170
|
+
Render-time resolution precedence:
|
|
171
|
+
`voiceover.voiceId → card.voiceId → job-level default`.
|
|
172
|
+
|
|
148
173
|
> **Source of truth**: `backend/services/paper-slide/deck-validator.js` — caps live at lines 39–46 (QUOTE_TEXT_MAX, DATA_VALUE_MAX, LIST_ITEM_MAX_LEN, etc.). Read it if anything below seems ambiguous.
|
|
149
174
|
|
|
150
175
|
### Controlled `figureKeyword` list
|
|
@@ -232,12 +257,59 @@ After writing `deck.json`, tell the user:
|
|
|
232
257
|
```
|
|
233
258
|
Wrote deck.json (<N> cards, theme: <theme-id>). Next:
|
|
234
259
|
|
|
235
|
-
voxflow slice
|
|
236
|
-
voxflow slice
|
|
260
|
+
voxflow slice preview deck.json # browser preview + per-card audition + render button
|
|
261
|
+
voxflow slice render deck.json --output out.mp4 # one-shot mp4 from the terminal
|
|
237
262
|
```
|
|
238
263
|
|
|
239
264
|
Do not run either command yourself unless the user asks.
|
|
240
265
|
|
|
266
|
+
Both commands work fully offline for the visual side. **Audio (per-card
|
|
267
|
+
TTS audition + render audio track) requires `voxflow login`** — 100 quota
|
|
268
|
+
per unique `(voice, text)` clip, then cached at
|
|
269
|
+
`~/.config/voxflow/stage-tts-cache/`. With no login, both commands fall
|
|
270
|
+
back silently to a Phase-0-style silent video; pass `--no-audio` to
|
|
271
|
+
`render` to suppress the audio pass entirely.
|
|
272
|
+
|
|
273
|
+
## Multi-turn editing loop (Claude Code / Cursor / native `Edit`)
|
|
274
|
+
|
|
275
|
+
When the user is iterating — "shorten card 2", "swap order", "different
|
|
276
|
+
voice for card 3", "hear card 1 again" — they are **NOT** asking for a
|
|
277
|
+
regen. Stay in this loop:
|
|
278
|
+
|
|
279
|
+
```bash
|
|
280
|
+
# Run once at the start of the session; auto-opens http://127.0.0.1:5180.
|
|
281
|
+
# The page hot-reloads on every save of deck.json (~50 ms fs watcher).
|
|
282
|
+
voxflow slice preview deck.json &
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
Then for every follow-up:
|
|
286
|
+
|
|
287
|
+
| User intent | Your move | Cost |
|
|
288
|
+
|---|---|---|
|
|
289
|
+
| "card N is too long" | `Edit` `cards[N-1].caption` / `.narration` — page hot-reloads | 0 |
|
|
290
|
+
| "swap card 2 and 3" | `Edit` the `cards` array order | 0 |
|
|
291
|
+
| "different voice for card 3" | `Edit` `cards[2].voiceover.voiceId` — or tell user to paste a voiceId in the toolbar voice picker | 0 (Edit) |
|
|
292
|
+
| "make card 4 silent" | `Edit` `cards[3].voiceover = { "enabled": false }` | 0 |
|
|
293
|
+
| "口播说点不一样的" | `Edit` `cards[i].voiceover.text` so TTS reads override while caption stays | 0 |
|
|
294
|
+
| "I want to hear card 3" | Tell user to click ▶ on card 3 in the browser; the toolbar shows cache hit / quota cost / error | 100 (first time per clip), 0 (cached) |
|
|
295
|
+
| "render mp4" | Tell user: click **Render mp4 (local)** in the browser, OR `voxflow slice render deck.json` | TTS pass (cached if auditioned) + render |
|
|
296
|
+
|
|
297
|
+
### Loop rules
|
|
298
|
+
|
|
299
|
+
1. **Edit only the fields the user asked about.** Other cards must stay
|
|
300
|
+
byte-identical — the stage UI's diff highlight is the user's "what
|
|
301
|
+
changed" indicator. Touching extra fields breaks that signal.
|
|
302
|
+
2. **Never re-run `voxflow slice <article>` during iteration** — that
|
|
303
|
+
costs 200 quota AND overwrites every user edit with a fresh LLM draft.
|
|
304
|
+
3. **Re-validate after every save** by re-reading the file. If the user
|
|
305
|
+
says "page shows old content" or "red banner appeared", the JSON has a
|
|
306
|
+
syntax error (trailing comma, unbalanced quote) — open, fix, save.
|
|
307
|
+
4. **Don't restart the preview server.** One process handles the whole
|
|
308
|
+
session; restarting wipes snapshot history.
|
|
309
|
+
5. **Don't call `/api/audition` yourself.** It's user-driven via the ▶
|
|
310
|
+
button. Editing `cards[i].voiceover.voiceId` is enough — the next ▶
|
|
311
|
+
click picks up the new voice.
|
|
312
|
+
|
|
241
313
|
## Self-review checklist
|
|
242
314
|
|
|
243
315
|
Before declaring the slice done:
|
|
@@ -254,6 +326,7 @@ Before declaring the slice done:
|
|
|
254
326
|
- [ ] If the theme is `photo-feature` or `atmospheric` and the user provided per-card images, `imageUrl` starts with `https://`
|
|
255
327
|
- [ ] No outro card unless the user explicitly asked for one
|
|
256
328
|
- [ ] No React, TSX, or CSS files were created
|
|
329
|
+
- [ ] If any card has a `voiceover` object, every key inside it (`enabled` / `voiceId` / `text` / `rate`) matches the schema (boolean / non-empty string ≤128 / string ≤500 / number in [0.5, 2.0])
|
|
257
330
|
|
|
258
331
|
## Anti-patterns
|
|
259
332
|
|