@hyperframes/producer 0.9.19 → 0.9.21
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 +322 -129
- package/dist/hyperframe.manifest.json +1 -1
- package/dist/hyperframe.runtime.iife.js +65 -65
- package/dist/index.js +349 -174
- package/dist/public-server.js +244 -101
- package/dist/renderRequest.d.ts +1 -0
- package/dist/services/distributed/plan.d.ts +7 -5
- package/dist/services/fileServer.d.ts +3 -3
- package/dist/services/render/stages/captureStreamingStage.d.ts +11 -2
- package/dist/services/renderOrchestrator.d.ts +3 -2
- package/dist/utils/paths.d.ts +2 -0
- package/package.json +7 -7
package/dist/distributed.js
CHANGED
|
@@ -8,13 +8,13 @@ import { audioFailureDiagnostics as audioFailureDiagnostics3 } from "@hyperframe
|
|
|
8
8
|
import {
|
|
9
9
|
copyFileSync as copyFileSync6,
|
|
10
10
|
cpSync as cpSync2,
|
|
11
|
-
existsSync as
|
|
11
|
+
existsSync as existsSync19,
|
|
12
12
|
linkSync,
|
|
13
13
|
mkdirSync as mkdirSync10,
|
|
14
14
|
rmSync as rmSync7,
|
|
15
15
|
writeFileSync as writeFileSync8
|
|
16
16
|
} from "node:fs";
|
|
17
|
-
import { extname as extname4, isAbsolute as isAbsolute3, join as join25, relative as relative5, sep as
|
|
17
|
+
import { extname as extname4, isAbsolute as isAbsolute3, join as join25, relative as relative5, sep as sep3 } from "node:path";
|
|
18
18
|
import {
|
|
19
19
|
fpsToNumber as fpsToNumber9,
|
|
20
20
|
HF_COLOR_GRADING_ATTR as HF_COLOR_GRADING_ATTR2
|
|
@@ -71,7 +71,7 @@ var defaultLogger = createConsoleLogger("info");
|
|
|
71
71
|
// src/services/renderOrchestrator.ts
|
|
72
72
|
import { audioFailureDiagnostics as audioFailureDiagnostics2 } from "@hyperframes/engine";
|
|
73
73
|
import {
|
|
74
|
-
existsSync as
|
|
74
|
+
existsSync as existsSync14,
|
|
75
75
|
mkdirSync as mkdirSync6,
|
|
76
76
|
readdirSync as readdirSync3,
|
|
77
77
|
rmSync as rmSync5,
|
|
@@ -430,6 +430,68 @@ ${seededRandomBlock}
|
|
|
430
430
|
}
|
|
431
431
|
};
|
|
432
432
|
|
|
433
|
+
// Timers set once rendering has started run on film time: each seek fires every timer due by then, in
|
|
434
|
+
// due order. Until a seek after the page reports ready, timers keep the wall clock: setup code waits on
|
|
435
|
+
// them while the renderer may already be seeking.
|
|
436
|
+
var rendering = false;
|
|
437
|
+
var timerQueue = [];
|
|
438
|
+
var nextTimerId = 1000000000;
|
|
439
|
+
var firingDepth = 0;
|
|
440
|
+
function addTimer(callback, delay, args, repeats) {
|
|
441
|
+
// A timer set from a firing timer waits at least 1 ms, so a zero-delay chain cannot stall a seek.
|
|
442
|
+
var ms = Math.max(firingDepth > 0 ? 1 : 0, Number(delay) || 0);
|
|
443
|
+
var entry = { id: nextTimerId++, callback: callback, args: args, due: virtualNowMs + ms, every: repeats ? Math.max(1, ms) : 0, cancelled: false };
|
|
444
|
+
timerQueue.push(entry);
|
|
445
|
+
return entry.id;
|
|
446
|
+
}
|
|
447
|
+
function cancelTimer(id) {
|
|
448
|
+
for (var i = 0; i < timerQueue.length; i++) {
|
|
449
|
+
if (timerQueue[i].id === id) {
|
|
450
|
+
timerQueue[i].cancelled = true;
|
|
451
|
+
return true;
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
return false;
|
|
455
|
+
}
|
|
456
|
+
function fireDueTimers(untilMs) {
|
|
457
|
+
// Frame times are fractional, so a timer due on a frame lands a rounding error past it; it still fires on it.
|
|
458
|
+
untilMs += 0.000001;
|
|
459
|
+
while (true) {
|
|
460
|
+
var next = null;
|
|
461
|
+
for (var i = 0; i < timerQueue.length; i++) {
|
|
462
|
+
var candidate = timerQueue[i];
|
|
463
|
+
if (!candidate.cancelled && candidate.due <= untilMs && (next === null || candidate.due < next.due)) next = candidate;
|
|
464
|
+
}
|
|
465
|
+
if (next === null) break;
|
|
466
|
+
var dueMs = next.due;
|
|
467
|
+
if (next.every) next.due += next.every;
|
|
468
|
+
else next.cancelled = true;
|
|
469
|
+
virtualNowMs = dueMs;
|
|
470
|
+
firingDepth++;
|
|
471
|
+
try {
|
|
472
|
+
next.callback.apply(window, next.args);
|
|
473
|
+
} catch (err) {
|
|
474
|
+
originalSetTimeout(function() { throw err; }, 0);
|
|
475
|
+
}
|
|
476
|
+
firingDepth--;
|
|
477
|
+
}
|
|
478
|
+
timerQueue = timerQueue.filter(function(entry) { return !entry.cancelled; });
|
|
479
|
+
}
|
|
480
|
+
window.setTimeout = function(callback, delay) {
|
|
481
|
+
if (!rendering || typeof callback !== "function") return originalSetTimeout.apply(null, arguments);
|
|
482
|
+
return addTimer(callback, delay, Array.prototype.slice.call(arguments, 2), false);
|
|
483
|
+
};
|
|
484
|
+
window.setInterval = function(callback, delay) {
|
|
485
|
+
if (!rendering || typeof callback !== "function") return originalSetInterval.apply(null, arguments);
|
|
486
|
+
return addTimer(callback, delay, Array.prototype.slice.call(arguments, 2), true);
|
|
487
|
+
};
|
|
488
|
+
window.clearTimeout = function(id) {
|
|
489
|
+
if (!cancelTimer(id)) originalClearTimeout(id);
|
|
490
|
+
};
|
|
491
|
+
window.clearInterval = function(id) {
|
|
492
|
+
if (!cancelTimer(id)) originalClearInterval(id);
|
|
493
|
+
};
|
|
494
|
+
|
|
433
495
|
window.__HF_VIRTUAL_TIME__ = {
|
|
434
496
|
originalSetTimeout: originalSetTimeout,
|
|
435
497
|
originalClearTimeout: originalClearTimeout,
|
|
@@ -439,8 +501,10 @@ ${seededRandomBlock}
|
|
|
439
501
|
originalCancelAnimationFrame: originalCancelAnimationFrame,
|
|
440
502
|
seekToTime: function(nextTimeMs) {
|
|
441
503
|
var safeTimeMs = Math.max(0, Number(nextTimeMs) || 0);
|
|
504
|
+
if (window.__renderReady === true) rendering = true;
|
|
505
|
+
${seekToTimeReseedCall}fireDueTimers(safeTimeMs);
|
|
442
506
|
virtualNowMs = safeTimeMs;
|
|
443
|
-
|
|
507
|
+
flushAnimationFrame();
|
|
444
508
|
return virtualNowMs;
|
|
445
509
|
},
|
|
446
510
|
getTime: function() {
|
|
@@ -887,7 +951,7 @@ function chunkGopFrames(framesInChunk, intervalFrames) {
|
|
|
887
951
|
import {
|
|
888
952
|
copyFileSync,
|
|
889
953
|
cpSync,
|
|
890
|
-
existsSync as
|
|
954
|
+
existsSync as existsSync4,
|
|
891
955
|
mkdirSync,
|
|
892
956
|
rmSync,
|
|
893
957
|
symlinkSync,
|
|
@@ -907,7 +971,9 @@ import {
|
|
|
907
971
|
relative as nodeRelative,
|
|
908
972
|
isAbsolute as nodeIsAbsolute
|
|
909
973
|
} from "node:path";
|
|
974
|
+
import { existsSync as existsSync3 } from "node:fs";
|
|
910
975
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
976
|
+
import { decodedUrlPath } from "@hyperframes/parsers/asset-paths";
|
|
911
977
|
var DEFAULT_RENDERS_DIR = process.env.PRODUCER_RENDERS_DIR ?? // fileURLToPath (not URL.pathname): on Windows .pathname is "/D:/..." which
|
|
912
978
|
// resolves to a bogus renders dir.
|
|
913
979
|
nodeResolve(fileURLToPath2(import.meta.url), "../../..", "renders");
|
|
@@ -938,6 +1004,13 @@ function formatCaptureFrameName(index, ext) {
|
|
|
938
1004
|
function formatExportFrameName(index, ext) {
|
|
939
1005
|
return `frame_${String(index + 1).padStart(6, "0")}.${ext}`;
|
|
940
1006
|
}
|
|
1007
|
+
function resolveCompositionSrc(projectDir, src) {
|
|
1008
|
+
const rooted = (value) => nodeResolve(projectDir, value.replace(/^(?:\.?[\\/])+/, ""));
|
|
1009
|
+
const decoded = rooted(decodedUrlPath(src));
|
|
1010
|
+
if (!isPathInside2(decoded, projectDir)) return null;
|
|
1011
|
+
const raw = rooted(src);
|
|
1012
|
+
return !existsSync3(decoded) && isPathInside2(raw, projectDir) && existsSync3(raw) ? raw : decoded;
|
|
1013
|
+
}
|
|
941
1014
|
|
|
942
1015
|
// src/services/render/shared.ts
|
|
943
1016
|
var BROWSER_MEDIA_EPSILON = 1e-4;
|
|
@@ -1116,7 +1189,7 @@ var materializePathModule = {
|
|
|
1116
1189
|
isAbsolute
|
|
1117
1190
|
};
|
|
1118
1191
|
var materializeFileSystem = {
|
|
1119
|
-
existsSync:
|
|
1192
|
+
existsSync: existsSync4,
|
|
1120
1193
|
mkdirSync,
|
|
1121
1194
|
symlinkSync,
|
|
1122
1195
|
cpSync,
|
|
@@ -1455,11 +1528,80 @@ function createStallClock(now = () => Date.now()) {
|
|
|
1455
1528
|
|
|
1456
1529
|
// src/services/render/stages/captureStreamingStage.ts
|
|
1457
1530
|
var DEFAULT_CAPTURE_STALL_MS = 6e4;
|
|
1531
|
+
var SESSION_INIT_STALL_MS = 3e5;
|
|
1532
|
+
var DE_STALL_POLL_MS = 5e3;
|
|
1533
|
+
var STALL_ADVICE = "Chrome stopped answering. If another app is keeping the GPU or CPU busy (a 3D render, a game, another export), let it finish or render one job at a time, then retry.";
|
|
1458
1534
|
function resolveCaptureStallTimeoutMs() {
|
|
1459
1535
|
const raw = process.env.HF_DE_STALL_MS ?? process.env.HF_DE_PARALLEL_STALL_MS;
|
|
1460
1536
|
const parsed = raw ? Number(raw) : Number.NaN;
|
|
1461
1537
|
return Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_CAPTURE_STALL_MS;
|
|
1462
1538
|
}
|
|
1539
|
+
function captureModeLabel(mode) {
|
|
1540
|
+
if (mode === "drawelement") return "drawElement";
|
|
1541
|
+
if (mode === "beginframe") return "BeginFrame";
|
|
1542
|
+
return "screenshot";
|
|
1543
|
+
}
|
|
1544
|
+
var SequentialCaptureStallError = class extends Error {
|
|
1545
|
+
captureMode;
|
|
1546
|
+
constructor(session, stallTimeoutMs, frameIndex, totalFrames) {
|
|
1547
|
+
const step = session.captureStep ? ` while ${session.captureStep}` : "";
|
|
1548
|
+
super(
|
|
1549
|
+
`[Render] Sequential ${captureModeLabel(session.captureMode)} capture stalled: no frame progress for ${stallTimeoutMs}ms (stuck at frame ${frameIndex}/${totalFrames}${step}). ` + STALL_ADVICE
|
|
1550
|
+
);
|
|
1551
|
+
this.name = "SequentialCaptureStallError";
|
|
1552
|
+
this.captureMode = session.captureMode;
|
|
1553
|
+
}
|
|
1554
|
+
};
|
|
1555
|
+
function raceAgainstStall(promise, deadlineMs, input, signal) {
|
|
1556
|
+
return new Promise((resolve10, reject) => {
|
|
1557
|
+
const clock = createStallClock();
|
|
1558
|
+
const trip = () => reject(
|
|
1559
|
+
signal?.aborted ? new Error(
|
|
1560
|
+
`[Render] Sequential ${captureModeLabel(input.session.captureMode)} capture aborted while a capture call was in flight (frame ${input.frameIndex}/${input.totalFrames}).`
|
|
1561
|
+
) : new SequentialCaptureStallError(
|
|
1562
|
+
input.session,
|
|
1563
|
+
input.stallTimeoutMs,
|
|
1564
|
+
input.frameIndex,
|
|
1565
|
+
input.totalFrames
|
|
1566
|
+
)
|
|
1567
|
+
);
|
|
1568
|
+
const deadline = Math.max(0, deadlineMs);
|
|
1569
|
+
let wait = Math.min(deadline, DE_STALL_POLL_MS);
|
|
1570
|
+
let timer;
|
|
1571
|
+
const look = () => {
|
|
1572
|
+
const idle = clock.idle(wait);
|
|
1573
|
+
if (idle >= deadline) return trip();
|
|
1574
|
+
wait = Math.min(deadline - idle, DE_STALL_POLL_MS);
|
|
1575
|
+
timer = setTimeout(look, wait);
|
|
1576
|
+
};
|
|
1577
|
+
timer = setTimeout(look, wait);
|
|
1578
|
+
promise.then(
|
|
1579
|
+
(value) => {
|
|
1580
|
+
clearTimeout(timer);
|
|
1581
|
+
resolve10(value);
|
|
1582
|
+
},
|
|
1583
|
+
(error) => {
|
|
1584
|
+
clearTimeout(timer);
|
|
1585
|
+
reject(error);
|
|
1586
|
+
}
|
|
1587
|
+
);
|
|
1588
|
+
});
|
|
1589
|
+
}
|
|
1590
|
+
function createSessionInitBudget(input) {
|
|
1591
|
+
const initStallMs = Math.max(SESSION_INIT_STALL_MS, resolveCaptureStallTimeoutMs());
|
|
1592
|
+
const setup = createStallClock();
|
|
1593
|
+
return (work, stalled) => raceAgainstStall(
|
|
1594
|
+
work,
|
|
1595
|
+
initStallMs - setup.idle(0),
|
|
1596
|
+
{
|
|
1597
|
+
session: stalled,
|
|
1598
|
+
frameIndex: input.frameIndex,
|
|
1599
|
+
totalFrames: input.totalFrames,
|
|
1600
|
+
stallTimeoutMs: initStallMs
|
|
1601
|
+
},
|
|
1602
|
+
input.abortSignal
|
|
1603
|
+
);
|
|
1604
|
+
}
|
|
1463
1605
|
|
|
1464
1606
|
// src/services/render/captureCost.ts
|
|
1465
1607
|
import { fpsToNumber as fpsToNumber4 } from "@hyperframes/core";
|
|
@@ -1674,7 +1816,7 @@ var ALLOWED_STRING_DATA_KEYS = /* @__PURE__ */ new Set([
|
|
|
1674
1816
|
"stagePhase",
|
|
1675
1817
|
// Full-fidelity fast-capture fallback trigger — the specific reason
|
|
1676
1818
|
// drawElement gated off ("filter:blur", "filter:drop-shadow",
|
|
1677
|
-
// "backdrop-filter", "clip-path", "at_risk_timeline", "swiftshader", …).
|
|
1819
|
+
// "backdrop-filter", "clip-path", "at_risk_timeline", "swiftshader", "webgl_unavailable", …).
|
|
1678
1820
|
// Populated on the `capture_fallback_profile` checkpoint emitted by
|
|
1679
1821
|
// `fallbackCaptureProfile.ts` when the operator opts into
|
|
1680
1822
|
// `HF_PROFILE_FALLBACK_CAPTURE=true` so downstream metric consumers can
|
|
@@ -1993,17 +2135,17 @@ import {
|
|
|
1993
2135
|
} from "@hyperframes/engine";
|
|
1994
2136
|
|
|
1995
2137
|
// src/services/render/stages/compileStage.ts
|
|
1996
|
-
import { existsSync as
|
|
2138
|
+
import { existsSync as existsSync8, writeFileSync as writeFileSync3 } from "node:fs";
|
|
1997
2139
|
import { join as join8 } from "node:path";
|
|
1998
2140
|
import { suggestMatchingPreset } from "@hyperframes/core";
|
|
1999
2141
|
import { extractStandaloneEntryFromIndex, isTemplateEntry } from "@hyperframes/core/compiler";
|
|
2000
2142
|
import { readTextFile as readTextFile2 } from "@hyperframes/parsers/asset-resolution";
|
|
2001
2143
|
|
|
2002
2144
|
// src/services/htmlCompiler.ts
|
|
2003
|
-
import { decodedUrlPath } from "@hyperframes/parsers/asset-paths";
|
|
2145
|
+
import { decodedUrlPath as decodedUrlPath2 } from "@hyperframes/parsers/asset-paths";
|
|
2004
2146
|
import { createHash as createHash4 } from "node:crypto";
|
|
2005
|
-
import { createReadStream as createReadStream2, existsSync as
|
|
2006
|
-
import { join as join7, dirname as dirname6, resolve as resolve6, basename as basename3, relative as relative2 } from "path";
|
|
2147
|
+
import { createReadStream as createReadStream2, existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync4 } from "fs";
|
|
2148
|
+
import { join as join7, dirname as dirname6, resolve as resolve6, basename as basename3, relative as relative2, sep as sep2 } from "path";
|
|
2007
2149
|
import { parseHTML as parseHTML5 } from "linkedom";
|
|
2008
2150
|
import {
|
|
2009
2151
|
COMPOSITION_SOURCE_URL,
|
|
@@ -2249,7 +2391,7 @@ import {
|
|
|
2249
2391
|
|
|
2250
2392
|
// src/services/animatedGifPrep.ts
|
|
2251
2393
|
import { createHash as createHash2 } from "node:crypto";
|
|
2252
|
-
import { copyFileSync as copyFileSync2, existsSync as
|
|
2394
|
+
import { copyFileSync as copyFileSync2, existsSync as existsSync5, mkdirSync as mkdirSync2, readFileSync as readFileSync2, rmSync as rmSync2, statSync as statSync2 } from "node:fs";
|
|
2253
2395
|
import { dirname as dirname4, isAbsolute as isAbsolute2, join as join5, resolve as resolve4 } from "node:path";
|
|
2254
2396
|
import { parseHTML as parseHTML3 } from "linkedom";
|
|
2255
2397
|
import { parseAnimatedGifMetadata } from "@hyperframes/core";
|
|
@@ -2288,14 +2430,14 @@ function resolveLocalImageSourcePath(src, options) {
|
|
|
2288
2430
|
const { basePath } = splitUrlSuffix(trimmed);
|
|
2289
2431
|
const normalizedBase = normalizeRelPath(basePath);
|
|
2290
2432
|
const mapped = options.sourceAssets?.get(trimmed) ?? options.sourceAssets?.get(basePath) ?? options.sourceAssets?.get(normalizedBase);
|
|
2291
|
-
if (mapped &&
|
|
2433
|
+
if (mapped && existsSync5(mapped)) return mapped;
|
|
2292
2434
|
if (isHttpUrl(trimmed)) return null;
|
|
2293
2435
|
const projectRelative = basePath.startsWith("/") ? basePath.slice(1) : basePath;
|
|
2294
2436
|
const candidates = [
|
|
2295
2437
|
isAbsolute2(basePath) ? basePath : resolve4(options.projectDir, projectRelative),
|
|
2296
2438
|
resolve4(options.downloadDir, normalizedBase)
|
|
2297
2439
|
];
|
|
2298
|
-
return candidates.find((candidate) =>
|
|
2440
|
+
return candidates.find((candidate) => existsSync5(candidate)) ?? null;
|
|
2299
2441
|
}
|
|
2300
2442
|
function isUsableFile(path) {
|
|
2301
2443
|
try {
|
|
@@ -2406,10 +2548,10 @@ async function runAnimatedGifTranscode(request) {
|
|
|
2406
2548
|
throw result.error ?? new Error(`Animated GIF transcode failed (${result.exitCode}): ${result.stderr.slice(-500)}`);
|
|
2407
2549
|
}
|
|
2408
2550
|
async function ensurePreparedWebm(input) {
|
|
2409
|
-
if (!
|
|
2551
|
+
if (!existsSync5(dirname4(input.cachePath))) {
|
|
2410
2552
|
mkdirSync2(dirname4(input.cachePath), { recursive: true });
|
|
2411
2553
|
}
|
|
2412
|
-
if (!
|
|
2554
|
+
if (!existsSync5(dirname4(input.outputPath))) {
|
|
2413
2555
|
mkdirSync2(dirname4(input.outputPath), { recursive: true });
|
|
2414
2556
|
}
|
|
2415
2557
|
if (!isUsableFile(input.cachePath)) {
|
|
@@ -2884,7 +3026,7 @@ import { createStudioPositionSeekReapplyScript } from "@hyperframes/studio-serve
|
|
|
2884
3026
|
import { getPositionEditsRenderScript } from "@hyperframes/core/runtime/position-edits-render";
|
|
2885
3027
|
|
|
2886
3028
|
// src/services/assetMediaType.ts
|
|
2887
|
-
import { existsSync as
|
|
3029
|
+
import { existsSync as existsSync6 } from "node:fs";
|
|
2888
3030
|
import {
|
|
2889
3031
|
fingerprintElementId,
|
|
2890
3032
|
isNotMediaPayload,
|
|
@@ -2957,7 +3099,7 @@ async function preflightCompositionAssetMediaTypes(input) {
|
|
|
2957
3099
|
input.projectDir,
|
|
2958
3100
|
input.compiledDir
|
|
2959
3101
|
);
|
|
2960
|
-
if (!
|
|
3102
|
+
if (!existsSync6(resolvedPath)) continue;
|
|
2961
3103
|
byPath.set(resolvedPath, [...byPath.get(resolvedPath) ?? [], reference]);
|
|
2962
3104
|
}
|
|
2963
3105
|
const mismatches = [];
|
|
@@ -3045,6 +3187,14 @@ Check that each file referenced by data-composition-src contains valid HTML with
|
|
|
3045
3187
|
this.problems = problems;
|
|
3046
3188
|
}
|
|
3047
3189
|
};
|
|
3190
|
+
function normalizeCompositionSrcs(html, projectDir) {
|
|
3191
|
+
return html.replace(/(\bdata-composition-src=)(["'])([^"']*)\2/g, (whole, name, quote, value) => {
|
|
3192
|
+
if (!value.includes("%") && !value.startsWith("/")) return whole;
|
|
3193
|
+
const file = resolveCompositionSrc(projectDir, value);
|
|
3194
|
+
if (file === null) return whole;
|
|
3195
|
+
return `${name}${quote}${relative2(projectDir, file).split(sep2).join("/")}${quote}`;
|
|
3196
|
+
});
|
|
3197
|
+
}
|
|
3048
3198
|
function assertSubCompositionsUsable(html, projectDir, visited = /* @__PURE__ */ new Set()) {
|
|
3049
3199
|
const { document: document2 } = parseHTML5(html);
|
|
3050
3200
|
const hosts = [...document2.querySelectorAll("[data-composition-src]")];
|
|
@@ -3053,7 +3203,11 @@ function assertSubCompositionsUsable(html, projectDir, visited = /* @__PURE__ */
|
|
|
3053
3203
|
const srcPath = el.getAttribute("data-composition-src");
|
|
3054
3204
|
if (!srcPath) continue;
|
|
3055
3205
|
if (isUnresolvedAssetPlaceholder(srcPath)) continue;
|
|
3056
|
-
const filePath =
|
|
3206
|
+
const filePath = resolveCompositionSrc(projectDir, srcPath);
|
|
3207
|
+
if (filePath === null) {
|
|
3208
|
+
problems.push({ srcPath, detail: "it is outside the project folder" });
|
|
3209
|
+
continue;
|
|
3210
|
+
}
|
|
3057
3211
|
if (visited.has(filePath)) continue;
|
|
3058
3212
|
const read = readProjectFile(filePath);
|
|
3059
3213
|
if (read.kind === "missing") {
|
|
@@ -3199,7 +3353,7 @@ async function resolveMediaDuration(src, mediaStart, playbackRate, baseDir, down
|
|
|
3199
3353
|
let filePath = src;
|
|
3200
3354
|
if (isHttpUrl(src)) {
|
|
3201
3355
|
const remoteDir = join7(downloadDir, REMOTE_MEDIA_SUBDIR);
|
|
3202
|
-
if (!
|
|
3356
|
+
if (!existsSync7(remoteDir)) mkdirSync4(remoteDir, { recursive: true });
|
|
3203
3357
|
try {
|
|
3204
3358
|
filePath = await downloadToTemp(src, remoteDir, void 0, void 0, void 0, {
|
|
3205
3359
|
onTelemetry: logRemoteDownloadTelemetry
|
|
@@ -3208,11 +3362,11 @@ async function resolveMediaDuration(src, mediaStart, playbackRate, baseDir, down
|
|
|
3208
3362
|
return { duration: null, resolvedPath: src };
|
|
3209
3363
|
}
|
|
3210
3364
|
} else {
|
|
3211
|
-
filePath =
|
|
3365
|
+
filePath = decodedUrlPath2(src);
|
|
3212
3366
|
if (!filePath.startsWith("/")) filePath = join7(baseDir, filePath);
|
|
3213
3367
|
filePath = urlByAbsentLocalPath?.get(filePath) ?? filePath;
|
|
3214
3368
|
}
|
|
3215
|
-
if (!isHttpUrl(filePath) && !
|
|
3369
|
+
if (!isHttpUrl(filePath) && !existsSync7(filePath)) {
|
|
3216
3370
|
return { duration: null, resolvedPath: filePath };
|
|
3217
3371
|
}
|
|
3218
3372
|
const withSrcContext = (error) => {
|
|
@@ -3353,7 +3507,8 @@ async function parseSubCompositions(html, projectDir, downloadDir, visited = /*
|
|
|
3353
3507
|
for (const el of compEls) {
|
|
3354
3508
|
const srcPath = el.getAttribute("data-composition-src");
|
|
3355
3509
|
if (!srcPath) continue;
|
|
3356
|
-
const filePath =
|
|
3510
|
+
const filePath = resolveCompositionSrc(projectDir, srcPath);
|
|
3511
|
+
if (filePath === null) continue;
|
|
3357
3512
|
if (visited.has(filePath)) {
|
|
3358
3513
|
continue;
|
|
3359
3514
|
}
|
|
@@ -3361,7 +3516,7 @@ async function parseSubCompositions(html, projectDir, downloadDir, visited = /*
|
|
|
3361
3516
|
if (read.kind !== "file") {
|
|
3362
3517
|
continue;
|
|
3363
3518
|
}
|
|
3364
|
-
const rawSubHtml = read.text;
|
|
3519
|
+
const rawSubHtml = normalizeCompositionSrcs(read.text, projectDir);
|
|
3365
3520
|
const nestedVisited = new Set(visited);
|
|
3366
3521
|
nestedVisited.add(filePath);
|
|
3367
3522
|
workItems.push({ srcPath, filePath, rawSubHtml, nestedVisited });
|
|
@@ -3549,8 +3704,9 @@ function inlineSubCompositions(html, subCompositions, projectDir, variableOverri
|
|
|
3549
3704
|
resolveHtml: (srcPath) => {
|
|
3550
3705
|
let compHtml = subCompositions.get(srcPath) || null;
|
|
3551
3706
|
if (!compHtml) {
|
|
3552
|
-
const
|
|
3553
|
-
|
|
3707
|
+
const filePath = resolveCompositionSrc(projectDir, srcPath);
|
|
3708
|
+
const read = filePath ? readProjectFile(filePath) : null;
|
|
3709
|
+
if (read?.kind === "file") {
|
|
3554
3710
|
compHtml = rewriteUnresolvableGsapToCdn(read.text, projectDir, srcPath);
|
|
3555
3711
|
}
|
|
3556
3712
|
}
|
|
@@ -3559,7 +3715,7 @@ function inlineSubCompositions(html, subCompositions, projectDir, variableOverri
|
|
|
3559
3715
|
parseHtml: (htmlStr) => parseHTML5(htmlStr).document,
|
|
3560
3716
|
// Mirrors the preview bundler: a sub-composition's SIBLING assets resolve
|
|
3561
3717
|
// against its own directory, project-root refs stay as authored.
|
|
3562
|
-
assetExists: (path) =>
|
|
3718
|
+
assetExists: (path) => existsSync7(resolve6(projectDir, path)),
|
|
3563
3719
|
// Preserve the authored root wrapper as a child of the host, matching
|
|
3564
3720
|
// the preview bundler's shape (htmlBundler.ts's prepareFlattenedInnerRoot,
|
|
3565
3721
|
// which the runtime compositionLoader mirrors with its own copy for the
|
|
@@ -3750,7 +3906,7 @@ function collectExternalAssets(html, projectDir) {
|
|
|
3750
3906
|
if (isPathInside2(absPath, absProjectDir)) {
|
|
3751
3907
|
return null;
|
|
3752
3908
|
}
|
|
3753
|
-
if (!
|
|
3909
|
+
if (!existsSync7(absPath)) return null;
|
|
3754
3910
|
const safeKey = toExternalAssetKey(absPath);
|
|
3755
3911
|
externalAssets.set(safeKey, absPath);
|
|
3756
3912
|
return safeKey;
|
|
@@ -3800,7 +3956,7 @@ var REMOTE_SOURCE_TAG_RE = /<source\b[^>]*?\bsrc\s*=\s*["'](https?:\/\/[^"']+)["
|
|
|
3800
3956
|
var REMOTE_IMG_TAG_RE = /<img\b[^>]*?(?<![\w-])src\s*=\s*["'](https?:\/\/[^"']+)["'][^>]*>/gi;
|
|
3801
3957
|
async function downloadAndRewriteUrls(urlSet, html, remoteDir, warnLabel, logLabel, extraRewrite) {
|
|
3802
3958
|
if (urlSet.size === 0) return { html, remoteMediaAssets: /* @__PURE__ */ new Map() };
|
|
3803
|
-
if (!
|
|
3959
|
+
if (!existsSync7(remoteDir)) mkdirSync4(remoteDir, { recursive: true });
|
|
3804
3960
|
const urlToLocal = /* @__PURE__ */ new Map();
|
|
3805
3961
|
await Promise.all(
|
|
3806
3962
|
[...urlSet].map(async (url) => {
|
|
@@ -3982,14 +4138,14 @@ function projectStylesheetFontFamilies(html, projectDir) {
|
|
|
3982
4138
|
for (const [tag] of html.matchAll(/<link\b[^>]*\brel=["']stylesheet["'][^>]*>/gi)) {
|
|
3983
4139
|
const href = /\bhref=["']([^"']+)["']/i.exec(tag)?.[1];
|
|
3984
4140
|
if (!href || /^(?:[a-z][a-z\d+.-]*:|\/\/)/i.test(href)) continue;
|
|
3985
|
-
const sheet = resolve6(projectDir,
|
|
3986
|
-
if (!isPathInside2(sheet, projectDir) || !
|
|
4141
|
+
const sheet = resolve6(projectDir, decodedUrlPath2(href).replace(/^(?:\.?\/)+/, ""));
|
|
4142
|
+
if (!isPathInside2(sheet, projectDir) || !existsSync7(sheet)) continue;
|
|
3987
4143
|
for (const face of extractFontFaceBlocks(readFileSync4(sheet, "utf8"))) {
|
|
3988
4144
|
const family = /font-family\s*:\s*([^;}]+)/i.exec(face)?.[1]?.trim().replace(/^['"]|['"]$/g, "");
|
|
3989
4145
|
const shipsFile = [...face.matchAll(LOCAL_FONTFACE_URL_RE)].some(([, url = ""]) => {
|
|
3990
|
-
const path =
|
|
4146
|
+
const path = decodedUrlPath2(url);
|
|
3991
4147
|
const file = path.startsWith("/") ? resolve6(projectDir, `.${path}`) : resolve6(dirname6(sheet), path);
|
|
3992
|
-
return isPathInside2(file, projectDir) &&
|
|
4148
|
+
return isPathInside2(file, projectDir) && existsSync7(file);
|
|
3993
4149
|
});
|
|
3994
4150
|
if (family && shipsFile) families.push(family);
|
|
3995
4151
|
}
|
|
@@ -4124,7 +4280,7 @@ function renderGsapDist() {
|
|
|
4124
4280
|
}
|
|
4125
4281
|
function rewriteUnresolvableGsapToCdn(html, projectDir, compSrcPath) {
|
|
4126
4282
|
let gsapDist;
|
|
4127
|
-
const exists = (path) =>
|
|
4283
|
+
const exists = (path) => existsSync7(resolve6(projectDir, path));
|
|
4128
4284
|
return html.replace(
|
|
4129
4285
|
/(<script\b[^>]*\bsrc=["'])([^"']*gsap[^"']*\/dist\/([^"']+))(["'][^>]*>)/gi,
|
|
4130
4286
|
(full, prefix, src, file, suffix) => {
|
|
@@ -4141,7 +4297,7 @@ function rebaseDirectEntryAssetPaths(html, projectDir, htmlPath) {
|
|
|
4141
4297
|
const entryPath = relative2(projectDir, htmlPath).replace(/\\/g, "/");
|
|
4142
4298
|
if (!entryPath.includes("/")) return html;
|
|
4143
4299
|
const { document: document2 } = parseHTML5(html);
|
|
4144
|
-
const assetExists = (path) =>
|
|
4300
|
+
const assetExists = (path) => existsSync7(resolve6(projectDir, path));
|
|
4145
4301
|
rewriteAssetPaths(
|
|
4146
4302
|
document2.querySelectorAll("[src], [href], [srcset]"),
|
|
4147
4303
|
entryPath,
|
|
@@ -4163,7 +4319,10 @@ function rebaseDirectEntryAssetPaths(html, projectDir, htmlPath) {
|
|
|
4163
4319
|
}
|
|
4164
4320
|
async function compileForRender(projectDir, htmlPath, downloadDir, options = {}) {
|
|
4165
4321
|
const entryHtml = rebaseDirectEntryAssetPaths(readTextFile(htmlPath), projectDir, htmlPath);
|
|
4166
|
-
const rawHtml =
|
|
4322
|
+
const rawHtml = normalizeCompositionSrcs(
|
|
4323
|
+
rewriteUnresolvableGsapToCdn(entryHtml, projectDir),
|
|
4324
|
+
projectDir
|
|
4325
|
+
);
|
|
4167
4326
|
assertSubCompositionsUsable(rawHtml, projectDir);
|
|
4168
4327
|
const probes = /* @__PURE__ */ new Map();
|
|
4169
4328
|
const { html: compiledHtml, unresolvedCompositions } = await compileHtmlFile(
|
|
@@ -4708,7 +4867,7 @@ function resolveEntryHtmlPath(input) {
|
|
|
4708
4867
|
const rawEntry = readTextFile2(htmlPath);
|
|
4709
4868
|
if (entryFile === "index.html" || !isTemplateEntry(rawEntry)) return htmlPath;
|
|
4710
4869
|
const projectIndexPath = join8(projectDir, "index.html");
|
|
4711
|
-
if (!
|
|
4870
|
+
if (!existsSync8(projectIndexPath)) {
|
|
4712
4871
|
throw new Error(
|
|
4713
4872
|
`Template entry file "${entryFile}" requires a project index.html to extract its render shell.`
|
|
4714
4873
|
);
|
|
@@ -5574,7 +5733,7 @@ function validateDistributedDuration(input) {
|
|
|
5574
5733
|
}
|
|
5575
5734
|
|
|
5576
5735
|
// src/services/render/stages/extractVideosStage.ts
|
|
5577
|
-
import { existsSync as
|
|
5736
|
+
import { existsSync as existsSync9 } from "node:fs";
|
|
5578
5737
|
import { join as join10 } from "node:path";
|
|
5579
5738
|
import {
|
|
5580
5739
|
classifyVideoExtractionError,
|
|
@@ -5849,7 +6008,7 @@ async function runExtractVideosStage(input) {
|
|
|
5849
6008
|
const probeFailures = await Promise.all(
|
|
5850
6009
|
composition.videos.map(async (v) => {
|
|
5851
6010
|
const videoPath = resolveProjectRelativeSrc3(v.src, projectDir, compiledDir);
|
|
5852
|
-
if (!
|
|
6011
|
+
if (!existsSync9(videoPath)) return null;
|
|
5853
6012
|
try {
|
|
5854
6013
|
const attempted = extractionPolicy.maxTransientRetries === 0 ? { result: await extractMediaMetadata3(videoPath), retries: 0 } : await runVideoExtractionWithRetry2(() => extractMediaMetadata3(videoPath), {
|
|
5855
6014
|
signal: abortSignal,
|
|
@@ -5887,7 +6046,7 @@ async function runExtractVideosStage(input) {
|
|
|
5887
6046
|
const probed = await Promise.all(
|
|
5888
6047
|
composition.images.map(async (img) => {
|
|
5889
6048
|
const imgPath = resolveProjectRelativeSrc3(img.src, projectDir, compiledDir);
|
|
5890
|
-
if (!
|
|
6049
|
+
if (!existsSync9(imgPath)) return null;
|
|
5891
6050
|
const colorSpace = await probeColorSpaceSafely(imgPath, log);
|
|
5892
6051
|
if (isHdrColorSpace2(colorSpace)) {
|
|
5893
6052
|
nativeHdrImageIds.add(img.id);
|
|
@@ -6007,7 +6166,7 @@ async function appendVideoAudioFromProbe(input) {
|
|
|
6007
6166
|
videos.map(async (video) => {
|
|
6008
6167
|
const path = isHttpUrl2(video.src) ? video.src : resolveProjectRelativeSrc3(video.src, projectDir, compiledDir);
|
|
6009
6168
|
const readFrom = urlByAbsentLocalPath?.get(path) ?? path;
|
|
6010
|
-
if (!isHttpUrl2(readFrom) && !
|
|
6169
|
+
if (!isHttpUrl2(readFrom) && !existsSync9(readFrom)) return null;
|
|
6011
6170
|
const metadata = await extractMediaMetadata3(readFrom);
|
|
6012
6171
|
const window2 = await resolveFinalFrameExtractionWindow2(
|
|
6013
6172
|
readFrom,
|
|
@@ -6035,7 +6194,7 @@ import {
|
|
|
6035
6194
|
} from "@hyperframes/engine";
|
|
6036
6195
|
|
|
6037
6196
|
// src/services/render/stages/audioStage.ts
|
|
6038
|
-
import { existsSync as
|
|
6197
|
+
import { existsSync as existsSync11 } from "node:fs";
|
|
6039
6198
|
import { join as join13 } from "node:path";
|
|
6040
6199
|
import {
|
|
6041
6200
|
MIXED_AUDIO_FILENAME,
|
|
@@ -6046,7 +6205,7 @@ import {
|
|
|
6046
6205
|
} from "@hyperframes/engine";
|
|
6047
6206
|
|
|
6048
6207
|
// src/services/render/audioDeliveryLimit.ts
|
|
6049
|
-
import { existsSync as
|
|
6208
|
+
import { existsSync as existsSync10 } from "node:fs";
|
|
6050
6209
|
import { dirname as dirname8, join as join12 } from "node:path";
|
|
6051
6210
|
import {
|
|
6052
6211
|
applyAudioFxChain,
|
|
@@ -6555,7 +6714,7 @@ async function limitDeliveryPeaks(input, deps = {
|
|
|
6555
6714
|
applyChain: applyAudioFxChain,
|
|
6556
6715
|
encode: encodeAac2,
|
|
6557
6716
|
rename: renameWithRetry6,
|
|
6558
|
-
sourceExists:
|
|
6717
|
+
sourceExists: existsSync10
|
|
6559
6718
|
}) {
|
|
6560
6719
|
const { audioPath, sourcePath, signal, timeoutMs } = input;
|
|
6561
6720
|
if (!deps.sourceExists(sourcePath)) return { success: true };
|
|
@@ -6650,7 +6809,7 @@ async function runAudioStageScoped(input) {
|
|
|
6650
6809
|
);
|
|
6651
6810
|
audioLoweredDb = guard.loweredDb;
|
|
6652
6811
|
input.log?.info("[audio] phases", { ...result.audioPhaseMs, guard: Date.now() - guardStart });
|
|
6653
|
-
if (input.keepDeliverySource && !guard.sourceStale &&
|
|
6812
|
+
if (input.keepDeliverySource && !guard.sourceStale && existsSync11(pcmSourcePath)) {
|
|
6654
6813
|
deliverySourcePath = pcmSourcePath;
|
|
6655
6814
|
}
|
|
6656
6815
|
}
|
|
@@ -6778,7 +6937,7 @@ async function mixAudioForDelivery(input, duration, pcmSourcePath) {
|
|
|
6778
6937
|
}
|
|
6779
6938
|
|
|
6780
6939
|
// src/services/render/stages/captureStage.ts
|
|
6781
|
-
import { existsSync as
|
|
6940
|
+
import { existsSync as existsSync12, readdirSync, statfsSync } from "node:fs";
|
|
6782
6941
|
import { join as join14 } from "node:path";
|
|
6783
6942
|
import {
|
|
6784
6943
|
classifyCaptureFailure as classifyCaptureFailure2,
|
|
@@ -6830,7 +6989,7 @@ function diskCaptureShortfallError(needBytes, freeBytes, framesDir, basis, remed
|
|
|
6830
6989
|
}
|
|
6831
6990
|
function measureCaptureFrameBytes(workDir, framesDir) {
|
|
6832
6991
|
let total = sampleDirectoryBytes(framesDir);
|
|
6833
|
-
if (!
|
|
6992
|
+
if (!existsSync12(workDir)) return total;
|
|
6834
6993
|
for (const attempt of readdirSync(workDir)) {
|
|
6835
6994
|
if (/^retry-\d+-batch-\d+-worker-\d+$/.test(attempt)) {
|
|
6836
6995
|
total += sampleDirectoryBytes(join14(workDir, attempt));
|
|
@@ -7015,7 +7174,11 @@ async function runSequentialDiskCapture(input, captureOptions, captureCfg, check
|
|
|
7015
7174
|
prepareCaptureSessionForReuse3(session, framesDir, videoInjector);
|
|
7016
7175
|
probeSession = null;
|
|
7017
7176
|
}
|
|
7018
|
-
await ensureSessionReady(session
|
|
7177
|
+
await ensureSessionReady(session, {
|
|
7178
|
+
frameIndex: resumeFrom,
|
|
7179
|
+
totalFrames,
|
|
7180
|
+
abortSignal: input.abortSignal
|
|
7181
|
+
});
|
|
7019
7182
|
assertAttemptActive();
|
|
7020
7183
|
lastBrowserConsole = session.browserConsoleBuffer;
|
|
7021
7184
|
await captureSessionFrames(session, {
|
|
@@ -7067,13 +7230,13 @@ function planTransientResume(error, ctx) {
|
|
|
7067
7230
|
if (!firstMissing) return null;
|
|
7068
7231
|
return isTransientCaptureRetryEligible(failure.kind, missing, ctx.retriesUsed) ? firstMissing.startFrame : null;
|
|
7069
7232
|
}
|
|
7070
|
-
async function ensureSessionReady(session) {
|
|
7233
|
+
async function ensureSessionReady(session, budget) {
|
|
7071
7234
|
if (!session.isInitialized) {
|
|
7072
7235
|
await initializeSession5(session);
|
|
7073
7236
|
return;
|
|
7074
7237
|
}
|
|
7075
7238
|
if (process.env.PRODUCER_EXPERIMENTAL_FAST_CAPTURE === "true") {
|
|
7076
|
-
await completeDeferredDrawElementInit3(session);
|
|
7239
|
+
await createSessionInitBudget(budget)(completeDeferredDrawElementInit3(session), session);
|
|
7077
7240
|
}
|
|
7078
7241
|
}
|
|
7079
7242
|
async function captureSessionFrames(session, { input, rangeStart, rangeEnd, resumeFrom, checkDiskProjection }) {
|
|
@@ -7167,7 +7330,7 @@ import {
|
|
|
7167
7330
|
// src/services/render/stages/encodeStage.ts
|
|
7168
7331
|
import {
|
|
7169
7332
|
copyFileSync as copyFileSync4,
|
|
7170
|
-
existsSync as
|
|
7333
|
+
existsSync as existsSync13,
|
|
7171
7334
|
mkdirSync as mkdirSync5,
|
|
7172
7335
|
readdirSync as readdirSync2,
|
|
7173
7336
|
readFileSync as readFileSync5,
|
|
@@ -7301,7 +7464,7 @@ async function encodeGifFromDir(framesDir, framePattern, outputPath, input) {
|
|
|
7301
7464
|
const failure = await reencodeWithClearedWholeFrames(argsInput, run, startTime);
|
|
7302
7465
|
if (failure) return failure;
|
|
7303
7466
|
}
|
|
7304
|
-
const fileSize =
|
|
7467
|
+
const fileSize = existsSync13(outputPath) ? statSync4(outputPath).size : 0;
|
|
7305
7468
|
return {
|
|
7306
7469
|
success: true,
|
|
7307
7470
|
outputPath,
|
|
@@ -7462,7 +7625,7 @@ async function runEncodeStage(input) {
|
|
|
7462
7625
|
const stage5Start = Date.now();
|
|
7463
7626
|
if (isPngSequence) {
|
|
7464
7627
|
const encoded = startEncodeProgress(job, "Writing PNG sequence", onProgress);
|
|
7465
|
-
if (!
|
|
7628
|
+
if (!existsSync13(outputPath)) mkdirSync5(outputPath, { recursive: true });
|
|
7466
7629
|
const captured = readdirSync2(framesDir).filter((name) => name.endsWith(".png")).sort();
|
|
7467
7630
|
if (captured.length === 0) {
|
|
7468
7631
|
throw new Error(
|
|
@@ -7473,7 +7636,7 @@ async function runEncodeStage(input) {
|
|
|
7473
7636
|
const dst = join15(outputPath, formatExportFrameName(i, "png"));
|
|
7474
7637
|
copyFileSync4(join15(framesDir, name), dst);
|
|
7475
7638
|
});
|
|
7476
|
-
if (hasAudio && audioOutputPath &&
|
|
7639
|
+
if (hasAudio && audioOutputPath && existsSync13(audioOutputPath)) {
|
|
7477
7640
|
copyFileSync4(audioOutputPath, join15(outputPath, MIXED_AUDIO_FILENAME2));
|
|
7478
7641
|
log.info(
|
|
7479
7642
|
`[Render] png-sequence: ${MIXED_AUDIO_FILENAME2} sidecar written to ${outputPath}/${MIXED_AUDIO_FILENAME2}`
|
|
@@ -7636,7 +7799,7 @@ function findMissingFrameRanges(totalFrames, framesDir, frameExt) {
|
|
|
7636
7799
|
let rangeStart = null;
|
|
7637
7800
|
for (let frameIndex = 0; frameIndex < totalFrames; frameIndex++) {
|
|
7638
7801
|
const framePath = join16(framesDir, formatCaptureFrameName(frameIndex, frameExt));
|
|
7639
|
-
const missing = !
|
|
7802
|
+
const missing = !existsSync14(framePath) || statSync5(framePath).size <= 8;
|
|
7640
7803
|
if (missing && rangeStart === null) {
|
|
7641
7804
|
rangeStart = frameIndex;
|
|
7642
7805
|
} else if (!missing && rangeStart !== null) {
|
|
@@ -7726,7 +7889,7 @@ function countCapturedFrames(totalFrames, framesDir, frameExt) {
|
|
|
7726
7889
|
let captured = 0;
|
|
7727
7890
|
for (let frameIndex = 0; frameIndex < totalFrames; frameIndex++) {
|
|
7728
7891
|
const framePath = join16(framesDir, formatCaptureFrameName(frameIndex, frameExt));
|
|
7729
|
-
if (
|
|
7892
|
+
if (existsSync14(framePath)) captured++;
|
|
7730
7893
|
}
|
|
7731
7894
|
return captured;
|
|
7732
7895
|
}
|
|
@@ -7953,7 +8116,7 @@ import {
|
|
|
7953
8116
|
} from "@hyperframes/engine";
|
|
7954
8117
|
|
|
7955
8118
|
// src/services/distributed/chunkFrames.ts
|
|
7956
|
-
import { existsSync as
|
|
8119
|
+
import { existsSync as existsSync16, readdirSync as readdirSync4 } from "node:fs";
|
|
7957
8120
|
import { extname as extname2, join as join18 } from "node:path";
|
|
7958
8121
|
import {
|
|
7959
8122
|
compositionRequiresWebGpu as compositionRequiresWebGpu3,
|
|
@@ -7963,7 +8126,7 @@ import {
|
|
|
7963
8126
|
// src/services/distributed/shared.ts
|
|
7964
8127
|
import { execFile as execFileCallback } from "node:child_process";
|
|
7965
8128
|
import { dirname as dirname11, join as join17 } from "node:path";
|
|
7966
|
-
import { existsSync as
|
|
8129
|
+
import { existsSync as existsSync15, readFileSync as readFileSync6 } from "node:fs";
|
|
7967
8130
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
7968
8131
|
import { promisify as promisify2 } from "node:util";
|
|
7969
8132
|
import {
|
|
@@ -7979,7 +8142,7 @@ function isPlanAudioArtifactPath(path) {
|
|
|
7979
8142
|
function resolvePlanAudioPath(planDir) {
|
|
7980
8143
|
for (const name of [PLAN_AUDIO_RELATIVE_PATH, PLAN_AUDIO_LEGACY_RELATIVE_PATH]) {
|
|
7981
8144
|
const candidate = join17(planDir, name);
|
|
7982
|
-
if (
|
|
8145
|
+
if (existsSync15(candidate)) return candidate;
|
|
7983
8146
|
}
|
|
7984
8147
|
return null;
|
|
7985
8148
|
}
|
|
@@ -8240,7 +8403,7 @@ function readProducerVersion() {
|
|
|
8240
8403
|
let current = startDir;
|
|
8241
8404
|
for (let i = 0; i < 10; i++) {
|
|
8242
8405
|
const candidate = join17(current, "package.json");
|
|
8243
|
-
if (
|
|
8406
|
+
if (existsSync15(candidate)) {
|
|
8244
8407
|
try {
|
|
8245
8408
|
const pkg = JSON.parse(readFileSync6(candidate, "utf-8"));
|
|
8246
8409
|
if (pkg.name === "@hyperframes/producer" && typeof pkg.version === "string") {
|
|
@@ -8294,7 +8457,7 @@ function rebuildExtractedFramesFromPlanDir(planDir, videos, indexMode = "dense-v
|
|
|
8294
8457
|
const result = [];
|
|
8295
8458
|
for (const v of videos) {
|
|
8296
8459
|
const outputDir = join18(planDir, "video-frames", v.videoId);
|
|
8297
|
-
if (!
|
|
8460
|
+
if (!existsSync16(outputDir)) {
|
|
8298
8461
|
throw new Error(
|
|
8299
8462
|
`[renderChunk] planDir missing extracted video frames for ${JSON.stringify(v.videoId)}: ${outputDir} not present. plan() should have written frames here; the planDir is malformed.`
|
|
8300
8463
|
);
|
|
@@ -8393,7 +8556,7 @@ async function extractDeferredVideoFramesForChunk(input) {
|
|
|
8393
8556
|
const range = planned.get(video.videoId).deferredRange;
|
|
8394
8557
|
const remoteUrl = remoteSources[range.sourcePath];
|
|
8395
8558
|
const sourcePath = remoteUrl ?? join18(input.planDir, ...range.sourcePath.split("/"));
|
|
8396
|
-
if (remoteUrl === void 0 && !
|
|
8559
|
+
if (remoteUrl === void 0 && !existsSync16(sourcePath)) {
|
|
8397
8560
|
throw new RenderChunkValidationError(
|
|
8398
8561
|
MISSING_PLAN_ARTIFACT,
|
|
8399
8562
|
`[renderChunk] planDir is missing the source for video ${JSON.stringify(video.videoId)}: ${range.sourcePath}`
|
|
@@ -8593,7 +8756,7 @@ async function readPlanReferenceBackground(input) {
|
|
|
8593
8756
|
}
|
|
8594
8757
|
|
|
8595
8758
|
// src/services/render/stages/freezePlan.ts
|
|
8596
|
-
import { existsSync as
|
|
8759
|
+
import { existsSync as existsSync17, mkdirSync as mkdirSync7, readFileSync as readFileSync7, readdirSync as readdirSync5, writeFileSync as writeFileSync6 } from "node:fs";
|
|
8597
8760
|
import { join as join21, relative as relative3, resolve as resolve8 } from "node:path";
|
|
8598
8761
|
import {
|
|
8599
8762
|
REFERENCE_BACKGROUND_FIELD,
|
|
@@ -8787,7 +8950,7 @@ function planFileSha256(absolutePath, digests) {
|
|
|
8787
8950
|
return sha256;
|
|
8788
8951
|
}
|
|
8789
8952
|
async function prehashPlanFiles(dir, digests) {
|
|
8790
|
-
if (!
|
|
8953
|
+
if (!existsSync17(dir)) return;
|
|
8791
8954
|
for (const { absolutePath } of listPlanFiles(dir)) {
|
|
8792
8955
|
if (!digests.has(absolutePath)) digests.set(absolutePath, await sha256FileAsync(absolutePath));
|
|
8793
8956
|
}
|
|
@@ -8857,15 +9020,15 @@ function writePlanRemoteFiles(planDir, files) {
|
|
|
8857
9020
|
}
|
|
8858
9021
|
function readPlanRemoteFiles(planDir) {
|
|
8859
9022
|
const path = join21(planDir, PLAN_REMOTE_FILES_RELATIVE_PATH);
|
|
8860
|
-
return
|
|
9023
|
+
return existsSync17(path) ? JSON.parse(readFileSync7(path, "utf-8")) : [];
|
|
8861
9024
|
}
|
|
8862
9025
|
function recomputePlanHashFromPlanDir(planDir, digests) {
|
|
8863
9026
|
const planJsonPath = join21(planDir, "plan.json");
|
|
8864
9027
|
const encoderJsonPath = join21(planDir, "meta", "encoder.json");
|
|
8865
|
-
if (!
|
|
9028
|
+
if (!existsSync17(planJsonPath)) {
|
|
8866
9029
|
throw new Error(`[freezePlan] plan.json missing: ${planJsonPath}`);
|
|
8867
9030
|
}
|
|
8868
|
-
if (!
|
|
9031
|
+
if (!existsSync17(encoderJsonPath)) {
|
|
8869
9032
|
throw new Error(`[freezePlan] meta/encoder.json missing: ${encoderJsonPath}`);
|
|
8870
9033
|
}
|
|
8871
9034
|
const planJson = JSON.parse(readFileSync7(planJsonPath, "utf-8"));
|
|
@@ -8895,11 +9058,11 @@ async function freezePlan(input) {
|
|
|
8895
9058
|
hasAudio,
|
|
8896
9059
|
digests
|
|
8897
9060
|
} = input;
|
|
8898
|
-
if (!
|
|
9061
|
+
if (!existsSync17(planDir)) {
|
|
8899
9062
|
throw new Error(`[freezePlan] planDir does not exist: ${planDir}`);
|
|
8900
9063
|
}
|
|
8901
9064
|
const metaDir = join21(planDir, "meta");
|
|
8902
|
-
if (!
|
|
9065
|
+
if (!existsSync17(metaDir)) mkdirSync7(metaDir, { recursive: true });
|
|
8903
9066
|
writeFileSync6(
|
|
8904
9067
|
join21(metaDir, "composition.json"),
|
|
8905
9068
|
`${JSON.stringify(composition, null, 2)}
|
|
@@ -8976,7 +9139,7 @@ function applyRuntimeEnvSnapshot(snapshot, env = process.env) {
|
|
|
8976
9139
|
}
|
|
8977
9140
|
|
|
8978
9141
|
// src/services/distributed/chunkAudio.ts
|
|
8979
|
-
import { closeSync as closeSync4, existsSync as
|
|
9142
|
+
import { closeSync as closeSync4, existsSync as existsSync18, ftruncateSync, mkdirSync as mkdirSync9, openSync as openSync4, readFileSync as readFileSync8 } from "node:fs";
|
|
8980
9143
|
import { writeFileSync as writeFileSync7, writeSync } from "node:fs";
|
|
8981
9144
|
import { join as join23 } from "node:path";
|
|
8982
9145
|
import { appendRenderProvenanceArgs, describeFfmpegFailure as describeFfmpegFailure4, runFfmpeg as runFfmpeg7 } from "@hyperframes/engine";
|
|
@@ -9467,7 +9630,7 @@ async function writeChunkAudioSlices(input) {
|
|
|
9467
9630
|
}
|
|
9468
9631
|
function chunkAudioSource(planDir, chunkIndex, scratchPath) {
|
|
9469
9632
|
const layoutPath = join23(planDir, SLICE_LAYOUT);
|
|
9470
|
-
if (!
|
|
9633
|
+
if (!existsSync18(layoutPath)) return resolvePlanAudioPath(planDir);
|
|
9471
9634
|
const layout = JSON.parse(readFileSync8(layoutPath, "utf-8"));
|
|
9472
9635
|
const frame = readFileSync8(join23(planDir, SLICE_FRAME));
|
|
9473
9636
|
const fd = openSync4(scratchPath, "w");
|
|
@@ -9840,7 +10003,7 @@ function assertPlanSizeWithinLimit(input) {
|
|
|
9840
10003
|
});
|
|
9841
10004
|
throw new PlanTooLargeError(breakdown.totalBytes, input.limitBytes, breakdown, input.observedAt);
|
|
9842
10005
|
}
|
|
9843
|
-
function resolveChunkPlan(totalFrames, configChunkSize, maxParallelChunks, targetChunkFrames) {
|
|
10006
|
+
function resolveChunkPlan(totalFrames, configChunkSize, maxParallelChunks, targetChunkFrames, minParallelChunks = 1) {
|
|
9844
10007
|
assertPositiveInteger("totalFrames", totalFrames);
|
|
9845
10008
|
assertPositiveInteger("maxParallelChunks", maxParallelChunks);
|
|
9846
10009
|
if (configChunkSize !== void 0) {
|
|
@@ -9849,7 +10012,11 @@ function resolveChunkPlan(totalFrames, configChunkSize, maxParallelChunks, targe
|
|
|
9849
10012
|
if (targetChunkFrames !== void 0) {
|
|
9850
10013
|
assertPositiveInteger("targetChunkFrames", targetChunkFrames);
|
|
9851
10014
|
}
|
|
9852
|
-
|
|
10015
|
+
assertPositiveInteger("minParallelChunks", minParallelChunks);
|
|
10016
|
+
const autoSizeParallel = targetChunkFrames === void 0 ? maxParallelChunks : Math.min(
|
|
10017
|
+
maxParallelChunks,
|
|
10018
|
+
Math.max(minParallelChunks, Math.ceil(totalFrames / targetChunkFrames))
|
|
10019
|
+
);
|
|
9853
10020
|
const resolvedChunkSize = configChunkSize ?? Math.max(MIN_CHUNK_SIZE, Math.ceil(totalFrames / autoSizeParallel));
|
|
9854
10021
|
const naiveCount = Math.ceil(totalFrames / resolvedChunkSize);
|
|
9855
10022
|
const chunkCount = Math.min(maxParallelChunks, Math.max(1, naiveCount));
|
|
@@ -10017,7 +10184,7 @@ function pathInside(root, path) {
|
|
|
10017
10184
|
function stableSourcePath(roots, srcPath) {
|
|
10018
10185
|
for (const [root, prefix] of roots) {
|
|
10019
10186
|
const rel = pathInside(root, srcPath);
|
|
10020
|
-
if (rel !== null) return [prefix, ...rel.split(
|
|
10187
|
+
if (rel !== null) return [prefix, ...rel.split(sep3)].filter(Boolean).join("/");
|
|
10021
10188
|
}
|
|
10022
10189
|
return srcPath;
|
|
10023
10190
|
}
|
|
@@ -10031,8 +10198,8 @@ function createVideoSourceShipper(input) {
|
|
|
10031
10198
|
let result = null;
|
|
10032
10199
|
for (const root of stagedRel === null ? [input.workCompiledDir, input.projectDir] : []) {
|
|
10033
10200
|
const rel = pathInside(root, srcPath);
|
|
10034
|
-
if (rel !== null && (
|
|
10035
|
-
result = ["compiled", ...rel.split(
|
|
10201
|
+
if (rel !== null && (existsSync19(join25(input.planDir, "compiled", rel)) || input.isRemote(srcPath))) {
|
|
10202
|
+
result = ["compiled", ...rel.split(sep3)].join("/");
|
|
10036
10203
|
break;
|
|
10037
10204
|
}
|
|
10038
10205
|
}
|
|
@@ -10059,7 +10226,7 @@ async function compilePlanComposition(projectDir, config, planDir, options) {
|
|
|
10059
10226
|
useGpu: false,
|
|
10060
10227
|
browserGpuMode: "software"
|
|
10061
10228
|
});
|
|
10062
|
-
if (!
|
|
10229
|
+
if (!existsSync19(planDir)) mkdirSync10(planDir, { recursive: true });
|
|
10063
10230
|
const log = config.logger ?? defaultLogger;
|
|
10064
10231
|
const sizeLimitBytes = options.executionPlanSizeLimitBytes ?? config.planDirSizeLimitBytes ?? PLAN_DIR_SIZE_LIMIT_BYTES;
|
|
10065
10232
|
const abortSignal = config.abortSignal;
|
|
@@ -10089,7 +10256,7 @@ async function compilePlanComposition(projectDir, config, planDir, options) {
|
|
|
10089
10256
|
});
|
|
10090
10257
|
const entryFile = config.entryFile ?? "index.html";
|
|
10091
10258
|
const htmlPath = join25(projectDir, entryFile);
|
|
10092
|
-
if (!
|
|
10259
|
+
if (!existsSync19(htmlPath)) {
|
|
10093
10260
|
throw new Error(`[plan] entry file not found: ${htmlPath}`);
|
|
10094
10261
|
}
|
|
10095
10262
|
const workDir = join25(planDir, ".plan-work");
|
|
@@ -10104,7 +10271,7 @@ async function compilePlanComposition(projectDir, config, planDir, options) {
|
|
|
10104
10271
|
filter: (src) => {
|
|
10105
10272
|
const rel = relative5(projectDir, src);
|
|
10106
10273
|
if (rel === "" || rel.startsWith("..")) return true;
|
|
10107
|
-
const firstSegment = rel.split(
|
|
10274
|
+
const firstSegment = rel.split(sep3, 1)[0];
|
|
10108
10275
|
return firstSegment === void 0 || !PLAN_PROJECT_DIR_SKIP_SEGMENTS.has(firstSegment);
|
|
10109
10276
|
}
|
|
10110
10277
|
});
|
|
@@ -10317,15 +10484,15 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
|
|
|
10317
10484
|
logPlanStage(log, "extract", stageStarted);
|
|
10318
10485
|
const stagedVideoFrames = join25(compiledDir, "__hyperframes_video_frames");
|
|
10319
10486
|
const videoFramesDst = join25(planDir, "video-frames");
|
|
10320
|
-
if (
|
|
10487
|
+
if (existsSync19(videoFramesDst)) rmSync7(videoFramesDst, { recursive: true, force: true });
|
|
10321
10488
|
const videoSourcesDir = join25(planDir, "video-sources");
|
|
10322
10489
|
rmSync7(videoSourcesDir, { recursive: true, force: true });
|
|
10323
|
-
if (
|
|
10490
|
+
if (existsSync19(stagedVideoFrames)) {
|
|
10324
10491
|
renameWithRetry8(stagedVideoFrames, videoFramesDst);
|
|
10325
10492
|
} else {
|
|
10326
10493
|
mkdirSync10(videoFramesDst, { recursive: true });
|
|
10327
10494
|
}
|
|
10328
|
-
if (
|
|
10495
|
+
if (existsSync19(finalCompiledDir)) rmSync7(finalCompiledDir, { recursive: true, force: true });
|
|
10329
10496
|
renameWithRetry8(compiledDir, finalCompiledDir);
|
|
10330
10497
|
const shipVideoSource = createVideoSourceShipper({
|
|
10331
10498
|
planDir,
|
|
@@ -10428,7 +10595,8 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
|
|
|
10428
10595
|
totalFrames,
|
|
10429
10596
|
config.chunkSize,
|
|
10430
10597
|
maxParallel,
|
|
10431
|
-
config.targetChunkFrames
|
|
10598
|
+
config.targetChunkFrames,
|
|
10599
|
+
config.minParallelChunks
|
|
10432
10600
|
);
|
|
10433
10601
|
const chunks = buildPlanChunkSlices(
|
|
10434
10602
|
frameWeights,
|
|
@@ -10445,10 +10613,10 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
|
|
|
10445
10613
|
applyDistributedAudioWarningPolicy(job, audioResult.audioError, audioResult.audioFailures, log);
|
|
10446
10614
|
}
|
|
10447
10615
|
const planAudioPath = join25(planDir, PLAN_AUDIO_RELATIVE_PATH);
|
|
10448
|
-
if (audioResult?.hasAudio &&
|
|
10616
|
+
if (audioResult?.hasAudio && existsSync19(audioResult.audioOutputPath)) {
|
|
10449
10617
|
renameWithRetry8(audioResult.audioOutputPath, planAudioPath);
|
|
10450
10618
|
}
|
|
10451
|
-
if (chunksCarryAudio &&
|
|
10619
|
+
if (chunksCarryAudio && existsSync19(planAudioPath)) {
|
|
10452
10620
|
if (options.sliceChunkAudio) {
|
|
10453
10621
|
stageStarted = Date.now();
|
|
10454
10622
|
await writeChunkAudioSlices({
|
|
@@ -10561,7 +10729,7 @@ async function buildPlanAudio(projectDir, config, workRoot, outputPath, options
|
|
|
10561
10729
|
if (mixed.audioError) {
|
|
10562
10730
|
applyDistributedAudioWarningPolicy(c.job, mixed.audioError, mixed.audioFailures, c.log);
|
|
10563
10731
|
}
|
|
10564
|
-
if (!mixed.hasAudio || !
|
|
10732
|
+
if (!mixed.hasAudio || !existsSync19(mixed.audioOutputPath)) return { hasAudio: false };
|
|
10565
10733
|
renameWithRetry8(mixed.audioOutputPath, outputPath);
|
|
10566
10734
|
return { hasAudio: true };
|
|
10567
10735
|
} finally {
|
|
@@ -10576,7 +10744,7 @@ import {
|
|
|
10576
10744
|
} from "@hyperframes/engine";
|
|
10577
10745
|
import {
|
|
10578
10746
|
copyFileSync as copyFileSync8,
|
|
10579
|
-
existsSync as
|
|
10747
|
+
existsSync as existsSync21,
|
|
10580
10748
|
lstatSync as lstatSync2,
|
|
10581
10749
|
mkdirSync as mkdirSync12,
|
|
10582
10750
|
mkdtempSync as mkdtempSync4,
|
|
@@ -10587,14 +10755,14 @@ import {
|
|
|
10587
10755
|
writeFileSync as writeFileSync10
|
|
10588
10756
|
} from "node:fs";
|
|
10589
10757
|
import { tmpdir } from "node:os";
|
|
10590
|
-
import { dirname as dirname14, isAbsolute as isAbsolute4, join as join28, relative as relative6, resolve as resolve9, sep as
|
|
10758
|
+
import { dirname as dirname14, isAbsolute as isAbsolute4, join as join28, relative as relative6, resolve as resolve9, sep as sep4 } from "node:path";
|
|
10591
10759
|
import { DISTRIBUTED_FPS, isDistributedFps } from "@hyperframes/core";
|
|
10592
10760
|
import { createFrameLookupTable as createFrameLookupTable5, renameWithRetry as renameWithRetry10 } from "@hyperframes/engine";
|
|
10593
10761
|
|
|
10594
10762
|
// src/services/distributed/planV2Publisher.ts
|
|
10595
10763
|
import {
|
|
10596
10764
|
copyFileSync as copyFileSync7,
|
|
10597
|
-
existsSync as
|
|
10765
|
+
existsSync as existsSync20,
|
|
10598
10766
|
linkSync as linkSync2,
|
|
10599
10767
|
mkdirSync as mkdirSync11,
|
|
10600
10768
|
mkdtempSync as mkdtempSync3,
|
|
@@ -10663,7 +10831,7 @@ var LocalPlanV2ArtifactPublisher = class {
|
|
|
10663
10831
|
#linkFile;
|
|
10664
10832
|
#committed = false;
|
|
10665
10833
|
constructor(destinationDir, options = {}) {
|
|
10666
|
-
if (
|
|
10834
|
+
if (existsSync20(destinationDir)) {
|
|
10667
10835
|
throw new PlanV2IntegrityError(`output directory already exists: ${destinationDir}`);
|
|
10668
10836
|
}
|
|
10669
10837
|
this.destinationDir = destinationDir;
|
|
@@ -10680,7 +10848,7 @@ var LocalPlanV2ArtifactPublisher = class {
|
|
|
10680
10848
|
);
|
|
10681
10849
|
}
|
|
10682
10850
|
const destinationPath = planV2BlobPath(this.temporaryDir, digest);
|
|
10683
|
-
if (
|
|
10851
|
+
if (existsSync20(destinationPath)) return;
|
|
10684
10852
|
mkdirSync11(dirname13(destinationPath), { recursive: true });
|
|
10685
10853
|
const stagingDir = mkdtempSync3(join27(dirname13(destinationPath), ".plan-v2-blob-"));
|
|
10686
10854
|
const temporaryPath = join27(stagingDir, "blob");
|
|
@@ -10698,7 +10866,7 @@ var LocalPlanV2ArtifactPublisher = class {
|
|
|
10698
10866
|
}
|
|
10699
10867
|
async commitManifest(manifestBytes) {
|
|
10700
10868
|
for (const digest of new Set(manifestDigests(manifestBytes))) {
|
|
10701
|
-
if (!
|
|
10869
|
+
if (!existsSync20(planV2BlobPath(this.temporaryDir, digest))) {
|
|
10702
10870
|
throw new PlanV2IntegrityError(
|
|
10703
10871
|
`cannot commit manifest before referenced blob is durable: ${digest}`
|
|
10704
10872
|
);
|
|
@@ -10754,7 +10922,7 @@ function listFiles(root) {
|
|
|
10754
10922
|
walk(absolutePath);
|
|
10755
10923
|
} else if (entry.isFile()) {
|
|
10756
10924
|
files.push({
|
|
10757
|
-
path: relative6(rootResolved, absolutePath).split(
|
|
10925
|
+
path: relative6(rootResolved, absolutePath).split(sep4).join("/"),
|
|
10758
10926
|
absolutePath
|
|
10759
10927
|
});
|
|
10760
10928
|
} else {
|
|
@@ -10777,7 +10945,7 @@ function assertValidExtractionCacheCompleteSentinel(path) {
|
|
|
10777
10945
|
}
|
|
10778
10946
|
function validateExtractionCacheCompleteSentinels(executionPlanDir) {
|
|
10779
10947
|
const videoRoot = join28(executionPlanDir, "video-frames");
|
|
10780
|
-
if (!
|
|
10948
|
+
if (!existsSync21(videoRoot)) return;
|
|
10781
10949
|
for (const videoEntry of readdirSync7(videoRoot, { withFileTypes: true })) {
|
|
10782
10950
|
if (!videoEntry.isDirectory()) continue;
|
|
10783
10951
|
const videoDir = join28(videoRoot, videoEntry.name);
|
|
@@ -10794,7 +10962,7 @@ function isExtractionCacheCompleteSentinelPath(path) {
|
|
|
10794
10962
|
function resolveExtractedVideoOutputDir(planDir, videoId) {
|
|
10795
10963
|
const videoRoot = resolve9(planDir, "video-frames");
|
|
10796
10964
|
const outputDir = resolve9(videoRoot, videoId);
|
|
10797
|
-
if (outputDir === videoRoot || !outputDir.startsWith(`${videoRoot}${
|
|
10965
|
+
if (outputDir === videoRoot || !outputDir.startsWith(`${videoRoot}${sep4}`)) {
|
|
10798
10966
|
throw new PlanV2IntegrityError(`unsafe extracted video id: ${JSON.stringify(videoId)}`);
|
|
10799
10967
|
}
|
|
10800
10968
|
return outputDir;
|
|
@@ -10878,7 +11046,7 @@ function parsePlanVideosJson2(value) {
|
|
|
10878
11046
|
}
|
|
10879
11047
|
function materializeExtractedVideoDirectories(planDir) {
|
|
10880
11048
|
const videosPath = join28(planDir, PLAN_VIDEOS_META_RELATIVE_PATH);
|
|
10881
|
-
if (!
|
|
11049
|
+
if (!existsSync21(videosPath)) return;
|
|
10882
11050
|
const videos = parsePlanVideosJson2(readJsonFile(videosPath, PLAN_VIDEOS_META_RELATIVE_PATH));
|
|
10883
11051
|
for (const video of videos.extracted) {
|
|
10884
11052
|
mkdirSync12(resolveExtractedVideoOutputDir(planDir, video.videoId), { recursive: true });
|
|
@@ -10912,9 +11080,9 @@ function parseChunkSlices(value) {
|
|
|
10912
11080
|
}
|
|
10913
11081
|
function buildVideoChunkDependencies(executionPlanDir, dimensions) {
|
|
10914
11082
|
const videoRoot = join28(executionPlanDir, "video-frames");
|
|
10915
|
-
const hasExtractedFrames =
|
|
11083
|
+
const hasExtractedFrames = existsSync21(videoRoot) && listFiles(videoRoot).length > 0;
|
|
10916
11084
|
const videosPath = join28(executionPlanDir, PLAN_VIDEOS_META_RELATIVE_PATH);
|
|
10917
|
-
if (!
|
|
11085
|
+
if (!existsSync21(videosPath)) {
|
|
10918
11086
|
return hasExtractedFrames ? { mode: "full-source-pack", dependencies: null } : { mode: "exact-rendered-frames", dependencies: /* @__PURE__ */ new Map() };
|
|
10919
11087
|
}
|
|
10920
11088
|
const chunksPath = join28(executionPlanDir, "meta", "chunks.json");
|
|
@@ -10954,7 +11122,7 @@ function collectChunkVideoDependencies(input) {
|
|
|
10954
11122
|
for (let frame = chunk.startFrame; frame < chunk.endFrame; frame++) {
|
|
10955
11123
|
const globalTime = frame * input.fpsDen / input.fpsNum;
|
|
10956
11124
|
for (const [videoId, payload] of input.table.getActiveFramePayloads(globalTime)) {
|
|
10957
|
-
const path = input.deferredSources.get(videoId) ?? relative6(resolve9(input.executionPlanDir), payload.framePath).split(
|
|
11125
|
+
const path = input.deferredSources.get(videoId) ?? relative6(resolve9(input.executionPlanDir), payload.framePath).split(sep4).join("/");
|
|
10958
11126
|
assertSafeRelativePath(path);
|
|
10959
11127
|
const indexes = mutable.get(path) ?? /* @__PURE__ */ new Set();
|
|
10960
11128
|
indexes.add(chunk.index);
|
|
@@ -10971,7 +11139,7 @@ function computeManifestHash(manifest) {
|
|
|
10971
11139
|
return sha256Hex(`${PLAN_V2_HASH_PREFIX}${canonicalJsonStringify(manifestPayload(manifest))}`);
|
|
10972
11140
|
}
|
|
10973
11141
|
function writeBlob(sourcePath, destinationPath) {
|
|
10974
|
-
if (
|
|
11142
|
+
if (existsSync21(destinationPath)) return;
|
|
10975
11143
|
mkdirSync12(dirname14(destinationPath), { recursive: true });
|
|
10976
11144
|
const temporaryDir = mkdtempSync4(join28(dirname14(destinationPath), ".plan-v2-blob-"));
|
|
10977
11145
|
const temporaryPath = join28(temporaryDir, "blob");
|
|
@@ -11011,7 +11179,7 @@ async function putTasks(tasks) {
|
|
|
11011
11179
|
}
|
|
11012
11180
|
function buildPlanV2Publication(executionPlanDir, digests) {
|
|
11013
11181
|
const executionPlanPath = join28(executionPlanDir, "plan.json");
|
|
11014
|
-
if (!
|
|
11182
|
+
if (!existsSync21(executionPlanPath)) {
|
|
11015
11183
|
throw new PlanV2IntegrityError(`execution plan is missing plan.json: ${executionPlanPath}`);
|
|
11016
11184
|
}
|
|
11017
11185
|
const executionPlanValue = readJsonFile(executionPlanPath, "execution plan.json");
|
|
@@ -11091,7 +11259,7 @@ function buildPlanV2Publication(executionPlanDir, digests) {
|
|
|
11091
11259
|
};
|
|
11092
11260
|
}
|
|
11093
11261
|
function createPlanV2FromExecutionPlan(executionPlanDir, planV2Dir) {
|
|
11094
|
-
if (
|
|
11262
|
+
if (existsSync21(planV2Dir)) {
|
|
11095
11263
|
throw new PlanV2IntegrityError(`output directory already exists: ${planV2Dir}`);
|
|
11096
11264
|
}
|
|
11097
11265
|
const publication = buildPlanV2Publication(executionPlanDir);
|
|
@@ -11177,7 +11345,7 @@ async function planV2WithPublisher(projectDir, config, publisher, options = {})
|
|
|
11177
11345
|
}
|
|
11178
11346
|
}
|
|
11179
11347
|
async function planV2(projectDir, config, planV2Dir) {
|
|
11180
|
-
if (
|
|
11348
|
+
if (existsSync21(planV2Dir)) {
|
|
11181
11349
|
throw new PlanV2IntegrityError(`output directory already exists: ${planV2Dir}`);
|
|
11182
11350
|
}
|
|
11183
11351
|
const publisher = new LocalPlanV2ArtifactPublisher(planV2Dir);
|
|
@@ -11331,7 +11499,7 @@ function parsePlanV2Manifest(value) {
|
|
|
11331
11499
|
}
|
|
11332
11500
|
function readPlanV2Manifest(planV2Dir) {
|
|
11333
11501
|
const manifestPath = join28(planV2Dir, "plan.json");
|
|
11334
|
-
if (!
|
|
11502
|
+
if (!existsSync21(manifestPath)) {
|
|
11335
11503
|
throw new PlanV2IntegrityError(`missing v2 manifest: ${manifestPath}`);
|
|
11336
11504
|
}
|
|
11337
11505
|
const value = readJsonFile(manifestPath, "v2 manifest");
|
|
@@ -11350,7 +11518,7 @@ function listPlanV2ArtifactsForTarget(manifest, target) {
|
|
|
11350
11518
|
}
|
|
11351
11519
|
function verifyBlob(planV2Dir, artifact) {
|
|
11352
11520
|
const sourcePath = planV2BlobPath(planV2Dir, artifact.sha256);
|
|
11353
|
-
if (!
|
|
11521
|
+
if (!existsSync21(sourcePath)) {
|
|
11354
11522
|
throw new PlanV2IntegrityError(`missing content-addressed artifact ${artifact.sha256}`);
|
|
11355
11523
|
}
|
|
11356
11524
|
const stats = statSync7(sourcePath);
|
|
@@ -11363,7 +11531,7 @@ function verifyBlob(planV2Dir, artifact) {
|
|
|
11363
11531
|
return sourcePath;
|
|
11364
11532
|
}
|
|
11365
11533
|
function materializePlanV2Target(planV2Dir, target, destinationDir, options = {}) {
|
|
11366
|
-
if (
|
|
11534
|
+
if (existsSync21(destinationDir)) {
|
|
11367
11535
|
throw new PlanV2IntegrityError(`materialization destination already exists: ${destinationDir}`);
|
|
11368
11536
|
}
|
|
11369
11537
|
const manifest = readPlanV2Manifest(planV2Dir);
|
|
@@ -11428,7 +11596,7 @@ function validatePlanV2MaterializedTarget(planDir, expectedTarget) {
|
|
|
11428
11596
|
}
|
|
11429
11597
|
function readValidatedPlanV2Materialization(planDir, expectedTarget) {
|
|
11430
11598
|
const markerPath = join28(planDir, PLAN_V2_MATERIALIZATION_MARKER);
|
|
11431
|
-
if (!
|
|
11599
|
+
if (!existsSync21(markerPath)) return null;
|
|
11432
11600
|
const marker = readJsonFile(markerPath, "materialization marker");
|
|
11433
11601
|
if (!isRecord3(marker) || !isRecord3(marker.manifest) || !isRecord3(marker.target)) {
|
|
11434
11602
|
throw new PlanV2IntegrityError("malformed materialization marker");
|
|
@@ -11446,7 +11614,7 @@ function readValidatedPlanV2Materialization(planDir, expectedTarget) {
|
|
|
11446
11614
|
for (const artifact of required) {
|
|
11447
11615
|
if (Object.hasOwn(remoteSources, artifact.path)) continue;
|
|
11448
11616
|
const path = join28(planDir, ...artifact.path.split("/"));
|
|
11449
|
-
if (!
|
|
11617
|
+
if (!existsSync21(path) || statSync7(path).size !== artifact.sizeBytes) {
|
|
11450
11618
|
throw new PlanV2IntegrityError(
|
|
11451
11619
|
`materialized artifact missing or truncated: ${artifact.path}`
|
|
11452
11620
|
);
|
|
@@ -11470,7 +11638,7 @@ import {
|
|
|
11470
11638
|
} from "@hyperframes/engine";
|
|
11471
11639
|
import {
|
|
11472
11640
|
cpSync as cpSync3,
|
|
11473
|
-
existsSync as
|
|
11641
|
+
existsSync as existsSync22,
|
|
11474
11642
|
mkdirSync as mkdirSync13,
|
|
11475
11643
|
mkdtempSync as mkdtempSync5,
|
|
11476
11644
|
readFileSync as readFileSync10,
|
|
@@ -11525,7 +11693,7 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
|
|
|
11525
11693
|
}
|
|
11526
11694
|
const format = plan.dimensions.format;
|
|
11527
11695
|
const cfrEncoder = cfr ? readCfrEncoder(planDir, format) : null;
|
|
11528
|
-
if (!
|
|
11696
|
+
if (!existsSync22(dirname15(outputPath))) {
|
|
11529
11697
|
mkdirSync13(dirname15(outputPath), { recursive: true });
|
|
11530
11698
|
}
|
|
11531
11699
|
const workDir = mkdtempSync5(`${outputPath}.assemble-work-`);
|
|
@@ -11549,7 +11717,7 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
|
|
|
11549
11717
|
try {
|
|
11550
11718
|
const videoSources = cfrEncoder ? [await reencodeCfr(ctx, chunkPaths, cfrEncoder)] : chunkPaths;
|
|
11551
11719
|
const finalPath = join29(workDir, `output.${format}`);
|
|
11552
|
-
const audio = audioPath !== null &&
|
|
11720
|
+
const audio = audioPath !== null && existsSync22(audioPath) ? audioPath : null;
|
|
11553
11721
|
await writeJoined(ctx, videoSources, audio, finalPath);
|
|
11554
11722
|
renameWithRetry11(finalPath, outputPath);
|
|
11555
11723
|
log.info("[assemble] stage timings", {
|
|
@@ -11567,7 +11735,7 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
|
|
|
11567
11735
|
});
|
|
11568
11736
|
}
|
|
11569
11737
|
}
|
|
11570
|
-
const fileSize =
|
|
11738
|
+
const fileSize = existsSync22(outputPath) ? statSync8(outputPath).size : 0;
|
|
11571
11739
|
return {
|
|
11572
11740
|
...referenceBackgroundResult6(plan[REFERENCE_BACKGROUND_FIELD4]),
|
|
11573
11741
|
outputPath,
|
|
@@ -11579,13 +11747,13 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
|
|
|
11579
11747
|
function readAssemblePlan(planDir, chunkPaths) {
|
|
11580
11748
|
const planJsonPath = join29(planDir, "plan.json");
|
|
11581
11749
|
const chunksJsonPath = join29(planDir, "meta", "chunks.json");
|
|
11582
|
-
if (!
|
|
11750
|
+
if (!existsSync22(planJsonPath)) {
|
|
11583
11751
|
throw new Error(`[assemble] planDir missing plan.json: ${planJsonPath}`);
|
|
11584
11752
|
}
|
|
11585
11753
|
const plan = JSON.parse(readFileSync10(planJsonPath, "utf-8"));
|
|
11586
11754
|
readPlanProtocolV1(plan, DISTRIBUTED_RENDER_CAPABILITIES.roles.assembler);
|
|
11587
11755
|
validatePlanV2MaterializedTarget(planDir, { role: "assembler" });
|
|
11588
|
-
if (!
|
|
11756
|
+
if (!existsSync22(chunksJsonPath)) {
|
|
11589
11757
|
throw new Error(`[assemble] planDir missing meta/chunks.json: ${chunksJsonPath}`);
|
|
11590
11758
|
}
|
|
11591
11759
|
const chunks = JSON.parse(readFileSync10(chunksJsonPath, "utf-8"));
|
|
@@ -11600,7 +11768,7 @@ function readAssemblePlan(planDir, chunkPaths) {
|
|
|
11600
11768
|
}
|
|
11601
11769
|
}
|
|
11602
11770
|
for (const path of chunkPaths.flat()) {
|
|
11603
|
-
if (!
|
|
11771
|
+
if (!existsSync22(path)) {
|
|
11604
11772
|
throw new Error(`[assemble] chunk path does not exist: ${path}`);
|
|
11605
11773
|
}
|
|
11606
11774
|
}
|
|
@@ -11739,7 +11907,7 @@ function planFormat(planDir) {
|
|
|
11739
11907
|
}
|
|
11740
11908
|
function lockedAudio(planDir) {
|
|
11741
11909
|
const encoderJsonPath = join29(planDir, "meta", "encoder.json");
|
|
11742
|
-
if (!
|
|
11910
|
+
if (!existsSync22(encoderJsonPath)) return {};
|
|
11743
11911
|
return JSON.parse(readFileSync10(encoderJsonPath, "utf-8"));
|
|
11744
11912
|
}
|
|
11745
11913
|
function readCfrEncoder(planDir, format) {
|
|
@@ -11749,7 +11917,7 @@ function readCfrEncoder(planDir, format) {
|
|
|
11749
11917
|
);
|
|
11750
11918
|
}
|
|
11751
11919
|
const encoderJsonPath = join29(planDir, "meta", "encoder.json");
|
|
11752
|
-
if (!
|
|
11920
|
+
if (!existsSync22(encoderJsonPath)) {
|
|
11753
11921
|
throw new Error(`[assemble] planDir missing meta/encoder.json: ${encoderJsonPath}`);
|
|
11754
11922
|
}
|
|
11755
11923
|
const encoderJson = JSON.parse(readFileSync10(encoderJsonPath, "utf-8"));
|
|
@@ -11761,7 +11929,7 @@ function readCfrEncoder(planDir, format) {
|
|
|
11761
11929
|
return encoderJson;
|
|
11762
11930
|
}
|
|
11763
11931
|
function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, startTimeMs) {
|
|
11764
|
-
if (
|
|
11932
|
+
if (existsSync22(outputPath)) rmSync10(outputPath, { recursive: true, force: true });
|
|
11765
11933
|
mkdirSync13(outputPath, { recursive: true });
|
|
11766
11934
|
let globalIdx = 0;
|
|
11767
11935
|
for (const chunkDir of chunkPaths) {
|
|
@@ -11785,7 +11953,7 @@ function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, start
|
|
|
11785
11953
|
`[assemble] png-sequence frame count mismatch: merged ${globalIdx} frames vs plan.totalFrames=${totalFrames}. Using on-disk count.`
|
|
11786
11954
|
);
|
|
11787
11955
|
}
|
|
11788
|
-
if (audioPath !== null &&
|
|
11956
|
+
if (audioPath !== null && existsSync22(audioPath)) {
|
|
11789
11957
|
const sidecar = join29(outputPath, MIXED_AUDIO_FILENAME4);
|
|
11790
11958
|
cpSync3(audioPath, sidecar);
|
|
11791
11959
|
}
|
|
@@ -11806,7 +11974,7 @@ function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, start
|
|
|
11806
11974
|
|
|
11807
11975
|
// src/services/distributed/renderChunk.ts
|
|
11808
11976
|
import { randomBytes } from "node:crypto";
|
|
11809
|
-
import { existsSync as
|
|
11977
|
+
import { existsSync as existsSync23, mkdirSync as mkdirSync14, readFileSync as readFileSync11, readdirSync as readdirSync9, rmSync as rmSync11, writeFileSync as writeFileSync12 } from "node:fs";
|
|
11810
11978
|
import { cpus } from "node:os";
|
|
11811
11979
|
import { join as join30 } from "node:path";
|
|
11812
11980
|
import {
|
|
@@ -11912,12 +12080,23 @@ async function runCaptureWithScreenshotFallback(input) {
|
|
|
11912
12080
|
try {
|
|
11913
12081
|
return await input.run(input.forceScreenshot);
|
|
11914
12082
|
} catch (error) {
|
|
11915
|
-
if (input.forceScreenshot || await input.canRetry?.() === false)
|
|
11916
|
-
|
|
12083
|
+
if (input.forceScreenshot || await input.canRetry?.() === false)
|
|
12084
|
+
throw imageMissingIn(error) ?? error;
|
|
12085
|
+
if (!shouldRetryChunkCaptureWithScreenshot(error)) throw imageMissingIn(error) ?? error;
|
|
11917
12086
|
input.onFallback?.(error);
|
|
11918
12087
|
await input.resetForScreenshotRetry();
|
|
11919
|
-
return await input.run(true)
|
|
12088
|
+
return await input.run(true).catch((retryError) => {
|
|
12089
|
+
throw imageMissingIn(retryError) ?? retryError;
|
|
12090
|
+
});
|
|
12091
|
+
}
|
|
12092
|
+
}
|
|
12093
|
+
function imageMissingIn(error) {
|
|
12094
|
+
let e = error;
|
|
12095
|
+
for (let depth = 0; depth < 5 && typeof e === "object" && e !== null; depth++) {
|
|
12096
|
+
if (e.name === "ImageMissingFromFrameError") return e;
|
|
12097
|
+
e = e.cause;
|
|
11920
12098
|
}
|
|
12099
|
+
return void 0;
|
|
11921
12100
|
}
|
|
11922
12101
|
async function beginFrameSessionNeedsScreenshotFallback(session, probe = probeBeginFrameLiveness2) {
|
|
11923
12102
|
if (session.launchCaptureMode !== "beginframe") return false;
|
|
@@ -11952,7 +12131,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
11952
12131
|
const planJsonPath = join30(planDir, "plan.json");
|
|
11953
12132
|
const encoderJsonPath = join30(planDir, "meta", "encoder.json");
|
|
11954
12133
|
const chunksJsonPath = join30(planDir, "meta", "chunks.json");
|
|
11955
|
-
if (!
|
|
12134
|
+
if (!existsSync23(planJsonPath)) {
|
|
11956
12135
|
throw new RenderChunkValidationError(
|
|
11957
12136
|
MISSING_PLAN_ARTIFACT,
|
|
11958
12137
|
`[renderChunk] planDir is missing required artifact: ${planJsonPath}`
|
|
@@ -11968,7 +12147,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
11968
12147
|
const v2Manifest = v2Materialization?.manifest ?? null;
|
|
11969
12148
|
const remoteSources = v2Materialization?.remoteSources ?? {};
|
|
11970
12149
|
for (const required of [encoderJsonPath, chunksJsonPath]) {
|
|
11971
|
-
if (!
|
|
12150
|
+
if (!existsSync23(required)) {
|
|
11972
12151
|
throw new RenderChunkValidationError(
|
|
11973
12152
|
MISSING_PLAN_ARTIFACT,
|
|
11974
12153
|
`[renderChunk] planDir is missing required artifact: ${required}`
|
|
@@ -11979,7 +12158,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
11979
12158
|
const chunks = JSON.parse(readFileSync11(chunksJsonPath, "utf-8"));
|
|
11980
12159
|
const videosJsonPath = join30(planDir, PLAN_VIDEOS_META_RELATIVE_PATH);
|
|
11981
12160
|
let planVideos = null;
|
|
11982
|
-
if (
|
|
12161
|
+
if (existsSync23(videosJsonPath)) {
|
|
11983
12162
|
try {
|
|
11984
12163
|
planVideos = validatePlanVideosForChunk(JSON.parse(readFileSync11(videosJsonPath, "utf-8")));
|
|
11985
12164
|
} catch (err) {
|
|
@@ -12010,7 +12189,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
12010
12189
|
const range = resolveChunkFrameRange(slice, chunkIndex, options?.frameRange);
|
|
12011
12190
|
const framesInChunk = range.endFrame - range.startFrame;
|
|
12012
12191
|
const compiledDir = join30(planDir, "compiled");
|
|
12013
|
-
if (!
|
|
12192
|
+
if (!existsSync23(compiledDir)) {
|
|
12014
12193
|
throw new RenderChunkValidationError(
|
|
12015
12194
|
MISSING_PLAN_ARTIFACT,
|
|
12016
12195
|
`[renderChunk] planDir missing compiled/ directory: ${compiledDir}`
|
|
@@ -12260,10 +12439,10 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
12260
12439
|
const effectiveBitrate = encoder.crf != null ? void 0 : encoder.bitrate;
|
|
12261
12440
|
const videoOnlyPath = encoder.chunksCarryAudio ? `${outputChunkPath}.video.mp4` : outputChunkPath;
|
|
12262
12441
|
if (isPngSequence) {
|
|
12263
|
-
if (!
|
|
12442
|
+
if (!existsSync23(outputChunkPath)) mkdirSync14(outputChunkPath, { recursive: true });
|
|
12264
12443
|
} else {
|
|
12265
12444
|
const outDir = join30(outputChunkPath, "..");
|
|
12266
|
-
if (!
|
|
12445
|
+
if (!existsSync23(outDir)) mkdirSync14(outDir, { recursive: true });
|
|
12267
12446
|
}
|
|
12268
12447
|
const encodeInput = {
|
|
12269
12448
|
job,
|
|
@@ -12693,6 +12872,20 @@ function validateDistributedRenderConfig(config) {
|
|
|
12693
12872
|
);
|
|
12694
12873
|
}
|
|
12695
12874
|
}
|
|
12875
|
+
if (config.minParallelChunks !== void 0) {
|
|
12876
|
+
if (!Number.isInteger(config.minParallelChunks) || config.minParallelChunks < 1) {
|
|
12877
|
+
throw new InvalidConfigError(
|
|
12878
|
+
"config.minParallelChunks",
|
|
12879
|
+
`must be a positive integer; got ${config.minParallelChunks}`
|
|
12880
|
+
);
|
|
12881
|
+
}
|
|
12882
|
+
if (config.minParallelChunks > MAX_PARALLEL_CHUNKS_CEILING) {
|
|
12883
|
+
throw new InvalidConfigError(
|
|
12884
|
+
"config.minParallelChunks",
|
|
12885
|
+
`must be <= ${MAX_PARALLEL_CHUNKS_CEILING}; got ${config.minParallelChunks}`
|
|
12886
|
+
);
|
|
12887
|
+
}
|
|
12888
|
+
}
|
|
12696
12889
|
for (const field of ["workerTimeoutSeconds", "workerConcurrency"]) {
|
|
12697
12890
|
const value = config[field];
|
|
12698
12891
|
if (value !== void 0 && (!Number.isInteger(value) || value < 1)) {
|