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.
@@ -0,0 +1,216 @@
1
+ export type MotionCurve = 'ease-in-out-sine' | 'linear';
2
+ export type NonEmptyArray<T> = [T, ...T[]];
3
+ export type ScrollAlignment = 'start' | 'center' | 'end';
4
+ export type OutputFormat = 'mp4' | 'webm';
5
+ export type SubtitleFormat = 'srt' | 'webvtt';
6
+ export type SubtitleMode = 'soft' | 'burn-in';
7
+ export type H264Preset = 'ultrafast' | 'superfast' | 'veryfast' | 'faster' | 'fast' | 'medium' | 'slow' | 'slower' | 'veryslow' | 'placebo';
8
+ export type Vp9Deadline = 'best' | 'good' | 'realtime';
9
+ export type WaitForCondition = {
10
+ kind: 'load';
11
+ } | {
12
+ kind: 'selector';
13
+ selector: string;
14
+ } | {
15
+ kind: 'delay';
16
+ ms: number;
17
+ };
18
+ export interface Viewport {
19
+ width: number;
20
+ height: number;
21
+ }
22
+ export interface CaptureOptions {
23
+ urls: NonEmptyArray<URL>;
24
+ outPath: string;
25
+ manifestPath: string;
26
+ logFilePath: string;
27
+ viewport: Viewport;
28
+ fps: number;
29
+ duration: number | 'auto';
30
+ motion: MotionCurve;
31
+ timeoutMs: number;
32
+ waitFor: WaitForCondition;
33
+ hideSelectors: string[];
34
+ pageGapSeconds: number;
35
+ debugFramesDir?: string;
36
+ force: boolean;
37
+ }
38
+ export type CaptureAction = {
39
+ kind: 'wait';
40
+ ms: number;
41
+ } | {
42
+ kind: 'click';
43
+ selector: string;
44
+ } | {
45
+ kind: 'hover';
46
+ selector: string;
47
+ } | {
48
+ kind: 'press';
49
+ key: string;
50
+ } | {
51
+ kind: 'type';
52
+ selector: string;
53
+ text: string;
54
+ clear: boolean;
55
+ } | {
56
+ kind: 'scroll-to';
57
+ selector: string;
58
+ block: ScrollAlignment;
59
+ };
60
+ export type CaptureTimelineScrollTarget = {
61
+ kind: 'bottom';
62
+ } | {
63
+ kind: 'absolute';
64
+ top: number;
65
+ } | {
66
+ kind: 'relative';
67
+ delta: number;
68
+ } | {
69
+ kind: 'selector';
70
+ selector: string;
71
+ block: ScrollAlignment;
72
+ };
73
+ export type CaptureTimelineSegment = {
74
+ kind: 'scroll';
75
+ duration: number | 'auto';
76
+ motion: MotionCurve;
77
+ target: CaptureTimelineScrollTarget;
78
+ } | {
79
+ kind: 'pause';
80
+ durationSeconds: number;
81
+ } | {
82
+ kind: 'action';
83
+ action: Exclude<CaptureAction, {
84
+ kind: 'wait';
85
+ }>;
86
+ holdAfterSeconds: number;
87
+ };
88
+ export interface CaptureScene {
89
+ name?: string;
90
+ url: URL;
91
+ duration: number | 'auto';
92
+ motion: MotionCurve;
93
+ waitFor: WaitForCondition;
94
+ hideSelectors: string[];
95
+ holdAfterSeconds: number;
96
+ actions: CaptureAction[];
97
+ timeline: CaptureTimelineSegment[];
98
+ }
99
+ export interface CaptureJob {
100
+ scenes: NonEmptyArray<CaptureScene>;
101
+ outPath: string;
102
+ format: OutputFormat;
103
+ viewport: Viewport;
104
+ fps: number;
105
+ timeoutMs: number;
106
+ audio?: CaptureAudioTrack;
107
+ subtitles?: CaptureSubtitleTrack;
108
+ transition?: CaptureTransition;
109
+ videoEncoding?: CaptureVideoEncodingSettings;
110
+ debugFramesDir?: string;
111
+ includeHoldAfterFinalScene?: boolean;
112
+ force: boolean;
113
+ }
114
+ export interface CaptureVideoEncodingSettings {
115
+ preset: H264Preset;
116
+ crf: number;
117
+ }
118
+ export interface IntermediateArtifactProfile {
119
+ format: 'mp4';
120
+ extension: '.mp4';
121
+ videoEncoding: CaptureVideoEncodingSettings;
122
+ }
123
+ export interface CaptureAudioTrack {
124
+ sourcePath: string;
125
+ volume: number;
126
+ loop: boolean;
127
+ }
128
+ export interface CaptureSubtitleTrack {
129
+ sourcePath: string;
130
+ format: SubtitleFormat;
131
+ mode: SubtitleMode;
132
+ }
133
+ export type CaptureTransition = {
134
+ kind: 'fade-in';
135
+ durationSeconds: number;
136
+ } | {
137
+ kind: 'crossfade';
138
+ durationSeconds: number;
139
+ };
140
+ export type FinalVideoEncodingSettings = {
141
+ format: 'mp4';
142
+ preset: H264Preset;
143
+ crf: number;
144
+ } | {
145
+ format: 'webm';
146
+ deadline: Vp9Deadline;
147
+ crf: number;
148
+ };
149
+ export interface ResolvedCaptureOptions extends CaptureOptions {
150
+ durationSeconds: number;
151
+ }
152
+ export interface PageMetrics {
153
+ scrollHeight: number;
154
+ viewportHeight: number;
155
+ maxScroll: number;
156
+ }
157
+ export interface PreflightResult extends PageMetrics {
158
+ truncated: boolean;
159
+ }
160
+ export interface PageCaptureResult {
161
+ name?: string;
162
+ url: string;
163
+ frameCount: number;
164
+ durationSeconds: number;
165
+ scrollHeight: number;
166
+ truncated: boolean;
167
+ }
168
+ export interface CaptureResult {
169
+ outPath: string;
170
+ frameCount: number;
171
+ durationSeconds: number;
172
+ pages: PageCaptureResult[];
173
+ truncated: boolean;
174
+ }
175
+ export interface CaptureRunResult {
176
+ capture: CaptureResult;
177
+ manifestPath: string;
178
+ logFilePath: string;
179
+ }
180
+ export interface CaptureManifest {
181
+ schemaVersion: 2;
182
+ status: 'succeeded' | 'failed' | 'cancelled';
183
+ startedAt: string;
184
+ finishedAt: string;
185
+ durationMs: number;
186
+ environment: {
187
+ nodeVersion: string;
188
+ platform: NodeJS.Platform;
189
+ arch: string;
190
+ };
191
+ options: {
192
+ urls: string[];
193
+ viewport: Viewport;
194
+ fps: number;
195
+ duration: number | 'auto';
196
+ motion: MotionCurve;
197
+ timeoutMs: number;
198
+ waitFor: WaitForCondition;
199
+ hideSelectors: string[];
200
+ pageGapSeconds: number;
201
+ };
202
+ artifacts: {
203
+ videoPath: string;
204
+ manifestPath: string;
205
+ logFilePath: string;
206
+ debugFramesDir?: string;
207
+ videoCreated: boolean;
208
+ };
209
+ result?: CaptureResult;
210
+ warnings: string[];
211
+ error?: {
212
+ name: string;
213
+ message: string;
214
+ stack?: string;
215
+ };
216
+ }
@@ -0,0 +1,14 @@
1
+ import type { Page } from 'playwright';
2
+ import type { PageMetrics } from './types.js';
3
+ export declare function clamp(value: number, min: number, max: number): number;
4
+ export declare function isLocalUrl(url: URL): boolean;
5
+ export declare function parseCaptureUrl(rawUrl: string): URL;
6
+ export declare function sanitizeUrl(url: URL): string;
7
+ export declare function formatFileSize(bytes: number): string;
8
+ export declare function validateHideSelector(selector: string): void;
9
+ export declare function fileExists(path: string): Promise<boolean>;
10
+ export declare function ensureParentDirectory(filePath: string): Promise<void>;
11
+ export declare function ensureDirectory(path: string): Promise<void>;
12
+ export declare function delay(ms: number): Promise<void>;
13
+ export declare function waitForAnimationFrames(page: Page, frameCount?: number): Promise<void>;
14
+ export declare function measurePage(page: Page): Promise<PageMetrics>;
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/cli.js CHANGED
@@ -4,11 +4,12 @@ import { basename } from 'node:path';
4
4
  import { AbortError } from './capture/capture.js';
