@hyperframes/engine 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +78 -0
- package/package.json +5 -3
- package/src/config.ts +32 -8
- package/src/index.ts +15 -17
- package/src/services/audioMixer.ts +33 -8
- package/src/services/browserManager.ts +7 -2
- package/src/services/chunkEncoder.ts +24 -3
- package/src/services/fileServer.ts +3 -1
- package/src/services/frameCapture.ts +46 -13
- package/src/services/parallelCoordinator.ts +25 -5
- package/src/services/screenshotService.ts +4 -1
- package/src/services/videoFrameExtractor.ts +19 -4
- package/src/services/videoFrameInjector.ts +9 -3
- package/src/utils/ffprobe.ts +25 -4
- package/src/utils/urlDownloader.ts +5 -1
- package/dist/config.d.ts +0 -62
- package/dist/config.d.ts.map +0 -1
- package/dist/config.js +0 -92
- package/dist/config.js.map +0 -1
- package/dist/config.test.d.ts +0 -2
- package/dist/config.test.d.ts.map +0 -1
- package/dist/config.test.js +0 -77
- package/dist/config.test.js.map +0 -1
- package/dist/index.d.ts +0 -49
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -55
- package/dist/index.js.map +0 -1
- package/dist/services/audioMixer.d.ts +0 -11
- package/dist/services/audioMixer.d.ts.map +0 -1
- package/dist/services/audioMixer.js +0 -306
- package/dist/services/audioMixer.js.map +0 -1
- package/dist/services/audioMixer.types.d.ts +0 -27
- package/dist/services/audioMixer.types.d.ts.map +0 -1
- package/dist/services/audioMixer.types.js +0 -2
- package/dist/services/audioMixer.types.js.map +0 -1
- package/dist/services/browserManager.d.ts +0 -29
- package/dist/services/browserManager.d.ts.map +0 -1
- package/dist/services/browserManager.js +0 -175
- package/dist/services/browserManager.js.map +0 -1
- package/dist/services/chunkEncoder.d.ts +0 -32
- package/dist/services/chunkEncoder.d.ts.map +0 -1
- package/dist/services/chunkEncoder.js +0 -347
- package/dist/services/chunkEncoder.js.map +0 -1
- package/dist/services/chunkEncoder.test.d.ts +0 -2
- package/dist/services/chunkEncoder.test.d.ts.map +0 -1
- package/dist/services/chunkEncoder.test.js +0 -24
- package/dist/services/chunkEncoder.test.js.map +0 -1
- package/dist/services/chunkEncoder.types.d.ts +0 -26
- package/dist/services/chunkEncoder.types.d.ts.map +0 -1
- package/dist/services/chunkEncoder.types.js +0 -2
- package/dist/services/chunkEncoder.types.js.map +0 -1
- package/dist/services/fileServer.d.ts +0 -25
- package/dist/services/fileServer.d.ts.map +0 -1
- package/dist/services/fileServer.js +0 -138
- package/dist/services/fileServer.js.map +0 -1
- package/dist/services/frameCapture.d.ts +0 -52
- package/dist/services/frameCapture.d.ts.map +0 -1
- package/dist/services/frameCapture.js +0 -317
- package/dist/services/frameCapture.js.map +0 -1
- package/dist/services/parallelCoordinator.d.ts +0 -40
- package/dist/services/parallelCoordinator.d.ts.map +0 -1
- package/dist/services/parallelCoordinator.js +0 -192
- package/dist/services/parallelCoordinator.js.map +0 -1
- package/dist/services/parallelCoordinator.test.d.ts +0 -2
- package/dist/services/parallelCoordinator.test.d.ts.map +0 -1
- package/dist/services/parallelCoordinator.test.js +0 -38
- package/dist/services/parallelCoordinator.test.js.map +0 -1
- package/dist/services/screenshotService.d.ts +0 -28
- package/dist/services/screenshotService.d.ts.map +0 -1
- package/dist/services/screenshotService.js +0 -190
- package/dist/services/screenshotService.js.map +0 -1
- package/dist/services/streamingEncoder.d.ts +0 -49
- package/dist/services/streamingEncoder.d.ts.map +0 -1
- package/dist/services/streamingEncoder.js +0 -227
- package/dist/services/streamingEncoder.js.map +0 -1
- package/dist/services/videoFrameExtractor.d.ts +0 -65
- package/dist/services/videoFrameExtractor.d.ts.map +0 -1
- package/dist/services/videoFrameExtractor.js +0 -302
- package/dist/services/videoFrameExtractor.js.map +0 -1
- package/dist/services/videoFrameInjector.d.ts +0 -17
- package/dist/services/videoFrameInjector.d.ts.map +0 -1
- package/dist/services/videoFrameInjector.js +0 -92
- package/dist/services/videoFrameInjector.js.map +0 -1
- package/dist/types.d.ts +0 -79
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -8
- package/dist/types.js.map +0 -1
- package/dist/utils/ffprobe.d.ts +0 -18
- package/dist/utils/ffprobe.d.ts.map +0 -1
- package/dist/utils/ffprobe.js +0 -137
- package/dist/utils/ffprobe.js.map +0 -1
- package/dist/utils/gpuEncoder.d.ts +0 -11
- package/dist/utils/gpuEncoder.d.ts.map +0 -1
- package/dist/utils/gpuEncoder.js +0 -55
- package/dist/utils/gpuEncoder.js.map +0 -1
- package/dist/utils/parityContract.d.ts +0 -3
- package/dist/utils/parityContract.d.ts.map +0 -1
- package/dist/utils/parityContract.js +0 -35
- package/dist/utils/parityContract.js.map +0 -1
- package/dist/utils/runFfmpeg.d.ts +0 -19
- package/dist/utils/runFfmpeg.d.ts.map +0 -1
- package/dist/utils/runFfmpeg.js +0 -62
- package/dist/utils/runFfmpeg.js.map +0 -1
- package/dist/utils/urlDownloader.d.ts +0 -3
- package/dist/utils/urlDownloader.d.ts.map +0 -1
- package/dist/utils/urlDownloader.js +0 -68
- package/dist/utils/urlDownloader.js.map +0 -1
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Frame Capture Service
|
|
3
|
-
*
|
|
4
|
-
* Uses Puppeteer to capture frames from any web page implementing the
|
|
5
|
-
* window.__hf seek protocol. Navigates to a file server URL, waits for
|
|
6
|
-
* the page to expose window.__hf, then captures frames deterministically
|
|
7
|
-
* via Chrome's BeginFrame API or Page.captureScreenshot fallback.
|
|
8
|
-
*/
|
|
9
|
-
import { type Browser, type Page } from "puppeteer-core";
|
|
10
|
-
import { type CaptureMode } from "./browserManager.js";
|
|
11
|
-
import { type EngineConfig } from "../config.js";
|
|
12
|
-
import type { CaptureOptions, CaptureResult, CaptureBufferResult, CapturePerfSummary } from "../types.js";
|
|
13
|
-
export type { CaptureOptions, CaptureResult, CaptureBufferResult, CapturePerfSummary };
|
|
14
|
-
/** Called after seeking, before screenshot. Use for video frame injection or other pre-capture work. */
|
|
15
|
-
export type BeforeCaptureHook = (page: Page, time: number) => Promise<void>;
|
|
16
|
-
export interface CaptureSession {
|
|
17
|
-
browser: Browser;
|
|
18
|
-
page: Page;
|
|
19
|
-
options: CaptureOptions;
|
|
20
|
-
serverUrl: string;
|
|
21
|
-
outputDir: string;
|
|
22
|
-
onBeforeCapture: BeforeCaptureHook | null;
|
|
23
|
-
isInitialized: boolean;
|
|
24
|
-
browserConsoleBuffer: string[];
|
|
25
|
-
capturePerf: {
|
|
26
|
-
frames: number;
|
|
27
|
-
seekMs: number;
|
|
28
|
-
beforeCaptureMs: number;
|
|
29
|
-
screenshotMs: number;
|
|
30
|
-
totalMs: number;
|
|
31
|
-
};
|
|
32
|
-
captureMode: CaptureMode;
|
|
33
|
-
beginFrameTimeTicks: number;
|
|
34
|
-
beginFrameIntervalMs: number;
|
|
35
|
-
beginFrameHasDamageCount: number;
|
|
36
|
-
beginFrameNoDamageCount: number;
|
|
37
|
-
/** Optional producer config — when set, overrides module-level env var constants. */
|
|
38
|
-
config?: Partial<EngineConfig>;
|
|
39
|
-
}
|
|
40
|
-
export declare function createCaptureSession(serverUrl: string, outputDir: string, options: CaptureOptions, onBeforeCapture?: BeforeCaptureHook | null, config?: Partial<EngineConfig>): Promise<CaptureSession>;
|
|
41
|
-
export declare function initializeSession(session: CaptureSession): Promise<void>;
|
|
42
|
-
export declare function captureFrame(session: CaptureSession, frameIndex: number, time: number): Promise<CaptureResult>;
|
|
43
|
-
/**
|
|
44
|
-
* Capture a frame and return the screenshot as a Buffer instead of writing to disk.
|
|
45
|
-
* Used by the streaming encode pipeline to pipe frames directly to FFmpeg stdin.
|
|
46
|
-
*/
|
|
47
|
-
export declare function captureFrameToBuffer(session: CaptureSession, frameIndex: number, time: number): Promise<CaptureBufferResult>;
|
|
48
|
-
export declare function closeCaptureSession(session: CaptureSession): Promise<void>;
|
|
49
|
-
export declare function prepareCaptureSessionForReuse(session: CaptureSession, outputDir: string, onBeforeCapture: BeforeCaptureHook | null): void;
|
|
50
|
-
export declare function getCompositionDuration(session: CaptureSession): Promise<number>;
|
|
51
|
-
export declare function getCapturePerfSummary(session: CaptureSession): CapturePerfSummary;
|
|
52
|
-
//# sourceMappingURL=frameCapture.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"frameCapture.d.ts","sourceRoot":"","sources":["../../src/services/frameCapture.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,KAAK,OAAO,EAAE,KAAK,IAAI,EAAsC,MAAM,gBAAgB,CAAC;AAM7F,OAAO,EAKL,KAAK,WAAW,EACjB,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EAAkB,KAAK,YAAY,EAAE,MAAM,cAAc,CAAC;AACjE,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAE1G,YAAY,EAAE,cAAc,EAAE,aAAa,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,CAAC;AAEvF,wGAAwG;AACxG,MAAM,MAAM,iBAAiB,GAAG,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;AAE5E,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,EAAE,IAAI,CAAC;IACX,OAAO,EAAE,cAAc,CAAC;IACxB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,eAAe,EAAE,iBAAiB,GAAG,IAAI,CAAC;IAC1C,aAAa,EAAE,OAAO,CAAC;IACvB,oBAAoB,EAAE,MAAM,EAAE,CAAC;IAC/B,WAAW,EAAE;QACX,MAAM,EAAE,MAAM,CAAC;QACf,MAAM,EAAE,MAAM,CAAC;QACf,eAAe,EAAE,MAAM,CAAC;QACxB,YAAY,EAAE,MAAM,CAAC;QACrB,OAAO,EAAE,MAAM,CAAC;KACjB,CAAC;IACF,WAAW,EAAE,WAAW,CAAC;IAEzB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,oBAAoB,EAAE,MAAM,CAAC;IAC7B,wBAAwB,EAAE,MAAM,CAAC;IACjC,uBAAuB,EAAE,MAAM,CAAC;IAChC,qFAAqF;IACrF,MAAM,CAAC,EAAE,OAAO,CAAC,YAAY,CAAC,CAAC;CAChC;AAMD,wBAAsB,oBAAoB,CACxC,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,cAAc,EACvB,eAAe,GAAE,iBAAiB,GAAG,IAAW,EAChD,MAAM,CAAC,EAAE,OAAO,CAAC,YAAY,CAAC,GAC7B,OAAO,CAAC,cAAc,CAAC,CAqDzB;AAED,wBAAsB,iBAAiB,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAiI9E;AAkID,wBAAsB,YAAY,CAAC,OAAO,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAUpH;AAED;;;GAGG;AACH,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,cAAc,EACvB,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,mBAAmB,CAAC,CAI9B;AAED,wBAAsB,mBAAmB,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAIhF;AAED,wBAAgB,6BAA6B,CAC3C,OAAO,EAAE,cAAc,EACvB,SAAS,EAAE,MAAM,EACjB,eAAe,EAAE,iBAAiB,GAAG,IAAI,GACxC,IAAI,CAeN;AAED,wBAAsB,sBAAsB,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAMrF;AAED,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,cAAc,GAAG,kBAAkB,CASjF"}
|
|
@@ -1,317 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Frame Capture Service
|
|
3
|
-
*
|
|
4
|
-
* Uses Puppeteer to capture frames from any web page implementing the
|
|
5
|
-
* window.__hf seek protocol. Navigates to a file server URL, waits for
|
|
6
|
-
* the page to expose window.__hf, then captures frames deterministically
|
|
7
|
-
* via Chrome's BeginFrame API or Page.captureScreenshot fallback.
|
|
8
|
-
*/
|
|
9
|
-
import { existsSync, mkdirSync, writeFileSync } from "fs";
|
|
10
|
-
import { join } from "path";
|
|
11
|
-
import { quantizeTimeToFrame } from "@hyperframes/core";
|
|
12
|
-
// ── Extracted modules ───────────────────────────────────────────────────────
|
|
13
|
-
import { acquireBrowser, releaseBrowser, buildChromeArgs, resolveHeadlessShellPath, } from "./browserManager.js";
|
|
14
|
-
import { beginFrameCapture, getCdpSession, pageScreenshotCapture } from "./screenshotService.js";
|
|
15
|
-
import { DEFAULT_CONFIG } from "../config.js";
|
|
16
|
-
// Circular buffer for browser console messages dumped on render failure diagnostics.
|
|
17
|
-
// Complex compositions produce 100+ messages; 50 was too small to capture relevant errors.
|
|
18
|
-
const BROWSER_CONSOLE_BUFFER_SIZE = 200;
|
|
19
|
-
export async function createCaptureSession(serverUrl, outputDir, options, onBeforeCapture = null, config) {
|
|
20
|
-
if (!existsSync(outputDir))
|
|
21
|
-
mkdirSync(outputDir, { recursive: true });
|
|
22
|
-
// Determine capture mode before building args — BeginFrame flags only apply on Linux
|
|
23
|
-
const headlessShell = resolveHeadlessShellPath(config);
|
|
24
|
-
const isLinux = process.platform === "linux";
|
|
25
|
-
const forceScreenshot = config?.forceScreenshot ?? DEFAULT_CONFIG.forceScreenshot;
|
|
26
|
-
const preMode = headlessShell && isLinux && !forceScreenshot ? "beginframe" : "screenshot";
|
|
27
|
-
const chromeArgs = buildChromeArgs({ width: options.width, height: options.height, captureMode: preMode }, config);
|
|
28
|
-
const { browser, captureMode } = await acquireBrowser(chromeArgs, config);
|
|
29
|
-
const page = await browser.newPage();
|
|
30
|
-
const browserVersion = await browser.version();
|
|
31
|
-
const expectedMajor = config?.expectedChromiumMajor;
|
|
32
|
-
if (Number.isFinite(expectedMajor)) {
|
|
33
|
-
const actualChromiumMajor = Number.parseInt((browserVersion.match(/(\d+)\./) || [])[1] || "", 10);
|
|
34
|
-
if (Number.isFinite(actualChromiumMajor) && actualChromiumMajor !== expectedMajor) {
|
|
35
|
-
throw new Error(`[FrameCapture] Chromium major mismatch expected=${expectedMajor} actual=${actualChromiumMajor} raw=${browserVersion}`);
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
const viewport = {
|
|
39
|
-
width: options.width,
|
|
40
|
-
height: options.height,
|
|
41
|
-
deviceScaleFactor: options.deviceScaleFactor || 1,
|
|
42
|
-
};
|
|
43
|
-
await page.setViewport(viewport);
|
|
44
|
-
return {
|
|
45
|
-
browser,
|
|
46
|
-
page,
|
|
47
|
-
options,
|
|
48
|
-
serverUrl,
|
|
49
|
-
outputDir,
|
|
50
|
-
onBeforeCapture,
|
|
51
|
-
isInitialized: false,
|
|
52
|
-
browserConsoleBuffer: [],
|
|
53
|
-
capturePerf: {
|
|
54
|
-
frames: 0,
|
|
55
|
-
seekMs: 0,
|
|
56
|
-
beforeCaptureMs: 0,
|
|
57
|
-
screenshotMs: 0,
|
|
58
|
-
totalMs: 0,
|
|
59
|
-
},
|
|
60
|
-
captureMode,
|
|
61
|
-
beginFrameTimeTicks: 0,
|
|
62
|
-
beginFrameIntervalMs: 1000 / Math.max(1, options.fps),
|
|
63
|
-
beginFrameHasDamageCount: 0,
|
|
64
|
-
beginFrameNoDamageCount: 0,
|
|
65
|
-
config,
|
|
66
|
-
};
|
|
67
|
-
}
|
|
68
|
-
export async function initializeSession(session) {
|
|
69
|
-
const { page, serverUrl, options } = session;
|
|
70
|
-
// Forward browser console to host with [Browser] prefix
|
|
71
|
-
page.on("console", (msg) => {
|
|
72
|
-
const type = msg.type();
|
|
73
|
-
const text = msg.text();
|
|
74
|
-
const prefix = type === "error" ? "[Browser:ERROR]" : type === "warn" ? "[Browser:WARN]" : "[Browser]";
|
|
75
|
-
console.log(`${prefix} ${text}`);
|
|
76
|
-
session.browserConsoleBuffer.push(`${prefix} ${text}`);
|
|
77
|
-
if (session.browserConsoleBuffer.length > BROWSER_CONSOLE_BUFFER_SIZE) {
|
|
78
|
-
session.browserConsoleBuffer.shift();
|
|
79
|
-
}
|
|
80
|
-
});
|
|
81
|
-
page.on("pageerror", (err) => {
|
|
82
|
-
const text = `[Browser:PAGEERROR] ${err instanceof Error ? err.message : String(err)}`;
|
|
83
|
-
console.error(text);
|
|
84
|
-
session.browserConsoleBuffer.push(text);
|
|
85
|
-
if (session.browserConsoleBuffer.length > BROWSER_CONSOLE_BUFFER_SIZE) {
|
|
86
|
-
session.browserConsoleBuffer.shift();
|
|
87
|
-
}
|
|
88
|
-
});
|
|
89
|
-
// Navigate to the file server
|
|
90
|
-
const url = `${serverUrl}/index.html`;
|
|
91
|
-
if (session.captureMode === "screenshot") {
|
|
92
|
-
// Screenshot mode: standard navigation, rAF works normally
|
|
93
|
-
await page.goto(url, { waitUntil: "domcontentloaded", timeout: 60000 });
|
|
94
|
-
const pageReadyTimeout = session.config?.playerReadyTimeout ?? DEFAULT_CONFIG.playerReadyTimeout;
|
|
95
|
-
await page.waitForFunction(`!!(window.__hf && typeof window.__hf.seek === "function" && window.__hf.duration > 0)`, { timeout: pageReadyTimeout });
|
|
96
|
-
// Wait for all video elements to have loaded metadata (dimensions + duration)
|
|
97
|
-
// Without this, frame 0 captures videos at their 300x150 default size
|
|
98
|
-
await page.waitForFunction(`document.querySelectorAll("video").length === 0 || Array.from(document.querySelectorAll("video")).every(v => v.readyState >= 1)`, { timeout: pageReadyTimeout });
|
|
99
|
-
await page.evaluate(`document.fonts?.ready`);
|
|
100
|
-
session.isInitialized = true;
|
|
101
|
-
return;
|
|
102
|
-
}
|
|
103
|
-
// In BeginFrame mode, Chrome's event loop is paused until we issue frames.
|
|
104
|
-
// Start a warmup loop to drive rAF/setTimeout callbacks during page load.
|
|
105
|
-
let warmupRunning = true;
|
|
106
|
-
let warmupTicks = 0;
|
|
107
|
-
let warmupFrameTime = 0;
|
|
108
|
-
const warmupIntervalMs = 33; // ~30fps
|
|
109
|
-
let warmupClient = null;
|
|
110
|
-
const warmupLoop = async () => {
|
|
111
|
-
try {
|
|
112
|
-
warmupClient = await getCdpSession(page);
|
|
113
|
-
await warmupClient.send("HeadlessExperimental.enable");
|
|
114
|
-
}
|
|
115
|
-
catch {
|
|
116
|
-
/* page not ready yet */
|
|
117
|
-
}
|
|
118
|
-
while (warmupRunning) {
|
|
119
|
-
if (warmupClient) {
|
|
120
|
-
try {
|
|
121
|
-
await warmupClient.send("HeadlessExperimental.beginFrame", {
|
|
122
|
-
frameTimeTicks: warmupFrameTime,
|
|
123
|
-
interval: warmupIntervalMs,
|
|
124
|
-
noDisplayUpdates: true,
|
|
125
|
-
});
|
|
126
|
-
warmupFrameTime += warmupIntervalMs;
|
|
127
|
-
warmupTicks++;
|
|
128
|
-
}
|
|
129
|
-
catch {
|
|
130
|
-
/* ignore warmup errors */
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
await new Promise((r) => setTimeout(r, warmupIntervalMs));
|
|
134
|
-
}
|
|
135
|
-
};
|
|
136
|
-
warmupLoop().catch(() => { });
|
|
137
|
-
await page.goto(url, { waitUntil: "domcontentloaded", timeout: 60000 });
|
|
138
|
-
// Poll for window.__hf readiness using manual evaluate loop (waitForFunction
|
|
139
|
-
// uses rAF polling internally, which won't fire in beginFrame mode).
|
|
140
|
-
const pageReadyTimeout = session.config?.playerReadyTimeout ?? DEFAULT_CONFIG.playerReadyTimeout;
|
|
141
|
-
const pollDeadline = Date.now() + pageReadyTimeout;
|
|
142
|
-
while (Date.now() < pollDeadline) {
|
|
143
|
-
const ready = await page.evaluate(`!!(window.__hf && typeof window.__hf.seek === "function" && window.__hf.duration > 0)`);
|
|
144
|
-
if (ready)
|
|
145
|
-
break;
|
|
146
|
-
await new Promise((r) => setTimeout(r, 100));
|
|
147
|
-
}
|
|
148
|
-
const pageReady = await page.evaluate(`!!(window.__hf && typeof window.__hf.seek === "function" && window.__hf.duration > 0)`);
|
|
149
|
-
if (!pageReady) {
|
|
150
|
-
warmupRunning = false;
|
|
151
|
-
throw new Error(`[FrameCapture] window.__hf not ready after ${pageReadyTimeout}ms. Page must expose window.__hf = { duration, seek }.`);
|
|
152
|
-
}
|
|
153
|
-
// Wait for all video elements to have loaded metadata (dimensions + duration).
|
|
154
|
-
// Without this, frame 0 captures videos at their 300x150 default size.
|
|
155
|
-
const videoDeadline = Date.now() + (session.config?.playerReadyTimeout ?? DEFAULT_CONFIG.playerReadyTimeout);
|
|
156
|
-
while (Date.now() < videoDeadline) {
|
|
157
|
-
const videosReady = await page.evaluate(`document.querySelectorAll("video").length === 0 || Array.from(document.querySelectorAll("video")).every(v => v.readyState >= 1)`);
|
|
158
|
-
if (videosReady)
|
|
159
|
-
break;
|
|
160
|
-
await new Promise((r) => setTimeout(r, 100));
|
|
161
|
-
}
|
|
162
|
-
// Font check (no rAF dependency — uses fonts.ready API directly)
|
|
163
|
-
await page.evaluate(`document.fonts?.ready`);
|
|
164
|
-
// Stop warmup
|
|
165
|
-
warmupRunning = false;
|
|
166
|
-
// Set base frame time ticks past warmup range
|
|
167
|
-
session.beginFrameTimeTicks = (warmupTicks + 10) * session.beginFrameIntervalMs;
|
|
168
|
-
session.isInitialized = true;
|
|
169
|
-
}
|
|
170
|
-
async function captureFrameErrorDiagnostics(session, frameIndex, time, error) {
|
|
171
|
-
try {
|
|
172
|
-
const diagnosticsDir = join(session.outputDir, "diagnostics");
|
|
173
|
-
if (!existsSync(diagnosticsDir))
|
|
174
|
-
mkdirSync(diagnosticsDir, { recursive: true });
|
|
175
|
-
const base = join(diagnosticsDir, `frame-error-${frameIndex}`);
|
|
176
|
-
await session.page.screenshot({ path: `${base}.png`, type: "png", fullPage: true });
|
|
177
|
-
const html = await session.page.content();
|
|
178
|
-
writeFileSync(`${base}.html`, html, "utf-8");
|
|
179
|
-
writeFileSync(`${base}.json`, JSON.stringify({
|
|
180
|
-
frameIndex,
|
|
181
|
-
time,
|
|
182
|
-
error: error.message,
|
|
183
|
-
stack: error.stack,
|
|
184
|
-
browserConsoleTail: session.browserConsoleBuffer.slice(-30),
|
|
185
|
-
}, null, 2), "utf-8");
|
|
186
|
-
return `${base}.json`;
|
|
187
|
-
}
|
|
188
|
-
catch {
|
|
189
|
-
return null;
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
/**
|
|
193
|
-
* Internal helper: seek timeline and inject video frames.
|
|
194
|
-
* Shared by captureFrame (disk) and captureFrameToBuffer (buffer).
|
|
195
|
-
* Returns timing breakdown for perf tracking.
|
|
196
|
-
*/
|
|
197
|
-
async function prepareFrameForCapture(session, frameIndex, time) {
|
|
198
|
-
const { page, options } = session;
|
|
199
|
-
if (!session.isInitialized) {
|
|
200
|
-
throw new Error("[FrameCapture] Session not initialized");
|
|
201
|
-
}
|
|
202
|
-
const quantizedTime = quantizeTimeToFrame(time, options.fps);
|
|
203
|
-
const seekStart = Date.now();
|
|
204
|
-
// Seek via the __hf protocol. The page's seek() implementation handles
|
|
205
|
-
// all framework-specific logic (GSAP stepping, CSS animation sync, etc.)
|
|
206
|
-
await page.evaluate((t) => {
|
|
207
|
-
if (window.__hf && typeof window.__hf.seek === "function") {
|
|
208
|
-
window.__hf.seek(t);
|
|
209
|
-
}
|
|
210
|
-
}, quantizedTime);
|
|
211
|
-
const seekMs = Date.now() - seekStart;
|
|
212
|
-
// Before-capture hook (e.g. video frame injection)
|
|
213
|
-
const beforeCaptureStart = Date.now();
|
|
214
|
-
if (session.onBeforeCapture) {
|
|
215
|
-
await session.onBeforeCapture(page, quantizedTime);
|
|
216
|
-
}
|
|
217
|
-
const beforeCaptureMs = Date.now() - beforeCaptureStart;
|
|
218
|
-
return { quantizedTime, seekMs, beforeCaptureMs };
|
|
219
|
-
}
|
|
220
|
-
/**
|
|
221
|
-
* Internal core: prepare, screenshot, and track perf.
|
|
222
|
-
* Shared by captureFrame (disk) and captureFrameToBuffer (buffer).
|
|
223
|
-
* Returns the screenshot buffer, quantized time, and total capture time.
|
|
224
|
-
*/
|
|
225
|
-
async function captureFrameCore(session, frameIndex, time) {
|
|
226
|
-
const { page, options } = session;
|
|
227
|
-
const startTime = Date.now();
|
|
228
|
-
try {
|
|
229
|
-
const { quantizedTime, seekMs, beforeCaptureMs } = await prepareFrameForCapture(session, frameIndex, time);
|
|
230
|
-
const screenshotStart = Date.now();
|
|
231
|
-
let screenshotBuffer;
|
|
232
|
-
if (session.captureMode === "beginframe") {
|
|
233
|
-
const frameTimeTicks = session.beginFrameTimeTicks + frameIndex * session.beginFrameIntervalMs;
|
|
234
|
-
const result = await beginFrameCapture(page, options, frameTimeTicks, session.beginFrameIntervalMs);
|
|
235
|
-
if (result.hasDamage)
|
|
236
|
-
session.beginFrameHasDamageCount++;
|
|
237
|
-
else
|
|
238
|
-
session.beginFrameNoDamageCount++;
|
|
239
|
-
screenshotBuffer = result.buffer;
|
|
240
|
-
}
|
|
241
|
-
else {
|
|
242
|
-
screenshotBuffer = await pageScreenshotCapture(page, options);
|
|
243
|
-
}
|
|
244
|
-
const screenshotMs = Date.now() - screenshotStart;
|
|
245
|
-
const captureTimeMs = Date.now() - startTime;
|
|
246
|
-
session.capturePerf.frames += 1;
|
|
247
|
-
session.capturePerf.seekMs += seekMs;
|
|
248
|
-
session.capturePerf.beforeCaptureMs += beforeCaptureMs;
|
|
249
|
-
session.capturePerf.screenshotMs += screenshotMs;
|
|
250
|
-
session.capturePerf.totalMs += captureTimeMs;
|
|
251
|
-
return { buffer: screenshotBuffer, quantizedTime, captureTimeMs };
|
|
252
|
-
}
|
|
253
|
-
catch (captureError) {
|
|
254
|
-
if (session.isInitialized) {
|
|
255
|
-
await captureFrameErrorDiagnostics(session, frameIndex, time, captureError instanceof Error ? captureError : new Error(String(captureError)));
|
|
256
|
-
}
|
|
257
|
-
throw captureError;
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
export async function captureFrame(session, frameIndex, time) {
|
|
261
|
-
const { options, outputDir } = session;
|
|
262
|
-
const { buffer, quantizedTime, captureTimeMs } = await captureFrameCore(session, frameIndex, time);
|
|
263
|
-
const ext = options.format === "png" ? "png" : "jpg";
|
|
264
|
-
const frameName = `frame_${String(frameIndex).padStart(6, "0")}.${ext}`;
|
|
265
|
-
const framePath = join(outputDir, frameName);
|
|
266
|
-
writeFileSync(framePath, buffer);
|
|
267
|
-
return { frameIndex, time: quantizedTime, path: framePath, captureTimeMs };
|
|
268
|
-
}
|
|
269
|
-
/**
|
|
270
|
-
* Capture a frame and return the screenshot as a Buffer instead of writing to disk.
|
|
271
|
-
* Used by the streaming encode pipeline to pipe frames directly to FFmpeg stdin.
|
|
272
|
-
*/
|
|
273
|
-
export async function captureFrameToBuffer(session, frameIndex, time) {
|
|
274
|
-
const { buffer, captureTimeMs } = await captureFrameCore(session, frameIndex, time);
|
|
275
|
-
return { buffer, captureTimeMs };
|
|
276
|
-
}
|
|
277
|
-
export async function closeCaptureSession(session) {
|
|
278
|
-
if (session.page)
|
|
279
|
-
await session.page.close().catch(() => { });
|
|
280
|
-
if (session.browser)
|
|
281
|
-
await releaseBrowser(session.browser, session.config);
|
|
282
|
-
session.isInitialized = false;
|
|
283
|
-
}
|
|
284
|
-
export function prepareCaptureSessionForReuse(session, outputDir, onBeforeCapture) {
|
|
285
|
-
if (!existsSync(outputDir)) {
|
|
286
|
-
mkdirSync(outputDir, { recursive: true });
|
|
287
|
-
}
|
|
288
|
-
session.outputDir = outputDir;
|
|
289
|
-
session.onBeforeCapture = onBeforeCapture;
|
|
290
|
-
session.capturePerf = {
|
|
291
|
-
frames: 0,
|
|
292
|
-
seekMs: 0,
|
|
293
|
-
beforeCaptureMs: 0,
|
|
294
|
-
screenshotMs: 0,
|
|
295
|
-
totalMs: 0,
|
|
296
|
-
};
|
|
297
|
-
session.beginFrameHasDamageCount = 0;
|
|
298
|
-
session.beginFrameNoDamageCount = 0;
|
|
299
|
-
}
|
|
300
|
-
export async function getCompositionDuration(session) {
|
|
301
|
-
if (!session.isInitialized)
|
|
302
|
-
throw new Error("[FrameCapture] Session not initialized");
|
|
303
|
-
return session.page.evaluate(() => {
|
|
304
|
-
return window.__hf?.duration ?? 0;
|
|
305
|
-
});
|
|
306
|
-
}
|
|
307
|
-
export function getCapturePerfSummary(session) {
|
|
308
|
-
const frames = Math.max(1, session.capturePerf.frames);
|
|
309
|
-
return {
|
|
310
|
-
frames: session.capturePerf.frames,
|
|
311
|
-
avgTotalMs: Math.round(session.capturePerf.totalMs / frames),
|
|
312
|
-
avgSeekMs: Math.round(session.capturePerf.seekMs / frames),
|
|
313
|
-
avgBeforeCaptureMs: Math.round(session.capturePerf.beforeCaptureMs / frames),
|
|
314
|
-
avgScreenshotMs: Math.round(session.capturePerf.screenshotMs / frames),
|
|
315
|
-
};
|
|
316
|
-
}
|
|
317
|
-
//# sourceMappingURL=frameCapture.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"frameCapture.js","sourceRoot":"","sources":["../../src/services/frameCapture.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Parallel Coordinator Service
|
|
3
|
-
*
|
|
4
|
-
* Coordinates parallel frame capture across multiple Puppeteer sessions.
|
|
5
|
-
* Auto-detects optimal worker count based on CPU/memory.
|
|
6
|
-
*/
|
|
7
|
-
import { type CaptureOptions, type CapturePerfSummary, type BeforeCaptureHook } from "./frameCapture.js";
|
|
8
|
-
import { type EngineConfig } from "../config.js";
|
|
9
|
-
export interface WorkerTask {
|
|
10
|
-
workerId: number;
|
|
11
|
-
startFrame: number;
|
|
12
|
-
endFrame: number;
|
|
13
|
-
outputDir: string;
|
|
14
|
-
}
|
|
15
|
-
export interface WorkerResult {
|
|
16
|
-
workerId: number;
|
|
17
|
-
framesCaptured: number;
|
|
18
|
-
startFrame: number;
|
|
19
|
-
endFrame: number;
|
|
20
|
-
durationMs: number;
|
|
21
|
-
perf?: CapturePerfSummary;
|
|
22
|
-
error?: string;
|
|
23
|
-
}
|
|
24
|
-
export interface ParallelProgress {
|
|
25
|
-
totalFrames: number;
|
|
26
|
-
capturedFrames: number;
|
|
27
|
-
activeWorkers: number;
|
|
28
|
-
workerProgress: Map<number, number>;
|
|
29
|
-
}
|
|
30
|
-
export declare function calculateOptimalWorkers(totalFrames: number, requested?: number, config?: Partial<Pick<EngineConfig, "concurrency" | "coresPerWorker" | "minParallelFrames" | "largeRenderThreshold">>): number;
|
|
31
|
-
export declare function distributeFrames(totalFrames: number, workerCount: number, workDir: string): WorkerTask[];
|
|
32
|
-
export declare function executeParallelCapture(serverUrl: string, workDir: string, tasks: WorkerTask[], captureOptions: CaptureOptions, createBeforeCaptureHook: () => BeforeCaptureHook | null, signal?: AbortSignal, onProgress?: (progress: ParallelProgress) => void, onFrameBuffer?: (frameIndex: number, buffer: Buffer) => Promise<void>, config?: Partial<EngineConfig>): Promise<WorkerResult[]>;
|
|
33
|
-
export declare function mergeWorkerFrames(workDir: string, tasks: WorkerTask[], outputDir: string): Promise<number>;
|
|
34
|
-
export declare function getSystemResources(): {
|
|
35
|
-
cpuCores: number;
|
|
36
|
-
totalMemoryMB: number;
|
|
37
|
-
freeMemoryMB: number;
|
|
38
|
-
recommendedWorkers: number;
|
|
39
|
-
};
|
|
40
|
-
//# sourceMappingURL=parallelCoordinator.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"parallelCoordinator.d.ts","sourceRoot":"","sources":["../../src/services/parallelCoordinator.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAOH,OAAO,EAQL,KAAK,cAAc,EACnB,KAAK,kBAAkB,EACvB,KAAK,iBAAiB,EACvB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAkB,KAAK,YAAY,EAAE,MAAM,cAAc,CAAC;AAEjE,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,EAAE,MAAM,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,kBAAkB,CAAC;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,gBAAgB;IAC/B,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACrC;AAQD,wBAAgB,uBAAuB,CACrC,WAAW,EAAE,MAAM,EACnB,SAAS,CAAC,EAAE,MAAM,EAClB,MAAM,CAAC,EAAE,OAAO,CAAC,IAAI,CAAC,YAAY,EAAE,aAAa,GAAG,gBAAgB,GAAG,mBAAmB,GAAG,sBAAsB,CAAC,CAAC,GACpH,MAAM,CA+CR;AAED,wBAAgB,gBAAgB,CAAC,WAAW,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,UAAU,EAAE,CAkBxG;AAoED,wBAAsB,sBAAsB,CAC1C,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,UAAU,EAAE,EACnB,cAAc,EAAE,cAAc,EAC9B,uBAAuB,EAAE,MAAM,iBAAiB,GAAG,IAAI,EACvD,MAAM,CAAC,EAAE,WAAW,EACpB,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,gBAAgB,KAAK,IAAI,EACjD,aAAa,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,EACrE,MAAM,CAAC,EAAE,OAAO,CAAC,YAAY,CAAC,GAC7B,OAAO,CAAC,YAAY,EAAE,CAAC,CA2CzB;AAED,wBAAsB,iBAAiB,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,EAAE,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CA4BhH;AAED,wBAAgB,kBAAkB,IAAI;IACpC,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,kBAAkB,EAAE,MAAM,CAAC;CAC5B,CAOA"}
|
|
@@ -1,192 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Parallel Coordinator Service
|
|
3
|
-
*
|
|
4
|
-
* Coordinates parallel frame capture across multiple Puppeteer sessions.
|
|
5
|
-
* Auto-detects optimal worker count based on CPU/memory.
|
|
6
|
-
*/
|
|
7
|
-
import { cpus, freemem, totalmem } from "os";
|
|
8
|
-
import { existsSync, mkdirSync, readdirSync } from "fs";
|
|
9
|
-
import { copyFile, rename } from "fs/promises";
|
|
10
|
-
import { join } from "path";
|
|
11
|
-
import { createCaptureSession, initializeSession, closeCaptureSession, captureFrame, captureFrameToBuffer, getCapturePerfSummary, } from "./frameCapture.js";
|
|
12
|
-
import { DEFAULT_CONFIG } from "../config.js";
|
|
13
|
-
const MEMORY_PER_WORKER_MB = 256;
|
|
14
|
-
const MIN_WORKERS = 1;
|
|
15
|
-
const ABSOLUTE_MAX_WORKERS = 10;
|
|
16
|
-
const DEFAULT_SAFE_MAX_WORKERS = 6;
|
|
17
|
-
const MIN_FRAMES_PER_WORKER = 30;
|
|
18
|
-
export function calculateOptimalWorkers(totalFrames, requested, config) {
|
|
19
|
-
// Resolve effective values: config overrides → DEFAULT_CONFIG fallback.
|
|
20
|
-
const effectiveMaxWorkers = (() => {
|
|
21
|
-
const concurrency = config?.concurrency ?? DEFAULT_CONFIG.concurrency;
|
|
22
|
-
if (concurrency !== "auto") {
|
|
23
|
-
return Math.max(MIN_WORKERS, Math.min(ABSOLUTE_MAX_WORKERS, Math.floor(concurrency)));
|
|
24
|
-
}
|
|
25
|
-
return DEFAULT_SAFE_MAX_WORKERS;
|
|
26
|
-
})();
|
|
27
|
-
const effectiveCoresPerWorker = config?.coresPerWorker ?? DEFAULT_CONFIG.coresPerWorker;
|
|
28
|
-
const effectiveMinParallelFrames = config?.minParallelFrames ?? DEFAULT_CONFIG.minParallelFrames;
|
|
29
|
-
const effectiveLargeRenderThreshold = config?.largeRenderThreshold ?? DEFAULT_CONFIG.largeRenderThreshold;
|
|
30
|
-
if (requested !== undefined) {
|
|
31
|
-
return Math.max(MIN_WORKERS, Math.min(effectiveMaxWorkers, requested));
|
|
32
|
-
}
|
|
33
|
-
if (totalFrames < MIN_FRAMES_PER_WORKER * 2)
|
|
34
|
-
return 1;
|
|
35
|
-
const cpuCount = cpus().length;
|
|
36
|
-
const cpuBasedWorkers = Math.max(1, cpuCount - 2);
|
|
37
|
-
// Use total memory instead of free memory — macOS reports misleadingly low
|
|
38
|
-
// freemem() because it aggressively caches files in "inactive" memory that
|
|
39
|
-
// is immediately reclaimable.
|
|
40
|
-
const totalMemoryMB = Math.round(totalmem() / (1024 * 1024));
|
|
41
|
-
const memoryBasedWorkers = Math.max(1, Math.floor((totalMemoryMB * 0.5) / MEMORY_PER_WORKER_MB));
|
|
42
|
-
const frameBasedWorkers = Math.floor(totalFrames / MIN_FRAMES_PER_WORKER);
|
|
43
|
-
const optimal = Math.min(cpuBasedWorkers, memoryBasedWorkers, frameBasedWorkers);
|
|
44
|
-
const minWorkersForJob = totalFrames >= effectiveMinParallelFrames ? 2 : MIN_WORKERS;
|
|
45
|
-
let finalWorkers = Math.max(minWorkersForJob, Math.min(effectiveMaxWorkers, optimal));
|
|
46
|
-
// Adaptive scaling: cap workers for large renders to prevent CPU contention.
|
|
47
|
-
// Each Chrome process (with SwiftShader) is CPU-heavy; too many on a long
|
|
48
|
-
// render causes protocol timeouts from compositor starvation.
|
|
49
|
-
// Scale proportionally to CPU count: ~3 cores per worker (benchmarked).
|
|
50
|
-
// 8 cores → 2 workers, 16 cores → 5 workers, 32 cores → 10 workers.
|
|
51
|
-
if (totalFrames >= effectiveLargeRenderThreshold) {
|
|
52
|
-
const cpuScaledMax = Math.max(2, Math.floor(cpuCount / effectiveCoresPerWorker));
|
|
53
|
-
if (finalWorkers > cpuScaledMax) {
|
|
54
|
-
finalWorkers = cpuScaledMax;
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
return finalWorkers;
|
|
58
|
-
}
|
|
59
|
-
export function distributeFrames(totalFrames, workerCount, workDir) {
|
|
60
|
-
const tasks = [];
|
|
61
|
-
const framesPerWorker = Math.ceil(totalFrames / workerCount);
|
|
62
|
-
for (let i = 0; i < workerCount; i++) {
|
|
63
|
-
const startFrame = i * framesPerWorker;
|
|
64
|
-
const endFrame = Math.min((i + 1) * framesPerWorker, totalFrames);
|
|
65
|
-
if (startFrame >= totalFrames)
|
|
66
|
-
break;
|
|
67
|
-
tasks.push({
|
|
68
|
-
workerId: i,
|
|
69
|
-
startFrame,
|
|
70
|
-
endFrame,
|
|
71
|
-
outputDir: join(workDir, `worker-${i}`),
|
|
72
|
-
});
|
|
73
|
-
}
|
|
74
|
-
return tasks;
|
|
75
|
-
}
|
|
76
|
-
async function executeWorkerTask(task, serverUrl, captureOptions, createBeforeCaptureHook, signal, onFrameCaptured, onFrameBuffer, config) {
|
|
77
|
-
const startTime = Date.now();
|
|
78
|
-
let framesCaptured = 0;
|
|
79
|
-
if (!existsSync(task.outputDir))
|
|
80
|
-
mkdirSync(task.outputDir, { recursive: true });
|
|
81
|
-
let session = null;
|
|
82
|
-
let perf;
|
|
83
|
-
try {
|
|
84
|
-
session = await createCaptureSession(serverUrl, task.outputDir, captureOptions, createBeforeCaptureHook(), config);
|
|
85
|
-
await initializeSession(session);
|
|
86
|
-
for (let i = task.startFrame; i < task.endFrame; i++) {
|
|
87
|
-
if (signal?.aborted) {
|
|
88
|
-
throw new Error("Parallel worker cancelled");
|
|
89
|
-
}
|
|
90
|
-
const time = i / captureOptions.fps;
|
|
91
|
-
if (onFrameBuffer) {
|
|
92
|
-
// Streaming mode: capture to buffer and invoke callback
|
|
93
|
-
const { buffer } = await captureFrameToBuffer(session, i, time);
|
|
94
|
-
await onFrameBuffer(i, buffer);
|
|
95
|
-
}
|
|
96
|
-
else {
|
|
97
|
-
// Disk mode: capture to file
|
|
98
|
-
await captureFrame(session, i, time);
|
|
99
|
-
}
|
|
100
|
-
framesCaptured++;
|
|
101
|
-
if (onFrameCaptured)
|
|
102
|
-
onFrameCaptured(task.workerId, i);
|
|
103
|
-
}
|
|
104
|
-
perf = getCapturePerfSummary(session);
|
|
105
|
-
return {
|
|
106
|
-
workerId: task.workerId,
|
|
107
|
-
framesCaptured,
|
|
108
|
-
startFrame: task.startFrame,
|
|
109
|
-
endFrame: task.endFrame,
|
|
110
|
-
durationMs: Date.now() - startTime,
|
|
111
|
-
perf,
|
|
112
|
-
};
|
|
113
|
-
}
|
|
114
|
-
catch (error) {
|
|
115
|
-
const errMsg = error instanceof Error ? error.message : String(error);
|
|
116
|
-
return {
|
|
117
|
-
workerId: task.workerId,
|
|
118
|
-
framesCaptured,
|
|
119
|
-
startFrame: task.startFrame,
|
|
120
|
-
endFrame: task.endFrame,
|
|
121
|
-
durationMs: Date.now() - startTime,
|
|
122
|
-
perf,
|
|
123
|
-
error: errMsg,
|
|
124
|
-
};
|
|
125
|
-
}
|
|
126
|
-
finally {
|
|
127
|
-
if (session)
|
|
128
|
-
await closeCaptureSession(session).catch(() => { });
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
export async function executeParallelCapture(serverUrl, workDir, tasks, captureOptions, createBeforeCaptureHook, signal, onProgress, onFrameBuffer, config) {
|
|
132
|
-
const totalFrames = tasks.reduce((sum, t) => sum + (t.endFrame - t.startFrame), 0);
|
|
133
|
-
const workerProgress = new Map();
|
|
134
|
-
for (const task of tasks)
|
|
135
|
-
workerProgress.set(task.workerId, 0);
|
|
136
|
-
const onFrameCaptured = (workerId, _frameIndex) => {
|
|
137
|
-
const current = workerProgress.get(workerId) || 0;
|
|
138
|
-
workerProgress.set(workerId, current + 1);
|
|
139
|
-
if (onProgress) {
|
|
140
|
-
const capturedFrames = Array.from(workerProgress.values()).reduce((a, b) => a + b, 0);
|
|
141
|
-
onProgress({
|
|
142
|
-
totalFrames,
|
|
143
|
-
capturedFrames,
|
|
144
|
-
activeWorkers: tasks.length,
|
|
145
|
-
workerProgress: new Map(workerProgress),
|
|
146
|
-
});
|
|
147
|
-
}
|
|
148
|
-
};
|
|
149
|
-
const results = await Promise.all(tasks.map((task) => executeWorkerTask(task, serverUrl, captureOptions, createBeforeCaptureHook, signal, onFrameCaptured, onFrameBuffer, config)));
|
|
150
|
-
const errors = results.filter((r) => r.error);
|
|
151
|
-
if (errors.length > 0) {
|
|
152
|
-
const errorMessages = errors.map((e) => `Worker ${e.workerId}: ${e.error}`).join("; ");
|
|
153
|
-
throw new Error(`[Parallel] Capture failed: ${errorMessages}`);
|
|
154
|
-
}
|
|
155
|
-
return results;
|
|
156
|
-
}
|
|
157
|
-
export async function mergeWorkerFrames(workDir, tasks, outputDir) {
|
|
158
|
-
if (!existsSync(outputDir))
|
|
159
|
-
mkdirSync(outputDir, { recursive: true });
|
|
160
|
-
let totalFrames = 0;
|
|
161
|
-
const sortedTasks = [...tasks].sort((a, b) => a.startFrame - b.startFrame);
|
|
162
|
-
for (const task of sortedTasks) {
|
|
163
|
-
if (!existsSync(task.outputDir)) {
|
|
164
|
-
continue;
|
|
165
|
-
}
|
|
166
|
-
const files = readdirSync(task.outputDir)
|
|
167
|
-
.filter((f) => f.startsWith("frame_") && (f.endsWith(".jpg") || f.endsWith(".png")))
|
|
168
|
-
.sort();
|
|
169
|
-
const copyTasks = files.map(async (file) => {
|
|
170
|
-
const sourcePath = join(task.outputDir, file);
|
|
171
|
-
const targetPath = join(outputDir, file);
|
|
172
|
-
try {
|
|
173
|
-
await rename(sourcePath, targetPath);
|
|
174
|
-
}
|
|
175
|
-
catch {
|
|
176
|
-
await copyFile(sourcePath, targetPath);
|
|
177
|
-
}
|
|
178
|
-
});
|
|
179
|
-
await Promise.all(copyTasks);
|
|
180
|
-
totalFrames += files.length;
|
|
181
|
-
}
|
|
182
|
-
return totalFrames;
|
|
183
|
-
}
|
|
184
|
-
export function getSystemResources() {
|
|
185
|
-
return {
|
|
186
|
-
cpuCores: cpus().length,
|
|
187
|
-
totalMemoryMB: Math.round(totalmem() / (1024 * 1024)),
|
|
188
|
-
freeMemoryMB: Math.round(freemem() / (1024 * 1024)),
|
|
189
|
-
recommendedWorkers: calculateOptimalWorkers(1000),
|
|
190
|
-
};
|
|
191
|
-
}
|
|
192
|
-
//# sourceMappingURL=parallelCoordinator.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"parallelCoordinator.js","sourceRoot":"","sources":["../../src/services/parallelCoordinator.ts"],"names":[],"mappings":"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"}
|