@mineo71/showtime 0.2.1 → 0.4.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.
Files changed (45) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/README.md +155 -19
  3. package/docs/script.schema.json +395 -0
  4. package/package.json +1 -1
  5. package/skill/SKILL.md +70 -13
  6. package/src/audio/decode.ts +19 -0
  7. package/src/audio/mix.ts +72 -0
  8. package/src/audio/sfx.ts +55 -0
  9. package/src/audio/soundtrack.ts +49 -0
  10. package/src/audio/tts.ts +186 -0
  11. package/src/audio/wav.ts +96 -0
  12. package/src/cli/format.ts +10 -0
  13. package/src/cli/main.ts +125 -40
  14. package/src/convert.ts +48 -0
  15. package/src/ffmpeg.ts +21 -0
  16. package/src/lint.ts +128 -0
  17. package/src/logo.ts +20 -0
  18. package/src/paths.ts +19 -0
  19. package/src/pipeline.ts +97 -19
  20. package/src/render/remotion/Intro.tsx +3 -1
  21. package/src/render/remotion/Logo.tsx +6 -0
  22. package/src/render/remotion/Outro.tsx +38 -0
  23. package/src/render/remotion/Overlays.tsx +41 -16
  24. package/src/render/remotion/ShowtimeVideo.tsx +56 -18
  25. package/src/render/render.ts +1 -1
  26. package/src/runner/browser.ts +47 -6
  27. package/src/runner/check.ts +10 -1
  28. package/src/runner/events.ts +2 -0
  29. package/src/runner/hooks.ts +111 -0
  30. package/src/runner/record.ts +53 -34
  31. package/src/runner/steps.ts +4 -1
  32. package/src/script/config.ts +74 -0
  33. package/src/script/hash.ts +7 -1
  34. package/src/script/load.ts +15 -2
  35. package/src/script/schema.ts +115 -1
  36. package/src/setup.ts +13 -4
  37. package/src/sheet.ts +47 -0
  38. package/src/timeline/build.ts +113 -13
  39. package/src/timeline/camera.ts +34 -11
  40. package/src/timeline/fades.ts +3 -0
  41. package/src/timeline/frames.ts +15 -4
  42. package/src/timeline/layout.ts +70 -0
  43. package/src/timeline/narration.ts +160 -0
  44. package/src/timeline/timemap.ts +40 -5
  45. package/src/timeline/types.ts +44 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,37 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.4.0 — 2026-10-13
4
+
5
+ - Narration: `show.say` on any step is spoken by a TTS engine (`audio.voice`: the macOS/Linux
6
+ system voice, OpenAI, ElevenLabs or any command). The video holds the frame until a line ends.
7
+ Lines are cached; editing them needs no new take.
8
+ - `audio.music`: a looped music bed that ducks under the voice. `audio.sounds`: click and
9
+ keystroke sounds (takes now log keystrokes; older takes spread them over each `type` step).
10
+ - Subtitles from the narration: an `.srt` next to the video, or burned in (`audio.subtitles`).
11
+ - `--aspect 9:16 | 1:1` on `render`, `make` and `frames`: cropped cuts with a camera that follows
12
+ the action, from the same take. Stills of a crop go to `frames-9x16/` or `frames-1x1/`.
13
+ - `--format gif | webp` (or an `-o` with that extension) and `--width`; `--no-audio`. Silent
14
+ outputs skip narration (no voice needed) unless subtitles are burned in.
15
+ - `render --json` adds `subtitles`; the brand file accepts `audio`.
16
+ - `lint`: `narration-stretch` flags lines that hold the frame for long.
17
+
18
+ ## 0.3.0 — 2026-10-13
19
+
20
+ - `showtime frames` also renders a contact sheet (`frames/sheet.png`) of every step.
21
+ - `showtime lint`: long captions, pauses that should be waits, zoom/spotlight that cannot apply,
22
+ and, with a take, captions too brief to read and overall length.
23
+ - `--json` on every command.
24
+ - `hooks.before` / `hooks.after`: shell commands or HTTP calls around every `check` and `record`,
25
+ so state can be reset between rehearsal and take.
26
+ - `app.mock` (canned responses for matching requests), `app.clock` (the page clock starts at a fixed date),
27
+ `app.dialogs` (accept or dismiss native dialogs; accepted by default).
28
+ - Safe re-takes: a failed `record` leaves the previous take untouched (`take.tmp` keeps the failure).
29
+ - Settling ignores event streams; relative `goto` URLs resolve against `app.url`.
30
+ - Captions never overlap, and move to the top when the action is at the bottom of the screen.
31
+ - `meta.outro` closing card and `meta.logo` on the intro and outro cards.
32
+ - `showtime.config.json` brand file with default `style` and `meta` for every script below it.
33
+ - Takes recorded with 0.2 stay valid.
34
+
3
35
  ## 0.2.1 — 2026-10-13
