rollberry 0.1.9 → 0.2.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/CHANGELOG.md +30 -0
- package/README.md +189 -7
- package/dist/capture/actions.d.ts +11 -0
- package/dist/capture/actions.js +81 -0
- package/dist/capture/browser-install.d.ts +3 -0
- package/dist/capture/browser.d.ts +13 -0
- package/dist/capture/capture.d.ts +8 -0
- package/dist/capture/capture.js +476 -56
- package/dist/capture/constants.d.ts +15 -0
- package/dist/capture/constants.js +3 -0
- package/dist/capture/ffmpeg.d.ts +41 -0
- package/dist/capture/ffmpeg.js +374 -28
- package/dist/capture/logger.d.ts +15 -0
- package/dist/capture/preflight.d.ts +4 -0
- package/dist/capture/progress.d.ts +7 -0
- package/dist/capture/scroll-plan.d.ts +9 -0
- package/dist/capture/scroll-plan.js +4 -1
- package/dist/capture/stabilize.d.ts +7 -0
- package/dist/capture/types.d.ts +216 -0
- package/dist/capture/utils.d.ts +14 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +52 -24
- package/dist/index.d.ts +7 -0
- package/dist/index.js +6 -0
- package/dist/options.d.ts +42 -0
- package/dist/options.js +187 -119
- package/dist/project.d.ts +27 -0
- package/dist/project.js +722 -0
- package/dist/render-plan.d.ts +103 -0
- package/dist/render-plan.js +144 -0
- package/dist/run-capture.d.ts +3 -0
- package/dist/run-render.d.ts +17 -0
- package/dist/run-render.js +434 -0
- package/dist/version.d.ts +1 -0
- package/package.json +10 -3
- package/rollberry.project.sample.json +92 -0
- package/rollberry.project.schema.json +474 -0
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,36 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on Keep a Changelog and the project stays on the `v0.x.x`
|
|
6
6
|
line until the CLI surface and capture behavior settle.
|
|
7
7
|
|
|
8
|
+
## [0.2.0] - 2026-03-22
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- `render <project.json>` command for project-based multi-scene rendering.
|
|
13
|
+
- Project config support for `scenes`, `outputs`, reusable defaults, and
|
|
14
|
+
project-level summary manifests.
|
|
15
|
+
- Scene `actions` and mid-capture `timeline` segments including `pause`,
|
|
16
|
+
`scroll`, `click`, `hover`, `press`, `type`, and `scroll-to`.
|
|
17
|
+
- Multi-output rendering with per-output viewport, audio, subtitles,
|
|
18
|
+
transitions, and encoder settings.
|
|
19
|
+
- Node library exports for project loading, render planning, and render
|
|
20
|
+
execution.
|
|
21
|
+
- JSON Schema and sample project config for editor validation and onboarding.
|
|
22
|
+
- Scene-to-scene `crossfade` transitions.
|
|
23
|
+
- Subtitle support for both soft and burn-in modes with `.srt`, `.vtt`, and
|
|
24
|
+
`.webvtt` inputs.
|
|
25
|
+
- `finalVideo` encoder controls for final `mp4` and `webm` outputs.
|
|
26
|
+
- `intermediateArtifact` profiles for scene clip generation.
|
|
27
|
+
|
|
28
|
+
### Changed
|
|
29
|
+
|
|
30
|
+
- Render orchestration now uses an explicit render-plan layer that separates
|
|
31
|
+
scene capture from final composition.
|
|
32
|
+
- Render manifests now use schema version `2` and separate `captureMetrics`
|
|
33
|
+
from `artifactMetrics`.
|
|
34
|
+
- Probe handling now records explicit status and warnings, and fails fast when
|
|
35
|
+
precise clip timing is required by composition capabilities such as
|
|
36
|
+
`crossfade`.
|
|
37
|
+
|
|
8
38
|
## [0.1.9] - 2026-03-20
|
|
9
39
|
|
|
10
40
|
### Added
|
package/README.md
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
# Rollberry
|
|
2
2
|
|
|
3
|
-
Rollberry is an MIT-licensed open source CLI for turning web
|
|
4
|
-
smooth top-to-bottom scroll videos. It
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
Rollberry is an MIT-licensed open source CLI and Node API for turning web
|
|
4
|
+
pages into smooth top-to-bottom scroll videos. It can capture one or more
|
|
5
|
+
URLs into a single MP4, or render a project JSON file into multiple output
|
|
6
|
+
variants such as desktop and mobile. It is built for real browser capture,
|
|
7
|
+
works with normal URLs and `localhost`, and is published for direct `npx`
|
|
8
|
+
usage.
|
|
7
9
|
|
|
8
10
|
Maintained by CORe Inc.
|
|
9
11
|
|
|
@@ -12,7 +14,7 @@ Maintained by CORe Inc.
|
|
|
12
14
|
Requirements:
|
|
13
15
|
|
|
14
16
|
- Node.js `24.12.0+`
|
|
15
|
-
- `ffmpeg`
|
|
17
|
+
- `ffmpeg` available on `PATH`
|
|
16
18
|
|
|
17
19
|
Install nothing globally. Run it directly:
|
|
18
20
|
|
|
@@ -29,6 +31,13 @@ npx rollberry capture http://localhost:3000 \
|
|
|
29
31
|
On the first run, if Playwright Chromium is missing, Rollberry installs it
|
|
30
32
|
automatically. `ffmpeg` is not auto-installed.
|
|
31
33
|
|
|
34
|
+
For project-based rendering, start from the bundled sample:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
cp rollberry.project.sample.json rollberry.project.json
|
|
38
|
+
npx rollberry render ./rollberry.project.json
|
|
39
|
+
```
|
|
40
|
+
|
|
32
41
|
## Using With npx
|
|
33
42
|
|
|
34
43
|
The normal way to run Rollberry is `npx`.
|
|
@@ -61,11 +70,17 @@ npx rollberry capture https://playwright.dev \
|
|
|
61
70
|
--duration 8
|
|
62
71
|
```
|
|
63
72
|
|
|
73
|
+
Render a project file with multiple outputs:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
npx rollberry render ./rollberry.project.json
|
|
77
|
+
```
|
|
78
|
+
|
|
64
79
|
Notes:
|
|
65
80
|
|
|
66
81
|
- `npx` downloads the published CLI package automatically
|
|
67
82
|
- on the first run, Rollberry installs Playwright Chromium if needed
|
|
68
|
-
- `ffmpeg`
|
|
83
|
+
- `ffmpeg` must already be available on your machine
|
|
69
84
|
- if you want reproducible automation, pin the package version with
|
|
70
85
|
`npx rollberry@<version> ...`
|
|
71
86
|
|
|
@@ -74,10 +89,12 @@ Notes:
|
|
|
74
89
|
Each run writes:
|
|
75
90
|
|
|
76
91
|
- `video.mp4`: the rendered capture
|
|
77
|
-
- `video.manifest.json`: environment,
|
|
92
|
+
- `video.manifest.json`: environment, scene definitions, capture metrics, artifact metrics, and failure details
|
|
78
93
|
- `video.log.jsonl`: structured operational logs
|
|
79
94
|
|
|
80
95
|
You can override the sidecar paths with `--manifest` and `--log-file`.
|
|
96
|
+
Project renders write the same sidecars for each configured output, plus a
|
|
97
|
+
project-level `*.render-summary.json`.
|
|
81
98
|
|
|
82
99
|
## Common Examples
|
|
83
100
|
|
|
@@ -133,10 +150,162 @@ npx rollberry capture http://localhost:3000 \
|
|
|
133
150
|
--debug-frames-dir ./artifacts/debug-frames
|
|
134
151
|
```
|
|
135
152
|
|
|
153
|
+
Render a project file into desktop and mobile outputs:
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
npx rollberry render ./rollberry.project.json
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Render only one named output from a project:
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
npx rollberry render ./rollberry.project.json --output mobile
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## Project Rendering
|
|
166
|
+
|
|
167
|
+
Use `render` when you want scene-by-scene control and multiple outputs from one
|
|
168
|
+
config file. `actions` run before capture as setup. `timeline` runs during the
|
|
169
|
+
capture itself and lets you interleave scroll, pause, and interactions inside
|
|
170
|
+
one scene.
|
|
171
|
+
|
|
172
|
+
```json
|
|
173
|
+
{
|
|
174
|
+
"$schema": "./rollberry.project.schema.json",
|
|
175
|
+
"schemaVersion": 1,
|
|
176
|
+
"summaryManifest": "./artifacts/demo.render-summary.json",
|
|
177
|
+
"defaults": {
|
|
178
|
+
"fps": 60,
|
|
179
|
+
"viewport": "1440x900",
|
|
180
|
+
"waitFor": "selector:body",
|
|
181
|
+
"hideSelectors": ["#cookie-banner"]
|
|
182
|
+
},
|
|
183
|
+
"scenes": [
|
|
184
|
+
{
|
|
185
|
+
"name": "home",
|
|
186
|
+
"url": "http://localhost:3000",
|
|
187
|
+
"actions": [
|
|
188
|
+
{ "type": "click", "selector": "[data-open-menu]" },
|
|
189
|
+
{ "type": "wait", "ms": 300 }
|
|
190
|
+
],
|
|
191
|
+
"timeline": [
|
|
192
|
+
{ "type": "pause", "duration": 0.4 },
|
|
193
|
+
{
|
|
194
|
+
"type": "click",
|
|
195
|
+
"selector": "[data-open-menu]",
|
|
196
|
+
"holdAfterSeconds": 0.4
|
|
197
|
+
},
|
|
198
|
+
{ "type": "scroll", "toSelector": "#pricing", "duration": 1.4 },
|
|
199
|
+
{ "type": "scroll", "to": "bottom", "duration": "auto" }
|
|
200
|
+
],
|
|
201
|
+
"holdAfterSeconds": 0.75
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
"name": "pricing",
|
|
205
|
+
"url": "http://localhost:3000/pricing"
|
|
206
|
+
}
|
|
207
|
+
],
|
|
208
|
+
"outputs": [
|
|
209
|
+
{
|
|
210
|
+
"name": "desktop",
|
|
211
|
+
"viewport": "1440x900",
|
|
212
|
+
"out": "./artifacts/demo-desktop.mp4",
|
|
213
|
+
"audio": {
|
|
214
|
+
"path": "./assets/demo-narration.wav",
|
|
215
|
+
"volume": 0.8,
|
|
216
|
+
"loop": true
|
|
217
|
+
},
|
|
218
|
+
"subtitles": {
|
|
219
|
+
"path": "./assets/demo-captions.vtt",
|
|
220
|
+
"mode": "burn-in"
|
|
221
|
+
},
|
|
222
|
+
"transition": {
|
|
223
|
+
"type": "crossfade",
|
|
224
|
+
"duration": 0.25
|
|
225
|
+
},
|
|
226
|
+
"intermediateArtifact": {
|
|
227
|
+
"format": "mp4",
|
|
228
|
+
"preset": "veryfast",
|
|
229
|
+
"crf": 20
|
|
230
|
+
},
|
|
231
|
+
"finalVideo": {
|
|
232
|
+
"preset": "medium",
|
|
233
|
+
"crf": 19
|
|
234
|
+
}
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
"name": "mobile",
|
|
238
|
+
"viewport": "430x932",
|
|
239
|
+
"out": "./artifacts/demo-mobile.webm",
|
|
240
|
+
"format": "webm",
|
|
241
|
+
"subtitles": {
|
|
242
|
+
"path": "./assets/demo-captions.vtt",
|
|
243
|
+
"mode": "soft"
|
|
244
|
+
},
|
|
245
|
+
"transition": {
|
|
246
|
+
"type": "crossfade",
|
|
247
|
+
"duration": 0.25
|
|
248
|
+
},
|
|
249
|
+
"intermediateArtifact": {
|
|
250
|
+
"format": "mp4",
|
|
251
|
+
"preset": "fast",
|
|
252
|
+
"crf": 22
|
|
253
|
+
},
|
|
254
|
+
"finalVideo": {
|
|
255
|
+
"deadline": "best",
|
|
256
|
+
"crf": 30
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
]
|
|
260
|
+
}
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
Supported setup actions:
|
|
264
|
+
|
|
265
|
+
- `wait`
|
|
266
|
+
- `click`
|
|
267
|
+
- `hover`
|
|
268
|
+
- `press`
|
|
269
|
+
- `type`
|
|
270
|
+
- `scroll-to`
|
|
271
|
+
|
|
272
|
+
Supported timeline segments:
|
|
273
|
+
|
|
274
|
+
- `pause`
|
|
275
|
+
- `wait`
|
|
276
|
+
- `scroll`
|
|
277
|
+
- `click`
|
|
278
|
+
- `hover`
|
|
279
|
+
- `press`
|
|
280
|
+
- `type`
|
|
281
|
+
- `scroll-to`
|
|
282
|
+
|
|
283
|
+
Supported output extensions:
|
|
284
|
+
|
|
285
|
+
- `mp4`
|
|
286
|
+
- `webm`
|
|
287
|
+
|
|
288
|
+
Supported media extensions:
|
|
289
|
+
|
|
290
|
+
- output-level background audio via `audio.path`
|
|
291
|
+
- output-level subtitles via `subtitles.path`
|
|
292
|
+
- `subtitles.mode: "soft"` for `mp4` and `webm`
|
|
293
|
+
- `subtitles.mode: "burn-in"` for `mp4` and `webm`
|
|
294
|
+
- `.srt`, `.vtt`, and `.webvtt` subtitle inputs
|
|
295
|
+
- output-level `transition.type: "fade-in"`
|
|
296
|
+
- output-level `transition.type: "crossfade"` between adjacent scenes
|
|
297
|
+
- output-level `intermediateArtifact.format` / `preset` / `crf` for scene clip profiles
|
|
298
|
+
- output-level `finalVideo.preset` / `finalVideo.crf` for `mp4` encode control
|
|
299
|
+
- output-level `finalVideo.deadline` / `finalVideo.crf` for `webm` encode control
|
|
300
|
+
- project-level summary manifest via `summaryManifest`
|
|
301
|
+
- render manifest separates `captureMetrics` and `artifactMetrics`
|
|
302
|
+
- `crossfade` uses `ffprobe`-backed clip timing when available and fails fast if scene clip probing is unavailable
|
|
303
|
+
|
|
136
304
|
## CLI Options
|
|
137
305
|
|
|
138
306
|
```text
|
|
139
307
|
rollberry capture <url...>
|
|
308
|
+
rollberry render <project.json>
|
|
140
309
|
|
|
141
310
|
--out <file> Output MP4 path
|
|
142
311
|
--viewport <WxH> Viewport size, example: 1440x900
|
|
@@ -150,6 +319,10 @@ rollberry capture <url...>
|
|
|
150
319
|
--debug-frames-dir <dir> Save raw PNG frames
|
|
151
320
|
--manifest <file> Manifest JSON output path
|
|
152
321
|
--log-file <file> Log JSONL output path
|
|
322
|
+
|
|
323
|
+
render:
|
|
324
|
+
--output <name> Render only the named output (repeatable)
|
|
325
|
+
--force Overwrite configured output files
|
|
153
326
|
```
|
|
154
327
|
|
|
155
328
|
## Localhost Behavior
|
|
@@ -168,6 +341,9 @@ If `ffmpeg` is missing:
|
|
|
168
341
|
brew install ffmpeg
|
|
169
342
|
```
|
|
170
343
|
|
|
344
|
+
If you are running the test suite, `ffprobe` may also be used for extra video
|
|
345
|
+
verification. Most FFmpeg installs include it alongside `ffmpeg`.
|
|
346
|
+
|
|
171
347
|
If capture fails, inspect:
|
|
172
348
|
|
|
173
349
|
- `*.manifest.json` for final status and error details
|
|
@@ -187,6 +363,11 @@ If a site keeps shifting during capture:
|
|
|
187
363
|
|
|
188
364
|
## Local Development
|
|
189
365
|
|
|
366
|
+
For local CLI usage and captures, Rollberry requires `ffmpeg` on `PATH`.
|
|
367
|
+
When running `pnpm test`, the integration suite uses `ffprobe` when available
|
|
368
|
+
to inspect generated videos, but falls back to basic file validation if it is
|
|
369
|
+
missing.
|
|
370
|
+
|
|
190
371
|
```bash
|
|
191
372
|
corepack pnpm install
|
|
192
373
|
corepack pnpm exec playwright install chromium
|
|
@@ -199,6 +380,7 @@ Run from the repository:
|
|
|
199
380
|
|
|
200
381
|
```bash
|
|
201
382
|
corepack pnpm dev -- capture http://localhost:3000 --out ./artifacts/demo.mp4
|
|
383
|
+
corepack pnpm dev -- render ./rollberry.project.sample.json
|
|
202
384
|
```
|
|
203
385
|
|
|
204
386
|
Run the regression suite:
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Page } from 'playwright';
|
|
2
|
+
import type { CaptureAction } from './types.js';
|
|
3
|
+
export declare function executeSceneActions(options: {
|
|
4
|
+
page: Page;
|
|
5
|
+
actions: CaptureAction[];
|
|
6
|
+
timeoutMs: number;
|
|
7
|
+
onActionStart?: (action: CaptureAction, index: number) => Promise<void> | void;
|
|
8
|
+
onActionComplete?: (action: CaptureAction, index: number) => Promise<void> | void;
|
|
9
|
+
}): Promise<void>;
|
|
10
|
+
export declare function executeCaptureAction(page: Page, action: CaptureAction, timeoutMs: number): Promise<void>;
|
|
11
|
+
export declare function resetScrollPosition(page: Page): Promise<void>;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { delay, waitForAnimationFrames } from './utils.js';
|
|
2
|
+
const ACTION_SETTLE_DELAY_MS = 75;
|
|
3
|
+
export async function executeSceneActions(options) {
|
|
4
|
+
const { page, actions, timeoutMs, onActionStart, onActionComplete } = options;
|
|
5
|
+
for (const [index, action] of actions.entries()) {
|
|
6
|
+
await onActionStart?.(action, index);
|
|
7
|
+
await executeCaptureAction(page, action, timeoutMs);
|
|
8
|
+
await onActionComplete?.(action, index);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
export async function executeCaptureAction(page, action, timeoutMs) {
|
|
12
|
+
switch (action.kind) {
|
|
13
|
+
case 'wait':
|
|
14
|
+
await delay(action.ms);
|
|
15
|
+
break;
|
|
16
|
+
case 'click':
|
|
17
|
+
await page.waitForSelector(action.selector, {
|
|
18
|
+
state: 'visible',
|
|
19
|
+
timeout: timeoutMs,
|
|
20
|
+
});
|
|
21
|
+
await page.locator(action.selector).click();
|
|
22
|
+
await settlePage(page);
|
|
23
|
+
break;
|
|
24
|
+
case 'hover':
|
|
25
|
+
await page.waitForSelector(action.selector, {
|
|
26
|
+
state: 'visible',
|
|
27
|
+
timeout: timeoutMs,
|
|
28
|
+
});
|
|
29
|
+
await page.locator(action.selector).hover();
|
|
30
|
+
await settlePage(page);
|
|
31
|
+
break;
|
|
32
|
+
case 'press':
|
|
33
|
+
await page.keyboard.press(action.key);
|
|
34
|
+
await settlePage(page);
|
|
35
|
+
break;
|
|
36
|
+
case 'type':
|
|
37
|
+
await page.waitForSelector(action.selector, {
|
|
38
|
+
state: 'visible',
|
|
39
|
+
timeout: timeoutMs,
|
|
40
|
+
});
|
|
41
|
+
if (action.clear) {
|
|
42
|
+
await page.locator(action.selector).fill(action.text);
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
await page.locator(action.selector).type(action.text);
|
|
46
|
+
}
|
|
47
|
+
await settlePage(page);
|
|
48
|
+
break;
|
|
49
|
+
case 'scroll-to':
|
|
50
|
+
await page.waitForSelector(action.selector, {
|
|
51
|
+
state: 'attached',
|
|
52
|
+
timeout: timeoutMs,
|
|
53
|
+
});
|
|
54
|
+
await page.evaluate(({ selector, block }) => {
|
|
55
|
+
const element = document.querySelector(selector);
|
|
56
|
+
if (!element) {
|
|
57
|
+
throw new Error(`Selector not found for scroll action: ${selector}`);
|
|
58
|
+
}
|
|
59
|
+
element.scrollIntoView({
|
|
60
|
+
block,
|
|
61
|
+
behavior: 'auto',
|
|
62
|
+
inline: 'nearest',
|
|
63
|
+
});
|
|
64
|
+
}, {
|
|
65
|
+
selector: action.selector,
|
|
66
|
+
block: action.block,
|
|
67
|
+
});
|
|
68
|
+
await settlePage(page);
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
export async function resetScrollPosition(page) {
|
|
73
|
+
await page.evaluate(() => {
|
|
74
|
+
window.scrollTo({ top: 0, behavior: 'auto' });
|
|
75
|
+
});
|
|
76
|
+
await waitForAnimationFrames(page);
|
|
77
|
+
}
|
|
78
|
+
async function settlePage(page) {
|
|
79
|
+
await waitForAnimationFrames(page);
|
|
80
|
+
await delay(ACTION_SETTLE_DELAY_MS);
|
|
81
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { chromium, type Page } from 'playwright';
|
|
2
|
+
import type { CaptureLogger } from './logger.js';
|
|
3
|
+
import type { Viewport } from './types.js';
|
|
4
|
+
export interface BrowserSession {
|
|
5
|
+
browser: Awaited<ReturnType<typeof chromium.launch>>;
|
|
6
|
+
page: Page;
|
|
7
|
+
}
|
|
8
|
+
export declare function openBrowserSession(options: {
|
|
9
|
+
viewport: Viewport;
|
|
10
|
+
timeoutMs: number;
|
|
11
|
+
urls: URL[];
|
|
12
|
+
}, logger: CaptureLogger): Promise<BrowserSession>;
|
|
13
|
+
export declare function navigateWithRetry(page: Page, url: URL, timeoutMs: number): Promise<void>;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { CaptureLogger } from './logger.js';
|
|
2
|
+
import type { ProgressReporter } from './progress.js';
|
|
3
|
+
import type { CaptureJob, CaptureOptions, CaptureResult } from './types.js';
|
|
4
|
+
export declare function captureVideo(options: CaptureOptions, logger: CaptureLogger, progress?: ProgressReporter, signal?: AbortSignal): Promise<CaptureResult>;
|
|
5
|
+
export declare function captureSceneVideo(job: CaptureJob, logger: CaptureLogger, progress?: ProgressReporter, signal?: AbortSignal): Promise<CaptureResult>;
|
|
6
|
+
export declare class AbortError extends Error {
|
|
7
|
+
constructor();
|
|
8
|
+
}
|