autokap 1.0.7 → 1.0.10
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/assets/cursors/macos.svg +4 -0
- package/assets/cursors/windows.svg +15 -0
- package/assets/skill/OPCODE-REFERENCE.md +635 -0
- package/assets/skill/README.md +39 -0
- package/assets/skill/SKILL.md +454 -468
- package/assets/skill/STUDIO-SKILL.md +476 -0
- package/assets/skill/references/examples.md +104 -0
- package/assets/skill/references/interactive-demo.md +225 -0
- package/assets/skill/references/mock-data.md +178 -0
- package/dist/action-verifier.d.ts +29 -0
- package/dist/action-verifier.js +133 -0
- package/dist/agent-action-recovery.d.ts +45 -0
- package/dist/agent-action-recovery.js +370 -0
- package/dist/agent-message-utils.d.ts +21 -0
- package/dist/agent-message-utils.js +77 -0
- package/dist/agent-url-utils.d.ts +30 -0
- package/dist/agent-url-utils.js +138 -0
- package/dist/agent.d.ts +92 -8
- package/dist/agent.js +2936 -781
- package/dist/ak-tree.d.ts +39 -0
- package/dist/ak-tree.js +368 -0
- package/dist/alt-text.d.ts +26 -0
- package/dist/alt-text.js +55 -0
- package/dist/auth-capture.d.ts +17 -0
- package/dist/auth-capture.js +197 -0
- package/dist/benchmark.d.ts +59 -0
- package/dist/benchmark.js +135 -0
- package/dist/billing-operation-logging.d.ts +3 -1
- package/dist/billing-operation-logging.js +4 -0
- package/dist/browser-bar.d.ts +14 -6
- package/dist/browser-bar.js +145 -8
- package/dist/browser-pool.d.ts +7 -0
- package/dist/browser-pool.js +15 -5
- package/dist/browser-utils.d.ts +31 -0
- package/dist/browser-utils.js +97 -0
- package/dist/browser.d.ts +61 -11
- package/dist/browser.js +1513 -59
- package/dist/capture-alt-text.js +2 -1
- package/dist/capture-encryption.d.ts +3 -1
- package/dist/capture-encryption.js +21 -6
- package/dist/capture-language-preflight.js +14 -0
- package/dist/capture-llm-page-identity.js +22 -10
- package/dist/capture-page-identity.d.ts +5 -7
- package/dist/capture-page-identity.js +211 -78
- package/dist/capture-preset-credentials.d.ts +50 -0
- package/dist/capture-preset-credentials.js +127 -0
- package/dist/capture-request-plan.d.ts +2 -2
- package/dist/capture-request-plan.js +64 -16
- package/dist/capture-run-optimizer.js +48 -33
- package/dist/capture-selector-memory.d.ts +5 -0
- package/dist/capture-selector-memory.js +18 -0
- package/dist/capture-strategy.d.ts +36 -0
- package/dist/capture-strategy.js +96 -0
- package/dist/capture-studio-sync.d.ts +1 -0
- package/dist/capture-studio-sync.js +9 -3
- package/dist/capture-surface-contract.d.ts +36 -0
- package/dist/capture-surface-contract.js +299 -0
- package/dist/capture-transition-engine.d.ts +28 -0
- package/dist/capture-transition-engine.js +292 -0
- package/dist/capture-variant-state.d.ts +2 -0
- package/dist/capture-variant-state.js +26 -0
- package/dist/capture-verification.d.ts +35 -0
- package/dist/capture-verification.js +95 -0
- package/dist/capture-viewport-lock.d.ts +48 -0
- package/dist/capture-viewport-lock.js +74 -0
- package/dist/circuit-breaker.d.ts +42 -0
- package/dist/circuit-breaker.js +119 -0
- package/dist/cli-config.d.ts +9 -1
- package/dist/cli-config.js +112 -7
- package/dist/cli-contract.d.ts +19 -0
- package/dist/cli-contract.js +173 -0
- package/dist/cli-runner-local.d.ts +12 -0
- package/dist/cli-runner-local.js +115 -0
- package/dist/cli-runner.d.ts +34 -0
- package/dist/cli-runner.js +580 -0
- package/dist/cli-utils.d.ts +0 -1
- package/dist/cli-utils.js +2 -5
- package/dist/cli.js +1011 -267
- package/dist/clip-begin-frame-recorder.d.ts +44 -0
- package/dist/clip-begin-frame-recorder.js +250 -0
- package/dist/clip-capture-backend.d.ts +25 -0
- package/dist/clip-capture-backend.js +189 -0
- package/dist/clip-capture-loop.d.ts +61 -0
- package/dist/clip-capture-loop.js +111 -0
- package/dist/clip-frame-recorder.d.ts +63 -0
- package/dist/clip-frame-recorder.js +305 -0
- package/dist/clip-orchestrator.js +9 -2
- package/dist/clip-postprocess.d.ts +31 -2
- package/dist/clip-postprocess.js +194 -68
- package/dist/clip-runtime.d.ts +18 -0
- package/dist/clip-runtime.js +67 -0
- package/dist/clip-scale.d.ts +10 -0
- package/dist/clip-scale.js +21 -0
- package/dist/clip-screencast-recorder.d.ts +42 -0
- package/dist/clip-screencast-recorder.js +242 -0
- package/dist/clip-sidecar.d.ts +54 -0
- package/dist/clip-sidecar.js +208 -0
- package/dist/cookie-dismiss.d.ts +2 -0
- package/dist/cookie-dismiss.js +48 -13
- package/dist/cost-logging.d.ts +8 -0
- package/dist/cost-logging.js +160 -46
- package/dist/cost-resolution-monitor.d.ts +16 -0
- package/dist/cost-resolution-monitor.js +34 -0
- package/dist/credential-templates.js +2 -2
- package/dist/cursor-overlay-script.d.ts +6 -0
- package/dist/cursor-overlay-script.js +169 -0
- package/dist/dom-css-purger.d.ts +65 -0
- package/dist/dom-css-purger.js +333 -0
- package/dist/dom-font-inliner.d.ts +45 -0
- package/dist/dom-font-inliner.js +148 -0
- package/dist/dom-patch-resolver.d.ts +52 -0
- package/dist/dom-patch-resolver.js +242 -0
- package/dist/dom-serializer.d.ts +82 -0
- package/dist/dom-serializer.js +378 -0
- package/dist/element-capture.d.ts +1 -41
- package/dist/element-capture.js +202 -446
- package/dist/env-validation.d.ts +5 -0
- package/dist/env-validation.js +63 -0
- package/dist/execution-schema.d.ts +4753 -0
- package/dist/execution-schema.js +563 -0
- package/dist/execution-types.d.ts +936 -0
- package/dist/execution-types.js +66 -0
- package/dist/fonts-loader.d.ts +14 -0
- package/dist/fonts-loader.js +55 -0
- package/dist/hybrid-navigator.js +12 -12
- package/dist/index.d.ts +11 -6
- package/dist/index.js +11 -4
- package/dist/legacy/agent-action-recovery.d.ts +45 -0
- package/dist/legacy/agent-action-recovery.js +370 -0
- package/dist/legacy/agent-message-utils.d.ts +21 -0
- package/dist/legacy/agent-message-utils.js +77 -0
- package/dist/legacy/agent-url-utils.d.ts +30 -0
- package/dist/legacy/agent-url-utils.js +138 -0
- package/dist/legacy/agent.d.ts +226 -0
- package/dist/legacy/agent.js +6666 -0
- package/dist/legacy/clip-orchestrator.d.ts +148 -0
- package/dist/legacy/clip-orchestrator.js +957 -0
- package/dist/legacy/credential-templates.d.ts +5 -0
- package/dist/legacy/credential-templates.js +60 -0
- package/dist/legacy/hybrid-navigator.d.ts +138 -0
- package/dist/legacy/hybrid-navigator.js +468 -0
- package/dist/legacy/llm-usage.d.ts +17 -0
- package/dist/legacy/llm-usage.js +45 -0
- package/dist/legacy/prompt-cache.d.ts +10 -0
- package/dist/legacy/prompt-cache.js +24 -0
- package/dist/legacy/prompts.d.ts +175 -0
- package/dist/legacy/prompts.js +1038 -0
- package/dist/legacy/tools.d.ts +4 -0
- package/dist/legacy/tools.js +216 -0
- package/dist/legacy/video-agent.d.ts +143 -0
- package/dist/legacy/video-agent.js +4788 -0
- package/dist/legacy/video-observation.d.ts +36 -0
- package/dist/legacy/video-observation.js +192 -0
- package/dist/legacy/video-planner.d.ts +12 -0
- package/dist/legacy/video-planner.js +501 -0
- package/dist/legacy/video-prompts.d.ts +37 -0
- package/dist/legacy/video-prompts.js +569 -0
- package/dist/legacy/video-tools.d.ts +3 -0
- package/dist/legacy/video-tools.js +59 -0
- package/dist/legacy/video-variant-state.d.ts +29 -0
- package/dist/legacy/video-variant-state.js +80 -0
- package/dist/legacy/vision-model.d.ts +17 -0
- package/dist/legacy/vision-model.js +74 -0
- package/dist/llm-healer.d.ts +55 -0
- package/dist/llm-healer.js +213 -0
- package/dist/llm-provider.d.ts +29 -0
- package/dist/llm-provider.js +83 -0
- package/dist/logger.d.ts +6 -2
- package/dist/logger.js +15 -1
- package/dist/mockup-html.js +35 -25
- package/dist/mockup.d.ts +95 -2
- package/dist/mockup.js +427 -166
- package/dist/mouse-animation.d.ts +2 -2
- package/dist/mouse-animation.js +34 -20
- package/dist/opcode-actions.d.ts +42 -0
- package/dist/opcode-actions.js +524 -0
- package/dist/opcode-runner.d.ts +51 -0
- package/dist/opcode-runner.js +779 -0
- package/dist/openrouter-client.d.ts +40 -0
- package/dist/openrouter-client.js +16 -0
- package/dist/overlay-engine.d.ts +24 -0
- package/dist/overlay-engine.js +176 -0
- package/dist/postcondition.d.ts +16 -0
- package/dist/postcondition.js +269 -0
- package/dist/program-patcher.d.ts +25 -0
- package/dist/program-patcher.js +44 -0
- package/dist/program-signing.d.ts +1094 -0
- package/dist/program-signing.js +140 -0
- package/dist/prompts.d.ts +13 -5
- package/dist/prompts.js +224 -351
- package/dist/provider-config.d.ts +17 -0
- package/dist/provider-config.js +42 -0
- package/dist/recovery-chain.d.ts +37 -0
- package/dist/recovery-chain.js +374 -0
- package/dist/remote-browser.d.ts +28 -4
- package/dist/remote-browser.js +60 -5
- package/dist/safari-browser-bar.d.ts +15 -0
- package/dist/safari-browser-bar.js +95 -0
- package/dist/safari-toolbar-asset.d.ts +15 -0
- package/dist/safari-toolbar-asset.js +12 -0
- package/dist/security.d.ts +2 -1
- package/dist/security.js +49 -10
- package/dist/selector-resolver.d.ts +34 -0
- package/dist/selector-resolver.js +181 -0
- package/dist/semantic-resolver.d.ts +35 -0
- package/dist/semantic-resolver.js +161 -0
- package/dist/server-capture-runtime.d.ts +5 -3
- package/dist/server-capture-runtime.js +42 -95
- package/dist/server-credit-usage.d.ts +2 -2
- package/dist/server-project-webhooks.d.ts +15 -1
- package/dist/server-project-webhooks.js +34 -8
- package/dist/server-screenshot-watermark.js +27 -5
- package/dist/session-profile.js +164 -1
- package/dist/sf-pro-symbols.d.ts +1 -0
- package/dist/sf-pro-symbols.js +55 -0
- package/dist/skill-packaging.d.ts +28 -0
- package/dist/skill-packaging.js +169 -0
- package/dist/smart-wait.d.ts +27 -0
- package/dist/smart-wait.js +81 -0
- package/dist/status-bar-render.d.ts +20 -0
- package/dist/status-bar-render.js +410 -0
- package/dist/status-bar.d.ts +9 -0
- package/dist/status-bar.js +298 -14
- package/dist/svg-browser-bar.d.ts +33 -0
- package/dist/svg-browser-bar.js +206 -0
- package/dist/svg-status-bar.d.ts +36 -0
- package/dist/svg-status-bar.js +597 -0
- package/dist/svg-text.d.ts +61 -0
- package/dist/svg-text.js +118 -0
- package/dist/tools.js +89 -451
- package/dist/types.d.ts +248 -7
- package/dist/types.js +23 -1
- package/dist/v2/action-verifier.d.ts +29 -0
- package/dist/v2/action-verifier.js +133 -0
- package/dist/v2/alt-text.d.ts +26 -0
- package/dist/v2/alt-text.js +55 -0
- package/dist/v2/benchmark.d.ts +59 -0
- package/dist/v2/benchmark.js +135 -0
- package/dist/v2/capture-strategy.d.ts +30 -0
- package/dist/v2/capture-strategy.js +67 -0
- package/dist/v2/capture-verification.d.ts +35 -0
- package/dist/v2/capture-verification.js +95 -0
- package/dist/v2/circuit-breaker.d.ts +42 -0
- package/dist/v2/circuit-breaker.js +119 -0
- package/dist/v2/cli-runner-local.d.ts +11 -0
- package/dist/v2/cli-runner-local.js +91 -0
- package/dist/v2/cli-runner.d.ts +34 -0
- package/dist/v2/cli-runner.js +300 -0
- package/dist/v2/compiler-prompts.d.ts +27 -0
- package/dist/v2/compiler-prompts.js +123 -0
- package/dist/v2/compiler.d.ts +37 -0
- package/dist/v2/compiler.js +147 -0
- package/dist/v2/explorer.d.ts +41 -0
- package/dist/v2/explorer.js +56 -0
- package/dist/v2/index.d.ts +37 -0
- package/dist/v2/index.js +31 -0
- package/dist/v2/llm-healer.d.ts +62 -0
- package/dist/v2/llm-healer.js +166 -0
- package/dist/v2/llm-provider.d.ts +29 -0
- package/dist/v2/llm-provider.js +80 -0
- package/dist/v2/opcode-runner.d.ts +47 -0
- package/dist/v2/opcode-runner.js +634 -0
- package/dist/v2/overlay-engine.d.ts +24 -0
- package/dist/v2/overlay-engine.js +150 -0
- package/dist/v2/postcondition.d.ts +16 -0
- package/dist/v2/postcondition.js +249 -0
- package/dist/v2/program-patcher.d.ts +25 -0
- package/dist/v2/program-patcher.js +44 -0
- package/dist/v2/recovery-chain.d.ts +30 -0
- package/dist/v2/recovery-chain.js +368 -0
- package/dist/v2/schema.d.ts +2580 -0
- package/dist/v2/schema.js +295 -0
- package/dist/v2/selector-resolver.d.ts +34 -0
- package/dist/v2/selector-resolver.js +181 -0
- package/dist/v2/semantic-resolver.d.ts +35 -0
- package/dist/v2/semantic-resolver.js +161 -0
- package/dist/v2/smart-wait.d.ts +27 -0
- package/dist/v2/smart-wait.js +81 -0
- package/dist/v2/types.d.ts +444 -0
- package/dist/v2/types.js +19 -0
- package/dist/v2/web-playwright-local.d.ts +69 -0
- package/dist/v2/web-playwright-local.js +392 -0
- package/dist/version.d.ts +1 -0
- package/dist/version.js +5 -0
- package/dist/video-agent.js +18 -13
- package/dist/video-planner.js +2 -1
- package/dist/video-prompts.js +3 -3
- package/dist/web-playwright-local.d.ts +156 -0
- package/dist/web-playwright-local.js +989 -0
- package/dist/ws-auth.js +4 -1
- package/dist/ws-broadcast.d.ts +34 -0
- package/dist/ws-broadcast.js +85 -0
- package/dist/ws-connection-limits.d.ts +12 -0
- package/dist/ws-connection-limits.js +44 -0
- package/dist/ws-handler-utils.d.ts +32 -0
- package/dist/ws-handler-utils.js +139 -0
- package/dist/ws-handler.js +294 -164
- package/dist/ws-metrics-server.d.ts +9 -0
- package/dist/ws-metrics-server.js +31 -0
- package/dist/ws-server.js +41 -1
- package/package.json +62 -35
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { Page } from 'playwright';
|
|
2
|
+
import type { RecordingResult } from './execution-types.js';
|
|
3
|
+
import type { ClipOptions } from './types.js';
|
|
4
|
+
interface ScrollOffset {
|
|
5
|
+
x: number;
|
|
6
|
+
y: number;
|
|
7
|
+
}
|
|
8
|
+
export declare function buildCdpViewportClip(viewport: {
|
|
9
|
+
width: number;
|
|
10
|
+
height: number;
|
|
11
|
+
}, effectiveScale: number, scrollOffset: ScrollOffset): {
|
|
12
|
+
x: number;
|
|
13
|
+
y: number;
|
|
14
|
+
width: number;
|
|
15
|
+
height: number;
|
|
16
|
+
scale: number;
|
|
17
|
+
};
|
|
18
|
+
export interface FrameSequenceClipRecorderOptions {
|
|
19
|
+
baseDir?: string;
|
|
20
|
+
viewport: {
|
|
21
|
+
width: number;
|
|
22
|
+
height: number;
|
|
23
|
+
};
|
|
24
|
+
requestedScale?: number;
|
|
25
|
+
effectiveScale: number;
|
|
26
|
+
clipOptions?: ClipOptions;
|
|
27
|
+
sourceFps?: number;
|
|
28
|
+
outputFps?: number;
|
|
29
|
+
}
|
|
30
|
+
export declare class FrameSequenceClipRecorder {
|
|
31
|
+
private readonly page;
|
|
32
|
+
private readonly options;
|
|
33
|
+
private readonly sourceFps;
|
|
34
|
+
private readonly outputFps;
|
|
35
|
+
private readonly workingDirPromise;
|
|
36
|
+
private readonly startedAt;
|
|
37
|
+
private readonly frames;
|
|
38
|
+
private stopRequested;
|
|
39
|
+
private loopPromise;
|
|
40
|
+
private finalResult;
|
|
41
|
+
private frameDimensions;
|
|
42
|
+
private lastCaptureError;
|
|
43
|
+
private readonly pendingWrites;
|
|
44
|
+
private pendingWriteError;
|
|
45
|
+
private cdpSession;
|
|
46
|
+
private cdpSessionPromise;
|
|
47
|
+
private cdpCaptureDisabled;
|
|
48
|
+
constructor(page: Page, options: FrameSequenceClipRecorderOptions);
|
|
49
|
+
start(): Promise<void>;
|
|
50
|
+
stop(): Promise<RecordingResult>;
|
|
51
|
+
abort(): Promise<void>;
|
|
52
|
+
private runCaptureLoop;
|
|
53
|
+
private captureFrame;
|
|
54
|
+
private captureFrameBuffer;
|
|
55
|
+
private readScrollOffset;
|
|
56
|
+
private captureFrameViaCdp;
|
|
57
|
+
private getCdpSession;
|
|
58
|
+
private disableCdpCapture;
|
|
59
|
+
private queueFrameWrite;
|
|
60
|
+
private waitForPendingWriteCapacity;
|
|
61
|
+
private flushPendingWrites;
|
|
62
|
+
}
|
|
63
|
+
export {};
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { postProcessClipFrames } from './clip-postprocess.js';
|
|
5
|
+
import { resolvePhysicalCaptureSize } from './clip-scale.js';
|
|
6
|
+
import { logger } from './logger.js';
|
|
7
|
+
const DEFAULT_CLIP_SOURCE_FPS = 30;
|
|
8
|
+
const DEFAULT_CLIP_OUTPUT_FPS = 30;
|
|
9
|
+
const INTERMEDIATE_FRAME_FORMAT = 'jpeg';
|
|
10
|
+
const INTERMEDIATE_FRAME_QUALITY = 80;
|
|
11
|
+
const MAX_PENDING_FRAME_WRITES = 4;
|
|
12
|
+
export function buildCdpViewportClip(viewport, effectiveScale, scrollOffset) {
|
|
13
|
+
return {
|
|
14
|
+
x: Math.max(0, scrollOffset.x),
|
|
15
|
+
y: Math.max(0, scrollOffset.y),
|
|
16
|
+
width: Math.max(1, Math.round(viewport.width)),
|
|
17
|
+
height: Math.max(1, Math.round(viewport.height)),
|
|
18
|
+
scale: Math.max(0.1, effectiveScale),
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
export class FrameSequenceClipRecorder {
|
|
22
|
+
page;
|
|
23
|
+
options;
|
|
24
|
+
sourceFps;
|
|
25
|
+
outputFps;
|
|
26
|
+
workingDirPromise;
|
|
27
|
+
startedAt = Date.now();
|
|
28
|
+
frames = [];
|
|
29
|
+
stopRequested = false;
|
|
30
|
+
loopPromise = null;
|
|
31
|
+
finalResult = null;
|
|
32
|
+
frameDimensions = null;
|
|
33
|
+
lastCaptureError = null;
|
|
34
|
+
pendingWrites = new Set();
|
|
35
|
+
pendingWriteError = null;
|
|
36
|
+
cdpSession = null;
|
|
37
|
+
cdpSessionPromise = null;
|
|
38
|
+
cdpCaptureDisabled = false;
|
|
39
|
+
constructor(page, options) {
|
|
40
|
+
this.page = page;
|
|
41
|
+
this.options = options;
|
|
42
|
+
this.sourceFps = Math.max(1, Math.round(options.sourceFps ?? DEFAULT_CLIP_SOURCE_FPS));
|
|
43
|
+
this.outputFps = Math.max(1, Math.round(options.outputFps ?? DEFAULT_CLIP_OUTPUT_FPS));
|
|
44
|
+
this.workingDirPromise = options.baseDir
|
|
45
|
+
? Promise.resolve(options.baseDir)
|
|
46
|
+
: fs.mkdtemp(path.join(os.tmpdir(), 'autokap-clip-frames-'));
|
|
47
|
+
this.frameDimensions = resolvePhysicalCaptureSize(options.viewport, options.effectiveScale)
|
|
48
|
+
?? {
|
|
49
|
+
width: Math.max(2, Math.round(options.viewport.width)) & ~1,
|
|
50
|
+
height: Math.max(2, Math.round(options.viewport.height)) & ~1,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
async start() {
|
|
54
|
+
const workingDir = await this.workingDirPromise;
|
|
55
|
+
await fs.mkdir(path.join(workingDir, 'frames'), { recursive: true });
|
|
56
|
+
await this.captureFrame(Date.now(), false);
|
|
57
|
+
this.loopPromise = this.runCaptureLoop();
|
|
58
|
+
}
|
|
59
|
+
async stop() {
|
|
60
|
+
if (this.finalResult) {
|
|
61
|
+
return this.finalResult;
|
|
62
|
+
}
|
|
63
|
+
this.stopRequested = true;
|
|
64
|
+
await this.loopPromise;
|
|
65
|
+
const stopAt = Date.now();
|
|
66
|
+
await this.captureFrame(stopAt, true);
|
|
67
|
+
await this.flushPendingWrites();
|
|
68
|
+
if (this.frames.length === 0) {
|
|
69
|
+
throw this.lastCaptureError ?? new Error('clip capture failed before any frame could be recorded');
|
|
70
|
+
}
|
|
71
|
+
if (this.pendingWriteError) {
|
|
72
|
+
throw this.pendingWriteError;
|
|
73
|
+
}
|
|
74
|
+
const workingDir = await this.workingDirPromise;
|
|
75
|
+
const outputDimensions = this.frameDimensions
|
|
76
|
+
?? resolvePhysicalCaptureSize(this.options.viewport, this.options.effectiveScale)
|
|
77
|
+
?? this.options.viewport;
|
|
78
|
+
const frameEntries = buildFrameEntries(this.frames, stopAt, this.sourceFps, this.outputFps);
|
|
79
|
+
const actualSourceFps = resolveActualSourceFps(this.frames, stopAt);
|
|
80
|
+
try {
|
|
81
|
+
const result = await postProcessClipFrames(frameEntries, workingDir, 'clip', {
|
|
82
|
+
...this.options.clipOptions,
|
|
83
|
+
mp4Width: outputDimensions.width,
|
|
84
|
+
mp4Height: outputDimensions.height,
|
|
85
|
+
});
|
|
86
|
+
if (!result.mp4Path || !result.thumbnailPath) {
|
|
87
|
+
throw new Error('clip packaging failed: missing MP4 or thumbnail output');
|
|
88
|
+
}
|
|
89
|
+
const [mp4Buffer, gifBuffer, thumbnailBuffer] = await Promise.all([
|
|
90
|
+
fs.readFile(result.mp4Path),
|
|
91
|
+
result.gifPath ? fs.readFile(result.gifPath) : Promise.resolve(null),
|
|
92
|
+
fs.readFile(result.thumbnailPath),
|
|
93
|
+
]);
|
|
94
|
+
const clipPackage = {
|
|
95
|
+
mp4: {
|
|
96
|
+
buffer: mp4Buffer,
|
|
97
|
+
mimeType: 'video/mp4',
|
|
98
|
+
},
|
|
99
|
+
...(gifBuffer
|
|
100
|
+
? {
|
|
101
|
+
gif: {
|
|
102
|
+
buffer: gifBuffer,
|
|
103
|
+
mimeType: 'image/gif',
|
|
104
|
+
},
|
|
105
|
+
}
|
|
106
|
+
: {}),
|
|
107
|
+
thumbnail: {
|
|
108
|
+
buffer: thumbnailBuffer,
|
|
109
|
+
mimeType: 'image/png',
|
|
110
|
+
},
|
|
111
|
+
captureMethod: 'frame_sequence',
|
|
112
|
+
requestedScale: this.options.requestedScale,
|
|
113
|
+
effectiveScale: this.options.effectiveScale,
|
|
114
|
+
sourceFps: this.sourceFps,
|
|
115
|
+
actualSourceFps,
|
|
116
|
+
outputFps: this.outputFps,
|
|
117
|
+
frameCount: this.frames.length,
|
|
118
|
+
dimensions: outputDimensions,
|
|
119
|
+
};
|
|
120
|
+
this.finalResult = {
|
|
121
|
+
buffer: mp4Buffer,
|
|
122
|
+
mimeType: 'video/mp4',
|
|
123
|
+
durationMs: result.durationMs,
|
|
124
|
+
trimStartMs: 0,
|
|
125
|
+
clipPackage,
|
|
126
|
+
};
|
|
127
|
+
return this.finalResult;
|
|
128
|
+
}
|
|
129
|
+
finally {
|
|
130
|
+
await this.cdpSession?.detach().catch(() => undefined);
|
|
131
|
+
await fs.rm(workingDir, { recursive: true, force: true }).catch(() => undefined);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
async abort() {
|
|
135
|
+
this.stopRequested = true;
|
|
136
|
+
await this.loopPromise?.catch(() => undefined);
|
|
137
|
+
await this.cdpSession?.detach().catch(() => undefined);
|
|
138
|
+
const workingDir = await this.workingDirPromise;
|
|
139
|
+
await fs.rm(workingDir, { recursive: true, force: true }).catch(() => undefined);
|
|
140
|
+
}
|
|
141
|
+
async runCaptureLoop() {
|
|
142
|
+
const frameIntervalMs = Math.max(1, Math.round(1000 / this.sourceFps));
|
|
143
|
+
let nextCaptureAt = this.startedAt + frameIntervalMs;
|
|
144
|
+
while (!this.stopRequested) {
|
|
145
|
+
const waitMs = nextCaptureAt - Date.now();
|
|
146
|
+
if (waitMs > 0) {
|
|
147
|
+
await sleep(waitMs);
|
|
148
|
+
}
|
|
149
|
+
if (this.stopRequested) {
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
await this.captureFrame(Date.now(), true);
|
|
153
|
+
nextCaptureAt += frameIntervalMs;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
async captureFrame(capturedAt, allowFailure) {
|
|
157
|
+
try {
|
|
158
|
+
const workingDir = await this.workingDirPromise;
|
|
159
|
+
const frameIndex = this.frames.length;
|
|
160
|
+
const framePath = path.join(workingDir, 'frames', `frame-${String(frameIndex).padStart(6, '0')}.jpg`);
|
|
161
|
+
const frameBuffer = await this.captureFrameBuffer();
|
|
162
|
+
await this.queueFrameWrite(framePath, frameBuffer);
|
|
163
|
+
this.frames.push({ path: framePath, capturedAt });
|
|
164
|
+
}
|
|
165
|
+
catch (error) {
|
|
166
|
+
const captureError = error instanceof Error ? error : new Error(String(error));
|
|
167
|
+
this.lastCaptureError = captureError;
|
|
168
|
+
if (!allowFailure) {
|
|
169
|
+
throw captureError;
|
|
170
|
+
}
|
|
171
|
+
logger.debug(`[clip] frame capture skipped: ${captureError.message}`);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
async captureFrameBuffer() {
|
|
175
|
+
const scrollOffset = await this.readScrollOffset();
|
|
176
|
+
const cdpBuffer = await this.captureFrameViaCdp(scrollOffset);
|
|
177
|
+
if (cdpBuffer) {
|
|
178
|
+
return cdpBuffer;
|
|
179
|
+
}
|
|
180
|
+
return this.page.screenshot({
|
|
181
|
+
type: INTERMEDIATE_FRAME_FORMAT,
|
|
182
|
+
quality: INTERMEDIATE_FRAME_QUALITY,
|
|
183
|
+
fullPage: false,
|
|
184
|
+
scale: 'device',
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
async readScrollOffset() {
|
|
188
|
+
try {
|
|
189
|
+
return await this.page.evaluate(() => ({
|
|
190
|
+
x: window.scrollX || window.pageXOffset || 0,
|
|
191
|
+
y: window.scrollY || window.pageYOffset || 0,
|
|
192
|
+
}));
|
|
193
|
+
}
|
|
194
|
+
catch {
|
|
195
|
+
return { x: 0, y: 0 };
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
async captureFrameViaCdp(scrollOffset) {
|
|
199
|
+
const session = await this.getCdpSession();
|
|
200
|
+
if (!session) {
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
try {
|
|
204
|
+
const screenshot = await session.send('Page.captureScreenshot', {
|
|
205
|
+
format: INTERMEDIATE_FRAME_FORMAT,
|
|
206
|
+
quality: INTERMEDIATE_FRAME_QUALITY,
|
|
207
|
+
optimizeForSpeed: true,
|
|
208
|
+
clip: buildCdpViewportClip(this.options.viewport, this.options.effectiveScale, scrollOffset),
|
|
209
|
+
});
|
|
210
|
+
return Buffer.from(screenshot.data, 'base64');
|
|
211
|
+
}
|
|
212
|
+
catch (error) {
|
|
213
|
+
this.disableCdpCapture(error);
|
|
214
|
+
return null;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
async getCdpSession() {
|
|
218
|
+
if (this.cdpCaptureDisabled) {
|
|
219
|
+
return null;
|
|
220
|
+
}
|
|
221
|
+
if (this.cdpSession) {
|
|
222
|
+
return this.cdpSession;
|
|
223
|
+
}
|
|
224
|
+
if (!this.cdpSessionPromise) {
|
|
225
|
+
this.cdpSessionPromise = (async () => {
|
|
226
|
+
try {
|
|
227
|
+
const context = this.page.context();
|
|
228
|
+
if (typeof context.newCDPSession !== 'function') {
|
|
229
|
+
this.cdpCaptureDisabled = true;
|
|
230
|
+
return null;
|
|
231
|
+
}
|
|
232
|
+
this.cdpSession = await context.newCDPSession(this.page);
|
|
233
|
+
return this.cdpSession;
|
|
234
|
+
}
|
|
235
|
+
catch (error) {
|
|
236
|
+
this.disableCdpCapture(error);
|
|
237
|
+
return null;
|
|
238
|
+
}
|
|
239
|
+
})();
|
|
240
|
+
}
|
|
241
|
+
return this.cdpSessionPromise;
|
|
242
|
+
}
|
|
243
|
+
disableCdpCapture(error) {
|
|
244
|
+
if (!this.cdpCaptureDisabled) {
|
|
245
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
246
|
+
logger.debug(`[clip] CDP frame capture unavailable, falling back to Playwright screenshot: ${message}`);
|
|
247
|
+
}
|
|
248
|
+
this.cdpCaptureDisabled = true;
|
|
249
|
+
this.cdpSessionPromise = Promise.resolve(null);
|
|
250
|
+
}
|
|
251
|
+
async queueFrameWrite(framePath, frameBuffer) {
|
|
252
|
+
await this.waitForPendingWriteCapacity();
|
|
253
|
+
if (this.pendingWriteError) {
|
|
254
|
+
throw this.pendingWriteError;
|
|
255
|
+
}
|
|
256
|
+
let writePromise;
|
|
257
|
+
writePromise = fs.writeFile(framePath, frameBuffer)
|
|
258
|
+
.catch((error) => {
|
|
259
|
+
this.pendingWriteError = error instanceof Error ? error : new Error(String(error));
|
|
260
|
+
throw this.pendingWriteError;
|
|
261
|
+
})
|
|
262
|
+
.finally(() => {
|
|
263
|
+
this.pendingWrites.delete(writePromise);
|
|
264
|
+
});
|
|
265
|
+
this.pendingWrites.add(writePromise);
|
|
266
|
+
}
|
|
267
|
+
async waitForPendingWriteCapacity() {
|
|
268
|
+
while (this.pendingWrites.size >= MAX_PENDING_FRAME_WRITES) {
|
|
269
|
+
await Promise.race(this.pendingWrites);
|
|
270
|
+
if (this.pendingWriteError) {
|
|
271
|
+
throw this.pendingWriteError;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
async flushPendingWrites() {
|
|
276
|
+
while (this.pendingWrites.size > 0) {
|
|
277
|
+
await Promise.allSettled(Array.from(this.pendingWrites));
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
function buildFrameEntries(frames, stopAt, sourceFps, outputFps) {
|
|
282
|
+
const entries = [];
|
|
283
|
+
const minFrameDurationMs = Math.max(1, Math.min(Math.round(1000 / sourceFps), Math.round(1000 / outputFps)));
|
|
284
|
+
for (let index = 0; index < frames.length; index += 1) {
|
|
285
|
+
const frame = frames[index];
|
|
286
|
+
const nextTimestamp = frames[index + 1]?.capturedAt ?? stopAt;
|
|
287
|
+
entries.push({
|
|
288
|
+
path: frame.path,
|
|
289
|
+
durationMs: Math.max(minFrameDurationMs, nextTimestamp - frame.capturedAt),
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
return entries;
|
|
293
|
+
}
|
|
294
|
+
function sleep(ms) {
|
|
295
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
296
|
+
}
|
|
297
|
+
function resolveActualSourceFps(frames, stopAt) {
|
|
298
|
+
if (frames.length <= 1) {
|
|
299
|
+
return frames.length;
|
|
300
|
+
}
|
|
301
|
+
const firstTimestamp = frames[0].capturedAt;
|
|
302
|
+
const durationSec = Math.max(0.001, (stopAt - firstTimestamp) / 1000);
|
|
303
|
+
return Math.round((frames.length / durationSec) * 100) / 100;
|
|
304
|
+
}
|
|
305
|
+
//# sourceMappingURL=clip-frame-recorder.js.map
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { mkdtemp } from 'node:fs/promises';
|
|
1
|
+
import { mkdtemp, rm } from 'node:fs/promises';
|
|
2
2
|
import os from 'node:os';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { logger } from './logger.js';
|
|
@@ -891,8 +891,9 @@ async function runClipVariantAttempt(clip, target, lang, theme, navResult, confi
|
|
|
891
891
|
clipOptions,
|
|
892
892
|
parentSignal: config.abortSignal,
|
|
893
893
|
run: async (_signal, _attempt, _phaseContext) => {
|
|
894
|
+
let outputDir = null;
|
|
894
895
|
try {
|
|
895
|
-
|
|
896
|
+
outputDir = await mkdtemp(path.join(os.tmpdir(), `clip-${clip.id}-`));
|
|
896
897
|
const outputScale = config.outputScale ?? 1;
|
|
897
898
|
const postResult = await postProcessClipRecording(recordResult.videoPath, outputDir, `${clip.id}_${variantId.replace(/:/g, '_')}`, {
|
|
898
899
|
...clipOptions,
|
|
@@ -906,6 +907,12 @@ async function runClipVariantAttempt(clip, target, lang, theme, navResult, confi
|
|
|
906
907
|
logger.error(`Post-processing failed for clip "${clip.name}" variant ${variantId}: ${err.message}`);
|
|
907
908
|
return { ok: false, reason: `Post-processing failed: ${err.message}` };
|
|
908
909
|
}
|
|
910
|
+
finally {
|
|
911
|
+
// Clean up temp directory to avoid orphaned files
|
|
912
|
+
if (outputDir) {
|
|
913
|
+
rm(outputDir, { recursive: true, force: true }).catch(() => { });
|
|
914
|
+
}
|
|
915
|
+
}
|
|
909
916
|
},
|
|
910
917
|
});
|
|
911
918
|
if (!postprocessPhase.ok) {
|
|
@@ -15,11 +15,40 @@ export declare function convertToGif(webmPath: string, outputPath: string, opts?
|
|
|
15
15
|
/**
|
|
16
16
|
* Convert a WebM recording to an MP4 with web-optimized settings.
|
|
17
17
|
*/
|
|
18
|
-
export declare function convertToMp4(webmPath: string, outputPath: string
|
|
18
|
+
export declare function convertToMp4(webmPath: string, outputPath: string, opts?: {
|
|
19
|
+
width?: number;
|
|
20
|
+
height?: number;
|
|
21
|
+
trimStartSec?: number;
|
|
22
|
+
maxDurationSec?: number;
|
|
23
|
+
holdLastFrameSec?: number;
|
|
24
|
+
fps?: number;
|
|
25
|
+
}): Promise<void>;
|
|
19
26
|
/**
|
|
20
27
|
* Extract the first frame of a WebM as a PNG thumbnail.
|
|
21
28
|
*/
|
|
22
29
|
export declare function extractThumbnail(webmPath: string, outputPath: string): Promise<void>;
|
|
30
|
+
/**
|
|
31
|
+
* Assemble a sequence of JPEG frames into an H.264 MP4.
|
|
32
|
+
* Expects frames named `frame_000000.jpg`, `frame_000001.jpg`, ... in framesDir.
|
|
33
|
+
*
|
|
34
|
+
* When `frameOffsetsMs` is provided (one offset per frame, ms from first
|
|
35
|
+
* frame), uses ffmpeg's concat demuxer to preserve the actual capture timing
|
|
36
|
+
* (VFR). This matters when the compositor is CPU-bound on heavy React UIs:
|
|
37
|
+
* frames arrive in bursts during idle periods and gaps during paint-heavy
|
|
38
|
+
* periods. Encoding those at uniform CFR would stretch bursts and compress
|
|
39
|
+
* gaps — viewers see temporally-distorted playback even though the average
|
|
40
|
+
* rate is correct. VFR lets each frame hold the screen for its true wall-
|
|
41
|
+
* clock duration.
|
|
42
|
+
*
|
|
43
|
+
* When `frameOffsetsMs` is absent, falls back to CFR at `fps` (useful for
|
|
44
|
+
* uniform-cadence captures like the synthetic smoke tests).
|
|
45
|
+
*/
|
|
46
|
+
export declare function assembleMp4FromFrames(opts: {
|
|
47
|
+
framesDir: string;
|
|
48
|
+
outputPath: string;
|
|
49
|
+
fps: number;
|
|
50
|
+
frameOffsetsMs?: number[];
|
|
51
|
+
}): Promise<void>;
|
|
23
52
|
/**
|
|
24
53
|
* Trim a recording: skip dead frames at the start and cap duration.
|
|
25
54
|
*/
|
|
@@ -37,6 +66,6 @@ export interface ClipPostProcessResult {
|
|
|
37
66
|
}
|
|
38
67
|
/**
|
|
39
68
|
* Full post-processing pipeline for a single clip recording:
|
|
40
|
-
*
|
|
69
|
+
* encode a normalized master MP4 → derive GIF + thumbnail → measure.
|
|
41
70
|
*/
|
|
42
71
|
export declare function postProcessClipRecording(webmPath: string, outputDir: string, clipId: string, options?: ClipOptions): Promise<ClipPostProcessResult>;
|