@hyperframes/producer 0.8.145 → 0.8.146
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 +44 -36
- package/dist/hyperframe.manifest.json +1 -1
- package/dist/hyperframe.runtime.iife.js +214 -68
- package/dist/index.js +60 -52
- package/dist/public-server.js +54 -46
- package/dist/services/htmlCompiler.d.ts +1 -0
- package/package.json +7 -7
package/dist/index.js
CHANGED
|
@@ -272,7 +272,7 @@ function buildVirtualTimeShim(options) {
|
|
|
272
272
|
const seedRandomFromFrame = options.seedRandomFromFrame === true;
|
|
273
273
|
const seededRandomBlock = seedRandomFromFrame ? String.raw`
|
|
274
274
|
// Seeded Math.random / crypto.getRandomValues, keyed by virtual time.
|
|
275
|
-
// Mulberry32
|
|
275
|
+
// Mulberry32: single uint32 state, deterministic, fast.
|
|
276
276
|
var rngState = 0;
|
|
277
277
|
function mulberry32() {
|
|
278
278
|
rngState |= 0; rngState = (rngState + 0x6D2B79F5) | 0;
|
|
@@ -283,7 +283,7 @@ function buildVirtualTimeShim(options) {
|
|
|
283
283
|
}
|
|
284
284
|
function reseedRngFromTime(ms) {
|
|
285
285
|
var ms32 = Math.max(0, Math.floor(Number(ms) || 0)) | 0;
|
|
286
|
-
// Knuth's multiplicative hash + golden-ratio offset
|
|
286
|
+
// Knuth's multiplicative hash + golden-ratio offset gives a well-
|
|
287
287
|
// distributed seed even for frame 0 (otherwise rngState=0 degenerates
|
|
288
288
|
// the PRNG's first few outputs).
|
|
289
289
|
rngState = (Math.imul(ms32, -1640531527) + 0x9E3779B9) | 0;
|
|
@@ -525,7 +525,7 @@ function buildRenderModeScript(fps) {
|
|
|
525
525
|
};
|
|
526
526
|
window.__playerReady = true;
|
|
527
527
|
// Media-fallback player has no timeline to bind, so render-ready is immediate.
|
|
528
|
-
// init.ts defers __renderReady until the timeline is bound
|
|
528
|
+
// init.ts defers __renderReady until the timeline is bound (a different runtime).
|
|
529
529
|
window.__renderReady = true;
|
|
530
530
|
return true;
|
|
531
531
|
}
|
|
@@ -2117,7 +2117,7 @@ function blitHdrVideoLayer(canvas, el, time, fps, hdrVideoFrameSources, hdrStart
|
|
|
2117
2117
|
if (matrix && !isTranslationOnly) {
|
|
2118
2118
|
if (clipped && log) {
|
|
2119
2119
|
log.debug(
|
|
2120
|
-
`HDR clip rect on affine-transformed element ${el.id}
|
|
2120
|
+
`HDR clip rect on affine-transformed element ${el.id}: clip not applied (affine scissor not yet supported)`
|
|
2121
2121
|
);
|
|
2122
2122
|
}
|
|
2123
2123
|
blitRgb48leAffine(
|
|
@@ -2174,7 +2174,7 @@ function blitHdrImageLayer(canvas, el, hdrImageBuffers, hdrImageTransferCache, w
|
|
|
2174
2174
|
return;
|
|
2175
2175
|
}
|
|
2176
2176
|
if (el.clipRect && log) {
|
|
2177
|
-
log.debug(`HDR clip rect on image element ${el.id}
|
|
2177
|
+
log.debug(`HDR clip rect on image element ${el.id}: clip not yet supported for images`);
|
|
2178
2178
|
}
|
|
2179
2179
|
try {
|
|
2180
2180
|
if (hdrPerf) hdrPerf.hdrImageLayerBlits += 1;
|
|
@@ -4366,7 +4366,7 @@ import {
|
|
|
4366
4366
|
function formatDowngradeWarning(outputFormat, forcedHdrWithoutSources) {
|
|
4367
4367
|
const hdrSourceReason = forcedHdrWithoutSources ? "HDR was forced without detected HDR sources" : "HDR source detected";
|
|
4368
4368
|
const formatReason = outputFormat === "hls" ? "HLS output is SDR-only (H.264 in MPEG-TS)" : "HDR + alpha is not supported";
|
|
4369
|
-
return `[Render] ${hdrSourceReason}, but format is "${outputFormat}"
|
|
4369
|
+
return `[Render] ${hdrSourceReason}, but format is "${outputFormat}", so falling back to SDR. ${formatReason}. Use --format mp4 for HDR10 output.`;
|
|
4370
4370
|
}
|
|
4371
4371
|
function findRenderHdrAutoPromotionTrigger(input) {
|
|
4372
4372
|
for (const extracted of input.extractionResult?.extracted ?? []) {
|
|
@@ -4379,12 +4379,12 @@ function findRenderHdrAutoPromotionTrigger(input) {
|
|
|
4379
4379
|
function logHdrResolution(input, hdrMode, effectiveHdr, forcedHdrWithoutSources) {
|
|
4380
4380
|
if (forcedHdrWithoutSources) {
|
|
4381
4381
|
input.log.warn(
|
|
4382
|
-
"[Render] HDR forced by --hdr flag, but no HDR sources were detected
|
|
4382
|
+
"[Render] HDR forced by --hdr flag, but no HDR sources were detected, defaulting to HLG. SDR-only compositions may look perceptually wrong on HDR displays."
|
|
4383
4383
|
);
|
|
4384
4384
|
}
|
|
4385
4385
|
if (!effectiveHdr) {
|
|
4386
4386
|
input.log.info(
|
|
4387
|
-
hdrMode === "force-sdr" ? "[Render] SDR forced by --sdr flag" : "[Render] No HDR sources detected
|
|
4387
|
+
hdrMode === "force-sdr" ? "[Render] SDR forced by --sdr flag" : "[Render] No HDR sources detected, rendering SDR"
|
|
4388
4388
|
);
|
|
4389
4389
|
return;
|
|
4390
4390
|
}
|
|
@@ -4399,9 +4399,9 @@ function logHdrResolution(input, hdrMode, effectiveHdr, forcedHdrWithoutSources)
|
|
|
4399
4399
|
);
|
|
4400
4400
|
return;
|
|
4401
4401
|
}
|
|
4402
|
-
const reason = forcedHdrWithoutSources ? "forced by --hdr flag (no HDR sources detected
|
|
4402
|
+
const reason = forcedHdrWithoutSources ? "forced by --hdr flag (no HDR sources detected, defaulting to HLG)" : "forced by --hdr flag";
|
|
4403
4403
|
input.log.info(
|
|
4404
|
-
`[Render] HDR ${reason}
|
|
4404
|
+
`[Render] HDR ${reason}, output: ${effectiveHdr.transfer.toUpperCase()} (BT.2020, 10-bit H.265)`
|
|
4405
4405
|
);
|
|
4406
4406
|
}
|
|
4407
4407
|
function resolveEffectiveHdrMode(input) {
|
|
@@ -4497,14 +4497,14 @@ function resolveRenderWorkerCount(totalFrames, requestedWorkers, cfg, compiled,
|
|
|
4497
4497
|
const reasonCodes = new Set(compiled.renderModeHints.reasons.map((r) => r.code));
|
|
4498
4498
|
if (reasonCodes.has("htmlInCanvas")) {
|
|
4499
4499
|
log.warn(
|
|
4500
|
-
"[Render] html-in-canvas (drawElementImage) detected
|
|
4500
|
+
"[Render] html-in-canvas (drawElementImage) detected, pinning to 1 worker (Chrome concurrency limitation).",
|
|
4501
4501
|
{ requestedWorkers }
|
|
4502
4502
|
);
|
|
4503
4503
|
return 1;
|
|
4504
4504
|
}
|
|
4505
4505
|
if (cfg.lowMemoryMode && requestedWorkers === void 0) {
|
|
4506
4506
|
log.info(
|
|
4507
|
-
"[Render] Low-memory profile
|
|
4507
|
+
"[Render] Low-memory profile, pinning to 1 capture worker (auto-worker calibration skipped)."
|
|
4508
4508
|
);
|
|
4509
4509
|
return 1;
|
|
4510
4510
|
}
|
|
@@ -5139,7 +5139,7 @@ function assertVideoFrameCoverage(reports, threshold) {
|
|
|
5139
5139
|
const thresholdPct = (threshold * 100).toFixed(1);
|
|
5140
5140
|
const suffix = sorted.length > 1 ? ` (+${sorted.length - 1} more clip(s) below threshold)` : "";
|
|
5141
5141
|
throw new VideoFrameCoverageError(
|
|
5142
|
-
`Video "${worst.videoId}" captured ${worst.capturedFrames} of expected ${worst.expectedFrames} frames (coverage ${pct}%, threshold ${thresholdPct}%). check/snapshot may pass while the encoded MP4 renders this clip blank
|
|
5142
|
+
`Video "${worst.videoId}" captured ${worst.capturedFrames} of expected ${worst.expectedFrames} frames (coverage ${pct}%, threshold ${thresholdPct}%). check/snapshot may pass while the encoded MP4 renders this clip blank, so the render is aborted to prevent shipping a wrong MP4.${suffix} Set HF_VIDEO_COVERAGE_THRESHOLD=0 to disable this gate.`,
|
|
5143
5143
|
{ threshold, worst, failedReports: sorted }
|
|
5144
5144
|
);
|
|
5145
5145
|
}
|
|
@@ -6035,7 +6035,7 @@ async function resolveMediaDuration(src, mediaStart, playbackRate, baseDir, down
|
|
|
6035
6035
|
if (tagName !== "video") {
|
|
6036
6036
|
if (error instanceof NotMediaPayloadError2) {
|
|
6037
6037
|
log?.warn(
|
|
6038
|
-
`[compile] Audio "${elementIdentity}" (${src}) is a text document, not a media file
|
|
6038
|
+
`[compile] Audio "${elementIdentity}" (${src}) is a text document, not a media file, so the element is dropped from the render. Point it at a rendered media file.`
|
|
6039
6039
|
);
|
|
6040
6040
|
}
|
|
6041
6041
|
return { duration: null, resolvedPath: filePath };
|
|
@@ -6124,7 +6124,7 @@ async function compileHtmlFile(html, baseDir, downloadDir, log) {
|
|
|
6124
6124
|
if (r.maxDuration != null && shouldClampResolvedMediaDuration(r.tagName, r.duration, r.maxDuration)) {
|
|
6125
6125
|
clampList.push({ id: r.id, duration: r.maxDuration });
|
|
6126
6126
|
log?.warn(
|
|
6127
|
-
`[compile] Audio "${r.id}" (${r.src}) is ${r.maxDuration.toFixed(2)}s but its data-duration is ${r.duration.toFixed(2)}s
|
|
6127
|
+
`[compile] Audio "${r.id}" (${r.src}) is ${r.maxDuration.toFixed(2)}s but its data-duration is ${r.duration.toFixed(2)}s, so the slot is shortened to the media length. Set data-duration to ~${r.maxDuration.toFixed(2)}s, trim data-media-start, or use a longer/looping source if that isn't intended.`
|
|
6128
6128
|
);
|
|
6129
6129
|
}
|
|
6130
6130
|
}
|
|
@@ -6513,7 +6513,7 @@ ${safeText}
|
|
|
6513
6513
|
} else {
|
|
6514
6514
|
if (download.reason instanceof ScriptIntegrityError) throw download.reason;
|
|
6515
6515
|
defaultLogger.warn(
|
|
6516
|
-
`[Compiler] WARNING: Failed to download CDN script: ${src}
|
|
6516
|
+
`[Compiler] WARNING: Failed to download CDN script: ${src} (${download.reason}). The render may fail if this script is required (e.g. GSAP). Consider bundling it locally in your project.`
|
|
6517
6517
|
);
|
|
6518
6518
|
}
|
|
6519
6519
|
}
|
|
@@ -6565,7 +6565,7 @@ function collectExternalAssets(html, projectDir) {
|
|
|
6565
6565
|
}
|
|
6566
6566
|
if (externalAssets.size > 0) {
|
|
6567
6567
|
defaultLogger.info(
|
|
6568
|
-
`[Compiler] Found ${externalAssets.size} asset(s) outside project directory
|
|
6568
|
+
`[Compiler] Found ${externalAssets.size} asset(s) outside project directory, will copy to render output`
|
|
6569
6569
|
);
|
|
6570
6570
|
}
|
|
6571
6571
|
return {
|
|
@@ -6590,7 +6590,7 @@ async function downloadAndRewriteUrls(urlSet, html, remoteDir, warnLabel, logLab
|
|
|
6590
6590
|
urlToLocal.set(url, localPath);
|
|
6591
6591
|
} catch (err) {
|
|
6592
6592
|
const identity = safeDownloadUrlIdentity(url);
|
|
6593
|
-
defaultLogger.warn(`[Compiler] ${warnLabel}
|
|
6593
|
+
defaultLogger.warn(`[Compiler] ${warnLabel}, using original URL as fallback.`, {
|
|
6594
6594
|
urlFingerprint: identity.urlFingerprint,
|
|
6595
6595
|
host: identity.host,
|
|
6596
6596
|
error: err instanceof Error ? err.message : String(err)
|
|
@@ -6683,7 +6683,7 @@ async function fetchExternalStylesheetCss(href) {
|
|
|
6683
6683
|
timeoutMs: 15e3
|
|
6684
6684
|
});
|
|
6685
6685
|
} catch (err) {
|
|
6686
|
-
defaultLogger.warn("[Compiler] External stylesheet fetch failed
|
|
6686
|
+
defaultLogger.warn("[Compiler] External stylesheet fetch failed, preserving link tag.", {
|
|
6687
6687
|
urlFingerprint: identity.urlFingerprint,
|
|
6688
6688
|
host: identity.host,
|
|
6689
6689
|
error: err instanceof Error ? err.message : String(err)
|
|
@@ -7327,12 +7327,15 @@ async function resolveCompositionDurations(page, unresolved) {
|
|
|
7327
7327
|
const resolved = [];
|
|
7328
7328
|
for (const id of compIds) {
|
|
7329
7329
|
const tl = timelines[id];
|
|
7330
|
-
|
|
7331
|
-
|
|
7332
|
-
|
|
7333
|
-
|
|
7334
|
-
|
|
7335
|
-
|
|
7330
|
+
let dur = 0;
|
|
7331
|
+
try {
|
|
7332
|
+
dur = typeof tl?.duration === "function" ? Number(tl.duration()) : 0;
|
|
7333
|
+
} catch {
|
|
7334
|
+
dur = 0;
|
|
7335
|
+
}
|
|
7336
|
+
if (dur > 0) {
|
|
7337
|
+
resolved.push({ id, duration: dur, source: "__timelines" });
|
|
7338
|
+
continue;
|
|
7336
7339
|
}
|
|
7337
7340
|
const el = document.getElementById(id);
|
|
7338
7341
|
if (el) {
|
|
@@ -7428,28 +7431,28 @@ async function runCompileStage(input) {
|
|
|
7428
7431
|
cfg.useDrawElement = false;
|
|
7429
7432
|
deCompileGate = "3d";
|
|
7430
7433
|
log.info(
|
|
7431
|
-
"[Render] Fast capture: composition uses a CSS 3D rendering context (perspective / preserve-3d / backface-visibility)
|
|
7434
|
+
"[Render] Fast capture: composition uses a CSS 3D rendering context (perspective / preserve-3d / backface-visibility), so disabling drawElementImage for this render. Capture uses the platform's baseline route."
|
|
7432
7435
|
);
|
|
7433
7436
|
}
|
|
7434
7437
|
if (cfg.useDrawElement && process.env.HF_FAST_CAPTURE_BLEND !== "true" && compiled.usesMixBlendMode) {
|
|
7435
7438
|
cfg.useDrawElement = false;
|
|
7436
7439
|
deCompileGate = "mix_blend_mode";
|
|
7437
7440
|
log.info(
|
|
7438
|
-
"[Render] Fast capture: composition uses mix-blend-mode
|
|
7441
|
+
"[Render] Fast capture: composition uses mix-blend-mode, so disabling drawElementImage for this render. Capture uses the platform's baseline route."
|
|
7439
7442
|
);
|
|
7440
7443
|
}
|
|
7441
7444
|
if (cfg.useDrawElement && process.env.HF_FAST_CAPTURE_ANCESTOR_BG !== "true" && compiled.hasAncestorBackgroundImage) {
|
|
7442
7445
|
cfg.useDrawElement = false;
|
|
7443
7446
|
deCompileGate = "ancestor_background_image";
|
|
7444
7447
|
log.info(
|
|
7445
|
-
"[Render] Fast capture: composition root's ancestors (body/html/wrapper) carry a background-image
|
|
7448
|
+
"[Render] Fast capture: composition root's ancestors (body/html/wrapper) carry a background-image, so disabling drawElementImage for this render. Capture uses the platform's baseline route."
|
|
7446
7449
|
);
|
|
7447
7450
|
}
|
|
7448
7451
|
if (cfg.useDrawElement && process.env.PRODUCER_EXPERIMENTAL_FAST_CAPTURE !== "true" && compiled.hasShaderTransitions) {
|
|
7449
7452
|
cfg.useDrawElement = false;
|
|
7450
7453
|
deCompileGate = "shader_transitions";
|
|
7451
7454
|
log.info(
|
|
7452
|
-
"[Render] Fast capture: composition uses shader transitions
|
|
7455
|
+
"[Render] Fast capture: composition uses shader transitions, so disabling drawElementImage so page-side compositing stays available."
|
|
7453
7456
|
);
|
|
7454
7457
|
}
|
|
7455
7458
|
if (!cfg.useDrawElement && cfg.pageSideCompositingAutoDisabled) {
|
|
@@ -7777,7 +7780,7 @@ async function runProbeStage(input) {
|
|
|
7777
7780
|
} else {
|
|
7778
7781
|
beginFrameStalled = true;
|
|
7779
7782
|
log.warn(
|
|
7780
|
-
"[Render] BeginFrame liveness probe timed out
|
|
7783
|
+
"[Render] BeginFrame liveness probe timed out: this composition stalls BeginFrame on this host (SwiftShader heavy-layer pattern). Relaunching the probe browser in screenshot capture mode; the render will use screenshot capture throughout.",
|
|
7781
7784
|
{ probeTimeoutMs }
|
|
7782
7785
|
);
|
|
7783
7786
|
lastBrowserConsole = probeSession.browserConsoleBuffer;
|
|
@@ -7991,7 +7994,7 @@ async function runProbeStage(input) {
|
|
|
7991
7994
|
video.start = win.visibleStart;
|
|
7992
7995
|
video.end = win.visibleEnd;
|
|
7993
7996
|
log.info(
|
|
7994
|
-
`[Probe] Runtime video discovery: ${video.id} visible ${win.visibleStart.toFixed(2)}s
|
|
7997
|
+
`[Probe] Runtime video discovery: ${video.id} visible ${win.visibleStart.toFixed(2)}s to ${win.visibleEnd.toFixed(2)}s`
|
|
7995
7998
|
);
|
|
7996
7999
|
}
|
|
7997
8000
|
}
|
|
@@ -8039,7 +8042,7 @@ async function runProbeStage(input) {
|
|
|
8039
8042
|
});
|
|
8040
8043
|
if (!timelinesInfo.gsapLoaded) {
|
|
8041
8044
|
diagnostics.push(
|
|
8042
|
-
"GSAP is not loaded
|
|
8045
|
+
"GSAP is not loaded. The CDN script may have failed to download. Bundle GSAP locally in your project instead of using a CDN <script src>."
|
|
8043
8046
|
);
|
|
8044
8047
|
} else if (timelinesInfo.timelineKeys.length === 0) {
|
|
8045
8048
|
diagnostics.push(
|
|
@@ -8056,10 +8059,10 @@ async function runProbeStage(input) {
|
|
|
8056
8059
|
log.warn("Failed to gather browser diagnostics for zero-duration composition", {
|
|
8057
8060
|
error: err instanceof Error ? err.message : String(err)
|
|
8058
8061
|
});
|
|
8059
|
-
diagnostics.push("(Could not gather browser diagnostics
|
|
8062
|
+
diagnostics.push("(Could not gather browser diagnostics. The page may have crashed.)");
|
|
8060
8063
|
}
|
|
8061
8064
|
const hint = diagnostics.length > 0 ? "\n\nDiagnostics:\n - " + diagnostics.join("\n - ") : "\n\nCheck that GSAP timelines are registered on window.__timelines.";
|
|
8062
|
-
throw new Error("Composition duration is 0
|
|
8065
|
+
throw new Error("Composition duration is 0, so this would produce a black video." + hint);
|
|
8063
8066
|
}
|
|
8064
8067
|
if (probeSession) {
|
|
8065
8068
|
const failedRequests = probeSession.browserConsoleBuffer.filter(isActionableProbeFailure);
|
|
@@ -8124,7 +8127,7 @@ function validateNoGpuEncode(config) {
|
|
|
8124
8127
|
if (config.useGpu === true) {
|
|
8125
8128
|
throw new PlanValidationError(
|
|
8126
8129
|
BROWSER_GPU_NOT_SOFTWARE,
|
|
8127
|
-
"[planValidation] GPU encode is banned in distributed mode: config.useGpu === true. Distributed retries must be byte-identical, but NVENC/QSV/VAAPI produce different output across machines. Set useGpu=false (the default)
|
|
8130
|
+
"[planValidation] GPU encode is banned in distributed mode: config.useGpu === true. Distributed retries must be byte-identical, but NVENC/QSV/VAAPI produce different output across machines. Set useGpu=false (the default): software libx264/libx265 is the only supported encoder in distributed mode."
|
|
8128
8131
|
);
|
|
8129
8132
|
}
|
|
8130
8133
|
if (config.browserGpuMode !== void 0 && config.browserGpuMode !== "software") {
|
|
@@ -8171,6 +8174,7 @@ import {
|
|
|
8171
8174
|
detectTransfer,
|
|
8172
8175
|
extractAllVideoFrames,
|
|
8173
8176
|
extractMediaMetadata as extractMediaMetadata3,
|
|
8177
|
+
isVideoPastTimelineEnd,
|
|
8174
8178
|
isHdrColorSpace as isHdrColorSpace2,
|
|
8175
8179
|
resolveProjectRelativeSrc as resolveProjectRelativeSrc3,
|
|
8176
8180
|
runVideoExtractionWithRetry,
|
|
@@ -8390,6 +8394,9 @@ async function runExtractVideosStage(input) {
|
|
|
8390
8394
|
} = input;
|
|
8391
8395
|
const stage2Start = Date.now();
|
|
8392
8396
|
const extractionPolicy = resolveVideoExtractionPolicy();
|
|
8397
|
+
composition.videos = composition.videos.filter(
|
|
8398
|
+
(video) => !isVideoPastTimelineEnd(video, composition.duration)
|
|
8399
|
+
);
|
|
8393
8400
|
let frameLookup = null;
|
|
8394
8401
|
let extractionResult = null;
|
|
8395
8402
|
let failureToEnforce = null;
|
|
@@ -8476,7 +8483,8 @@ async function runExtractVideosStage(input) {
|
|
|
8476
8483
|
timelineEnd: composition.duration,
|
|
8477
8484
|
maxTransientRetries: extractionPolicy.maxTransientRetries,
|
|
8478
8485
|
collectProbeFailures: extractionPolicy.failureMode === "enforce",
|
|
8479
|
-
deferRangeExtraction
|
|
8486
|
+
deferRangeExtraction,
|
|
8487
|
+
contentKeyedDirs: [join16(compiledDir, REMOTE_MEDIA_SUBDIR)]
|
|
8480
8488
|
},
|
|
8481
8489
|
abortSignal,
|
|
8482
8490
|
cfg,
|
|
@@ -11102,7 +11110,7 @@ async function executeDiskCaptureWithAdaptiveRetry(options) {
|
|
|
11102
11110
|
const madeProgress = captureAttemptMadeProgress(frameCount, remainingCount);
|
|
11103
11111
|
if (!madeProgress) {
|
|
11104
11112
|
options.log.warn(
|
|
11105
|
-
"[Render] Capture attempt made no forward progress; composition is likely structurally broken
|
|
11113
|
+
"[Render] Capture attempt made no forward progress; composition is likely structurally broken, not retrying.",
|
|
11106
11114
|
{ attempt, frameCount, remainingCount, workers: currentWorkers }
|
|
11107
11115
|
);
|
|
11108
11116
|
}
|
|
@@ -11171,7 +11179,7 @@ async function executeDiskCaptureWithAdaptiveRetry(options) {
|
|
|
11171
11179
|
}
|
|
11172
11180
|
if (!madeProgress) {
|
|
11173
11181
|
options.log.warn(
|
|
11174
|
-
"[Render] Capture attempt made no forward progress; composition is likely structurally broken
|
|
11182
|
+
"[Render] Capture attempt made no forward progress; composition is likely structurally broken, not retrying.",
|
|
11175
11183
|
{ attempt, frameCount, remainingCount, workers: currentWorkers }
|
|
11176
11184
|
);
|
|
11177
11185
|
}
|
|
@@ -11634,7 +11642,7 @@ async function executeRenderJob(job, projectDir, outputPath, progressSink, abort
|
|
|
11634
11642
|
execution.defer("remove workDir", () => {
|
|
11635
11643
|
if (job.config.debug) return;
|
|
11636
11644
|
if (job.status === "complete" && process.env.KEEP_TEMP === "1") {
|
|
11637
|
-
log.info("KEEP_TEMP=1
|
|
11645
|
+
log.info("KEEP_TEMP=1, leaving workDir on disk for inspection", { workDir });
|
|
11638
11646
|
return;
|
|
11639
11647
|
}
|
|
11640
11648
|
rmSync9(workDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
|
|
@@ -11871,7 +11879,7 @@ async function executeRenderPipeline(input) {
|
|
|
11871
11879
|
captureForceScreenshot = true;
|
|
11872
11880
|
updateCaptureObservability({ forceScreenshot: captureForceScreenshot });
|
|
11873
11881
|
log.info(
|
|
11874
|
-
"[Render] Low-memory render profile active
|
|
11882
|
+
"[Render] Low-memory render profile active: screenshot capture, auto-worker calibration skipped" + (job.config.workers === void 0 ? ", pinned to 1 worker" : "") + ". Override with --no-low-memory-mode or PRODUCER_LOW_MEMORY_MODE=false.",
|
|
11875
11883
|
{ totalMemMb: getSystemTotalMb(), thresholdMb: LOW_MEMORY_TOTAL_MB_THRESHOLD }
|
|
11876
11884
|
);
|
|
11877
11885
|
}
|
|
@@ -12439,7 +12447,7 @@ async function executeRenderPipeline(input) {
|
|
|
12439
12447
|
cfg.useDrawElement = false;
|
|
12440
12448
|
deClampReason = workerCount > 1 ? "parallel" : "disk_path";
|
|
12441
12449
|
log.info(
|
|
12442
|
-
"[Render] Fast capture: default-on drawElement disabled for this render
|
|
12450
|
+
"[Render] Fast capture: default-on drawElement disabled for this render: " + (workerCount > 1 ? "parallel capture" : "the disk capture path") + " has no runtime self-verification. Set PRODUCER_EXPERIMENTAL_FAST_CAPTURE=true to override."
|
|
12443
12451
|
);
|
|
12444
12452
|
if (probeSession && probeSession.captureMode === "drawelement") {
|
|
12445
12453
|
lastBrowserConsole = probeSession.browserConsoleBuffer;
|
|
@@ -12535,7 +12543,7 @@ async function executeRenderPipeline(input) {
|
|
|
12535
12543
|
});
|
|
12536
12544
|
updateCaptureObservability({ forceScreenshot: captureForceScreenshot });
|
|
12537
12545
|
log.info(
|
|
12538
|
-
`[Render] Page-side compositing enabled
|
|
12546
|
+
`[Render] Page-side compositing enabled, bypassing Node-side layered shader-blend path. Engine will capture one ${needsAlpha ? "RGBA PNG" : "opaque RGB"} screenshot per output frame.`
|
|
12539
12547
|
);
|
|
12540
12548
|
}
|
|
12541
12549
|
const useLayeredComposite = !usePageSideCompositingForTransitions && shouldUseLayeredComposite({
|
|
@@ -13032,7 +13040,7 @@ async function executeRenderPipeline(input) {
|
|
|
13032
13040
|
cfg.useDrawElement = false;
|
|
13033
13041
|
deClampReason = deClampReason ?? "disk_path";
|
|
13034
13042
|
log.info(
|
|
13035
|
-
"[Render] Fast capture: drawElement disabled for the disk fallback
|
|
13043
|
+
"[Render] Fast capture: drawElement disabled for the disk fallback: streaming encoder spawn failed and the disk path has no runtime self-verification."
|
|
13036
13044
|
);
|
|
13037
13045
|
if (probeSession && probeSession.captureMode === "drawelement") {
|
|
13038
13046
|
lastBrowserConsole = probeSession.browserConsoleBuffer;
|
|
@@ -13584,10 +13592,10 @@ function validateJsonSafeValue(value, field) {
|
|
|
13584
13592
|
walkVariables(value, field, /* @__PURE__ */ new WeakSet());
|
|
13585
13593
|
}
|
|
13586
13594
|
var LEAF_REJECTIONS = {
|
|
13587
|
-
undefined: "undefined leaves are silently dropped by JSON.stringify
|
|
13595
|
+
undefined: "undefined leaves are silently dropped by JSON.stringify. Use null if you mean an absent value",
|
|
13588
13596
|
function: "functions are not JSON-serializable",
|
|
13589
13597
|
symbol: "Symbols are not JSON-serializable",
|
|
13590
|
-
bigint: "BigInt values throw at JSON.stringify
|
|
13598
|
+
bigint: "BigInt values throw at JSON.stringify. Encode as a string if you need 64-bit integers"
|
|
13591
13599
|
};
|
|
13592
13600
|
function walkVariables(value, path, seen) {
|
|
13593
13601
|
const t = typeof value;
|
|
@@ -13608,7 +13616,7 @@ function walkVariables(value, path, seen) {
|
|
|
13608
13616
|
if (seen.has(value)) {
|
|
13609
13617
|
throw new InvalidConfigError(
|
|
13610
13618
|
path,
|
|
13611
|
-
"circular reference detected
|
|
13619
|
+
"circular reference detected: JSON.stringify cannot serialize cycles"
|
|
13612
13620
|
);
|
|
13613
13621
|
}
|
|
13614
13622
|
seen.add(value);
|
|
@@ -14299,7 +14307,7 @@ function validateOutputResolutionOverride(body) {
|
|
|
14299
14307
|
return `Invalid outputResolution "${body.outputResolution}". Must be one of: landscape, portrait, landscape-4k, portrait-4k, square, square-4k (aliases: 1080p, 4k, \u2026).`;
|
|
14300
14308
|
}
|
|
14301
14309
|
if (normalized !== void 0 && (body.format === "webm" || body.format === "mov")) {
|
|
14302
|
-
return `outputResolution is not supported with format "${body.format}"
|
|
14310
|
+
return `outputResolution is not supported with format "${body.format}": the alpha (webm/mov) capture path can't supersample. Use format "mp4", or omit outputResolution to render at the composition's native dimensions.`;
|
|
14303
14311
|
}
|
|
14304
14312
|
return void 0;
|
|
14305
14313
|
}
|
|
@@ -15024,7 +15032,7 @@ function collectPlanAssetShas(planDir) {
|
|
|
15024
15032
|
}
|
|
15025
15033
|
if (compositionHtml === null) {
|
|
15026
15034
|
throw new Error(
|
|
15027
|
-
`[freezePlan] compiled HTML missing at ${COMPILED_INDEX_RELATIVE_PATH}
|
|
15035
|
+
`[freezePlan] compiled HTML missing at ${COMPILED_INDEX_RELATIVE_PATH} (upstream compile stage did not materialize the expected file).`
|
|
15028
15036
|
);
|
|
15029
15037
|
}
|
|
15030
15038
|
return { compositionHtml, assets };
|
|
@@ -15607,7 +15615,7 @@ var PlanTooLargeError = class extends Error {
|
|
|
15607
15615
|
const breakdownSuffix = breakdown ? ` Breakdown: video-frames=${formatBytes(breakdown.videoFramesBytes)}, compiled=${formatBytes(breakdown.compiledBytes)} (source-media=${formatBytes(breakdown.sourceMediaBytes)}), audio=${formatBytes(breakdown.audioBytes)}, metadata=${formatBytes(breakdown.metadataBytes)}, other=${formatBytes(breakdown.otherBytes)}, files=${breakdown.fileCount}.` : "";
|
|
15608
15616
|
const observationSuffix = observedAt ? ` Observed at ${observedAt}.` : "";
|
|
15609
15617
|
super(
|
|
15610
|
-
`[plan] planDir size ${formatBytes(sizeBytes)} exceeds the configured ceiling ${formatBytes(limitBytes)} (PLAN_TOO_LARGE). The default 2 GB cap fits inside AWS Lambda's 10 GB /tmp budget alongside the chunk worker's frame buffer and ffmpeg's working set. To unblock: use the content-addressed \`planV2()\` transport, shorten the composition, lower the framerate, or use the in-process renderer (\`executeRenderJob\`)
|
|
15618
|
+
`[plan] planDir size ${formatBytes(sizeBytes)} exceeds the configured ceiling ${formatBytes(limitBytes)} (PLAN_TOO_LARGE). The default 2 GB cap fits inside AWS Lambda's 10 GB /tmp budget alongside the chunk worker's frame buffer and ffmpeg's working set. To unblock: use the content-addressed \`planV2()\` transport, shorten the composition, lower the framerate, or use the in-process renderer (\`executeRenderJob\`), which has no planDir size cap.` + observationSuffix + breakdownSuffix
|
|
15611
15619
|
);
|
|
15612
15620
|
this.name = "PlanTooLargeError";
|
|
15613
15621
|
this.sizeBytes = sizeBytes;
|
|
@@ -15624,7 +15632,7 @@ var FormatNotSupportedInDistributedError = class extends Error {
|
|
|
15624
15632
|
reason;
|
|
15625
15633
|
constructor(format, reason) {
|
|
15626
15634
|
super(
|
|
15627
|
-
`[plan] format ${JSON.stringify(format)} is not supported in distributed mode: ${reason}. Render with the in-process renderer (\`executeRenderJob\`)
|
|
15635
|
+
`[plan] format ${JSON.stringify(format)} is not supported in distributed mode: ${reason}. Render with the in-process renderer (\`executeRenderJob\`), which has full format support, or pick a distributed-supported format: mp4 SDR, mov ProRes 4444, png-sequence, or webm VP9.`
|
|
15628
15636
|
);
|
|
15629
15637
|
this.name = "FormatNotSupportedInDistributedError";
|
|
15630
15638
|
this.format = format;
|
|
@@ -15713,7 +15721,7 @@ async function readFontSnapshotSha() {
|
|
|
15713
15721
|
const data = module.EMBEDDED_FONT_DATA;
|
|
15714
15722
|
if (!data || typeof data !== "object") {
|
|
15715
15723
|
throw new Error(
|
|
15716
|
-
"[plan] EMBEDDED_FONT_DATA missing from @hyperframes/core/fonts/embedded-data
|
|
15724
|
+
"[plan] EMBEDDED_FONT_DATA missing from @hyperframes/core/fonts/embedded-data. Was `bun run build:fonts` run?"
|
|
15717
15725
|
);
|
|
15718
15726
|
}
|
|
15719
15727
|
const dataObj = data;
|
|
@@ -15763,7 +15771,7 @@ function resolveEncoderTriple(config) {
|
|
|
15763
15771
|
}
|
|
15764
15772
|
if (config.codec !== void 0) {
|
|
15765
15773
|
throw new Error(
|
|
15766
|
-
`[plan] DistributedRenderConfig.codec is only valid for format="mp4"; received codec=${JSON.stringify(config.codec)} with format=${JSON.stringify(config.format)}. Omit codec for non-mp4 formats
|
|
15774
|
+
`[plan] DistributedRenderConfig.codec is only valid for format="mp4"; received codec=${JSON.stringify(config.codec)} with format=${JSON.stringify(config.format)}. Omit codec for non-mp4 formats: mov is always ProRes 4444, webm is always libvpx-vp9, and png-sequence has no encoder.`
|
|
15767
15775
|
);
|
|
15768
15776
|
}
|
|
15769
15777
|
return resolveNonMp4EncoderTriple(config.format, config.quality ?? "standard");
|