agent-screenplay 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -7
- package/assets/editor/index.html +143 -23
- package/dist/cli.js +3 -2
- package/dist/cli.js.map +1 -1
- package/dist/core.d.ts +2 -0
- package/dist/core.js +3 -15
- package/dist/core.js.map +1 -1
- package/dist/direct/plan.d.ts +2 -0
- package/dist/direct/plan.js +8 -0
- package/dist/direct/plan.js.map +1 -1
- package/dist/drivers/browser.d.ts +14 -0
- package/dist/drivers/browser.js +39 -1
- package/dist/drivers/browser.js.map +1 -1
- package/dist/drivers/web.d.ts +17 -0
- package/dist/drivers/web.js +80 -8
- package/dist/drivers/web.js.map +1 -1
- package/dist/editor/server.d.ts +28 -2
- package/dist/editor/server.js +39 -7
- package/dist/editor/server.js.map +1 -1
- package/dist/login.d.ts +9 -1
- package/dist/login.js +62 -29
- package/dist/login.js.map +1 -1
- package/dist/mcp/server.js +13 -3
- package/dist/mcp/server.js.map +1 -1
- package/dist/render/presets.d.ts +20 -3
- package/dist/render/presets.js +30 -3
- package/dist/render/presets.js.map +1 -1
- package/dist/render/render.d.ts +2 -0
- package/dist/render/render.js +5 -0
- package/dist/render/render.js.map +1 -1
- package/dist/render/style.d.ts +18 -5
- package/dist/render/style.js +124 -15
- package/dist/render/style.js.map +1 -1
- package/dist/scenario/config.d.ts +1 -0
- package/dist/scenario/fingerprint.js +2 -2
- package/dist/scenario/fingerprint.js.map +1 -1
- package/dist/scenario/schema.d.ts +43 -1
- package/dist/scenario/schema.js +25 -6
- package/dist/scenario/schema.js.map +1 -1
- package/dist/scenario/target.js +2 -1
- package/dist/scenario/target.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/voice/audio.d.ts +6 -0
- package/dist/voice/audio.js +36 -0
- package/dist/voice/audio.js.map +1 -0
- package/dist/voice/cartesia.d.ts +5 -0
- package/dist/voice/cartesia.js +17 -6
- package/dist/voice/cartesia.js.map +1 -1
- package/dist/voice/elevenlabs.d.ts +8 -0
- package/dist/voice/elevenlabs.js +29 -0
- package/dist/voice/elevenlabs.js.map +1 -0
- package/dist/voice/narration.d.ts +16 -0
- package/dist/voice/narration.js +83 -0
- package/dist/voice/narration.js.map +1 -0
- package/package.json +7 -3
- package/schema/scenario.schema.json +78 -6
- package/skills/screenplay/SKILL.md +21 -13
package/README.md
CHANGED
|
@@ -16,7 +16,18 @@ npx agent-screenplay init
|
|
|
16
16
|
|
|
17
17
|
The npm package is `agent-screenplay`; installed globally (`npm i -g agent-screenplay`), its command is `screenplay`.
|
|
18
18
|
|
|
19
|
-
`init` downloads Chromium (once), adds the Screenplay MCP server to the agents it finds (Claude Code, Codex, Cursor), and installs the guide that teaches them to make good videos.
|
|
19
|
+
`init` downloads Chromium (once), adds the Screenplay MCP server to the agents it finds (Claude Code, Codex, Cursor), and installs the guide that teaches them to make good videos. Restart your agent after it.
|
|
20
|
+
|
|
21
|
+
## Use it
|
|
22
|
+
|
|
23
|
+
In **Claude Code**, type `/screenplay` and say what you want:
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
/screenplay Make a 30-second walkthrough of https://askli.co
|
|
27
|
+
/screenplay Film how to invite a teammate in our app, with a French voice-over
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
In **Codex, Cursor** or any agent with the MCP server, just ask: *"Make a screen recording of …"*. The agent explores the site, asks whether you want a voice-over, writes the scenario, films it, checks the storyboard and gives you the MP4. To change the look afterwards: `npx agent-screenplay style <scenario.json>`.
|
|
20
31
|
|
|
21
32
|
Without MCP, any agent that can run commands can use the CLI directly:
|
|
22
33
|
|
|
@@ -34,7 +45,7 @@ Requirements: Node 20+. ffmpeg and the drawing library ship with the package.
|
|
|
34
45
|
```json
|
|
35
46
|
{
|
|
36
47
|
"url": "https://app.example.com/inbox",
|
|
37
|
-
"style": { "background": "
|
|
48
|
+
"style": { "background": "dawn" },
|
|
38
49
|
"steps": [
|
|
39
50
|
{ "title": "Human handoff, in one click", "subtitle": "New in 2.4" },
|
|
40
51
|
{ "click": "text=Camille Laurent", "say": "When the AI agent gets stuck, the conversation lands in your inbox." },
|
|
@@ -51,7 +62,7 @@ Requirements: Node 20+. ffmpeg and the drawing library ship with the package.
|
|
|
51
62
|
- **Targets** name elements the way people see them: `text=…`, `role=button[name='…']`, `label=…`, `placeholder=…`, `testid=…`, `css=…` (web only), `at=x,y` (desktop apps without accessible elements only).
|
|
52
63
|
- **Actions**: `goto`, `click`, `type`, `press`, `hover`, `scroll`, `select`, `wait`, `pause`.
|
|
53
64
|
- **Direction**: `title` and `end` cards, `callout` (a bubble pointing at an element), `highlight` (spotlight), `zoomTo`, and on any step `say` (caption and voice), `zoom`, `hold`.
|
|
54
|
-
- **Style**: background preset (`
|
|
65
|
+
- **Style**: background preset (soft light gradients: `dawn`, `iris`, `citrus`, `apricot`, `coral`, `blaze`, `cloud`, `glacier`, `tide`, `meadow`), your own gradient, a color or an image; padding, corner radius, shadow, cursor style, maximum zoom, browser frame, accent color. See [Style](#style).
|
|
55
66
|
- **Output**: 1920×1080 at 30 fps by default; any size up to 4K, 24 to 60 fps, optional GIF.
|
|
56
67
|
|
|
57
68
|
The full format is the JSON schema in [`schema/scenario.schema.json`](schema/scenario.schema.json) (`npx agent-screenplay schema`), and the agent guide in [`skills/screenplay/SKILL.md`](skills/screenplay/SKILL.md). Examples are in [`examples/`](examples).
|
|
@@ -60,7 +71,7 @@ The full format is the JSON schema in [`schema/scenario.schema.json`](schema/sce
|
|
|
60
71
|
|
|
61
72
|
## Style
|
|
62
73
|
|
|
63
|
-
Set the look by eye: `npx agent-screenplay style demo.json` opens an editor in your browser, on a frame of the last recording. Pick a gradient (or make your own), a color or an image (drop it on the page), set the
|
|
74
|
+
Set the look by eye: `npx agent-screenplay style demo.json` opens an editor in your browser, on a frame of the last recording. Pick a gradient (or make your own), a color or an image (drop it on the page), set the padding, corners and shadow, then save to **this video** or to **all videos**, and render without filming again.
|
|
64
75
|
|
|
65
76
|

|
|
66
77
|
|
|
@@ -77,11 +88,14 @@ Set the look by eye: `npx agent-screenplay style demo.json` opens an editor in y
|
|
|
77
88
|
}
|
|
78
89
|
```
|
|
79
90
|
|
|
80
|
-
|
|
91
|
+
Title and end cards (`title` and `end` steps) are **off by default**: turn them on with `"cards": true`, or with the switch in the editor.
|
|
92
|
+
|
|
93
|
+
`background` is a preset name (`dawn` by default; earlier presets such as `aurora` or `ocean` still work), a color (`"#0f172a"`), an image path (relative to the file that sets it), or `{ "gradient": [2 to 4 colors], "angle": degrees }`.
|
|
81
94
|
|
|
82
95
|
## Signing in
|
|
83
96
|
|
|
84
|
-
- `npx agent-screenplay login https://app.example.com` opens a browser
|
|
97
|
+
- `npx agent-screenplay login https://app.example.com` opens a browser window: sign in, then **close the window**. The session is saved to `.screenplay/auth.json` (while you go, and when the window closes; Enter in the terminal works too). Use it with `"auth": { "storageState": ".screenplay/auth.json" }`. Keep that file out of git.
|
|
98
|
+
- **Bot checks** (Cloudflare's "Just a moment…"): Screenplay's browser presents itself as an ordinary Chrome, so the checks that need no person pass by themselves. When a site asks a person to prove they are human, `check` says so: run `login` on that site, pass the check in the window, close it, and set `auth.storageState`. Some sites ask again on every visit; those cannot be filmed automatically.
|
|
85
99
|
- Agents without a screen can sign in with steps: `{ "type": "${EMAIL}", "into": "label=Email" }`, `{ "type": "${PASSWORD}", "into": "label=Password", "secret": true }`. `${VAR}` is read from the environment; `secret` masks the text in captions and logs.
|
|
86
100
|
|
|
87
101
|
## Voice-over
|
|
@@ -92,7 +106,13 @@ Off by default. Turn it on per scenario:
|
|
|
92
106
|
"voice": { "enabled": true, "language": "fr" }
|
|
93
107
|
```
|
|
94
108
|
|
|
95
|
-
Each step's `say` is spoken
|
|
109
|
+
Each step's `say` is spoken, and the video waits for each sentence to finish, so the voice sets the pace. Three ways to get the voice:
|
|
110
|
+
|
|
111
|
+
- **[Cartesia](https://cartesia.ai)**: set `CARTESIA_API_KEY`.
|
|
112
|
+
- **[ElevenLabs](https://elevenlabs.io)**: set `ELEVENLABS_API_KEY`. When both keys are set, Cartesia is used unless `"provider": "elevenlabs"`.
|
|
113
|
+
- **Your own audio files**, with no key: give a step `"audio": "voice/02.mp3"` (MP3, WAV, M4A…), for instance lines you generated in ElevenLabs' or Cartesia's web app. The agent can write the narration for you, one line per step, ready to paste there. Adding audio needs no new filming: `render`.
|
|
114
|
+
|
|
115
|
+
Narration is cached: re-rendering with unchanged text costs nothing. `voice.id` picks another voice, `voice.speed` its pace. Without voice, `say` still shows as a caption.
|
|
96
116
|
|
|
97
117
|
## Desktop apps
|
|
98
118
|
|
package/assets/editor/index.html
CHANGED
|
@@ -31,6 +31,7 @@ button:focus-visible, input:focus-visible { outline: 2px solid rgba(255,255,255,
|
|
|
31
31
|
.frame { position: relative; border-radius: 12px; overflow: hidden; box-shadow: 0 0 0 1px var(--line), 0 30px 80px rgba(0,0,0,.5); }
|
|
32
32
|
.comp { position: absolute; left: 0; top: 0; transform-origin: 0 0; overflow: hidden; }
|
|
33
33
|
.bg { position: absolute; inset: 0; background-size: cover; background-position: center; transition: opacity 320ms var(--ease-out); }
|
|
34
|
+
.grain { position: absolute; inset: 0; opacity: 0; background-size: 128px 128px; transition: opacity 320ms var(--ease-out); pointer-events: none; }
|
|
34
35
|
.glow { position: absolute; inset: 0; background: radial-gradient(circle 1536px at 20% 10%, rgba(255,255,255,.18), rgba(255,255,255,0)); transition: opacity 320ms var(--ease-out); }
|
|
35
36
|
.win { position: absolute; overflow: hidden; background: #fff;
|
|
36
37
|
transition: left 280ms var(--ease-out), top 280ms var(--ease-out), width 280ms var(--ease-out), height 280ms var(--ease-out), border-radius 280ms var(--ease-out), box-shadow 280ms var(--ease-out); }
|
|
@@ -45,12 +46,27 @@ button:focus-visible, input:focus-visible { outline: 2px solid rgba(255,255,255,
|
|
|
45
46
|
.video { position: absolute; inset: 0; width: 100%; height: 100%; background: #000; opacity: 0; transform: scale(.985); pointer-events: none; transition: opacity 240ms var(--ease-out), transform 240ms var(--ease-out); }
|
|
46
47
|
.showVideo .video { opacity: 1; transform: none; pointer-events: auto; }
|
|
47
48
|
|
|
48
|
-
.under { display: flex; justify-content: center;
|
|
49
|
+
.under { display: flex; justify-content: center; padding: 0 24px 20px; }
|
|
50
|
+
/* The caption describes the preview: right under the frame, and only on the Preview tab. */
|
|
51
|
+
.frameWrap { position: relative; }
|
|
52
|
+
.hint { position: absolute; left: 0; right: 0; top: calc(100% + 12px); text-align: center; font-size: 12px; color: var(--muted); transition: opacity 180ms ease, transform 180ms var(--ease-out); }
|
|
53
|
+
.videoMode .hint { opacity: 0; transform: translateY(-2px); }
|
|
49
54
|
|
|
50
55
|
/* Sidebar */
|
|
51
56
|
.side { background: var(--panel); border-left: 1px solid var(--line); display: grid; grid-template-rows: 1fr auto; min-height: 0; }
|
|
52
57
|
.scroll { overflow-y: auto; padding: 20px 20px 8px; }
|
|
53
|
-
.section { padding:
|
|
58
|
+
.section { padding: 18px 0 20px; border-top: 1px solid var(--line); animation: rise 420ms var(--ease-out) both; }
|
|
59
|
+
.head + .section { border-top: 0; padding-top: 4px; }
|
|
60
|
+
.head { display: flex; align-items: center; justify-content: space-between; margin: -4px 0 12px; font-size: 13px; font-weight: 600; }
|
|
61
|
+
.reset { display: inline-flex; align-items: center; gap: 6px; height: 26px; padding: 0 9px; border-radius: 7px; font-size: 12px; font-weight: 500; color: var(--muted); transition: color 160ms ease, background 160ms ease, opacity 160ms ease, transform 160ms var(--ease-out); }
|
|
62
|
+
.reset:hover:not(:disabled) { color: var(--text); background: var(--raised); }
|
|
63
|
+
.reset:active:not(:disabled) { transform: scale(.96); }
|
|
64
|
+
.reset:disabled { opacity: .35; cursor: default; }
|
|
65
|
+
.reset.spun svg { animation: unwind 420ms var(--ease-out); }
|
|
66
|
+
@keyframes unwind { from { transform: rotate(0); } to { transform: rotate(-360deg); } }
|
|
67
|
+
.custom { margin-top: 12px; padding: 12px; border-radius: var(--r); background: var(--bg); border: 1px solid var(--line); }
|
|
68
|
+
.custom .stops { margin-top: 0; }
|
|
69
|
+
.custom .stop { background: var(--panel); }
|
|
54
70
|
.section:nth-child(2) { animation-delay: 50ms; } .section:nth-child(3) { animation-delay: 100ms; }
|
|
55
71
|
@keyframes rise { from { opacity: 0; transform: translateY(6px); } }
|
|
56
72
|
.label { font-size: 12px; font-weight: 500; color: var(--muted); margin: 0 0 10px; display: flex; justify-content: space-between; align-items: baseline; }
|
|
@@ -103,6 +119,8 @@ button:focus-visible, input:focus-visible { outline: 2px solid rgba(255,255,255,
|
|
|
103
119
|
.range input::-webkit-slider-thumb { -webkit-appearance: none; width: 16px; height: 16px; margin-top: -6px; border-radius: 50%; background: #fff; box-shadow: 0 1px 4px rgba(0,0,0,.5); transition: transform 140ms var(--ease-out); }
|
|
104
120
|
.range input:active::-webkit-slider-thumb { transform: scale(1.15); }
|
|
105
121
|
.range input::-moz-range-thumb { width: 16px; height: 16px; border: 0; border-radius: 50%; background: #fff; }
|
|
122
|
+
.note { margin: 8px 0 0; font-size: 12px; line-height: 1.45; color: var(--faint); display: none; }
|
|
123
|
+
.note.on { display: block; animation: paneIn 200ms var(--ease-out); }
|
|
106
124
|
.row { display: flex; align-items: center; justify-content: space-between; font-size: 13px; }
|
|
107
125
|
.dial { width: 18px; height: 18px; border-radius: 50%; border: 1px solid var(--line-2); position: relative; }
|
|
108
126
|
.dial::after { content: ""; position: absolute; left: 50%; top: 2px; width: 1.5px; height: 7px; margin-left: -.75px; border-radius: 1px; background: #fff; transform-origin: 50% 6px; transform: rotate(var(--a)); }
|
|
@@ -125,8 +143,15 @@ button:focus-visible, input:focus-visible { outline: 2px solid rgba(255,255,255,
|
|
|
125
143
|
.btn.swapping .txt { opacity: 0; filter: blur(3px); }
|
|
126
144
|
.dirty { width: 6px; height: 6px; border-radius: 50%; background: #0a0a0b; opacity: 0; transform: scale(.5); transition: opacity 180ms var(--ease-out), transform 180ms var(--ease-out); }
|
|
127
145
|
.isDirty .dirty { opacity: .55; transform: none; }
|
|
128
|
-
|
|
129
|
-
|
|
146
|
+
/* macOS activity indicator: eight bars, the brightest leading, turning in eight steps. */
|
|
147
|
+
.mspin { position: relative; flex: none; width: 14px; height: 14px; animation: mspin 800ms steps(8) infinite; }
|
|
148
|
+
.mspin i { position: absolute; left: 6.25px; top: 0; width: 1.5px; height: 4.2px; border-radius: 1px; background: currentColor; transform-origin: .75px 7px; }
|
|
149
|
+
@keyframes mspin { to { transform: rotate(360deg); } }
|
|
150
|
+
/* While rendering, the button fills up with the progress and is not dimmed. */
|
|
151
|
+
.btn.busy:disabled { opacity: 1; cursor: progress; }
|
|
152
|
+
.btn .fill { position: absolute; inset: 0; border-radius: inherit; background: rgba(255,255,255,.09); transform-origin: left; transform: scaleX(0); transition: transform 240ms linear, opacity 200ms ease; pointer-events: none; }
|
|
153
|
+
.btn .txt { position: relative; display: inline-flex; align-items: center; gap: 8px; }
|
|
154
|
+
.pct { font-variant-numeric: tabular-nums; }
|
|
130
155
|
kbd { font: 11px ui-monospace, Menlo, monospace; color: var(--faint); }
|
|
131
156
|
|
|
132
157
|
.toast { position: fixed; left: calc((100% - 320px) / 2); top: 14px; transform: translate(-50%, -8px); opacity: 0; padding: 9px 14px; border-radius: 10px; background: var(--raised); box-shadow: 0 0 0 1px var(--line-2), 0 10px 30px rgba(0,0,0,.5); font-size: 13px; pointer-events: none; transition: opacity 200ms var(--ease-out), transform 200ms var(--ease-out); max-width: 60vw; }
|
|
@@ -149,23 +174,33 @@ kbd { font: 11px ui-monospace, Menlo, monospace; color: var(--faint); }
|
|
|
149
174
|
<div class="main">
|
|
150
175
|
<div class="top"><b>Screenplay</b><span class="sep">/</span><span id="name"></span></div>
|
|
151
176
|
<div class="stage"><div class="fit" id="fit">
|
|
177
|
+
<div class="frameWrap">
|
|
152
178
|
<div class="frame" id="frame">
|
|
153
179
|
<div class="comp" id="comp">
|
|
154
180
|
<div class="bg" id="bgA"></div><div class="bg" id="bgB" style="opacity:0"></div>
|
|
155
181
|
<div class="glow" id="glow"></div>
|
|
182
|
+
<div class="grain" id="grain"></div>
|
|
156
183
|
<div class="win" id="win"></div>
|
|
157
184
|
</div>
|
|
158
185
|
<video class="video" id="video" controls playsinline></video>
|
|
159
186
|
</div>
|
|
187
|
+
<span class="hint" id="hint"></span>
|
|
188
|
+
</div>
|
|
160
189
|
</div></div>
|
|
161
190
|
<div class="under">
|
|
162
191
|
<div class="tabs"><button id="tabPreview" aria-pressed="true">Preview</button><button id="tabVideo" disabled>Video</button></div>
|
|
163
|
-
<span id="hint"></span>
|
|
164
192
|
</div>
|
|
165
193
|
</div>
|
|
166
194
|
|
|
167
195
|
<aside class="side">
|
|
168
196
|
<div class="scroll">
|
|
197
|
+
<div class="head">
|
|
198
|
+
<span>Style</span>
|
|
199
|
+
<button class="reset" id="reset" title="Back to Screenplay's defaults">
|
|
200
|
+
<svg width="13" height="13" viewBox="0 0 14 14" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M2.5 7a4.5 4.5 0 1 0 1.3-3.2"/><path d="M2.5 1.8v2.4h2.4"/></svg>
|
|
201
|
+
Reset
|
|
202
|
+
</button>
|
|
203
|
+
</div>
|
|
169
204
|
<div class="section">
|
|
170
205
|
<p class="label">Background</p>
|
|
171
206
|
<div class="seg" id="bgKind"><span class="pill"></span>
|
|
@@ -173,6 +208,7 @@ kbd { font: 11px ui-monospace, Menlo, monospace; color: var(--faint); }
|
|
|
173
208
|
</div>
|
|
174
209
|
<div class="pane" data-pane="gradient">
|
|
175
210
|
<div class="swatches" id="presets"></div>
|
|
211
|
+
<div class="custom">
|
|
176
212
|
<div class="stops">
|
|
177
213
|
<label class="stop"><i id="s1i"></i><span id="s1t"></span><input type="color" id="s1"></label>
|
|
178
214
|
<button class="swap" id="swap" title="Swap colors" aria-label="Swap colors">
|
|
@@ -180,10 +216,11 @@ kbd { font: 11px ui-monospace, Menlo, monospace; color: var(--faint); }
|
|
|
180
216
|
</button>
|
|
181
217
|
<label class="stop"><i id="s2i"></i><span id="s2t"></span><input type="color" id="s2"></label>
|
|
182
218
|
</div>
|
|
183
|
-
<div style="margin-top:
|
|
184
|
-
<p class="label"><span>
|
|
219
|
+
<div style="margin-top:14px">
|
|
220
|
+
<p class="label"><span>Gradient angle</span><span style="display:flex;gap:8px;align-items:center"><span class="dial" id="dial"></span><output id="angleV"></output></span></p>
|
|
185
221
|
<div class="range"><input type="range" id="angle" min="0" max="360" step="5"></div>
|
|
186
222
|
</div>
|
|
223
|
+
</div>
|
|
187
224
|
</div>
|
|
188
225
|
<div class="pane" data-pane="color">
|
|
189
226
|
<div class="swatches" id="colors"></div>
|
|
@@ -200,14 +237,16 @@ kbd { font: 11px ui-monospace, Menlo, monospace; color: var(--faint); }
|
|
|
200
237
|
</div>
|
|
201
238
|
|
|
202
239
|
<div class="section">
|
|
203
|
-
<p class="label"><span>
|
|
240
|
+
<p class="label"><span>Padding</span><output id="padV"></output></p>
|
|
204
241
|
<div class="range"><input type="range" id="pad" min="0" max="240" step="2"></div>
|
|
242
|
+
<p class="note" id="shapeNote"></p>
|
|
205
243
|
<p class="label" style="margin-top:18px"><span>Corners</span><output id="radV"></output></p>
|
|
206
244
|
<div class="range"><input type="range" id="rad" min="0" max="48" step="1"></div>
|
|
207
245
|
</div>
|
|
208
246
|
|
|
209
247
|
<div class="section">
|
|
210
248
|
<div class="row"><span>Shadow</span><button class="switch" id="shadow" role="switch" aria-label="Shadow"></button></div>
|
|
249
|
+
<div class="row" style="margin-top:16px"><span>Intro & outro cards</span><button class="switch" id="cards" role="switch" aria-label="Intro and outro cards"></button></div>
|
|
211
250
|
</div>
|
|
212
251
|
</div>
|
|
213
252
|
|
|
@@ -218,7 +257,7 @@ kbd { font: 11px ui-monospace, Menlo, monospace; color: var(--faint); }
|
|
|
218
257
|
</div>
|
|
219
258
|
<div class="dest" id="dest"></div>
|
|
220
259
|
<div class="actions">
|
|
221
|
-
<button class="btn ghost" id="renderBtn"><span class="txt">Render video</span></button>
|
|
260
|
+
<button class="btn ghost" id="renderBtn"><span class="fill" id="renderFill"></span><span class="txt">Render video</span></button>
|
|
222
261
|
<button class="btn primary" id="saveBtn"><span class="txt">Save</span><span class="dirty"></span></button>
|
|
223
262
|
</div>
|
|
224
263
|
</div>
|
|
@@ -245,7 +284,7 @@ async function load() {
|
|
|
245
284
|
S = await (await api("/api/state")).json();
|
|
246
285
|
$("name").textContent = S.name;
|
|
247
286
|
const eff = { ...S.defaults, ...S.projectStyle, ...S.scenarioStyle };
|
|
248
|
-
style = { background: eff.background, padding: eff.padding, radius: eff.radius, shadow: eff.shadow };
|
|
287
|
+
style = { background: eff.background, padding: eff.padding, radius: eff.radius, shadow: eff.shadow, cards: eff.cards };
|
|
249
288
|
if (kindOf(style.background) === "image") imageRef = { path: style.background, from: S.scenarioStyle.background !== undefined ? "scenario" : "project" };
|
|
250
289
|
saved = JSON.stringify(style);
|
|
251
290
|
// A video that already has its own look keeps it; otherwise the look is for the whole project.
|
|
@@ -274,16 +313,42 @@ function layout() {
|
|
|
274
313
|
|
|
275
314
|
function fitStage() {
|
|
276
315
|
const L = layout(), box = $("fit").getBoundingClientRect();
|
|
277
|
-
const sc = Math.min(box.width / L.OW, box.height / L.OH);
|
|
316
|
+
const sc = Math.min(box.width / L.OW, (box.height - 32) / L.OH); // room for the caption under the frame
|
|
278
317
|
$("frame").style.width = L.OW * sc + "px"; $("frame").style.height = L.OH * sc + "px";
|
|
279
318
|
$("comp").style.width = L.OW + "px"; $("comp").style.height = L.OH + "px";
|
|
280
319
|
$("comp").style.transform = `scale(${sc})`;
|
|
281
320
|
}
|
|
282
321
|
|
|
322
|
+
/* Same drawing as the renderer (src/render/presets.ts, paintMesh): color fields over a base. */
|
|
323
|
+
const rgba = (hex, a) => { const n = parseInt(hex.slice(1), 16); return `rgba(${(n >> 16) & 255},${(n >> 8) & 255},${n & 255},${Math.max(0, Math.min(1, a)).toFixed(3)})`; };
|
|
324
|
+
function meshCss(p, w, h) {
|
|
325
|
+
const side = Math.max(w, h);
|
|
326
|
+
const layers = p.blobs.map((b) => `radial-gradient(circle ${b.r * side}px at ${b.x * w}px ${b.y * h}px, ${rgba(b.color, b.alpha)} 0%, ${rgba(b.color, b.alpha * 0.6)} 40%, ${rgba(b.color, 0)} 100%)`);
|
|
327
|
+
return [...layers.reverse(), p.base].join(", ");
|
|
328
|
+
}
|
|
329
|
+
function presetCss(p, w, h, name) {
|
|
330
|
+
// Glow presets are drawn by the renderer on the server: the preview is the video's own image.
|
|
331
|
+
if (p.kind === "glow") return `center / cover no-repeat url("/api/preset?t=${TOKEN}&name=${encodeURIComponent(name)}&w=${Math.round(w)}&h=${Math.round(h)}")`;
|
|
332
|
+
return p.kind === "mesh" ? meshCss(p, w, h) : `linear-gradient(${p.angle}deg, ${p.stops.join(", ")})`;
|
|
333
|
+
}
|
|
334
|
+
/* The grain tile: the renderer's seeded noise (mulberry32, seed 7), 128×128. */
|
|
335
|
+
const grainUrl = (() => {
|
|
336
|
+
const N = 128, c = document.createElement("canvas"); c.width = c.height = N;
|
|
337
|
+
const ctx = c.getContext("2d"), img = ctx.createImageData(N, N);
|
|
338
|
+
let a = 7;
|
|
339
|
+
for (let i = 0; i < N * N; i++) {
|
|
340
|
+
a = (a + 0x6d2b79f5) >>> 0; let t = a;
|
|
341
|
+
t = Math.imul(t ^ (t >>> 15), t | 1); t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
|
|
342
|
+
const v = ((t ^ (t >>> 14)) >>> 0) % 256;
|
|
343
|
+
img.data[i * 4] = img.data[i * 4 + 1] = img.data[i * 4 + 2] = v; img.data[i * 4 + 3] = 255;
|
|
344
|
+
}
|
|
345
|
+
ctx.putImageData(img, 0, 0);
|
|
346
|
+
return c.toDataURL();
|
|
347
|
+
})();
|
|
283
348
|
function bgCss(bg) {
|
|
284
349
|
if (typeof bg === "object") return `linear-gradient(${bg.angle ?? 135}deg, ${bg.gradient.join(", ")})`;
|
|
285
350
|
const p = S.presets[bg];
|
|
286
|
-
if (p) return
|
|
351
|
+
if (p) return presetCss(p, S.output.width, S.output.height, bg);
|
|
287
352
|
if (/^#/.test(bg)) return bg;
|
|
288
353
|
if (imageRef) return `url("/api/image?t=${TOKEN}&from=${imageRef.from}&path=${encodeURIComponent(imageRef.path)}")`;
|
|
289
354
|
return "#111";
|
|
@@ -300,7 +365,14 @@ function draw(initial) {
|
|
|
300
365
|
if (initial) { $(back).style.transition = "none"; requestAnimationFrame(() => $(back).style.transition = ""); }
|
|
301
366
|
$(back).style.opacity = 1; $(front).style.opacity = 0; front = back; lastBg = css;
|
|
302
367
|
}
|
|
303
|
-
|
|
368
|
+
const preset = typeof style.background === "string" ? S.presets[style.background] : null;
|
|
369
|
+
// The light from the top left belongs to linear gradients only.
|
|
370
|
+
$("glow").style.opacity = kindOf(style.background) === "gradient" && !(preset && preset.kind !== "linear") ? 1 : 0;
|
|
371
|
+
$("grain").style.backgroundImage = `url(${grainUrl})`;
|
|
372
|
+
// Same tile size as the renderer: 128 px, doubled at 4K.
|
|
373
|
+
const tile = 128 * Math.max(1, Math.round(S.output.height / 1080));
|
|
374
|
+
$("grain").style.backgroundSize = `${tile}px ${tile}px`;
|
|
375
|
+
$("grain").style.opacity = preset && preset.kind === "mesh" ? preset.grain : 0;
|
|
304
376
|
Object.assign(win.style, { left: L.x + "px", top: L.y + "px", width: L.w + "px", height: L.h + "px", borderRadius: style.radius * L.k + "px",
|
|
305
377
|
boxShadow: style.shadow ? `0 ${24 * L.k}px ${60 * L.k}px rgba(0,0,0,.35), 0 ${2 * L.k}px ${8 * L.k}px rgba(0,0,0,.18)` : "none" });
|
|
306
378
|
if (!win.dataset.built) {
|
|
@@ -332,7 +404,7 @@ function setKind(k, initial) {
|
|
|
332
404
|
if (initial) return;
|
|
333
405
|
const cur = style.background, curKind = kindOf(cur);
|
|
334
406
|
if (k === curKind) return;
|
|
335
|
-
if (k === "gradient") style.background = lastGradient ||
|
|
407
|
+
if (k === "gradient") style.background = lastGradient || S.defaults.background;
|
|
336
408
|
if (k === "color") style.background = lastColor || "#0f172a";
|
|
337
409
|
if (k === "image") { if (imageRef) style.background = imageRef.path; else { sync(); return; } }
|
|
338
410
|
change();
|
|
@@ -341,10 +413,12 @@ let lastGradient = null, lastColor = null;
|
|
|
341
413
|
|
|
342
414
|
function buildSwatches() {
|
|
343
415
|
const pre = $("presets");
|
|
344
|
-
for (const
|
|
416
|
+
for (const name of S.offered) {
|
|
417
|
+
const p = S.presets[name];
|
|
345
418
|
const b = document.createElement("button");
|
|
346
419
|
b.className = "sw"; b.dataset.v = name; b.title = name; b.setAttribute("aria-label", name);
|
|
347
|
-
|
|
420
|
+
// A swatch is the same mesh, drawn on a square.
|
|
421
|
+
b.style.background = presetCss(p, 120, 120, name);
|
|
348
422
|
b.onclick = () => { style.background = name; change(); };
|
|
349
423
|
pre.appendChild(b);
|
|
350
424
|
}
|
|
@@ -364,8 +438,8 @@ function buildSwatches() {
|
|
|
364
438
|
function currentStops() {
|
|
365
439
|
const bg = style.background;
|
|
366
440
|
if (typeof bg === "object") return { stops: bg.gradient, angle: bg.angle ?? 135 };
|
|
367
|
-
const p = S.presets[bg] || S.presets.
|
|
368
|
-
return { stops:
|
|
441
|
+
const p = S.presets[bg] || S.presets[S.defaults.background];
|
|
442
|
+
return { stops: p.colors, angle: p.kind === "linear" ? p.angle : p.kind === "glow" ? 90 + p.angle : 135 };
|
|
369
443
|
}
|
|
370
444
|
function customGradient() {
|
|
371
445
|
if (typeof style.background === "object") return style.background;
|
|
@@ -386,6 +460,16 @@ $("hex").onchange = (e) => { const v = e.target.value.trim(); if (/^#([0-9a-f]{3
|
|
|
386
460
|
$("pad").oninput = (e) => { live(); style.padding = +e.target.value; change(); };
|
|
387
461
|
$("rad").oninput = (e) => { live(); style.radius = +e.target.value; change(); };
|
|
388
462
|
$("shadow").onclick = () => { style.shadow = !style.shadow; change(); };
|
|
463
|
+
// Cards are not in the frame preview: the rendered video shows them.
|
|
464
|
+
$("cards").onclick = () => { style.cards = !style.cards; change(); };
|
|
465
|
+
$("reset").onclick = () => {
|
|
466
|
+
style = { ...S.defaults };
|
|
467
|
+
setKind(kindOf(style.background), true);
|
|
468
|
+
change();
|
|
469
|
+
const b = $("reset"); b.classList.remove("spun"); void b.offsetWidth; b.classList.add("spun");
|
|
470
|
+
setTimeout(() => b.classList.remove("spun"), 400);
|
|
471
|
+
toast("Back to the defaults · Save to keep them");
|
|
472
|
+
};
|
|
389
473
|
$("bgKind").querySelectorAll("button").forEach((b) => (b.onclick = () => setKind(b.dataset.k)));
|
|
390
474
|
$("target").querySelectorAll("button").forEach((b) => (b.onclick = () => { target = b.dataset.k; setSeg($("target"), target); sync(); }));
|
|
391
475
|
|
|
@@ -449,6 +533,16 @@ function sync() {
|
|
|
449
533
|
$(id + "V").textContent = v + unit;
|
|
450
534
|
}
|
|
451
535
|
$("shadow").setAttribute("aria-checked", String(style.shadow));
|
|
536
|
+
$("cards").setAttribute("aria-checked", String(style.cards));
|
|
537
|
+
$("reset").disabled = JSON.stringify(style) === JSON.stringify({ ...S.defaults });
|
|
538
|
+
// The recording keeps its shape: when it is not the video's, the background shows on two sides even with no padding.
|
|
539
|
+
const cap = S.capture || { width: 1440, height: 900 }, r = cap.width / cap.height, R = S.output.width / S.output.height;
|
|
540
|
+
const off = Math.abs(r - R) > 0.01;
|
|
541
|
+
$("shapeNote").classList.toggle("on", off && style.padding < 24);
|
|
542
|
+
const shape = Math.abs(R - 16 / 9) < 0.01 ? "16:9" : `${S.output.width}×${S.output.height}`;
|
|
543
|
+
const fit = S.defaultViewport;
|
|
544
|
+
$("shapeNote").textContent = !off ? "" : `The ${S.url ? "recording" : "window"} is ${cap.width}×${cap.height}, not ${shape} like the video, so the background shows on the ${r < R ? "sides" : "top and bottom"}.` +
|
|
545
|
+
(S.url ? ` Record with a ${shape} viewport (such as ${fit.width}×${fit.height}, the default) to fill the frame.` : "");
|
|
452
546
|
const file = target === "project" ? S.configFile : S.scenarioFile;
|
|
453
547
|
$("dest").textContent = (target === "project" ? (S.configExists ? "Saves to " : "Creates ") : "Saves to ") + file.split(/[\\/]/).pop();
|
|
454
548
|
$("dest").title = file;
|
|
@@ -496,11 +590,34 @@ $("renderBtn").onclick = async () => {
|
|
|
496
590
|
if (JSON.stringify(style) !== saved && !(await save())) return;
|
|
497
591
|
if (JSON.stringify(style) !== saved) return toast("Save your latest changes first", true);
|
|
498
592
|
btn.disabled = true;
|
|
499
|
-
|
|
500
|
-
const
|
|
501
|
-
|
|
593
|
+
btn.classList.add("busy");
|
|
594
|
+
const fill = $("renderFill");
|
|
595
|
+
fill.style.opacity = 1; fill.style.transform = "scaleX(0)";
|
|
596
|
+
const spinner = '<span class="mspin">' + [0, 1, 2, 3, 4, 5, 6, 7].map((n) => `<i style="transform:rotate(${n * 45}deg);opacity:${(1 - ((8 - n) % 8) * 0.11).toFixed(2)}"></i>`).join("") + "</span>";
|
|
597
|
+
await swapText(btn, `${spinner}Rendering <span class="pct" id="pct">0%</span>`);
|
|
598
|
+
// The server reports the editing progress; the button fills up with it.
|
|
599
|
+
const poll = setInterval(async () => {
|
|
600
|
+
try {
|
|
601
|
+
const p = await (await api("/api/progress")).json();
|
|
602
|
+
if (!p.rendering) return;
|
|
603
|
+
fill.style.transform = `scaleX(${p.progress})`;
|
|
604
|
+
if ($("pct")) $("pct").textContent = Math.round(p.progress * 100) + "%";
|
|
605
|
+
} catch {}
|
|
606
|
+
}, 250);
|
|
607
|
+
let r, j;
|
|
608
|
+
try {
|
|
609
|
+
r = await api("/api/render", { method: "POST" });
|
|
610
|
+
j = await r.json();
|
|
611
|
+
} finally {
|
|
612
|
+
clearInterval(poll);
|
|
613
|
+
}
|
|
614
|
+
fill.style.transform = "scaleX(1)";
|
|
615
|
+
await new Promise((res) => setTimeout(res, 250));
|
|
616
|
+
fill.style.opacity = 0;
|
|
502
617
|
btn.disabled = false;
|
|
618
|
+
btn.classList.remove("busy");
|
|
503
619
|
await swapText(btn, "Render video");
|
|
620
|
+
setTimeout(() => (fill.style.transform = "scaleX(0)"), 220);
|
|
504
621
|
if (!r.ok) return toast(j.error || "Render failed", true);
|
|
505
622
|
$("video").src = `/api/video?t=${TOKEN}&v=${j.at}`;
|
|
506
623
|
$("tabVideo").disabled = false;
|
|
@@ -511,6 +628,7 @@ $("renderBtn").onclick = async () => {
|
|
|
511
628
|
|
|
512
629
|
function showVideo(on) {
|
|
513
630
|
$("frame").classList.toggle("showVideo", on);
|
|
631
|
+
document.body.classList.toggle("videoMode", on);
|
|
514
632
|
$("tabPreview").setAttribute("aria-pressed", String(!on));
|
|
515
633
|
$("tabVideo").setAttribute("aria-pressed", String(on));
|
|
516
634
|
if (!on) $("video").pause();
|
|
@@ -518,8 +636,10 @@ function showVideo(on) {
|
|
|
518
636
|
}
|
|
519
637
|
$("tabPreview").onclick = () => showVideo(false);
|
|
520
638
|
$("tabVideo").onclick = () => showVideo(true);
|
|
521
|
-
|
|
522
|
-
|
|
639
|
+
// Changing a setting shows the preview again; using the video's own controls (seek, volume) does not.
|
|
640
|
+
const editsStyle = (e) => e.target instanceof Element && e.target.closest(".side") && !e.target.closest(".foot");
|
|
641
|
+
document.addEventListener("input", (e) => { if (editsStyle(e)) showVideo(false); }, true);
|
|
642
|
+
document.addEventListener("click", (e) => { if (editsStyle(e)) showVideo(false); }, true);
|
|
523
643
|
|
|
524
644
|
let toastT;
|
|
525
645
|
function toast(msg, err) {
|
package/dist/cli.js
CHANGED
|
@@ -36,6 +36,7 @@ program
|
|
|
36
36
|
log(` storyboard: ${r.storyboard}`);
|
|
37
37
|
if (r.gif)
|
|
38
38
|
log(` gif: ${r.gif}`);
|
|
39
|
+
log(`\nAdjust the look (background, padding, cards…): npx agent-screenplay style ${file}`);
|
|
39
40
|
}
|
|
40
41
|
}
|
|
41
42
|
catch (e) {
|
|
@@ -64,7 +65,7 @@ program
|
|
|
64
65
|
program
|
|
65
66
|
.command("style")
|
|
66
67
|
.argument("<scenario>", "scenario JSON file")
|
|
67
|
-
.description("Open the style editor in your browser: background,
|
|
68
|
+
.description("Open the style editor in your browser: background, padding, corners and shadow, on a frame of the recording.")
|
|
68
69
|
.option("--port <port>", "port to listen on (default: any free port)")
|
|
69
70
|
.option("--no-open", "print the link instead of opening the browser")
|
|
70
71
|
.action(async (file, o) => {
|
|
@@ -130,7 +131,7 @@ program
|
|
|
130
131
|
.command("login")
|
|
131
132
|
.argument("<url>", "page to sign in on")
|
|
132
133
|
.option("-o, --out <file>", "where to save the session", ".screenplay/auth.json")
|
|
133
|
-
.description("Open a browser to sign in once; the session is saved for scenarios (auth.storageState).")
|
|
134
|
+
.description("Open a browser to sign in once, or to pass a bot check (Cloudflare) by hand; close the window when done. The session is saved for scenarios (auth.storageState).")
|
|
134
135
|
.action(async (url, o) => {
|
|
135
136
|
try {
|
|
136
137
|
const file = await login(url, o.out, log);
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAClD,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACnD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAEvC,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;AAC9B,OAAO;KACJ,IAAI,CAAC,YAAY,CAAC;KAClB,WAAW,CAAC,wDAAwD,CAAC;KACrE,OAAO,CAAC,OAAO,CAAC,CAAC;AAEpB,MAAM,GAAG,GAAG,CAAC,GAAW,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,GAAG,IAAI,CAAC,CAAC;AAE9D,SAAS,IAAI,CAAC,CAAU;IACtB,MAAM,GAAG,GAAG,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;IACvD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO,GAAG,IAAI,CAAC,CAAC;IACrC,IAAI,OAAO,CAAC,GAAG,CAAC,gBAAgB,IAAI,CAAC,YAAY,KAAK,IAAI,CAAC,CAAC,CAAC,YAAY,aAAa,CAAC;QAAE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC;IAC9H,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AAED,OAAO;KACJ,OAAO,CAAC,QAAQ,CAAC;KACjB,QAAQ,CAAC,YAAY,EAAE,oBAAoB,CAAC;KAC5C,WAAW,CAAC,0DAA0D,CAAC;KACvE,MAAM,CAAC,YAAY,EAAE,qDAAqD,CAAC;KAC3E,MAAM,CAAC,OAAO,EAAE,kBAAkB,CAAC;KACnC,MAAM,CAAC,QAAQ,EAAE,0BAA0B,CAAC;KAC5C,MAAM,CAAC,KAAK,EAAE,IAAY,EAAE,CAAoD,EAAE,EAAE;IACnF,IAAI,CAAC;QACH,MAAM,CAAC,GAAG,MAAM,MAAM,CAAC,IAAI,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,KAAK,EAAE,GAAG,EAAE,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC,CAAC;QACrE,IAAI,CAAC,CAAC,IAAI;YAAE,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;aAC/C,CAAC;YACJ,GAAG,CAAC,OAAO,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,KAAK,qBAAqB,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;YACtG,GAAG,CAAC,iBAAiB,CAAC,CAAC,UAAU,EAAE,CAAC,CAAC;YACrC,IAAI,CAAC,CAAC,GAAG;gBAAE,GAAG,CAAC,UAAU,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
package/dist/core.d.ts
CHANGED
|
@@ -5,6 +5,8 @@ export interface RunOptions {
|
|
|
5
5
|
noVoice?: boolean;
|
|
6
6
|
gif?: boolean;
|
|
7
7
|
log?: (msg: string) => void;
|
|
8
|
+
/** Editing progress, 0 to 1, a few times a second. */
|
|
9
|
+
onProgress?: (fraction: number) => void;
|
|
8
10
|
}
|
|
9
11
|
export interface RecordResult extends RenderResult {
|
|
10
12
|
take: string;
|
package/dist/core.js
CHANGED
|
@@ -6,21 +6,9 @@ import { readingTime } from "./drivers/pace.js";
|
|
|
6
6
|
import { readTake, writeTake } from "./take.js";
|
|
7
7
|
import { captureInputs } from "./scenario/fingerprint.js";
|
|
8
8
|
import { renderTake } from "./render/render.js";
|
|
9
|
-
import {
|
|
9
|
+
import { narrate } from "./voice/narration.js";
|
|
10
10
|
async function narration(loaded, opts) {
|
|
11
|
-
|
|
12
|
-
if (!voice?.enabled || opts.noVoice)
|
|
13
|
-
return new Map();
|
|
14
|
-
const texts = new Map();
|
|
15
|
-
loaded.scenario.steps.forEach((s, i) => {
|
|
16
|
-
const say = s.say;
|
|
17
|
-
if (say)
|
|
18
|
-
texts.set(i, say);
|
|
19
|
-
});
|
|
20
|
-
if (texts.size === 0)
|
|
21
|
-
return new Map();
|
|
22
|
-
opts.log?.(`Generating narration for ${texts.size} step(s)`);
|
|
23
|
-
return synthesizeAll(texts, voice, path.join(loaded.workDir, "cache", "voice"));
|
|
11
|
+
return narrate(loaded, { noVoice: opts.noVoice, log: opts.log });
|
|
24
12
|
}
|
|
25
13
|
/** How long each step must last so its narration or caption can be heard or read. */
|
|
26
14
|
function minDurations(loaded, audio) {
|
|
@@ -55,7 +43,7 @@ export async function render(file, opts = {}) {
|
|
|
55
43
|
const loaded = loadScenario(file);
|
|
56
44
|
const take = readTake(loaded.takeDir);
|
|
57
45
|
const audio = await narration(loaded, opts);
|
|
58
|
-
return renderTake(loaded, take, { audio, gif: opts.gif, log: opts.log });
|
|
46
|
+
return renderTake(loaded, take, { audio, gif: opts.gif, log: opts.log, onProgress: opts.onProgress });
|
|
59
47
|
}
|
|
60
48
|
export async function check(file, opts = {}) {
|
|
61
49
|
const loaded = loadScenario(file);
|
package/dist/core.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"core.js","sourceRoot":"","sources":["../src/core.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,YAAY,EAAuB,MAAM,oBAAoB,CAAC;AACvE,OAAO,EAAE,MAAM,EAAoB,MAAM,kBAAkB,CAAC;AAC5D,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAa,MAAM,WAAW,CAAC;AAC3D,OAAO,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAC1D,OAAO,EAAE,UAAU,EAAqB,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"core.js","sourceRoot":"","sources":["../src/core.ts"],"names":[],"mappings":"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"}
|
package/dist/direct/plan.d.ts
CHANGED
|
@@ -85,6 +85,8 @@ export interface PlanOptions {
|
|
|
85
85
|
autoZoom: boolean;
|
|
86
86
|
maxZoom: number;
|
|
87
87
|
captions: boolean;
|
|
88
|
+
/** Show the title and end cards (style.cards). When false they are left out of the video. */
|
|
89
|
+
cards?: boolean;
|
|
88
90
|
}
|
|
89
91
|
export declare const OVERLAY_DELAY = 0.55;
|
|
90
92
|
export declare function planTake(take: Take, scenario: Scenario, opts: PlanOptions): Plan;
|
package/dist/direct/plan.js
CHANGED
|
@@ -48,6 +48,14 @@ export function planTake(take, scenario, opts) {
|
|
|
48
48
|
const voice = [];
|
|
49
49
|
for (const st of steps) {
|
|
50
50
|
const audio = opts.audio.get(st.index);
|
|
51
|
+
if (st.card && opts.cards === false) {
|
|
52
|
+
// Cards are off: the step takes no time in the video. A video that started with one
|
|
53
|
+
// still drops the still lead-in recorded before it.
|
|
54
|
+
if (pieces.length === 0)
|
|
55
|
+
sp = st.start;
|
|
56
|
+
emitCapture(st.start);
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
51
59
|
if (st.card) {
|
|
52
60
|
// An opening card starts the video: drop the still lead-in recorded before it.
|
|
53
61
|
if (pieces.length === 0)
|