screenwright 0.1.45 → 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/dist/src/commands/compose.d.ts.map +1 -1
- package/dist/src/commands/compose.js +60 -41
- package/dist/src/commands/compose.js.map +1 -1
- package/dist/src/commands/preview.d.ts.map +1 -1
- package/dist/src/commands/preview.js +9 -12
- package/dist/src/commands/preview.js.map +1 -1
- package/dist/src/composition/DemoVideo.d.ts.map +1 -1
- package/dist/src/composition/DemoVideo.js +24 -78
- package/dist/src/composition/DemoVideo.js.map +1 -1
- package/dist/src/composition/frame-resolve.d.ts +37 -0
- package/dist/src/composition/frame-resolve.d.ts.map +1 -0
- package/dist/src/composition/frame-resolve.js +114 -0
- package/dist/src/composition/frame-resolve.js.map +1 -0
- package/dist/src/composition/remotion-root.d.ts.map +1 -1
- package/dist/src/composition/remotion-root.js +7 -14
- package/dist/src/composition/remotion-root.js.map +1 -1
- package/dist/src/composition/render.d.ts.map +1 -1
- package/dist/src/composition/render.js +2 -6
- package/dist/src/composition/render.js.map +1 -1
- package/dist/src/index.d.ts +1 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/runtime/action-helpers.d.ts +15 -5
- package/dist/src/runtime/action-helpers.d.ts.map +1 -1
- package/dist/src/runtime/action-helpers.js +218 -60
- package/dist/src/runtime/action-helpers.js.map +1 -1
- package/dist/src/runtime/instrumented-page.d.ts +3 -2
- package/dist/src/runtime/instrumented-page.d.ts.map +1 -1
- package/dist/src/runtime/instrumented-page.js +105 -101
- package/dist/src/runtime/instrumented-page.js.map +1 -1
- package/dist/src/runtime/narration-preprocess.d.ts +30 -0
- package/dist/src/runtime/narration-preprocess.d.ts.map +1 -0
- package/dist/src/runtime/narration-preprocess.js +79 -0
- package/dist/src/runtime/narration-preprocess.js.map +1 -0
- package/dist/src/runtime/timeline-collector.d.ts +1 -7
- package/dist/src/runtime/timeline-collector.d.ts.map +1 -1
- package/dist/src/runtime/timeline-collector.js +2 -17
- package/dist/src/runtime/timeline-collector.js.map +1 -1
- package/dist/src/timeline/schema.d.ts +143 -162
- package/dist/src/timeline/schema.d.ts.map +1 -1
- package/dist/src/timeline/schema.js +12 -18
- package/dist/src/timeline/schema.js.map +1 -1
- package/dist/src/timeline/types.d.ts +15 -18
- package/dist/src/timeline/types.d.ts.map +1 -1
- package/dist/src/timeline/types.js.map +1 -1
- package/dist/src/version.d.ts +1 -1
- package/dist/src/version.d.ts.map +1 -1
- package/dist/src/version.js +1 -1
- package/dist/src/version.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/skill/SKILL.md +3 -3
- package/dist/src/composition/SceneSlide.d.ts +0 -12
- package/dist/src/composition/SceneSlide.d.ts.map +0 -1
- package/dist/src/composition/SceneSlide.js +0 -71
- package/dist/src/composition/SceneSlide.js.map +0 -1
- package/dist/src/composition/frame-lookup.d.ts +0 -8
- package/dist/src/composition/frame-lookup.d.ts.map +0 -1
- package/dist/src/composition/frame-lookup.js +0 -26
- package/dist/src/composition/frame-lookup.js.map +0 -1
- package/dist/src/composition/time-remap.d.ts +0 -87
- package/dist/src/composition/time-remap.d.ts.map +0 -1
- package/dist/src/composition/time-remap.js +0 -218
- package/dist/src/composition/time-remap.js.map +0 -1
- package/dist/src/voiceover/narration-timing.d.ts +0 -18
- package/dist/src/voiceover/narration-timing.d.ts.map +0 -1
- package/dist/src/voiceover/narration-timing.js +0 -40
- package/dist/src/voiceover/narration-timing.js.map +0 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Timeline } from '../timeline/types.js';
|
|
2
2
|
import { type ScreenwrightHelpers } from './action-helpers.js';
|
|
3
|
+
import type { PregeneratedNarration } from './narration-preprocess.js';
|
|
3
4
|
export type ScenarioFn = (sw: ScreenwrightHelpers) => Promise<void>;
|
|
4
5
|
export interface RunOptions {
|
|
5
6
|
scenarioFile: string;
|
|
@@ -11,12 +12,12 @@ export interface RunOptions {
|
|
|
11
12
|
colorScheme?: 'light' | 'dark';
|
|
12
13
|
locale?: string;
|
|
13
14
|
timezoneId?: string;
|
|
14
|
-
|
|
15
|
+
pregenerated?: PregeneratedNarration[];
|
|
15
16
|
}
|
|
16
17
|
export interface RunResult {
|
|
17
18
|
timeline: Timeline;
|
|
18
|
-
videoFile?: string;
|
|
19
19
|
tempDir: string;
|
|
20
|
+
narrationCount: number;
|
|
20
21
|
}
|
|
21
22
|
export declare function runScenario(scenario: ScenarioFn, opts: RunOptions): Promise<RunResult>;
|
|
22
23
|
//# sourceMappingURL=instrumented-page.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"instrumented-page.d.ts","sourceRoot":"","sources":["../../../src/runtime/instrumented-page.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,QAAQ,
|
|
1
|
+
{"version":3,"file":"instrumented-page.d.ts","sourceRoot":"","sources":["../../../src/runtime/instrumented-page.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,QAAQ,EAAmC,MAAM,sBAAsB,CAAC;AAEtF,OAAO,EAAiB,KAAK,mBAAmB,EAAyB,MAAM,qBAAqB,CAAC;AACrG,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,2BAA2B,CAAC;AAEvE,MAAM,MAAM,UAAU,GAAG,CAAC,EAAE,EAAE,mBAAmB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;AAEpE,MAAM,WAAW,UAAU;IACzB,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAC7C,WAAW,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;IAC/B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,qBAAqB,EAAE,CAAC;CACxC;AAED,MAAM,WAAW,SAAS;IACxB,QAAQ,EAAE,QAAQ,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,EAAE,MAAM,CAAC;CACxB;AASD,wBAAsB,WAAW,CAAC,QAAQ,EAAE,UAAU,EAAE,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,SAAS,CAAC,CAkK5F"}
|
|
@@ -4,25 +4,26 @@ import { tmpdir } from 'node:os';
|
|
|
4
4
|
import { join } from 'node:path';
|
|
5
5
|
import { TimelineCollector } from './timeline-collector.js';
|
|
6
6
|
import { createHelpers } from './action-helpers.js';
|
|
7
|
+
const FRAME_INTERVAL_MS = 1000 / 30;
|
|
8
|
+
const DPR = 2;
|
|
9
|
+
function sleep(ms) {
|
|
10
|
+
return new Promise(r => setTimeout(r, Math.max(0, ms)));
|
|
11
|
+
}
|
|
7
12
|
export async function runScenario(scenario, opts) {
|
|
8
13
|
const viewport = opts.viewport ?? { width: 1280, height: 720 };
|
|
9
|
-
const DPR = 2;
|
|
10
14
|
const tempDir = await mkdtemp(join(tmpdir(), 'screenwright-'));
|
|
11
|
-
const
|
|
15
|
+
const framesDir = join(tempDir, 'frames');
|
|
16
|
+
await mkdir(framesDir, { recursive: true });
|
|
12
17
|
const browser = await chromium.launch({
|
|
13
18
|
args: ['--disable-gpu', '--font-render-hinting=none', '--disable-lcd-text'],
|
|
14
19
|
});
|
|
15
|
-
const
|
|
20
|
+
const context = await browser.newContext({
|
|
16
21
|
viewport,
|
|
17
22
|
deviceScaleFactor: DPR,
|
|
18
23
|
colorScheme: opts.colorScheme ?? 'light',
|
|
19
24
|
locale: opts.locale ?? 'en-US',
|
|
20
25
|
timezoneId: opts.timezoneId ?? 'America/New_York',
|
|
21
|
-
};
|
|
22
|
-
if (captureMode === 'video') {
|
|
23
|
-
contextOpts.recordVideo = { dir: tempDir, size: viewport };
|
|
24
|
-
}
|
|
25
|
-
const context = await browser.newContext(contextOpts);
|
|
26
|
+
});
|
|
26
27
|
// Hide the native cursor so only the Screenwright overlay cursor appears
|
|
27
28
|
await context.addInitScript(`
|
|
28
29
|
const s = document.createElement('style');
|
|
@@ -31,119 +32,122 @@ export async function runScenario(scenario, opts) {
|
|
|
31
32
|
`);
|
|
32
33
|
const page = await context.newPage();
|
|
33
34
|
const collector = new TimelineCollector();
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
35
|
+
const manifest = [];
|
|
36
|
+
const transitionMarkers = [];
|
|
37
|
+
// Narration queue from pre-generated audio
|
|
38
|
+
const narrationQueue = opts.pregenerated ? [...opts.pregenerated] : [];
|
|
39
|
+
let narrationConsumed = 0;
|
|
40
|
+
// Virtual clock: each frame = exactly 1000/30 ms
|
|
41
|
+
let virtualFrameIndex = 0;
|
|
42
|
+
let frameFileCounter = 0;
|
|
43
|
+
// Capture loop state
|
|
44
|
+
let captureRunning = false;
|
|
38
45
|
let pendingScreenshot = Promise.resolve();
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
frameManifest = [];
|
|
46
|
-
let snapshotCounter = 0;
|
|
47
|
-
captureSnapshot = async () => {
|
|
48
|
-
snapshotCounter++;
|
|
49
|
-
const filename = `snapshot-${String(snapshotCounter).padStart(6, '0')}.jpg`;
|
|
50
|
-
await page.screenshot({ path: join(snapshotsDir, filename), type: 'jpeg', quality: 90 });
|
|
51
|
-
return `snapshots/${filename}`;
|
|
52
|
-
};
|
|
53
|
-
cdpSession = await context.newCDPSession(page);
|
|
54
|
-
// Hybrid approach: CDP screencast detects WHEN the page changes (high fps,
|
|
55
|
-
// low overhead). On each change we take a page.screenshot() which captures
|
|
56
|
-
// at the full deviceScaleFactor resolution (2×). The screencast pixel data
|
|
57
|
-
// itself is discarded — it only serves as a timing signal.
|
|
58
|
-
cdpSession.on('Page.screencastFrame', (params) => {
|
|
59
|
-
cdpSession.send('Page.screencastFrameAck', { sessionId: params.sessionId }).catch(() => { });
|
|
60
|
-
if (capturing)
|
|
61
|
-
return;
|
|
62
|
-
capturing = true;
|
|
63
|
-
const capturedAt = collector.elapsed();
|
|
64
|
-
pendingScreenshot = (async () => {
|
|
65
|
-
try {
|
|
66
|
-
frameCounter++;
|
|
67
|
-
const filename = `frame-${String(frameCounter).padStart(6, '0')}.jpg`;
|
|
68
|
-
const filePath = join(framesDir, filename);
|
|
69
|
-
await page.screenshot({ path: filePath, type: 'jpeg', quality: 90 });
|
|
70
|
-
frameManifest.push({
|
|
71
|
-
timestampMs: capturedAt,
|
|
72
|
-
file: `frames/${filename}`,
|
|
73
|
-
});
|
|
74
|
-
}
|
|
75
|
-
catch {
|
|
76
|
-
// Page may not be ready or is closing
|
|
77
|
-
}
|
|
78
|
-
capturing = false;
|
|
79
|
-
})();
|
|
80
|
-
});
|
|
81
|
-
}
|
|
82
|
-
collector.start();
|
|
83
|
-
if (cdpSession) {
|
|
84
|
-
await cdpSession.send('Page.startScreencast', {
|
|
85
|
-
format: 'jpeg',
|
|
86
|
-
quality: 10,
|
|
87
|
-
maxWidth: viewport.width,
|
|
88
|
-
maxHeight: viewport.height,
|
|
89
|
-
everyNthFrame: 1,
|
|
90
|
-
});
|
|
91
|
-
}
|
|
92
|
-
const sw = createHelpers(page, collector, captureSnapshot ? { captureSnapshot } : undefined);
|
|
93
|
-
let videoFile;
|
|
94
|
-
try {
|
|
95
|
-
await scenario(sw);
|
|
96
|
-
// Stop screencast and flush pending captures
|
|
97
|
-
if (cdpSession) {
|
|
98
|
-
await page.waitForTimeout(100);
|
|
99
|
-
await cdpSession.send('Page.stopScreencast').catch(() => { });
|
|
100
|
-
await pendingScreenshot;
|
|
101
|
-
// Take one final 2× screenshot
|
|
46
|
+
async function runCaptureLoop() {
|
|
47
|
+
captureRunning = true;
|
|
48
|
+
while (captureRunning) {
|
|
49
|
+
const start = performance.now();
|
|
50
|
+
frameFileCounter++;
|
|
51
|
+
const filename = `frame-${String(frameFileCounter).padStart(6, '0')}.jpg`;
|
|
102
52
|
try {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
await page.screenshot({ path: filePath, type: 'jpeg', quality: 90 });
|
|
107
|
-
frameManifest.push({
|
|
108
|
-
timestampMs: collector.elapsed(),
|
|
109
|
-
file: `frames/${filename}`,
|
|
110
|
-
});
|
|
53
|
+
await page.screenshot({ path: join(framesDir, filename), type: 'jpeg', quality: 90 });
|
|
54
|
+
manifest.push({ type: 'frame', file: `frames/${filename}` });
|
|
55
|
+
virtualFrameIndex++;
|
|
111
56
|
}
|
|
112
57
|
catch {
|
|
113
|
-
// Page may
|
|
58
|
+
// Page may not be ready or is closing
|
|
114
59
|
}
|
|
115
|
-
|
|
60
|
+
const elapsed = performance.now() - start;
|
|
61
|
+
await sleep(FRAME_INTERVAL_MS - elapsed);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
async function pauseCapture() {
|
|
65
|
+
captureRunning = false;
|
|
66
|
+
await pendingScreenshot;
|
|
67
|
+
}
|
|
68
|
+
function resumeCapture() {
|
|
69
|
+
if (!captureRunning) {
|
|
70
|
+
pendingScreenshot = runCaptureLoop();
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
async function captureOneFrame() {
|
|
74
|
+
frameFileCounter++;
|
|
75
|
+
const filename = `frame-${String(frameFileCounter).padStart(6, '0')}.jpg`;
|
|
76
|
+
await page.screenshot({ path: join(framesDir, filename), type: 'jpeg', quality: 90 });
|
|
77
|
+
const file = `frames/${filename}`;
|
|
78
|
+
manifest.push({ type: 'frame', file });
|
|
79
|
+
virtualFrameIndex++;
|
|
80
|
+
return file;
|
|
81
|
+
}
|
|
82
|
+
function addHold(file, count) {
|
|
83
|
+
if (count <= 0)
|
|
84
|
+
return;
|
|
85
|
+
manifest.push({ type: 'hold', file, count });
|
|
86
|
+
virtualFrameIndex += count;
|
|
87
|
+
}
|
|
88
|
+
function addTransitionMarker(marker) {
|
|
89
|
+
transitionMarkers.push(marker);
|
|
90
|
+
}
|
|
91
|
+
function popNarration() {
|
|
92
|
+
if (narrationQueue.length === 0) {
|
|
93
|
+
throw new Error('No pre-generated narrations remaining in queue');
|
|
94
|
+
}
|
|
95
|
+
narrationConsumed++;
|
|
96
|
+
return narrationQueue.shift();
|
|
97
|
+
}
|
|
98
|
+
function currentTimeMs() {
|
|
99
|
+
return virtualFrameIndex * FRAME_INTERVAL_MS;
|
|
100
|
+
}
|
|
101
|
+
const ctx = {
|
|
102
|
+
pauseCapture,
|
|
103
|
+
resumeCapture,
|
|
104
|
+
captureOneFrame,
|
|
105
|
+
addHold,
|
|
106
|
+
addTransitionMarker,
|
|
107
|
+
popNarration,
|
|
108
|
+
currentTimeMs,
|
|
109
|
+
get manifest() { return manifest; },
|
|
110
|
+
transitionPending: false,
|
|
111
|
+
get narrationCount() { return narrationConsumed; },
|
|
112
|
+
};
|
|
113
|
+
// Expose transitionMarkers for the back-to-back transition warning hack
|
|
114
|
+
ctx._transitionMarkers = transitionMarkers;
|
|
115
|
+
const sw = createHelpers(page, collector, ctx);
|
|
116
|
+
try {
|
|
117
|
+
// Start capture loop
|
|
118
|
+
resumeCapture();
|
|
119
|
+
await scenario(sw);
|
|
120
|
+
// Stop capture loop and flush
|
|
121
|
+
await pauseCapture();
|
|
122
|
+
// Take one final frame
|
|
123
|
+
try {
|
|
124
|
+
await captureOneFrame();
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
// Page may already be closing
|
|
116
128
|
}
|
|
117
|
-
//
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
129
|
+
// Warn about trailing transition
|
|
130
|
+
if (ctx.transitionPending) {
|
|
131
|
+
console.warn('sw.transition() at end of scenario with no following content — discarding marker.');
|
|
132
|
+
transitionMarkers.pop();
|
|
133
|
+
ctx.transitionPending = false;
|
|
122
134
|
}
|
|
123
135
|
}
|
|
124
136
|
finally {
|
|
125
|
-
// Ensure browser resources are always cleaned up (idempotent if already closed)
|
|
126
137
|
await page.close().catch(() => { });
|
|
127
138
|
await context.close().catch(() => { });
|
|
128
139
|
await browser.close().catch(() => { });
|
|
129
140
|
}
|
|
130
|
-
const videoDurationMs = collector.getEvents()
|
|
131
|
-
.filter(e => e.type !== 'transition')
|
|
132
|
-
.reduce((max, e) => {
|
|
133
|
-
const ts = e.timestampMs + ('durationMs' in e ? (e.durationMs ?? 0) : 0);
|
|
134
|
-
return Math.max(max, ts);
|
|
135
|
-
}, 0);
|
|
136
141
|
const timeline = collector.finalize({
|
|
137
142
|
testFile: opts.testFile,
|
|
138
143
|
scenarioFile: opts.scenarioFile,
|
|
139
144
|
recordedAt: new Date().toISOString(),
|
|
140
145
|
viewport,
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
frameManifest,
|
|
146
|
+
frameManifest: manifest,
|
|
147
|
+
transitionMarkers,
|
|
144
148
|
});
|
|
145
149
|
const timelinePath = join(tempDir, 'timeline.json');
|
|
146
150
|
await writeFile(timelinePath, JSON.stringify(timeline, null, 2));
|
|
147
|
-
return { timeline,
|
|
151
|
+
return { timeline, tempDir, narrationCount: narrationConsumed };
|
|
148
152
|
}
|
|
149
153
|
//# sourceMappingURL=instrumented-page.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"instrumented-page.js","sourceRoot":"","sources":["../../../src/runtime/instrumented-page.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,
|
|
1
|
+
{"version":3,"file":"instrumented-page.js","sourceRoot":"","sources":["../../../src/runtime/instrumented-page.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { ScreenwrightHelpers } from './action-helpers.js';
|
|
2
|
+
import type { OpenaiVoice } from '../config/config-schema.js';
|
|
3
|
+
export type ScenarioFn = (sw: ScreenwrightHelpers) => Promise<void>;
|
|
4
|
+
export interface PregeneratedNarration {
|
|
5
|
+
text: string;
|
|
6
|
+
audioFile: string;
|
|
7
|
+
durationMs: number;
|
|
8
|
+
}
|
|
9
|
+
export interface PreprocessOptions {
|
|
10
|
+
tempDir: string;
|
|
11
|
+
ttsProvider?: 'piper' | 'openai';
|
|
12
|
+
modelPath?: string;
|
|
13
|
+
openaiVoice?: OpenaiVoice;
|
|
14
|
+
openaiTtsInstructions?: string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Run the scenario with a stub sw that collects narration texts in order.
|
|
18
|
+
* All page interactions are no-ops.
|
|
19
|
+
*/
|
|
20
|
+
export declare function extractNarrations(scenarioFn: ScenarioFn): Promise<string[]>;
|
|
21
|
+
/**
|
|
22
|
+
* Pre-generate all narration audio files in parallel.
|
|
23
|
+
*/
|
|
24
|
+
export declare function pregenerateNarrations(texts: string[], opts: PreprocessOptions): Promise<PregeneratedNarration[]>;
|
|
25
|
+
/**
|
|
26
|
+
* Validate that the number of narrations consumed during recording matches
|
|
27
|
+
* the number pre-generated during preprocessing.
|
|
28
|
+
*/
|
|
29
|
+
export declare function validateNarrationCount(pregenerated: number, consumed: number): void;
|
|
30
|
+
//# sourceMappingURL=narration-preprocess.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"narration-preprocess.d.ts","sourceRoot":"","sources":["../../../src/runtime/narration-preprocess.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAG/D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AAE9D,MAAM,MAAM,UAAU,GAAG,CAAC,EAAE,EAAE,mBAAmB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;AAEpE,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAC;IACjC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,qBAAqB,CAAC,EAAE,MAAM,CAAC;CAChC;AA0BD;;;GAGG;AACH,wBAAsB,iBAAiB,CAAC,UAAU,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAgBjF;AAED;;GAEG;AACH,wBAAsB,qBAAqB,CACzC,KAAK,EAAE,MAAM,EAAE,EACf,IAAI,EAAE,iBAAiB,GACtB,OAAO,CAAC,qBAAqB,EAAE,CAAC,CAWlC;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,YAAY,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAOnF"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { join } from 'node:path';
|
|
2
|
+
import { synthesize as piperSynthesize } from '../voiceover/piper-engine.js';
|
|
3
|
+
import { synthesize as openaiSynthesize } from '../voiceover/openai-engine.js';
|
|
4
|
+
/**
|
|
5
|
+
* Recursive proxy that returns async no-ops for any property/method access.
|
|
6
|
+
* Handles arbitrary page.evaluate(), page.waitForSelector(), etc.
|
|
7
|
+
*/
|
|
8
|
+
function noopPageProxy() {
|
|
9
|
+
const handler = {
|
|
10
|
+
get(_target, prop) {
|
|
11
|
+
// Prevent infinite thenable loop: await checks .then, which must be
|
|
12
|
+
// undefined so the proxy isn't treated as a thenable.
|
|
13
|
+
if (prop === 'then')
|
|
14
|
+
return undefined;
|
|
15
|
+
return new Proxy(function () { }, {
|
|
16
|
+
apply() {
|
|
17
|
+
return Promise.resolve(new Proxy({}, handler));
|
|
18
|
+
},
|
|
19
|
+
get(_t, p) {
|
|
20
|
+
if (p === 'then')
|
|
21
|
+
return undefined;
|
|
22
|
+
return new Proxy(function () { }, this);
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
return new Proxy({}, handler);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Run the scenario with a stub sw that collects narration texts in order.
|
|
31
|
+
* All page interactions are no-ops.
|
|
32
|
+
*/
|
|
33
|
+
export async function extractNarrations(scenarioFn) {
|
|
34
|
+
const narrations = [];
|
|
35
|
+
const stub = {
|
|
36
|
+
page: noopPageProxy(),
|
|
37
|
+
navigate: async (_url, opts) => { if (opts?.narration)
|
|
38
|
+
narrations.push(opts.narration); },
|
|
39
|
+
click: async (_sel, opts) => { if (opts?.narration)
|
|
40
|
+
narrations.push(opts.narration); },
|
|
41
|
+
fill: async (_sel, _v, opts) => { if (opts?.narration)
|
|
42
|
+
narrations.push(opts.narration); },
|
|
43
|
+
hover: async (_sel, opts) => { if (opts?.narration)
|
|
44
|
+
narrations.push(opts.narration); },
|
|
45
|
+
press: async (_key, opts) => { if (opts?.narration)
|
|
46
|
+
narrations.push(opts.narration); },
|
|
47
|
+
wait: async () => { },
|
|
48
|
+
narrate: async (text) => { narrations.push(text); },
|
|
49
|
+
scene: async () => { },
|
|
50
|
+
transition: async () => { },
|
|
51
|
+
};
|
|
52
|
+
await scenarioFn(stub);
|
|
53
|
+
return narrations;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Pre-generate all narration audio files in parallel.
|
|
57
|
+
*/
|
|
58
|
+
export async function pregenerateNarrations(texts, opts) {
|
|
59
|
+
const provider = opts.ttsProvider ?? 'piper';
|
|
60
|
+
const ext = provider === 'openai' ? '.mp3' : '.wav';
|
|
61
|
+
return Promise.all(texts.map(async (text, i) => {
|
|
62
|
+
const outputPath = join(opts.tempDir, `narration-${i}${ext}`);
|
|
63
|
+
const result = provider === 'openai'
|
|
64
|
+
? await openaiSynthesize(text, outputPath, opts.openaiVoice, opts.openaiTtsInstructions)
|
|
65
|
+
: await piperSynthesize(text, outputPath, opts.modelPath);
|
|
66
|
+
return { text, audioFile: result.audioPath, durationMs: result.durationMs };
|
|
67
|
+
}));
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Validate that the number of narrations consumed during recording matches
|
|
71
|
+
* the number pre-generated during preprocessing.
|
|
72
|
+
*/
|
|
73
|
+
export function validateNarrationCount(pregenerated, consumed) {
|
|
74
|
+
if (pregenerated !== consumed) {
|
|
75
|
+
throw new Error(`Scenario produced ${pregenerated} narrations during preprocessing but ${consumed} during recording. ` +
|
|
76
|
+
`Conditional narration is not supported.`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
//# sourceMappingURL=narration-preprocess.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"narration-preprocess.js","sourceRoot":"","sources":["../../../src/runtime/narration-preprocess.ts"],"names":[],"mappings":"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"}
|
|
@@ -2,18 +2,12 @@ import type { Timeline, TimelineMetadata, TimelineEvent } from '../timeline/type
|
|
|
2
2
|
type PartialEvent = {
|
|
3
3
|
type: string;
|
|
4
4
|
id?: string;
|
|
5
|
-
timestampMs
|
|
5
|
+
timestampMs: number;
|
|
6
6
|
[key: string]: unknown;
|
|
7
7
|
};
|
|
8
8
|
export declare class TimelineCollector {
|
|
9
9
|
private events;
|
|
10
10
|
private counter;
|
|
11
|
-
private startTime;
|
|
12
|
-
private excludedMs;
|
|
13
|
-
start(): void;
|
|
14
|
-
elapsed(): number;
|
|
15
|
-
/** Subtract wall-clock time that shouldn't count as scenario time (e.g. screenshot I/O). */
|
|
16
|
-
excludeTime(ms: number): void;
|
|
17
11
|
nextId(): string;
|
|
18
12
|
emit(event: PartialEvent): string;
|
|
19
13
|
getEvents(): readonly TimelineEvent[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"timeline-collector.d.ts","sourceRoot":"","sources":["../../../src/runtime/timeline-collector.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAGtF,KAAK,YAAY,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,EAAE,CAAC,EAAE,MAAM,CAAC;IAAC,WAAW,
|
|
1
|
+
{"version":3,"file":"timeline-collector.d.ts","sourceRoot":"","sources":["../../../src/runtime/timeline-collector.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAGtF,KAAK,YAAY,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,EAAE,CAAC,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CAAE,CAAC;AAE/F,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,MAAM,CAAuB;IACrC,OAAO,CAAC,OAAO,CAAK;IAEpB,MAAM,IAAI,MAAM;IAIhB,IAAI,CAAC,KAAK,EAAE,YAAY,GAAG,MAAM;IAOjC,SAAS,IAAI,SAAS,aAAa,EAAE;IAIrC,QAAQ,CAAC,QAAQ,EAAE,gBAAgB,GAAG,QAAQ;CAc/C"}
|
|
@@ -2,27 +2,12 @@ import { timelineSchema } from '../timeline/schema.js';
|
|
|
2
2
|
export class TimelineCollector {
|
|
3
3
|
events = [];
|
|
4
4
|
counter = 0;
|
|
5
|
-
startTime = null;
|
|
6
|
-
excludedMs = 0;
|
|
7
|
-
start() {
|
|
8
|
-
this.startTime = performance.now();
|
|
9
|
-
}
|
|
10
|
-
elapsed() {
|
|
11
|
-
if (this.startTime === null)
|
|
12
|
-
throw new Error('TimelineCollector not started');
|
|
13
|
-
return Math.round(performance.now() - this.startTime - this.excludedMs);
|
|
14
|
-
}
|
|
15
|
-
/** Subtract wall-clock time that shouldn't count as scenario time (e.g. screenshot I/O). */
|
|
16
|
-
excludeTime(ms) {
|
|
17
|
-
this.excludedMs += ms;
|
|
18
|
-
}
|
|
19
5
|
nextId() {
|
|
20
6
|
return `ev-${String(++this.counter).padStart(3, '0')}`;
|
|
21
7
|
}
|
|
22
8
|
emit(event) {
|
|
23
9
|
const id = event.id ?? this.nextId();
|
|
24
|
-
const
|
|
25
|
-
const full = { ...event, id, timestampMs };
|
|
10
|
+
const full = { ...event, id };
|
|
26
11
|
this.events.push(full);
|
|
27
12
|
return id;
|
|
28
13
|
}
|
|
@@ -31,7 +16,7 @@ export class TimelineCollector {
|
|
|
31
16
|
}
|
|
32
17
|
finalize(metadata) {
|
|
33
18
|
const timeline = {
|
|
34
|
-
version:
|
|
19
|
+
version: 2,
|
|
35
20
|
metadata,
|
|
36
21
|
events: [...this.events],
|
|
37
22
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"timeline-collector.js","sourceRoot":"","sources":["../../../src/runtime/timeline-collector.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAIvD,MAAM,OAAO,iBAAiB;IACpB,MAAM,GAAoB,EAAE,CAAC;IAC7B,OAAO,GAAG,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"timeline-collector.js","sourceRoot":"","sources":["../../../src/runtime/timeline-collector.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAIvD,MAAM,OAAO,iBAAiB;IACpB,MAAM,GAAoB,EAAE,CAAC;IAC7B,OAAO,GAAG,CAAC,CAAC;IAEpB,MAAM;QACJ,OAAO,MAAM,MAAM,CAAC,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC;IACzD,CAAC;IAED,IAAI,CAAC,KAAmB;QACtB,MAAM,EAAE,GAAG,KAAK,CAAC,EAAE,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;QACrC,MAAM,IAAI,GAAG,EAAE,GAAG,KAAK,EAAE,EAAE,EAAmB,CAAC;QAC/C,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACvB,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,SAAS;QACP,OAAO,IAAI,CAAC,MAAM,CAAC;IACrB,CAAC;IAED,QAAQ,CAAC,QAA0B;QACjC,MAAM,QAAQ,GAAa;YACzB,OAAO,EAAE,CAAC;YACV,QAAQ;YACR,MAAM,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC;SACzB,CAAC;QAEF,MAAM,MAAM,GAAG,cAAc,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;QAClD,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;YACpB,MAAM,IAAI,KAAK,CAAC,qBAAqB,MAAM,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;QAC/D,CAAC;QAED,OAAO,QAAQ,CAAC;IAClB,CAAC;CACF"}
|