@hyperframes/producer 0.8.207 → 0.8.208
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/distributed.js +94 -48
- package/dist/hyperframe.manifest.json +1 -1
- package/dist/hyperframe.runtime.iife.js +67 -67
- package/dist/index.js +94 -48
- package/dist/public-server.js +8 -4
- package/dist/services/distributed/renderChunk.d.ts +2 -0
- package/dist/services/render/stages/captureStage.d.ts +1 -0
- package/dist/services/render/stages/extractVideosStage.d.ts +1 -1
- package/dist/services/renderOrchestrator.d.ts +1 -0
- package/package.json +7 -7
package/dist/index.js
CHANGED
|
@@ -9256,10 +9256,11 @@ var VideoExtractionStageError = class extends Error {
|
|
|
9256
9256
|
kind: failure.kind,
|
|
9257
9257
|
affectedElementCount: failure.count
|
|
9258
9258
|
}))
|
|
9259
|
-
)) {
|
|
9259
|
+
), ffmpegReason) {
|
|
9260
9260
|
const total = failures.reduce((sum2, failure) => sum2 + failure.count, 0);
|
|
9261
9261
|
const breakdown = failures.map((failure) => `${failure.kind}=${failure.count}`).join(",");
|
|
9262
|
-
|
|
9262
|
+
const reason = ffmpegReason ? `: ffmpeg said: ${ffmpegReason}` : "";
|
|
9263
|
+
super(`Video extraction failed for ${total} source(s) [${code}; ${breakdown}]${reason}`);
|
|
9263
9264
|
this.code = code;
|
|
9264
9265
|
this.retryable = retryable;
|
|
9265
9266
|
this.failures = failures;
|
|
@@ -9306,7 +9307,8 @@ function buildVideoExtractionStageError(result) {
|
|
|
9306
9307
|
retryable ? "VIDEO_EXTRACTION_FAILED" : "VIDEO_SOURCE_UNRENDERABLE",
|
|
9307
9308
|
retryable,
|
|
9308
9309
|
failures,
|
|
9309
|
-
extractionFailureMetadataFromResult(result)
|
|
9310
|
+
extractionFailureMetadataFromResult(result),
|
|
9311
|
+
result.errors.find((failure) => failure.ffmpegReason)?.ffmpegReason
|
|
9310
9312
|
);
|
|
9311
9313
|
}
|
|
9312
9314
|
function buildHdrProbeStageError(failures) {
|
|
@@ -10303,6 +10305,7 @@ async function runCaptureStage(input) {
|
|
|
10303
10305
|
dedupPerfs,
|
|
10304
10306
|
pageErrorLines: workerPageErrors,
|
|
10305
10307
|
openAttempt: input.openAttempt,
|
|
10308
|
+
verifyFirstSession: input.verifyFirstSession,
|
|
10306
10309
|
onProgress: (progress) => {
|
|
10307
10310
|
if (progress.latestWorkerPhase) {
|
|
10308
10311
|
if (progress.capturedFrames === 0) reportWorkerStartup(job, progress, onProgress);
|
|
@@ -12362,7 +12365,8 @@ async function executeDiskCaptureWithAdaptiveRetry(options) {
|
|
|
12362
12365
|
});
|
|
12363
12366
|
} : void 0,
|
|
12364
12367
|
void 0,
|
|
12365
|
-
options.cfg
|
|
12368
|
+
options.cfg,
|
|
12369
|
+
{ verifyFirstSession: options.verifyFirstSession }
|
|
12366
12370
|
);
|
|
12367
12371
|
pushWorkerDedupPerfs(workerResults, options.dedupPerfs);
|
|
12368
12372
|
if (options.pageErrorLines) pushWorkerPageErrors(workerResults, options.pageErrorLines);
|
|
@@ -20245,16 +20249,18 @@ function observedChunkCaptureMode(perfs) {
|
|
|
20245
20249
|
}
|
|
20246
20250
|
return mode;
|
|
20247
20251
|
}
|
|
20252
|
+
var PROBE_SELECTED_SCREENSHOT = "[renderChunk] probe selected screenshot capture";
|
|
20248
20253
|
function shouldRetryChunkCaptureWithScreenshot(error) {
|
|
20249
20254
|
const failure = classifyCaptureFailure4(error);
|
|
20250
20255
|
if (failure.kind === "cancelled" || failure.kind === "memory_exhaustion") return false;
|
|
20251
|
-
return /HeadlessExperimental\.beginFrame/i.test(failure.message) || /beginFrame probe timeout/i.test(failure.message) || /Another frame is pending|Frame still pending/i.test(failure.message);
|
|
20256
|
+
return /HeadlessExperimental\.beginFrame/i.test(failure.message) || /beginFrame probe timeout/i.test(failure.message) || failure.message.includes(PROBE_SELECTED_SCREENSHOT) || /Another frame is pending|Frame still pending/i.test(failure.message);
|
|
20252
20257
|
}
|
|
20253
20258
|
async function runCaptureWithScreenshotFallback(input) {
|
|
20254
20259
|
try {
|
|
20255
20260
|
return await input.run(input.forceScreenshot);
|
|
20256
20261
|
} catch (error) {
|
|
20257
|
-
if (input.forceScreenshot ||
|
|
20262
|
+
if (input.forceScreenshot || await input.canRetry?.() === false) throw error;
|
|
20263
|
+
if (!shouldRetryChunkCaptureWithScreenshot(error)) throw error;
|
|
20258
20264
|
input.onFallback?.(error);
|
|
20259
20265
|
await input.resetForScreenshotRetry();
|
|
20260
20266
|
return await input.run(true);
|
|
@@ -20463,7 +20469,24 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
20463
20469
|
let extraction;
|
|
20464
20470
|
const overlap = { current: null };
|
|
20465
20471
|
try {
|
|
20466
|
-
|
|
20472
|
+
const verifyInWorker = chunkWorkerCount > 1 && !forceScreenshotForChunk;
|
|
20473
|
+
let boxesMeasured;
|
|
20474
|
+
let boxesFailed;
|
|
20475
|
+
const boxesReady = new Promise((resolve13, reject) => {
|
|
20476
|
+
boxesMeasured = resolve13;
|
|
20477
|
+
boxesFailed = reject;
|
|
20478
|
+
});
|
|
20479
|
+
boxesReady.catch(() => {
|
|
20480
|
+
});
|
|
20481
|
+
const measureBoxes = async (page) => {
|
|
20482
|
+
shownBoxes = await measureVideoShownBoxes(
|
|
20483
|
+
page,
|
|
20484
|
+
[...shownFrames.keys()],
|
|
20485
|
+
chunkFrameNumbers.map((frame) => frame * chunkFps.den / chunkFps.num)
|
|
20486
|
+
);
|
|
20487
|
+
boxesMeasured();
|
|
20488
|
+
};
|
|
20489
|
+
if (chunkWorkerCount === 1) {
|
|
20467
20490
|
const bootStarted = Date.now();
|
|
20468
20491
|
session = await createVerifiedDistributedCaptureSession(
|
|
20469
20492
|
fileServer.url,
|
|
@@ -20472,11 +20495,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
20472
20495
|
cfg
|
|
20473
20496
|
);
|
|
20474
20497
|
sessionBootMs = Date.now() - bootStarted;
|
|
20475
|
-
|
|
20476
|
-
session.page,
|
|
20477
|
-
[...shownFrames.keys()],
|
|
20478
|
-
chunkFrameNumbers.map((frame) => frame * chunkFps.den / chunkFps.num)
|
|
20479
|
-
);
|
|
20498
|
+
await measureBoxes(session.page);
|
|
20480
20499
|
const browserSelectedScreenshot = session.launchCaptureMode === "screenshot";
|
|
20481
20500
|
const beginFrameStalled = !browserSelectedScreenshot && await beginFrameSessionNeedsScreenshotFallback(session);
|
|
20482
20501
|
if (browserSelectedScreenshot) {
|
|
@@ -20485,10 +20504,6 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
20485
20504
|
"[renderChunk] Browser capability probe selected screenshot capture for the entire chunk",
|
|
20486
20505
|
{ chunkIndex }
|
|
20487
20506
|
);
|
|
20488
|
-
if (chunkWorkerCount > 1) {
|
|
20489
|
-
await closeCaptureSession11(session);
|
|
20490
|
-
session = null;
|
|
20491
|
-
}
|
|
20492
20507
|
} else if (beginFrameStalled) {
|
|
20493
20508
|
forceScreenshotForChunk = true;
|
|
20494
20509
|
log.warn(
|
|
@@ -20498,41 +20513,66 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
20498
20513
|
await closeCaptureSession11(session).catch(() => {
|
|
20499
20514
|
});
|
|
20500
20515
|
session = null;
|
|
20501
|
-
|
|
20502
|
-
|
|
20503
|
-
|
|
20504
|
-
|
|
20505
|
-
|
|
20506
|
-
|
|
20507
|
-
|
|
20508
|
-
|
|
20509
|
-
|
|
20510
|
-
|
|
20511
|
-
|
|
20512
|
-
|
|
20516
|
+
const screenshotBootStarted = Date.now();
|
|
20517
|
+
const screenshotCfg = {
|
|
20518
|
+
...cfg,
|
|
20519
|
+
forceScreenshot: true,
|
|
20520
|
+
forceScreenshotExplicitlyOptedOut: false
|
|
20521
|
+
};
|
|
20522
|
+
session = await createVerifiedDistributedCaptureSession(
|
|
20523
|
+
fileServer.url,
|
|
20524
|
+
framesDir,
|
|
20525
|
+
captureOptions,
|
|
20526
|
+
screenshotCfg
|
|
20527
|
+
);
|
|
20528
|
+
sessionBootMs += Date.now() - screenshotBootStarted;
|
|
20529
|
+
}
|
|
20530
|
+
} else if (!verifyInWorker) {
|
|
20531
|
+
boxesMeasured();
|
|
20532
|
+
}
|
|
20533
|
+
let verification;
|
|
20534
|
+
const verifyFirstWorker = (first) => verification ??= (async () => {
|
|
20535
|
+
sessionBootMs = Date.now() - verifyStarted;
|
|
20536
|
+
try {
|
|
20537
|
+
await measureBoxes(first.page);
|
|
20538
|
+
const selectedScreenshot = first.launchCaptureMode === "screenshot";
|
|
20539
|
+
if (selectedScreenshot || await beginFrameSessionNeedsScreenshotFallback(first)) {
|
|
20540
|
+
forceScreenshotForChunk = true;
|
|
20541
|
+
log.warn(
|
|
20542
|
+
selectedScreenshot ? "[renderChunk] Browser capability probe selected screenshot capture for the entire chunk" : "[renderChunk] BeginFrame liveness probe failed; using screenshot capture for the entire chunk",
|
|
20543
|
+
{ chunkIndex }
|
|
20513
20544
|
);
|
|
20514
|
-
|
|
20545
|
+
throw new Error(PROBE_SELECTED_SCREENSHOT);
|
|
20515
20546
|
}
|
|
20516
|
-
}
|
|
20517
|
-
|
|
20518
|
-
|
|
20547
|
+
} catch (error) {
|
|
20548
|
+
if (!forceScreenshotForChunk) {
|
|
20549
|
+
verifyFailure = error;
|
|
20550
|
+
boxesFailed(error);
|
|
20551
|
+
}
|
|
20552
|
+
throw error;
|
|
20519
20553
|
}
|
|
20520
|
-
}
|
|
20521
|
-
|
|
20522
|
-
|
|
20523
|
-
|
|
20524
|
-
|
|
20525
|
-
|
|
20526
|
-
|
|
20527
|
-
|
|
20528
|
-
|
|
20529
|
-
|
|
20530
|
-
|
|
20531
|
-
|
|
20532
|
-
|
|
20533
|
-
|
|
20534
|
-
|
|
20554
|
+
})();
|
|
20555
|
+
let verifyStarted = 0;
|
|
20556
|
+
let verifyFailure;
|
|
20557
|
+
const deferredVideoFrames = planVideos ? boxesReady.then(() => {
|
|
20558
|
+
const extractStarted = Date.now();
|
|
20559
|
+
return extractDeferredVideoFramesForChunk({
|
|
20560
|
+
planDir,
|
|
20561
|
+
planVideos,
|
|
20562
|
+
frames: chunkFrameNumbers,
|
|
20563
|
+
fps: chunkFps,
|
|
20564
|
+
outputRoot: videoFramesDir,
|
|
20565
|
+
cfg,
|
|
20566
|
+
remoteSources,
|
|
20567
|
+
shownBoxes
|
|
20568
|
+
}).then((frames) => {
|
|
20569
|
+
videoExtractMs = Date.now() - extractStarted;
|
|
20570
|
+
return frames;
|
|
20571
|
+
});
|
|
20572
|
+
}) : boxesReady.then(() => []);
|
|
20535
20573
|
extraction = deferredVideoFrames;
|
|
20574
|
+
deferredVideoFrames.catch(() => {
|
|
20575
|
+
});
|
|
20536
20576
|
const videoFrameLookup = planVideos && planVideos.extracted.length > 0 ? deferredVideoFrames.then(
|
|
20537
20577
|
(deferred) => createFrameLookupTable5(
|
|
20538
20578
|
planVideos.videos,
|
|
@@ -20611,6 +20651,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
20611
20651
|
const captureStarted = Date.now();
|
|
20612
20652
|
captureMode = await runCaptureWithScreenshotFallback({
|
|
20613
20653
|
forceScreenshot: forceScreenshotForChunk,
|
|
20654
|
+
canRetry: () => verifyFailure === void 0,
|
|
20614
20655
|
// fallow-ignore-next-line complexity
|
|
20615
20656
|
run: async (forceScreenshot) => {
|
|
20616
20657
|
const captureCfg = cfg.forceScreenshot === forceScreenshot ? cfg : {
|
|
@@ -20640,6 +20681,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
20640
20681
|
}
|
|
20641
20682
|
const captureSession = session;
|
|
20642
20683
|
session = null;
|
|
20684
|
+
verifyStarted = Date.now();
|
|
20643
20685
|
overlap.current = canEncodeWhileCapturing(encodeInput, chunkWorkerCount, cpus3().length) ? encodeWhileCapturing(encodeInput, framesInChunk) : null;
|
|
20644
20686
|
await observeRenderStage(
|
|
20645
20687
|
new RenderObservabilityRecorder({ pipelineStartMs: start, log }),
|
|
@@ -20667,7 +20709,8 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
20667
20709
|
frameRange: range,
|
|
20668
20710
|
onFrameLanded: (frame) => overlap.current?.onFrameLanded(frame),
|
|
20669
20711
|
interleaveFrames: interleavesCapture(overlap.current !== null, options),
|
|
20670
|
-
openAttempt
|
|
20712
|
+
openAttempt,
|
|
20713
|
+
verifyFirstSession: forceScreenshot || !verifyInWorker ? void 0 : verifyFirstWorker
|
|
20671
20714
|
}),
|
|
20672
20715
|
{ stall: frameStallRule(job) }
|
|
20673
20716
|
).catch(async (captureError) => {
|
|
@@ -20679,6 +20722,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
20679
20722
|
},
|
|
20680
20723
|
resetForScreenshotRetry: () => {
|
|
20681
20724
|
rmSync17(workDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
|
|
20725
|
+
boxesMeasured();
|
|
20682
20726
|
mkdirSync19(framesDir, { recursive: true });
|
|
20683
20727
|
capturePerfs.length = 0;
|
|
20684
20728
|
captureAttempts.length = 0;
|
|
@@ -20694,6 +20738,8 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
20694
20738
|
);
|
|
20695
20739
|
}
|
|
20696
20740
|
}).catch(async (captureError) => {
|
|
20741
|
+
if (verifyFailure !== void 0) throw verifyFailure;
|
|
20742
|
+
boxesFailed(captureError);
|
|
20697
20743
|
await deferredVideoFrames;
|
|
20698
20744
|
throw captureError;
|
|
20699
20745
|
});
|
package/dist/public-server.js
CHANGED
|
@@ -9242,10 +9242,11 @@ var VideoExtractionStageError = class extends Error {
|
|
|
9242
9242
|
kind: failure.kind,
|
|
9243
9243
|
affectedElementCount: failure.count
|
|
9244
9244
|
}))
|
|
9245
|
-
)) {
|
|
9245
|
+
), ffmpegReason) {
|
|
9246
9246
|
const total = failures.reduce((sum, failure) => sum + failure.count, 0);
|
|
9247
9247
|
const breakdown = failures.map((failure) => `${failure.kind}=${failure.count}`).join(",");
|
|
9248
|
-
|
|
9248
|
+
const reason = ffmpegReason ? `: ffmpeg said: ${ffmpegReason}` : "";
|
|
9249
|
+
super(`Video extraction failed for ${total} source(s) [${code}; ${breakdown}]${reason}`);
|
|
9249
9250
|
this.code = code;
|
|
9250
9251
|
this.retryable = retryable;
|
|
9251
9252
|
this.failures = failures;
|
|
@@ -9287,7 +9288,8 @@ function buildVideoExtractionStageError(result) {
|
|
|
9287
9288
|
retryable ? "VIDEO_EXTRACTION_FAILED" : "VIDEO_SOURCE_UNRENDERABLE",
|
|
9288
9289
|
retryable,
|
|
9289
9290
|
failures,
|
|
9290
|
-
extractionFailureMetadataFromResult(result)
|
|
9291
|
+
extractionFailureMetadataFromResult(result),
|
|
9292
|
+
result.errors.find((failure) => failure.ffmpegReason)?.ffmpegReason
|
|
9291
9293
|
);
|
|
9292
9294
|
}
|
|
9293
9295
|
function buildHdrProbeStageError(failures) {
|
|
@@ -10284,6 +10286,7 @@ async function runCaptureStage(input) {
|
|
|
10284
10286
|
dedupPerfs,
|
|
10285
10287
|
pageErrorLines: workerPageErrors,
|
|
10286
10288
|
openAttempt: input.openAttempt,
|
|
10289
|
+
verifyFirstSession: input.verifyFirstSession,
|
|
10287
10290
|
onProgress: (progress) => {
|
|
10288
10291
|
if (progress.latestWorkerPhase) {
|
|
10289
10292
|
if (progress.capturedFrames === 0) reportWorkerStartup(job, progress, onProgress);
|
|
@@ -12343,7 +12346,8 @@ async function executeDiskCaptureWithAdaptiveRetry(options) {
|
|
|
12343
12346
|
});
|
|
12344
12347
|
} : void 0,
|
|
12345
12348
|
void 0,
|
|
12346
|
-
options.cfg
|
|
12349
|
+
options.cfg,
|
|
12350
|
+
{ verifyFirstSession: options.verifyFirstSession }
|
|
12347
12351
|
);
|
|
12348
12352
|
pushWorkerDedupPerfs(workerResults, options.dedupPerfs);
|
|
12349
12353
|
if (options.pageErrorLines) pushWorkerPageErrors(workerResults, options.pageErrorLines);
|
|
@@ -156,6 +156,8 @@ export declare function shouldRetryChunkCaptureWithScreenshot(error: unknown): b
|
|
|
156
156
|
*/
|
|
157
157
|
export declare function runCaptureWithScreenshotFallback<T>(input: {
|
|
158
158
|
forceScreenshot: boolean;
|
|
159
|
+
/** A failure that is not capture's (the probe's own) is never retried. */
|
|
160
|
+
canRetry?: () => boolean | Promise<boolean>;
|
|
159
161
|
run: (forceScreenshot: boolean) => Promise<T>;
|
|
160
162
|
resetForScreenshotRetry: () => Promise<void> | void;
|
|
161
163
|
onFallback?: (error: unknown) => void;
|
|
@@ -88,6 +88,7 @@ export interface CaptureStageInput {
|
|
|
88
88
|
interleaveFrames?: boolean;
|
|
89
89
|
/** Each capture attempt opens one, so a stalled attempt is retried by the ladders below. */
|
|
90
90
|
openAttempt?: OpenStallAttempt;
|
|
91
|
+
verifyFirstSession?: (session: CaptureSession) => Promise<void>;
|
|
91
92
|
}
|
|
92
93
|
export interface CaptureStageResult {
|
|
93
94
|
/** Final worker count after any adaptive retry. */
|
|
@@ -117,7 +117,7 @@ export declare class VideoExtractionStageError extends Error {
|
|
|
117
117
|
* validating and applying policy to it.
|
|
118
118
|
*/
|
|
119
119
|
readonly publicMetadata: Readonly<Record<string, unknown>>;
|
|
120
|
-
constructor(code: VideoExtractionStageErrorCode, retryable: boolean, failures: readonly VideoExtractionStageFailureSummary[], extractionFailure?: ExtractionFailureMetadataV1);
|
|
120
|
+
constructor(code: VideoExtractionStageErrorCode, retryable: boolean, failures: readonly VideoExtractionStageFailureSummary[], extractionFailure?: ExtractionFailureMetadataV1, ffmpegReason?: string);
|
|
121
121
|
}
|
|
122
122
|
export declare function assertVideoExtractionSucceeded(result: ExtractionResult): void;
|
|
123
123
|
export declare function buildHdrProbeStageError(failures: readonly Pick<ReturnType<typeof classifyVideoExtractionError>, "kind" | "retryable">[]): VideoExtractionStageError;
|
|
@@ -610,6 +610,7 @@ export declare function executeDiskCaptureWithAdaptiveRetry(options: {
|
|
|
610
610
|
/** Mutated in place: the workers' page-error console lines, for the render's error count. */
|
|
611
611
|
pageErrorLines?: string[];
|
|
612
612
|
openAttempt?: OpenStallAttempt;
|
|
613
|
+
verifyFirstSession?: (session: CaptureSession) => Promise<void>;
|
|
613
614
|
}): Promise<CaptureAttemptSummary[]>;
|
|
614
615
|
export type RenderConfigInput = Omit<RenderConfig, "fps"> & {
|
|
615
616
|
fps: FpsInput;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hyperframes/producer",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.208",
|
|
4
4
|
"description": "HTML-to-video rendering engine using Chrome's BeginFrame API",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -35,11 +35,11 @@
|
|
|
35
35
|
"postcss": "^8.4.0",
|
|
36
36
|
"puppeteer": "^25.2.1",
|
|
37
37
|
"puppeteer-core": "^25.2.1",
|
|
38
|
-
"@hyperframes/
|
|
39
|
-
"@hyperframes/
|
|
40
|
-
"@hyperframes/
|
|
41
|
-
"@hyperframes/
|
|
42
|
-
"@hyperframes/
|
|
38
|
+
"@hyperframes/core": "0.8.208",
|
|
39
|
+
"@hyperframes/lint": "0.8.208",
|
|
40
|
+
"@hyperframes/parsers": "0.8.208",
|
|
41
|
+
"@hyperframes/studio-server": "0.8.208",
|
|
42
|
+
"@hyperframes/engine": "0.8.208"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
45
|
"@types/node": "^25.0.10",
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
"tsx": "^4.21.0",
|
|
49
49
|
"typescript": "^5.7.2",
|
|
50
50
|
"vitest": "^4.1.11",
|
|
51
|
-
"@hyperframes/aws-lambda": "0.8.
|
|
51
|
+
"@hyperframes/aws-lambda": "0.8.208"
|
|
52
52
|
},
|
|
53
53
|
"engines": {
|
|
54
54
|
"node": ">=22"
|