4
36
 
5
37
  - Releases are automated: `npm version patch|minor|major` dates the changelog, tags and pushes;
package/README.md CHANGED
@@ -8,7 +8,8 @@
8
8
 
9
9
  Your AI agent (Claude Code or Codex) writes a short JSON script. showTime clicks through your web app, films it and edits it
10
10
  the way a motion designer would (auto-zoom, a smooth cursor, click ripples, spotlights, captions,
11
- sped-up loading) and hands you a polished 1080p MP4.<br />
11
+ sped-up loading, narration) and hands you a polished video: a 1080p MP4, a 9:16 cut for social or
12
+ a GIF for your README.<br />
12
13
  No timeline editor. No re-take when a caption changes.
13
14
 
14
15
  [![tests](https://img.shields.io/github/actions/workflow/status/mineo71/showTime/ci.yml?branch=main&style=flat-square&label=tests)](https://github.com/mineo71/showTime/actions/workflows/ci.yml)
@@ -26,7 +27,7 @@ No timeline editor. No re-take when a caption changes.
26
27
  <div align="center">
27
28
  <a href="docs/media/demo.mp4"><img src="docs/media/hero.webp" width="860" alt="A demo of a fictional invoicing app, filmed and edited by showTime: the camera zooms on a chart, a cursor fills in a new invoice, a spotlight lands on the total, and the sent invoice appears in the table" /></a>
28
29
  <br />
29
- <sub>Made by <a href="examples/fennel/demo.json">one 44-line script</a> and one command, with no manual editing. <a href="docs/media/demo.mp4">▶ Full-quality MP4</a></sub>
30
+ <sub>Made by <a href="examples/fennel/demo.json">a 39-line script</a>, a <a href="examples/fennel/showtime.config.json">brand file</a> and one command, with no manual editing. <a href="docs/media/demo.mp4">▶ Full-quality MP4, with sound</a></sub>
30
31
  </div>
31
32
 
32
33
  ---
@@ -121,7 +122,40 @@ again.
121
122
  <td valign="top">
122
123
  <img src="docs/media/intro.jpg" alt="Intro card with title and subtitle" /><br />
123
124
  <b>🎬 Intro card and window chrome</b><br />
124
- <sub>Title card, a browser frame with your URL, a gradient backdrop, rounded corners and a shadow. Each of them is a <code>style</code> field.</sub>
125
+ <sub>Title card with your logo, a browser frame with your URL, a gradient backdrop, rounded corners and a shadow. Each of them is a <code>style</code> or <code>meta</code> field.</sub>
126
+ </td>
127
+ </tr>
128
+ <tr>
129
+ <td valign="top">
130
+ <pre lang="json">
131
+ "show": { "say": "Send it before lunch." }
132
+
133
+ "audio": {
134
+ "voice": { "engine": "openai", "name": "nova" },
135
+ "music": { "file": "bed.mp3" },
136
+ "sounds": true,
137
+ "subtitles": "burn"
138
+ }
139
+ </pre>
140
+ <b>🎙️ Narration, music and clicks</b><br />
141
+ <sub>Give a step a line and a voice reads it: the built-in voice of macOS or Linux, OpenAI, ElevenLabs or any TTS command. The video holds the frame until the line ends, music ducks under the voice, and subtitles come as an <code>.srt</code> or burned in.</sub>
142
+ </td>
143
+ <td valign="top">
144
+ <img src="docs/media/formats.jpg" alt="The same moment in 16:9, 9:16 and 1:1" /><br />
145
+ <b>📱 Every format from one take</b><br />
146
+ <sub><code>--aspect 9:16</code> or <code>1:1</code> crops the window and a follow camera keeps the action in view. <code>--format gif</code> or <code>webp</code> for READMEs and docs. Nothing is re-recorded.</sub>
147
+ </td>
148
+ </tr>
149
+ <tr>
150
+ <td valign="top">
151
+ <img src="docs/media/outro.jpg" alt="Outro card with logo, title and URL" /><br />
152
+ <b>📣 Ends on your call to action</b><br />
153
+ <sub>An outro card with your logo, a headline and the URL to visit. Put it in a brand file once and every demo ends the same way.</sub>
154
+ </td>
155
+ <td valign="top">
156
+ <img src="docs/media/sheet.jpg" alt="Contact sheet of every step" /><br />
157
+ <b>🧪 Reviews its own work</b><br />
158
+ <sub><code>showtime frames</code> renders a contact sheet of every step, and <code>showtime lint</code> flags captions that are too long or too brief to read. The agent fixes them before you look.</sub>
125
159
  </td>
126
160
  </tr>
127
161
  </table>
@@ -132,8 +166,16 @@ And under the hood:
132
166
  When a step fails it prints an ARIA snapshot of the page, so Claude can fix its own selectors.
133
167
  - 🔁 **Stale-take detection.** The take stores a hash of the actions. Edit a caption and `render`
134
168
  reuses the take. Edit a click and `render` tells you to `record` again.
135
- - 🧪 **Self-review.** `showtime frames` renders a still per step so the agent can *look* at its
136
- work before you do.
169
+ - 🧹 **Clean state for every take.** `hooks` run a reset command or call an endpoint before and
170
+ after each rehearsal and take, so a flow that creates data works twice in a row.
171
+ - 🎭 **Deterministic pages.** `app.mock` answers flaky or slow APIs with canned JSON, `app.clock`
172
+ pins the date, and `app.dialogs` answers `confirm()` the way the demo needs.
173
+ - 🎨 **One brand file.** `showtime.config.json` holds your colours, logo and outro; every script
174
+ below it inherits them.
175
+ - 🛟 **Safe re-takes.** A take that fails halfway leaves the last good one untouched.
176
+ - 🤖 **JSON everywhere.** Every command takes `--json`, so agents read results instead of parsing
177
+ prose. Captions also move to the top on their own when the action happens at the bottom of the
178
+ screen.
137
179
  - 📐 **Typed scripts.** Point `"$schema"` at [`docs/script.schema.json`](docs/script.schema.json)
138
180
  for autocomplete and validation in your editor.
139
181
  - 🖥️ **Crisp at 1080p.** The app is captured at 2× device scale, so zoomed shots stay sharp.
@@ -169,7 +211,8 @@ The agent takes it from there:
169
211
  3. **Rehearses** with `showtime check` until every step passes, fixing selectors from the ARIA
170
212
  snapshot.
171
213
  4. **Records and renders** with `showtime record` and `showtime render`.
172
- 5. **Reviews** the stills from `showtime frames`, tightens captions and pacing, and re-renders.
214
+ 5. **Reviews** the contact sheet from `showtime frames` and the warnings from `showtime lint`,
215
+ tightens captions and pacing, and re-renders.
173
216
 
174
217
  The skill ([`skill/SKILL.md`](skill/SKILL.md)) is a plain-Markdown
175
218
  [Agent Skill](https://agentskills.io). Any agent that can run shell commands can follow it, and so
@@ -185,16 +228,19 @@ flowchart LR
185
228
  R --> E["events.json<br/>targets · clicks · idle"]
186
229
  S --> T["timeline.json<br/>camera · cursor · captions · speed"]
187
230
  E --> T
231
+ T --> A["mix.wav<br/>voice · music · clicks"]
188
232
  T --> M["Remotion"]
233
+ A --> M
189
234
  V --> M
190
- M --> O["demo.mp4"]
235
+ M --> O["demo.mp4 · 9:16 · GIF"]
191
236
  ```
192
237
 
193
238
  The core idea is to **separate capture from direction**. `record` drives a real Chromium through
194
239
  the script and writes two things: a clean video with no cursor, and an event log of where every
195
240
  target was, when each click landed and when the page sat idle. A pure function,
196
241
  `buildTimeline(script, events)`, turns that log into speed segments, camera keyframes, cursor
197
- paths, captions and spotlights. Remotion renders the result frame by frame.
242
+ paths, captions and spotlights, and holds the frame wherever a narration line needs more time.
243
+ The soundtrack is mixed from the same timeline, and Remotion renders the result frame by frame.
198
244
 
199
245
  So presentation is cheap to change and recording stays honest: the app really is clicked, typed
200
246
  into and waited on, and everything cinematic is computed afterwards.
@@ -224,8 +270,9 @@ into and waited on, and everything cinematic is computed afterwards.
224
270
  }
225
271
  ```
226
272
 
227
- `meta.title` adds an intro card. `setup` steps run before the camera starts (cookie banners,
228
- onboarding). Scenes group steps for readability and for naming the stills.
273
+ `meta.title` adds an intro card, `meta.outro` a closing card and `meta.logo` puts your logo on
274
+ both. `setup` steps run before the camera starts (cookie banners, onboarding). Scenes group steps
275
+ for readability and for naming the stills.
229
276
 
230
277
  <details>
231
278
  <summary><b>Actions</b> (<code>do</code>)</summary>
@@ -257,6 +304,7 @@ Targets are Playwright selectors: `role=button[name='Save']`, `text=Save`, `#id`
257
304
  | `spotlight` | boolean | dim everything except the target |
258
305
  | `hold` | ms | extra real time after the step (changes the take) |
259
306
  | `speed` | 0.25–16 | playback speed for this step |
307
+ | `say` | string | narration spoken from the start of the step; the next narrated step waits for it |
260
308
 
261
309
  </details>
262
310
 
@@ -291,6 +339,83 @@ Targets are Playwright selectors: `role=button[name='Save']`, `text=Save`, `#id`
291
339
  | `colorScheme` | none | `light` or `dark`; also picks the browser-bar theme |
292
340
  | `locale`, `timezoneId` | none | passed to the browser context |
293
341
  | `mask` | `[]` | CSS selectors to blur (plain CSS, not `text=`) |
342
+ | `mock` | `[]` | `[{ url, method?, status?, json? \| body?, headers?, delayMs? }]`: answer matching requests (`url` is a glob like `**/api/stats*`; the first match wins) |
343
+ | `clock` | none | start the page clock at this ISO date-time; it then runs normally |
344
+ | `dialogs` | `accept` | answer `alert`/`confirm`/`prompt` with `accept` or `dismiss` |
345
+
346
+ </details>
347
+
348
+ <details>
349
+ <summary><b>Sound</b> (<code>audio</code>)</summary>
350
+
351
+ ```json
352
+ "audio": {
353
+ "voice": { "engine": "system", "name": "Samantha", "speed": 1 },
354
+ "music": { "file": "music/bed.mp3", "volume": 0.25 },
355
+ "sounds": true,
356
+ "subtitles": "file"
357
+ }
358
+ ```
359
+
360
+ | field | default | |
361
+ |----------------------|------------|-|
362
+ | `voice.engine` | `system` | `system` (macOS `say`, Linux `espeak-ng`), `openai`, `elevenlabs`, or `command` |
363
+ | `voice.name` | engine default | voice name (`system`, `openai`), or voice id (`elevenlabs`, required) |
364
+ | `voice.speed` | `1` | 0.5–2 |
365
+ | `voice.model` | engine default | `gpt-4o-mini-tts` (OpenAI), `eleven_multilingual_v2` (ElevenLabs) |
366
+ | `voice.instructions` | none | OpenAI only: how to speak ("warm, confident") |
367
+ | `voice.command` | none | `command` only: a shell command that reads the text on stdin and writes audio to `{out}` (inserted already quoted, so write it bare; also in `$SHOWTIME_OUT`), e.g. `piper -m en_US.onnx -f {out}` |
368
+ | `music` | none | `{ file, volume? }`: a bed under the whole video, looped and ducked under the voice |
369
+ | `sounds` | `false` | click and keystroke sounds |
370
+ | `subtitles` | `file` | `file`: an `.srt` next to the video; `burn`: also on the video (captions move up); `off` |
371
+
372
+ `openai` and `elevenlabs` read `OPENAI_API_KEY` / `ELEVENLABS_API_KEY` from the environment
373
+ (`OPENAI_BASE_URL` points at any OpenAI-compatible server). Each line is synthesized once and
374
+ cached; edits to `say` or `audio` only need `render`. Silent outputs (GIF, WebP, `--no-audio`) skip
375
+ the narration and its pauses, unless subtitles are burned in.
376
+
377
+ </details>
378
+
379
+ <details>
380
+ <summary><b>Cards</b> (<code>meta</code>)</summary>
381
+
382
+ | field | |
383
+ |------------|-|
384
+ | `title`, `subtitle` | intro card |
385
+ | `logo` | SVG, PNG, JPEG or WebP (≤ 2 MB), relative to the file that names it; shown on the intro and outro |
386
+ | `outro` | `{ title, subtitle?, url? }`: closing card after the video |
387
+
388
+ </details>
389
+
390
+ <details>
391
+ <summary><b>Hooks</b> (<code>hooks</code>)</summary>
392
+
393
+ ```json
394
+ "hooks": {
395
+ "before": [{ "run": "npm run db:reset" }],
396
+ "after": [{ "request": { "url": "http://localhost:3000/api/test/reset", "method": "POST" } }]
397
+ }
398
+ ```
399
+
400
+ `before` hooks run at the start of every `check` and `record` (so `make` resets between the
401
+ rehearsal and the take); `after` hooks run when they end, even after a failure, and a failing
402
+ `after` hook is a warning that never costs you the take. `run` is a shell
403
+ command in the script's folder (120 s timeout); `request` is an HTTP call that must answer 2xx.
404
+
405
+ </details>
406
+
407
+ <details>
408
+ <summary><b>Brand file</b> (<code>showtime.config.json</code>)</summary>
409
+
410
+ Put it next to your scripts or in any folder above them; the nearest one applies. Its `style`,
411
+ `meta` and `audio` are defaults, and a script's own fields win key by key.
412
+
413
+ ```json
414
+ {
415
+ "style": { "background": "linear-gradient(135deg, #0f172a, #312e81)", "accent": "#6366f1", "address": "app.acme.com" },
416
+ "meta": { "logo": "brand/logo.svg", "outro": { "title": "Try Acme free", "url": "acme.com" } }
417
+ }
418
+ ```
294
419
 
295
420
  </details>
296
421
 
@@ -299,14 +424,17 @@ Targets are Playwright selectors: `role=button[name='Save']`, `text=Save`, `#id`
299
424
  | command | what it does |
300
425
  |---|---|
301
426
  | `showtime setup [--agent claude\|codex\|all] [--no-browsers]` | download the browsers, link the skill |
302
- | `showtime make <script> [-o out.mp4]` | check → record → render |
427
+ | `showtime make <script> [render options]` | check → record → render |
303
428
  | `showtime check <script> [--json] [--headed]` | rehearse; a screenshot per step; the first failure with an ARIA snapshot |
304
429
  | `showtime record <script> [--headed] [--keep-frames]` | record a take into `.showtime/<name>/take` |
305
- | `showtime render <script> [-o out.mp4]` | render from the last take, without re-recording |
306
- | `showtime frames <script>` | QA stills: the intro plus one per step |
430
+ | `showtime render <script> [-o out] [--aspect 16:9\|9:16\|1:1] [--format mp4\|gif\|webp] [--width px] [--no-audio]` | render from the last take, without re-recording |
431
+ | `showtime frames <script> [--aspect …]` | QA stills (intro, one per step, outro) and a contact sheet of them |
432
+ | `showtime lint <script>` | warnings about captions, pauses and length; timing checks need a take |
307
433
  | `showtime schema` | print the script's JSON Schema |
308
434
 
309
- The video lands next to the script (`demo/pitch.json` → `demo/pitch.mp4`). Working files live in
435
+ Every command takes `--json` and then prints exactly one JSON document. The video lands next to
436
+ the script (`demo/pitch.json` → `demo/pitch.mp4`, `demo/pitch-9x16.mp4`, `demo/pitch.gif`), with
437
+ `demo/pitch.srt` when it is narrated. Working files live in
310
438
  `.showtime/<name>/`, which is worth adding to your `.gitignore`.
311
439
 
312
440
  ## Tips for a great demo
@@ -317,15 +445,22 @@ The video lands next to the script (`demo/pitch.json` → `demo/pitch.mp4`). Wor
317
445
  `spotlight` on the one number that sells it.
318
446
  - **Never `pause` for loading.** Use `wait` with a selector and auto-speed compresses the spinner.
319
447
  - **Start clean.** Use `setup` for cookie banners, `app.storageState` to skip the login, `app.mask`
320
- for anything personal, and seed data you can reset between `check` and `record`.
448
+ for anything personal, and `hooks` to reset the data between `check` and `record`.
449
+ - **Pin the world.** Mock slow or random APIs with `app.mock` and pin the date with `app.clock`,
450
+ so every take looks the same.
451
+ - **Narrate the why, not the clicks.** One short sentence per moment. If `showtime lint` says a
452
+ line holds the frame for seconds, shorten it or give the step more to show.
321
453
 
322
454
  ## FAQ
323
455
 
324
456
  **Which apps does it work with?** Any web app Chromium can open: a localhost dev server, staging
325
457
  or production. Logged-in flows work through `app.storageState`.
326
458
 
327
- **Does showTime call an AI model?** No. showTime is deterministic and needs no API keys. The AI
328
- part (writing, fixing and reviewing the script) happens in your agent, guided by the skill.
459
+ **Does showTime call an AI model?** Not for directing: the AI part (writing, fixing and reviewing
460
+ the script) happens in your agent, guided by the skill. Narration is text-to-speech. The built-in
461
+ voice of macOS or Linux needs no key and nothing leaves your machine; with `openai` or
462
+ `elevenlabs`, only the narration text is sent to that provider, with your key from the
463
+ environment.
329
464
 
330
465
  **Why not just screen-record?** You can, once. A screen recording can't be re-edited when the copy
331
466
  changes, the cursor is wherever your hand was, and the spinner plays at full length. With
@@ -336,8 +471,9 @@ needs Playwright and ffmpeg. Windows is untested.
336
471
 
337
472
  ## Roadmap
338
473
 
339
- - 🎙️ Narration: text-to-speech per scene, with steps stretched to fit the audio, and burnt-in subtitles.
340
- - 📱 Vertical 9:16 and square 1:1 cuts with a follow-cam, from the same take.
474
+ - 🧑‍🎨 `showtime studio`: a local page with a live player and a timeline to drag captions, zooms and
475
+ speed, saving back to the JSON.
476
+ - 🎥 `showtime record-human <url>`: click through the app yourself and get a script back.
341
477
  - 🎞️ Deterministic frame-by-frame capture in virtual time, for a perfect 60 fps.
342
478
  - 🧩 An MCP server for Claude Desktop, and a desktop studio for timeline tweaks.
343
479
  - 📲 Mobile (iOS Simulator) and desktop-app drivers that produce the same `events.json`.