5
5
  import { createProgressReporter } from './capture/progress.js';
6
6
  import { formatFileSize } from './capture/utils.js';
7
- import { CliError, formatUsage, HelpRequest, parseCliArgs, VersionRequest, } from './options.js';
7
+ import { CliError, formatUsage, HelpRequest, parseCommandArgs, VersionRequest, } from './options.js';
8
8
  import { runCaptureCommand } from './run-capture.js';
9
+ import { runRenderCommand } from './run-render.js';
9
10
  async function main() {
10
11
  try {
11
- const options = parseCliArgs();
12
+ const command = parseCommandArgs();
12
13
  const controller = new AbortController();
13
14
  const onSignal = () => {
14
15
  controller.abort();
@@ -17,30 +18,33 @@ async function main() {
17
18
  process.on('SIGTERM', onSignal);
18
19
  const progress = createProgressReporter();
19
20
  try {
20
- const result = await runCaptureCommand(options, progress, controller.signal);
21
- process.stdout.write(`${result.capture.outPath}\n`);
22
- const fileName = basename(result.capture.outPath);
23
- let fileSizeStr = '';
24
- try {
25
- const stat = statSync(result.capture.outPath);
26
- fileSizeStr = formatFileSize(stat.size);
21
+ if (command.kind === 'capture') {
22
+ const result = await runCaptureCommand(command.options, progress, controller.signal);
23
+ process.stdout.write(`${result.capture.outPath}\n`);
24
+ process.stderr.write(`${formatOutputSummary({
25
+ label: 'Capture complete',
26
+ outPath: result.capture.outPath,
27
+ durationSeconds: result.capture.durationSeconds,
28
+ frameCount: result.capture.frameCount,
29
+ fps: command.options.fps,
30
+ sceneCount: result.capture.pages.length,
31
+ manifestPath: result.manifestPath,
32
+ })}\n`);
27
33
  }
28
- catch {
29
- // File size unavailable
34
+ else {
35
+ const result = await runRenderCommand(command.options, progress, controller.signal);
36
+ process.stdout.write(`${result.outputs.map((output) => output.capture.outPath).join('\n')}\n`);
37
+ const blocks = result.outputs.map((output) => formatOutputSummary({
38
+ label: `Render complete: ${output.name}`,
39
+ outPath: output.capture.outPath,
40
+ durationSeconds: output.capture.durationSeconds,
41
+ frameCount: output.capture.frameCount,
42
+ fps: output.fps,
43
+ sceneCount: output.capture.pages.length,
44
+ manifestPath: output.manifestPath,
45
+ }));
46
+ process.stderr.write(`\n${blocks.join('\n\n')}\n Summary: ${basename(result.summaryManifestPath)}\n`);
30
47
  }
31
- const lines = [
32
- '',
33
- `Capture complete: ${fileName}`,
34
- ` Duration: ${result.capture.durationSeconds.toFixed(1)}s (${result.capture.frameCount} frames at ${options.fps}fps)`,
35
- ];
36
- if (fileSizeStr) {
37
- lines.push(` File size: ${fileSizeStr}`);
38
- }
39
- if (result.capture.pages.length > 1) {
40
- lines.push(` Pages: ${result.capture.pages.length}`);
41
- }
42
- lines.push(` Manifest: ${basename(result.manifestPath)}`);
43
- process.stderr.write(`${lines.join('\n')}\n`);
44
48
  }
45
49
  finally {
46
50
  process.off('SIGINT', onSignal);
@@ -73,4 +77,28 @@ async function main() {
73
77
  process.exitCode = 1;
74
78
  }
75
79
  }
80
+ function formatOutputSummary(input) {
81
+ const fileName = basename(input.outPath);
82
+ let fileSizeStr = '';
83
+ try {
84
+ const stat = statSync(input.outPath);
85
+ fileSizeStr = formatFileSize(stat.size);
86
+ }
87
+ catch {
88
+ // File size unavailable
89
+ }
90
+ const lines = [
91
+ '',
92
+ `${input.label}: ${fileName}`,
93
+ ` Duration: ${input.durationSeconds.toFixed(1)}s (${input.frameCount} frames at ${input.fps.toFixed(0)}fps)`,
94
+ ];
95
+ if (fileSizeStr) {
96
+ lines.push(` File size: ${fileSizeStr}`);
97
+ }
98
+ if (input.sceneCount > 1) {
99
+ lines.push(` Scenes: ${input.sceneCount}`);
100
+ }
101
+ lines.push(` Manifest: ${basename(input.manifestPath)}`);
102
+ return lines.join('\n');
103
+ }
76
104
  await main();
@@ -0,0 +1,7 @@
1
+ export { captureSceneVideo, captureVideo } from './capture/capture.js';
2
+ export type { CaptureAction, CaptureAudioTrack, CaptureJob, CaptureOptions, CaptureResult, CaptureScene, CaptureSubtitleTrack, CaptureTimelineScrollTarget, CaptureTimelineSegment, CaptureTransition, CaptureVideoEncodingSettings, FinalVideoEncodingSettings, H264Preset, IntermediateArtifactProfile, MotionCurve, OutputFormat, PageCaptureResult, Viewport, Vp9Deadline, WaitForCondition, } from './capture/types.js';
3
+ export { DEFAULT_DURATION, DEFAULT_FPS, DEFAULT_MOTION, DEFAULT_OUT_FILE, DEFAULT_TIMEOUT_MS, DEFAULT_VIEWPORT, formatUsage, type ParsedCliCommand, parseCliArgs, parseCommandArgs, type RenderCliOptions, } from './options.js';
4
+ export { loadRenderProject, type ResolvedRenderOutput, type ResolvedRenderProject, } from './project.js';
5
+ export { buildArtifactMetrics, buildCaptureMetrics, buildComposedCaptureResult, buildProbeWarningEvent, buildSceneCaptureArtifact, buildSceneClipProbeFailureMessage, collectProbeWarnings, createRenderPlan, type PlannedComposition, type PlannedSceneCapture, type RenderArtifactMetrics, type RenderCaptureMetrics, type RenderPlan, type SceneCaptureArtifact, shouldFailOnSceneClipProbe, } from './render-plan.js';
6
+ export { runCaptureCommand } from './run-capture.js';
7
+ export { type RenderCommandResult, type RenderOutputResult, runRenderCommand, } from './run-render.js';
package/dist/index.js ADDED
@@ -0,0 +1,6 @@
1
+ export { captureSceneVideo, captureVideo } from './capture/capture.js';
2
+ export { DEFAULT_DURATION, DEFAULT_FPS, DEFAULT_MOTION, DEFAULT_OUT_FILE, DEFAULT_TIMEOUT_MS, DEFAULT_VIEWPORT, formatUsage, parseCliArgs, parseCommandArgs, } from './options.js';
3
+ export { loadRenderProject, } from './project.js';
4
+ export { buildArtifactMetrics, buildCaptureMetrics, buildComposedCaptureResult, buildProbeWarningEvent, buildSceneCaptureArtifact, buildSceneClipProbeFailureMessage, collectProbeWarnings, createRenderPlan, shouldFailOnSceneClipProbe, } from './render-plan.js';
5
+ export { runCaptureCommand } from './run-capture.js';
6
+ export { runRenderCommand, } from './run-render.js';
@@ -0,0 +1,42 @@
1
+ import type { CaptureOptions, MotionCurve, WaitForCondition } from './capture/types.js';
2
+ export declare const DEFAULT_OUT_FILE = "rollberry.mp4";
3
+ export declare const DEFAULT_VIEWPORT = "1440x900";
4
+ export declare const DEFAULT_FPS = 60;
5
+ export declare const DEFAULT_DURATION = "auto";
6
+ export declare const DEFAULT_MOTION: MotionCurve;
7
+ export declare const DEFAULT_TIMEOUT_MS = 30000;
8
+ export interface RenderCliOptions {
9
+ projectPath: string;
10
+ outputNames: string[];
11
+ force: boolean;
12
+ }
13
+ export type ParsedCliCommand = {
14
+ kind: 'capture';
15
+ options: CaptureOptions;
16
+ } | {
17
+ kind: 'render';
18
+ options: RenderCliOptions;
19
+ };
20
+ export declare class CliError extends Error {
21
+ readonly showUsage: boolean;
22
+ constructor(message: string, showUsage?: boolean);
23
+ }
24
+ export declare class HelpRequest extends Error {
25
+ constructor(message: string);
26
+ }
27
+ export declare class VersionRequest extends Error {
28
+ readonly version = "0.1.9";
29
+ constructor();
30
+ }
31
+ export declare function parseCommandArgs(argv?: string[]): ParsedCliCommand;
32
+ export declare function parseCliArgs(argv?: string[]): CaptureOptions;
33
+ export declare function formatUsage(): string;
34
+ export declare function parseWithCliError<T>(rawValue: string, parser: (value: string) => T): T;
35
+ export declare function resolveOutPath(path: string): string;
36
+ export declare function deriveSidecarPath(path: string, suffix: string): string;
37
+ export declare function parseViewport(rawViewport: string): CaptureOptions['viewport'];
38
+ export declare function parsePositiveInt(rawValue: string): number;
39
+ export declare function parsePositiveNumber(rawValue: string): number;
40
+ export declare function parseNonNegativeNumber(rawValue: string): number;
41
+ export declare function parseMotion(rawMotion: string): MotionCurve;
42
+ export declare function parseWaitFor(rawWaitFor: string): WaitForCondition;