@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/index.js
CHANGED
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
// src/services/renderOrchestrator.ts
|
|
10
10
|
import { audioFailureDiagnostics as audioFailureDiagnostics2 } from "@hyperframes/engine";
|
|
11
11
|
import {
|
|
12
|
-
existsSync as
|
|
12
|
+
existsSync as existsSync21,
|
|
13
13
|
mkdirSync as mkdirSync12,
|
|
14
14
|
readdirSync as readdirSync6,
|
|
15
15
|
rmSync as rmSync12,
|
|
@@ -410,6 +410,68 @@ ${seededRandomBlock}
|
|
|
410
410
|
}
|
|
411
411
|
};
|
|
412
412
|
|
|
413
|
+
// Timers set once rendering has started run on film time: each seek fires every timer due by then, in
|
|
414
|
+
// due order. Until a seek after the page reports ready, timers keep the wall clock: setup code waits on
|
|
415
|
+
// them while the renderer may already be seeking.
|
|
416
|
+
var rendering = false;
|
|
417
|
+
var timerQueue = [];
|
|
418
|
+
var nextTimerId = 1000000000;
|
|
419
|
+
var firingDepth = 0;
|
|
420
|
+
function addTimer(callback, delay, args, repeats) {
|
|
421
|
+
// A timer set from a firing timer waits at least 1 ms, so a zero-delay chain cannot stall a seek.
|
|
422
|
+
var ms = Math.max(firingDepth > 0 ? 1 : 0, Number(delay) || 0);
|
|
423
|
+
var entry = { id: nextTimerId++, callback: callback, args: args, due: virtualNowMs + ms, every: repeats ? Math.max(1, ms) : 0, cancelled: false };
|
|
424
|
+
timerQueue.push(entry);
|
|
425
|
+
return entry.id;
|
|
426
|
+
}
|
|
427
|
+
function cancelTimer(id) {
|
|
428
|
+
for (var i = 0; i < timerQueue.length; i++) {
|
|
429
|
+
if (timerQueue[i].id === id) {
|
|
430
|
+
timerQueue[i].cancelled = true;
|
|
431
|
+
return true;
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
return false;
|
|
435
|
+
}
|
|
436
|
+
function fireDueTimers(untilMs) {
|
|
437
|
+
// Frame times are fractional, so a timer due on a frame lands a rounding error past it; it still fires on it.
|
|
438
|
+
untilMs += 0.000001;
|
|
439
|
+
while (true) {
|
|
440
|
+
var next = null;
|
|
441
|
+
for (var i = 0; i < timerQueue.length; i++) {
|
|
442
|
+
var candidate = timerQueue[i];
|
|
443
|
+
if (!candidate.cancelled && candidate.due <= untilMs && (next === null || candidate.due < next.due)) next = candidate;
|
|
444
|
+
}
|
|
445
|
+
if (next === null) break;
|
|
446
|
+
var dueMs = next.due;
|
|
447
|
+
if (next.every) next.due += next.every;
|
|
448
|
+
else next.cancelled = true;
|
|
449
|
+
virtualNowMs = dueMs;
|
|
450
|
+
firingDepth++;
|
|
451
|
+
try {
|
|
452
|
+
next.callback.apply(window, next.args);
|
|
453
|
+
} catch (err) {
|
|
454
|
+
originalSetTimeout(function() { throw err; }, 0);
|
|
455
|
+
}
|
|
456
|
+
firingDepth--;
|
|
457
|
+
}
|
|
458
|
+
timerQueue = timerQueue.filter(function(entry) { return !entry.cancelled; });
|
|
459
|
+
}
|
|
460
|
+
window.setTimeout = function(callback, delay) {
|
|
461
|
+
if (!rendering || typeof callback !== "function") return originalSetTimeout.apply(null, arguments);
|
|
462
|
+
return addTimer(callback, delay, Array.prototype.slice.call(arguments, 2), false);
|
|
463
|
+
};
|
|
464
|
+
window.setInterval = function(callback, delay) {
|
|
465
|
+
if (!rendering || typeof callback !== "function") return originalSetInterval.apply(null, arguments);
|
|
466
|
+
return addTimer(callback, delay, Array.prototype.slice.call(arguments, 2), true);
|
|
467
|
+
};
|
|
468
|
+
window.clearTimeout = function(id) {
|
|
469
|
+
if (!cancelTimer(id)) originalClearTimeout(id);
|
|
470
|
+
};
|
|
471
|
+
window.clearInterval = function(id) {
|
|
472
|
+
if (!cancelTimer(id)) originalClearInterval(id);
|
|
473
|
+
};
|
|
474
|
+
|
|
413
475
|
window.__HF_VIRTUAL_TIME__ = {
|
|
414
476
|
originalSetTimeout: originalSetTimeout,
|
|
415
477
|
originalClearTimeout: originalClearTimeout,
|
|
@@ -419,8 +481,10 @@ ${seededRandomBlock}
|
|
|
419
481
|
originalCancelAnimationFrame: originalCancelAnimationFrame,
|
|
420
482
|
seekToTime: function(nextTimeMs) {
|
|
421
483
|
var safeTimeMs = Math.max(0, Number(nextTimeMs) || 0);
|
|
484
|
+
if (window.__renderReady === true) rendering = true;
|
|
485
|
+
${seekToTimeReseedCall}fireDueTimers(safeTimeMs);
|
|
422
486
|
virtualNowMs = safeTimeMs;
|
|
423
|
-
|
|
487
|
+
flushAnimationFrame();
|
|
424
488
|
return virtualNowMs;
|
|
425
489
|
},
|
|
426
490
|
getTime: function() {
|
|
@@ -918,7 +982,7 @@ function resolveDeliveryEncoderOptions(config) {
|
|
|
918
982
|
import {
|
|
919
983
|
copyFileSync,
|
|
920
984
|
cpSync,
|
|
921
|
-
existsSync as
|
|
985
|
+
existsSync as existsSync4,
|
|
922
986
|
mkdirSync,
|
|
923
987
|
rmSync,
|
|
924
988
|
symlinkSync,
|
|
@@ -938,7 +1002,9 @@ import {
|
|
|
938
1002
|
relative as nodeRelative,
|
|
939
1003
|
isAbsolute as nodeIsAbsolute
|
|
940
1004
|
} from "node:path";
|
|
1005
|
+
import { existsSync as existsSync3 } from "node:fs";
|
|
941
1006
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
1007
|
+
import { decodedUrlPath } from "@hyperframes/parsers/asset-paths";
|
|
942
1008
|
var DEFAULT_RENDERS_DIR = process.env.PRODUCER_RENDERS_DIR ?? // fileURLToPath (not URL.pathname): on Windows .pathname is "/D:/..." which
|
|
943
1009
|
// resolves to a bogus renders dir.
|
|
944
1010
|
nodeResolve(fileURLToPath2(import.meta.url), "../../..", "renders");
|
|
@@ -976,6 +1042,13 @@ function resolveRenderPaths(projectDir, outputPath, rendersDir = DEFAULT_RENDERS
|
|
|
976
1042
|
const absoluteOutputPath = nodeResolve(resolvedOutputPath);
|
|
977
1043
|
return { absoluteProjectDir, absoluteOutputPath };
|
|
978
1044
|
}
|
|
1045
|
+
function resolveCompositionSrc(projectDir, src) {
|
|
1046
|
+
const rooted = (value) => nodeResolve(projectDir, value.replace(/^(?:\.?[\\/])+/, ""));
|
|
1047
|
+
const decoded = rooted(decodedUrlPath(src));
|
|
1048
|
+
if (!isPathInside2(decoded, projectDir)) return null;
|
|
1049
|
+
const raw = rooted(src);
|
|
1050
|
+
return !existsSync3(decoded) && isPathInside2(raw, projectDir) && existsSync3(raw) ? raw : decoded;
|
|
1051
|
+
}
|
|
979
1052
|
|
|
980
1053
|
// src/services/render/shared.ts
|
|
981
1054
|
var BROWSER_MEDIA_EPSILON = 1e-4;
|
|
@@ -1179,7 +1252,7 @@ var materializePathModule = {
|
|
|
1179
1252
|
isAbsolute
|
|
1180
1253
|
};
|
|
1181
1254
|
var materializeFileSystem = {
|
|
1182
|
-
existsSync:
|
|
1255
|
+
existsSync: existsSync4,
|
|
1183
1256
|
mkdirSync,
|
|
1184
1257
|
symlinkSync,
|
|
1185
1258
|
cpSync,
|
|
@@ -1481,7 +1554,7 @@ var RenderExecutionContext = class {
|
|
|
1481
1554
|
};
|
|
1482
1555
|
|
|
1483
1556
|
// src/services/render/artifactTransaction.ts
|
|
1484
|
-
import { closeSync as closeSync2, existsSync as
|
|
1557
|
+
import { closeSync as closeSync2, existsSync as existsSync6, fstatSync as fstatSync2, openSync as openSync2, readSync, readdirSync as readdirSync2, rmSync as rmSync3 } from "node:fs";
|
|
1485
1558
|
import { basename as basename3, dirname as dirname4, extname as extname2, join as join5, resolve as resolve4 } from "node:path";
|
|
1486
1559
|
import { HLS_MASTER_PLAYLIST, HLS_VIDEO_PLAYLIST, renameWithRetry } from "@hyperframes/engine";
|
|
1487
1560
|
|
|
@@ -1494,7 +1567,7 @@ import {
|
|
|
1494
1567
|
|
|
1495
1568
|
// src/services/render/renderDirOwner.ts
|
|
1496
1569
|
import {
|
|
1497
|
-
existsSync as
|
|
1570
|
+
existsSync as existsSync5,
|
|
1498
1571
|
lstatSync,
|
|
1499
1572
|
mkdtempSync,
|
|
1500
1573
|
readFileSync as readFileSync2,
|
|
@@ -1570,7 +1643,7 @@ function newestWriteMs(path) {
|
|
|
1570
1643
|
}
|
|
1571
1644
|
function isAbandoned(dir, ownerlessIdleMs, now) {
|
|
1572
1645
|
try {
|
|
1573
|
-
if (!lstatSync(dir).isDirectory() ||
|
|
1646
|
+
if (!lstatSync(dir).isDirectory() || existsSync5(join4(dir, TRANSACTION_BACKUP))) return false;
|
|
1574
1647
|
const owner = ownerState(dir);
|
|
1575
1648
|
if (owner === "gone") return true;
|
|
1576
1649
|
return owner === "none" && ownerlessIdleMs !== void 0 && now - newestWriteMs(dir) >= ownerlessIdleMs;
|
|
@@ -1602,7 +1675,7 @@ function createRenderWorkDir(prefix, outputDir) {
|
|
|
1602
1675
|
|
|
1603
1676
|
// src/services/render/artifactTransaction.ts
|
|
1604
1677
|
var defaultFileSystem = {
|
|
1605
|
-
existsSync:
|
|
1678
|
+
existsSync: existsSync6,
|
|
1606
1679
|
renameSync: (source, destination) => renameWithRetry(source, destination),
|
|
1607
1680
|
rmSync: rmSync3
|
|
1608
1681
|
};
|
|
@@ -1756,7 +1829,7 @@ var ArtifactTransaction = class {
|
|
|
1756
1829
|
async assertDirectoryExpectation(expected, files) {
|
|
1757
1830
|
for (const required of expected.requiredFiles ?? []) {
|
|
1758
1831
|
const path = join5(this.stagingPath, required);
|
|
1759
|
-
if (!
|
|
1832
|
+
if (!existsSync6(path)) {
|
|
1760
1833
|
throw new Error(`Render artifact directory is missing ${required}: ${this.stagingPath}`);
|
|
1761
1834
|
}
|
|
1762
1835
|
assertReadableNonEmptyFile(path);
|
|
@@ -2005,7 +2078,7 @@ function replanAfterFailure(plan2, failure) {
|
|
|
2005
2078
|
}
|
|
2006
2079
|
|
|
2007
2080
|
// src/services/render/stages/captureSegmentedStage.ts
|
|
2008
|
-
import { existsSync as
|
|
2081
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync3, rmSync as rmSync6, statSync as statSync3 } from "node:fs";
|
|
2009
2082
|
import { join as join10 } from "node:path";
|
|
2010
2083
|
import {
|
|
2011
2084
|
captureFrameToBuffer as captureFrameToBuffer2,
|
|
@@ -2302,6 +2375,19 @@ function aggregateBeginFrameReuse(perfs) {
|
|
|
2302
2375
|
if (noDamageFrames + hasDamageFrames === 0) return void 0;
|
|
2303
2376
|
return { noDamageFrames, hasDamageFrames };
|
|
2304
2377
|
}
|
|
2378
|
+
function aggregateMotionBlur(perfs) {
|
|
2379
|
+
const ran = perfs.flatMap((p) => p.motionBlur ?? []).filter((m) => m.tier || m.failedFrames > 0);
|
|
2380
|
+
const first = ran[0];
|
|
2381
|
+
if (!first) return void 0;
|
|
2382
|
+
return {
|
|
2383
|
+
tier: first.tier ?? ran.find((m) => m.tier)?.tier ?? null,
|
|
2384
|
+
reasonCode: first.reasonCode,
|
|
2385
|
+
movers: first.movers,
|
|
2386
|
+
failedFrames: ran.reduce((sum2, m) => sum2 + m.failedFrames, 0),
|
|
2387
|
+
readEvents: ran.some((m) => m.readEvents),
|
|
2388
|
+
fingerprint: ran.find((m) => m.fingerprint)?.fingerprint ?? null
|
|
2389
|
+
};
|
|
2390
|
+
}
|
|
2305
2391
|
function buildRenderPerfSummary(input) {
|
|
2306
2392
|
return {
|
|
2307
2393
|
renderId: input.job.id,
|
|
@@ -2352,6 +2438,7 @@ function buildRenderPerfSummary(input) {
|
|
|
2352
2438
|
staticDedup: aggregateDedup(input.dedupPerfs),
|
|
2353
2439
|
chromeMemory: aggregateChromeMemory(input.dedupPerfs),
|
|
2354
2440
|
beginFrameReuse: aggregateBeginFrameReuse(input.dedupPerfs),
|
|
2441
|
+
motionBlur: aggregateMotionBlur(input.dedupPerfs),
|
|
2355
2442
|
drawElement: aggregateDrawElement(
|
|
2356
2443
|
input.dedupPerfs,
|
|
2357
2444
|
input.drawElement ?? { selfVerifyFallback: false }
|
|
@@ -3826,6 +3913,21 @@ function raceAgainstStall(promise, deadlineMs, input, signal) {
|
|
|
3826
3913
|
);
|
|
3827
3914
|
});
|
|
3828
3915
|
}
|
|
3916
|
+
function createSessionInitBudget(input) {
|
|
3917
|
+
const initStallMs = Math.max(SESSION_INIT_STALL_MS, resolveCaptureStallTimeoutMs());
|
|
3918
|
+
const setup = createStallClock();
|
|
3919
|
+
return (work, stalled) => raceAgainstStall(
|
|
3920
|
+
work,
|
|
3921
|
+
initStallMs - setup.idle(0),
|
|
3922
|
+
{
|
|
3923
|
+
session: stalled,
|
|
3924
|
+
frameIndex: input.frameIndex,
|
|
3925
|
+
totalFrames: input.totalFrames,
|
|
3926
|
+
stallTimeoutMs: initStallMs
|
|
3927
|
+
},
|
|
3928
|
+
input.abortSignal
|
|
3929
|
+
);
|
|
3930
|
+
}
|
|
3829
3931
|
function createDrainFrameGuard(args) {
|
|
3830
3932
|
const { log, stats, frameTime } = args;
|
|
3831
3933
|
const verifyMinDb = resolveDeVerifyMinDb();
|
|
@@ -4298,14 +4400,11 @@ async function runCaptureStreamingStage(input) {
|
|
|
4298
4400
|
let resumes = 0;
|
|
4299
4401
|
for (; ; ) {
|
|
4300
4402
|
const videoInjector = createRenderVideoFrameInjector();
|
|
4301
|
-
const
|
|
4302
|
-
|
|
4303
|
-
|
|
4304
|
-
work,
|
|
4305
|
-
initStallMs - setup.idle(0),
|
|
4306
|
-
{ session: stalled, frameIndex: resume.from, totalFrames, stallTimeoutMs: initStallMs },
|
|
4403
|
+
const withinInitBudget = createSessionInitBudget({
|
|
4404
|
+
frameIndex: resume.from,
|
|
4405
|
+
totalFrames,
|
|
4307
4406
|
abortSignal
|
|
4308
|
-
);
|
|
4407
|
+
});
|
|
4309
4408
|
const openSession = async () => {
|
|
4310
4409
|
const opening = createCaptureSession(
|
|
4311
4410
|
fileServer.url,
|
|
@@ -4583,7 +4682,7 @@ async function captureSegmentToEncoder(ctx, segment) {
|
|
|
4583
4682
|
path: segmentPath,
|
|
4584
4683
|
// Recorded so resume can prove the file on disk is the one that
|
|
4585
4684
|
// finished; absent only in unit tests, whose fake encoder writes none.
|
|
4586
|
-
bytes:
|
|
4685
|
+
bytes: existsSync7(segmentPath) ? statSync3(segmentPath).size : 0
|
|
4587
4686
|
});
|
|
4588
4687
|
return encodeResult;
|
|
4589
4688
|
} finally {
|
|
@@ -4613,14 +4712,29 @@ function defaultSessionFactory(input) {
|
|
|
4613
4712
|
});
|
|
4614
4713
|
probeSession = null;
|
|
4615
4714
|
}
|
|
4616
|
-
const
|
|
4715
|
+
const withinInitBudget = createSessionInitBudget({
|
|
4716
|
+
frameIndex: input.job.framesRendered ?? 0,
|
|
4717
|
+
totalFrames: input.totalFrames,
|
|
4718
|
+
abortSignal: input.abortSignal
|
|
4719
|
+
});
|
|
4720
|
+
const openSession = async () => {
|
|
4721
|
+
const opening = openSegmentedSession(input, injector);
|
|
4722
|
+
try {
|
|
4723
|
+
return await withinInitBudget(opening, OPENING_CAPTURE_SESSION);
|
|
4724
|
+
} catch (error) {
|
|
4725
|
+
void opening.then(closeCaptureSession2, () => {
|
|
4726
|
+
});
|
|
4727
|
+
throw error;
|
|
4728
|
+
}
|
|
4729
|
+
};
|
|
4730
|
+
const session = probeSession ?? await openSession();
|
|
4617
4731
|
if (probeSession) {
|
|
4618
4732
|
openSegmentedSessionReuse(input, probeSession, injector);
|
|
4619
4733
|
probeSession = null;
|
|
4620
4734
|
}
|
|
4621
4735
|
try {
|
|
4622
|
-
if (!session.isInitialized) await initializeSession2(session);
|
|
4623
|
-
await completeDeferredDrawElementInit2(session);
|
|
4736
|
+
if (!session.isInitialized) await withinInitBudget(initializeSession2(session), session);
|
|
4737
|
+
await withinInitBudget(completeDeferredDrawElementInit2(session), session);
|
|
4624
4738
|
} catch (error) {
|
|
4625
4739
|
await closeCaptureSession2(session).catch(() => {
|
|
4626
4740
|
});
|
|
@@ -5552,7 +5666,7 @@ function trackSegmentFramePeak(frames, rootDir, onPeak) {
|
|
|
5552
5666
|
|
|
5553
5667
|
// src/services/render/segmentManifest.ts
|
|
5554
5668
|
import { createHash as createHash2 } from "node:crypto";
|
|
5555
|
-
import { existsSync as
|
|
5669
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync6, readFileSync as readFileSync4, statSync as statSync5, writeFileSync as writeFileSync3 } from "node:fs";
|
|
5556
5670
|
import { join as join15 } from "node:path";
|
|
5557
5671
|
import { renameWithRetry as renameWithRetry2 } from "@hyperframes/engine";
|
|
5558
5672
|
var MANIFEST_FILENAME = "segments.json";
|
|
@@ -5600,7 +5714,7 @@ function isManifest(value) {
|
|
|
5600
5714
|
}
|
|
5601
5715
|
function readSegmentManifest(segmentDir) {
|
|
5602
5716
|
const path = join15(segmentDir, MANIFEST_FILENAME);
|
|
5603
|
-
if (!
|
|
5717
|
+
if (!existsSync8(path)) return null;
|
|
5604
5718
|
try {
|
|
5605
5719
|
const parsed = JSON.parse(readFileSync4(path, "utf-8"));
|
|
5606
5720
|
return isManifest(parsed) ? parsed : null;
|
|
@@ -5629,7 +5743,7 @@ async function validateCompletedSegments(manifest, expectedHash, probeFrames) {
|
|
|
5629
5743
|
const ok = /* @__PURE__ */ new Set();
|
|
5630
5744
|
if (manifest.planHash !== expectedHash) return ok;
|
|
5631
5745
|
for (const entry of manifest.completed) {
|
|
5632
|
-
if (!
|
|
5746
|
+
if (!existsSync8(entry.path)) continue;
|
|
5633
5747
|
const size = statSync5(entry.path).size;
|
|
5634
5748
|
if (size <= 0 || size !== entry.bytes) continue;
|
|
5635
5749
|
const frames = await probeFrames(entry.path);
|
|
@@ -5769,7 +5883,7 @@ var ALLOWED_STRING_DATA_KEYS = /* @__PURE__ */ new Set([
|
|
|
5769
5883
|
"stagePhase",
|
|
5770
5884
|
// Full-fidelity fast-capture fallback trigger — the specific reason
|
|
5771
5885
|
// drawElement gated off ("filter:blur", "filter:drop-shadow",
|
|
5772
|
-
// "backdrop-filter", "clip-path", "at_risk_timeline", "swiftshader", …).
|
|
5886
|
+
// "backdrop-filter", "clip-path", "at_risk_timeline", "swiftshader", "webgl_unavailable", …).
|
|
5773
5887
|
// Populated on the `capture_fallback_profile` checkpoint emitted by
|
|
5774
5888
|
// `fallbackCaptureProfile.ts` when the operator opts into
|
|
5775
5889
|
// `HF_PROFILE_FALLBACK_CAPTURE=true` so downstream metric consumers can
|
|
@@ -6208,17 +6322,17 @@ function countAuthoredTimedClips(html) {
|
|
|
6208
6322
|
}
|
|
6209
6323
|
|
|
6210
6324
|
// src/services/render/stages/compileStage.ts
|
|
6211
|
-
import { existsSync as
|
|
6325
|
+
import { existsSync as existsSync12, writeFileSync as writeFileSync5 } from "node:fs";
|
|
6212
6326
|
import { join as join19 } from "node:path";
|
|
6213
6327
|
import { suggestMatchingPreset } from "@hyperframes/core";
|
|
6214
6328
|
import { extractStandaloneEntryFromIndex, isTemplateEntry } from "@hyperframes/core/compiler";
|
|
6215
6329
|
import { readTextFile as readTextFile2 } from "@hyperframes/parsers/asset-resolution";
|
|
6216
6330
|
|
|
6217
6331
|
// src/services/htmlCompiler.ts
|
|
6218
|
-
import { decodedUrlPath } from "@hyperframes/parsers/asset-paths";
|
|
6332
|
+
import { decodedUrlPath as decodedUrlPath2 } from "@hyperframes/parsers/asset-paths";
|
|
6219
6333
|
import { createHash as createHash6 } from "node:crypto";
|
|
6220
|
-
import { createReadStream as createReadStream2, existsSync as
|
|
6221
|
-
import { join as join18, dirname as dirname8, resolve as resolve7, basename as basename4, relative as relative2 } from "path";
|
|
6334
|
+
import { createReadStream as createReadStream2, existsSync as existsSync11, mkdirSync as mkdirSync9, readFileSync as readFileSync7 } from "fs";
|
|
6335
|
+
import { join as join18, dirname as dirname8, resolve as resolve7, basename as basename4, relative as relative2, sep as sep2 } from "path";
|
|
6222
6336
|
import { parseHTML as parseHTML5 } from "linkedom";
|
|
6223
6337
|
import {
|
|
6224
6338
|
COMPOSITION_SOURCE_URL,
|
|
@@ -6464,7 +6578,7 @@ import {
|
|
|
6464
6578
|
|
|
6465
6579
|
// src/services/animatedGifPrep.ts
|
|
6466
6580
|
import { createHash as createHash4 } from "node:crypto";
|
|
6467
|
-
import { copyFileSync as copyFileSync2, existsSync as
|
|
6581
|
+
import { copyFileSync as copyFileSync2, existsSync as existsSync9, mkdirSync as mkdirSync7, readFileSync as readFileSync5, rmSync as rmSync9, statSync as statSync6 } from "node:fs";
|
|
6468
6582
|
import { dirname as dirname6, isAbsolute as isAbsolute2, join as join16, resolve as resolve5 } from "node:path";
|
|
6469
6583
|
import { parseHTML as parseHTML3 } from "linkedom";
|
|
6470
6584
|
import { parseAnimatedGifMetadata } from "@hyperframes/core";
|
|
@@ -6503,14 +6617,14 @@ function resolveLocalImageSourcePath(src, options) {
|
|
|
6503
6617
|
const { basePath } = splitUrlSuffix(trimmed);
|
|
6504
6618
|
const normalizedBase = normalizeRelPath(basePath);
|
|
6505
6619
|
const mapped = options.sourceAssets?.get(trimmed) ?? options.sourceAssets?.get(basePath) ?? options.sourceAssets?.get(normalizedBase);
|
|
6506
|
-
if (mapped &&
|
|
6620
|
+
if (mapped && existsSync9(mapped)) return mapped;
|
|
6507
6621
|
if (isHttpUrl(trimmed)) return null;
|
|
6508
6622
|
const projectRelative = basePath.startsWith("/") ? basePath.slice(1) : basePath;
|
|
6509
6623
|
const candidates = [
|
|
6510
6624
|
isAbsolute2(basePath) ? basePath : resolve5(options.projectDir, projectRelative),
|
|
6511
6625
|
resolve5(options.downloadDir, normalizedBase)
|
|
6512
6626
|
];
|
|
6513
|
-
return candidates.find((candidate) =>
|
|
6627
|
+
return candidates.find((candidate) => existsSync9(candidate)) ?? null;
|
|
6514
6628
|
}
|
|
6515
6629
|
function isUsableFile(path) {
|
|
6516
6630
|
try {
|
|
@@ -6621,10 +6735,10 @@ async function runAnimatedGifTranscode(request) {
|
|
|
6621
6735
|
throw result.error ?? new Error(`Animated GIF transcode failed (${result.exitCode}): ${result.stderr.slice(-500)}`);
|
|
6622
6736
|
}
|
|
6623
6737
|
async function ensurePreparedWebm(input) {
|
|
6624
|
-
if (!
|
|
6738
|
+
if (!existsSync9(dirname6(input.cachePath))) {
|
|
6625
6739
|
mkdirSync7(dirname6(input.cachePath), { recursive: true });
|
|
6626
6740
|
}
|
|
6627
|
-
if (!
|
|
6741
|
+
if (!existsSync9(dirname6(input.outputPath))) {
|
|
6628
6742
|
mkdirSync7(dirname6(input.outputPath), { recursive: true });
|
|
6629
6743
|
}
|
|
6630
6744
|
if (!isUsableFile(input.cachePath)) {
|
|
@@ -7099,7 +7213,7 @@ import { createStudioPositionSeekReapplyScript } from "@hyperframes/studio-serve
|
|
|
7099
7213
|
import { getPositionEditsRenderScript } from "@hyperframes/core/runtime/position-edits-render";
|
|
7100
7214
|
|
|
7101
7215
|
// src/services/assetMediaType.ts
|
|
7102
|
-
import { existsSync as
|
|
7216
|
+
import { existsSync as existsSync10 } from "node:fs";
|
|
7103
7217
|
import {
|
|
7104
7218
|
fingerprintElementId,
|
|
7105
7219
|
isNotMediaPayload,
|
|
@@ -7172,7 +7286,7 @@ async function preflightCompositionAssetMediaTypes(input) {
|
|
|
7172
7286
|
input.projectDir,
|
|
7173
7287
|
input.compiledDir
|
|
7174
7288
|
);
|
|
7175
|
-
if (!
|
|
7289
|
+
if (!existsSync10(resolvedPath)) continue;
|
|
7176
7290
|
byPath.set(resolvedPath, [...byPath.get(resolvedPath) ?? [], reference]);
|
|
7177
7291
|
}
|
|
7178
7292
|
const mismatches = [];
|
|
@@ -7260,6 +7374,14 @@ Check that each file referenced by data-composition-src contains valid HTML with
|
|
|
7260
7374
|
this.problems = problems;
|
|
7261
7375
|
}
|
|
7262
7376
|
};
|
|
7377
|
+
function normalizeCompositionSrcs(html, projectDir) {
|
|
7378
|
+
return html.replace(/(\bdata-composition-src=)(["'])([^"']*)\2/g, (whole, name, quote, value) => {
|
|
7379
|
+
if (!value.includes("%") && !value.startsWith("/")) return whole;
|
|
7380
|
+
const file = resolveCompositionSrc(projectDir, value);
|
|
7381
|
+
if (file === null) return whole;
|
|
7382
|
+
return `${name}${quote}${relative2(projectDir, file).split(sep2).join("/")}${quote}`;
|
|
7383
|
+
});
|
|
7384
|
+
}
|
|
7263
7385
|
function assertSubCompositionsUsable(html, projectDir, visited = /* @__PURE__ */ new Set()) {
|
|
7264
7386
|
const { document: document2 } = parseHTML5(html);
|
|
7265
7387
|
const hosts = [...document2.querySelectorAll("[data-composition-src]")];
|
|
@@ -7268,7 +7390,11 @@ function assertSubCompositionsUsable(html, projectDir, visited = /* @__PURE__ */
|
|
|
7268
7390
|
const srcPath = el.getAttribute("data-composition-src");
|
|
7269
7391
|
if (!srcPath) continue;
|
|
7270
7392
|
if (isUnresolvedAssetPlaceholder(srcPath)) continue;
|
|
7271
|
-
const filePath =
|
|
7393
|
+
const filePath = resolveCompositionSrc(projectDir, srcPath);
|
|
7394
|
+
if (filePath === null) {
|
|
7395
|
+
problems.push({ srcPath, detail: "it is outside the project folder" });
|
|
7396
|
+
continue;
|
|
7397
|
+
}
|
|
7272
7398
|
if (visited.has(filePath)) continue;
|
|
7273
7399
|
const read = readProjectFile(filePath);
|
|
7274
7400
|
if (read.kind === "missing") {
|
|
@@ -7414,7 +7540,7 @@ async function resolveMediaDuration(src, mediaStart, playbackRate, baseDir, down
|
|
|
7414
7540
|
let filePath = src;
|
|
7415
7541
|
if (isHttpUrl(src)) {
|
|
7416
7542
|
const remoteDir = join18(downloadDir, REMOTE_MEDIA_SUBDIR);
|
|
7417
|
-
if (!
|
|
7543
|
+
if (!existsSync11(remoteDir)) mkdirSync9(remoteDir, { recursive: true });
|
|
7418
7544
|
try {
|
|
7419
7545
|
filePath = await downloadToTemp(src, remoteDir, void 0, void 0, void 0, {
|
|
7420
7546
|
onTelemetry: logRemoteDownloadTelemetry
|
|
@@ -7423,11 +7549,11 @@ async function resolveMediaDuration(src, mediaStart, playbackRate, baseDir, down
|
|
|
7423
7549
|
return { duration: null, resolvedPath: src };
|
|
7424
7550
|
}
|
|
7425
7551
|
} else {
|
|
7426
|
-
filePath =
|
|
7552
|
+
filePath = decodedUrlPath2(src);
|
|
7427
7553
|
if (!filePath.startsWith("/")) filePath = join18(baseDir, filePath);
|
|
7428
7554
|
filePath = urlByAbsentLocalPath?.get(filePath) ?? filePath;
|
|
7429
7555
|
}
|
|
7430
|
-
if (!isHttpUrl(filePath) && !
|
|
7556
|
+
if (!isHttpUrl(filePath) && !existsSync11(filePath)) {
|
|
7431
7557
|
return { duration: null, resolvedPath: filePath };
|
|
7432
7558
|
}
|
|
7433
7559
|
const withSrcContext = (error) => {
|
|
@@ -7568,7 +7694,8 @@ async function parseSubCompositions(html, projectDir, downloadDir, visited = /*
|
|
|
7568
7694
|
for (const el of compEls) {
|
|
7569
7695
|
const srcPath = el.getAttribute("data-composition-src");
|
|
7570
7696
|
if (!srcPath) continue;
|
|
7571
|
-
const filePath =
|
|
7697
|
+
const filePath = resolveCompositionSrc(projectDir, srcPath);
|
|
7698
|
+
if (filePath === null) continue;
|
|
7572
7699
|
if (visited.has(filePath)) {
|
|
7573
7700
|
continue;
|
|
7574
7701
|
}
|
|
@@ -7576,7 +7703,7 @@ async function parseSubCompositions(html, projectDir, downloadDir, visited = /*
|
|
|
7576
7703
|
if (read.kind !== "file") {
|
|
7577
7704
|
continue;
|
|
7578
7705
|
}
|
|
7579
|
-
const rawSubHtml = read.text;
|
|
7706
|
+
const rawSubHtml = normalizeCompositionSrcs(read.text, projectDir);
|
|
7580
7707
|
const nestedVisited = new Set(visited);
|
|
7581
7708
|
nestedVisited.add(filePath);
|
|
7582
7709
|
workItems.push({ srcPath, filePath, rawSubHtml, nestedVisited });
|
|
@@ -7764,8 +7891,9 @@ function inlineSubCompositions(html, subCompositions, projectDir, variableOverri
|
|
|
7764
7891
|
resolveHtml: (srcPath) => {
|
|
7765
7892
|
let compHtml = subCompositions.get(srcPath) || null;
|
|
7766
7893
|
if (!compHtml) {
|
|
7767
|
-
const
|
|
7768
|
-
|
|
7894
|
+
const filePath = resolveCompositionSrc(projectDir, srcPath);
|
|
7895
|
+
const read = filePath ? readProjectFile(filePath) : null;
|
|
7896
|
+
if (read?.kind === "file") {
|
|
7769
7897
|
compHtml = rewriteUnresolvableGsapToCdn(read.text, projectDir, srcPath);
|
|
7770
7898
|
}
|
|
7771
7899
|
}
|
|
@@ -7774,7 +7902,7 @@ function inlineSubCompositions(html, subCompositions, projectDir, variableOverri
|
|
|
7774
7902
|
parseHtml: (htmlStr) => parseHTML5(htmlStr).document,
|
|
7775
7903
|
// Mirrors the preview bundler: a sub-composition's SIBLING assets resolve
|
|
7776
7904
|
// against its own directory, project-root refs stay as authored.
|
|
7777
|
-
assetExists: (path) =>
|
|
7905
|
+
assetExists: (path) => existsSync11(resolve7(projectDir, path)),
|
|
7778
7906
|
// Preserve the authored root wrapper as a child of the host, matching
|
|
7779
7907
|
// the preview bundler's shape (htmlBundler.ts's prepareFlattenedInnerRoot,
|
|
7780
7908
|
// which the runtime compositionLoader mirrors with its own copy for the
|
|
@@ -7965,7 +8093,7 @@ function collectExternalAssets(html, projectDir) {
|
|
|
7965
8093
|
if (isPathInside2(absPath, absProjectDir)) {
|
|
7966
8094
|
return null;
|
|
7967
8095
|
}
|
|
7968
|
-
if (!
|
|
8096
|
+
if (!existsSync11(absPath)) return null;
|
|
7969
8097
|
const safeKey = toExternalAssetKey(absPath);
|
|
7970
8098
|
externalAssets.set(safeKey, absPath);
|
|
7971
8099
|
return safeKey;
|
|
@@ -8015,7 +8143,7 @@ var REMOTE_SOURCE_TAG_RE = /<source\b[^>]*?\bsrc\s*=\s*["'](https?:\/\/[^"']+)["
|
|
|
8015
8143
|
var REMOTE_IMG_TAG_RE = /<img\b[^>]*?(?<![\w-])src\s*=\s*["'](https?:\/\/[^"']+)["'][^>]*>/gi;
|
|
8016
8144
|
async function downloadAndRewriteUrls(urlSet, html, remoteDir, warnLabel, logLabel, extraRewrite) {
|
|
8017
8145
|
if (urlSet.size === 0) return { html, remoteMediaAssets: /* @__PURE__ */ new Map() };
|
|
8018
|
-
if (!
|
|
8146
|
+
if (!existsSync11(remoteDir)) mkdirSync9(remoteDir, { recursive: true });
|
|
8019
8147
|
const urlToLocal = /* @__PURE__ */ new Map();
|
|
8020
8148
|
await Promise.all(
|
|
8021
8149
|
[...urlSet].map(async (url) => {
|
|
@@ -8197,14 +8325,14 @@ function projectStylesheetFontFamilies(html, projectDir) {
|
|
|
8197
8325
|
for (const [tag] of html.matchAll(/<link\b[^>]*\brel=["']stylesheet["'][^>]*>/gi)) {
|
|
8198
8326
|
const href = /\bhref=["']([^"']+)["']/i.exec(tag)?.[1];
|
|
8199
8327
|
if (!href || /^(?:[a-z][a-z\d+.-]*:|\/\/)/i.test(href)) continue;
|
|
8200
|
-
const sheet = resolve7(projectDir,
|
|
8201
|
-
if (!isPathInside2(sheet, projectDir) || !
|
|
8328
|
+
const sheet = resolve7(projectDir, decodedUrlPath2(href).replace(/^(?:\.?\/)+/, ""));
|
|
8329
|
+
if (!isPathInside2(sheet, projectDir) || !existsSync11(sheet)) continue;
|
|
8202
8330
|
for (const face of extractFontFaceBlocks(readFileSync7(sheet, "utf8"))) {
|
|
8203
8331
|
const family = /font-family\s*:\s*([^;}]+)/i.exec(face)?.[1]?.trim().replace(/^['"]|['"]$/g, "");
|
|
8204
8332
|
const shipsFile = [...face.matchAll(LOCAL_FONTFACE_URL_RE)].some(([, url = ""]) => {
|
|
8205
|
-
const path =
|
|
8333
|
+
const path = decodedUrlPath2(url);
|
|
8206
8334
|
const file = path.startsWith("/") ? resolve7(projectDir, `.${path}`) : resolve7(dirname8(sheet), path);
|
|
8207
|
-
return isPathInside2(file, projectDir) &&
|
|
8335
|
+
return isPathInside2(file, projectDir) && existsSync11(file);
|
|
8208
8336
|
});
|
|
8209
8337
|
if (family && shipsFile) families.push(family);
|
|
8210
8338
|
}
|
|
@@ -8342,7 +8470,7 @@ function renderGsapDist() {
|
|
|
8342
8470
|
}
|
|
8343
8471
|
function rewriteUnresolvableGsapToCdn(html, projectDir, compSrcPath) {
|
|
8344
8472
|
let gsapDist;
|
|
8345
|
-
const exists = (path) =>
|
|
8473
|
+
const exists = (path) => existsSync11(resolve7(projectDir, path));
|
|
8346
8474
|
return html.replace(
|
|
8347
8475
|
/(<script\b[^>]*\bsrc=["'])([^"']*gsap[^"']*\/dist\/([^"']+))(["'][^>]*>)/gi,
|
|
8348
8476
|
(full, prefix, src, file, suffix) => {
|
|
@@ -8359,7 +8487,7 @@ function rebaseDirectEntryAssetPaths(html, projectDir, htmlPath) {
|
|
|
8359
8487
|
const entryPath = relative2(projectDir, htmlPath).replace(/\\/g, "/");
|
|
8360
8488
|
if (!entryPath.includes("/")) return html;
|
|
8361
8489
|
const { document: document2 } = parseHTML5(html);
|
|
8362
|
-
const assetExists = (path) =>
|
|
8490
|
+
const assetExists = (path) => existsSync11(resolve7(projectDir, path));
|
|
8363
8491
|
rewriteAssetPaths(
|
|
8364
8492
|
document2.querySelectorAll("[src], [href], [srcset]"),
|
|
8365
8493
|
entryPath,
|
|
@@ -8381,7 +8509,10 @@ function rebaseDirectEntryAssetPaths(html, projectDir, htmlPath) {
|
|
|
8381
8509
|
}
|
|
8382
8510
|
async function compileForRender(projectDir, htmlPath, downloadDir, options = {}) {
|
|
8383
8511
|
const entryHtml = rebaseDirectEntryAssetPaths(readTextFile(htmlPath), projectDir, htmlPath);
|
|
8384
|
-
const rawHtml =
|
|
8512
|
+
const rawHtml = normalizeCompositionSrcs(
|
|
8513
|
+
rewriteUnresolvableGsapToCdn(entryHtml, projectDir),
|
|
8514
|
+
projectDir
|
|
8515
|
+
);
|
|
8385
8516
|
assertSubCompositionsUsable(rawHtml, projectDir);
|
|
8386
8517
|
const probes = /* @__PURE__ */ new Map();
|
|
8387
8518
|
const { html: compiledHtml, unresolvedCompositions } = await compileHtmlFile(
|
|
@@ -8926,7 +9057,7 @@ function resolveEntryHtmlPath(input) {
|
|
|
8926
9057
|
const rawEntry = readTextFile2(htmlPath);
|
|
8927
9058
|
if (entryFile === "index.html" || !isTemplateEntry(rawEntry)) return htmlPath;
|
|
8928
9059
|
const projectIndexPath = join19(projectDir, "index.html");
|
|
8929
|
-
if (!
|
|
9060
|
+
if (!existsSync12(projectIndexPath)) {
|
|
8930
9061
|
throw new Error(
|
|
8931
9062
|
`Template entry file "${entryFile}" requires a project index.html to extract its render shell.`
|
|
8932
9063
|
);
|
|
@@ -9792,7 +9923,7 @@ function validateDistributedDuration(input) {
|
|
|
9792
9923
|
}
|
|
9793
9924
|
|
|
9794
9925
|
// src/services/render/stages/extractVideosStage.ts
|
|
9795
|
-
import { existsSync as
|
|
9926
|
+
import { existsSync as existsSync13 } from "node:fs";
|
|
9796
9927
|
import { join as join21 } from "node:path";
|
|
9797
9928
|
import {
|
|
9798
9929
|
classifyVideoExtractionError,
|
|
@@ -10067,7 +10198,7 @@ async function runExtractVideosStage(input) {
|
|
|
10067
10198
|
const probeFailures = await Promise.all(
|
|
10068
10199
|
composition.videos.map(async (v) => {
|
|
10069
10200
|
const videoPath = resolveProjectRelativeSrc3(v.src, projectDir, compiledDir);
|
|
10070
|
-
if (!
|
|
10201
|
+
if (!existsSync13(videoPath)) return null;
|
|
10071
10202
|
try {
|
|
10072
10203
|
const attempted = extractionPolicy.maxTransientRetries === 0 ? { result: await extractMediaMetadata3(videoPath), retries: 0 } : await runVideoExtractionWithRetry2(() => extractMediaMetadata3(videoPath), {
|
|
10073
10204
|
signal: abortSignal,
|
|
@@ -10105,7 +10236,7 @@ async function runExtractVideosStage(input) {
|
|
|
10105
10236
|
const probed = await Promise.all(
|
|
10106
10237
|
composition.images.map(async (img) => {
|
|
10107
10238
|
const imgPath = resolveProjectRelativeSrc3(img.src, projectDir, compiledDir);
|
|
10108
|
-
if (!
|
|
10239
|
+
if (!existsSync13(imgPath)) return null;
|
|
10109
10240
|
const colorSpace = await probeColorSpaceSafely(imgPath, log);
|
|
10110
10241
|
if (isHdrColorSpace2(colorSpace)) {
|
|
10111
10242
|
nativeHdrImageIds.add(img.id);
|
|
@@ -10225,7 +10356,7 @@ async function appendVideoAudioFromProbe(input) {
|
|
|
10225
10356
|
videos.map(async (video) => {
|
|
10226
10357
|
const path = isHttpUrl2(video.src) ? video.src : resolveProjectRelativeSrc3(video.src, projectDir, compiledDir);
|
|
10227
10358
|
const readFrom = urlByAbsentLocalPath?.get(path) ?? path;
|
|
10228
|
-
if (!isHttpUrl2(readFrom) && !
|
|
10359
|
+
if (!isHttpUrl2(readFrom) && !existsSync13(readFrom)) return null;
|
|
10229
10360
|
const metadata = await extractMediaMetadata3(readFrom);
|
|
10230
10361
|
const window2 = await resolveFinalFrameExtractionWindow2(
|
|
10231
10362
|
readFrom,
|
|
@@ -10245,7 +10376,7 @@ async function appendVideoAudioFromProbe(input) {
|
|
|
10245
10376
|
}
|
|
10246
10377
|
|
|
10247
10378
|
// src/services/render/videoLayerPassthroughPlan.ts
|
|
10248
|
-
import { existsSync as
|
|
10379
|
+
import { existsSync as existsSync14 } from "node:fs";
|
|
10249
10380
|
import {
|
|
10250
10381
|
buildVideoLayerInput,
|
|
10251
10382
|
isHttpUrl as isHttpUrl3,
|
|
@@ -10313,7 +10444,7 @@ async function planVideoLayerPassthrough(input) {
|
|
|
10313
10444
|
const { video } = q;
|
|
10314
10445
|
if (isHttpUrl3(video.src)) return { reason: "remote_source" };
|
|
10315
10446
|
const path = resolveProjectRelativeSrc4(video.src, input.projectDir, input.compiledDir);
|
|
10316
|
-
if (!
|
|
10447
|
+
if (!existsSync14(path)) return { reason: "source_missing" };
|
|
10317
10448
|
const layout = await probeVideoLayout(input.page, video.id, input.canvas, input.duration);
|
|
10318
10449
|
if ("reason" in layout) return layout;
|
|
10319
10450
|
const layer = await buildVideoLayerInput(
|
|
@@ -10328,7 +10459,7 @@ async function planVideoLayerPassthrough(input) {
|
|
|
10328
10459
|
}
|
|
10329
10460
|
|
|
10330
10461
|
// src/services/render/stages/audioStage.ts
|
|
10331
|
-
import { existsSync as
|
|
10462
|
+
import { existsSync as existsSync16 } from "node:fs";
|
|
10332
10463
|
import { join as join24 } from "node:path";
|
|
10333
10464
|
import {
|
|
10334
10465
|
MIXED_AUDIO_FILENAME,
|
|
@@ -10339,7 +10470,7 @@ import {
|
|
|
10339
10470
|
} from "@hyperframes/engine";
|
|
10340
10471
|
|
|
10341
10472
|
// src/services/render/audioDeliveryLimit.ts
|
|
10342
|
-
import { existsSync as
|
|
10473
|
+
import { existsSync as existsSync15 } from "node:fs";
|
|
10343
10474
|
import { dirname as dirname10, join as join23 } from "node:path";
|
|
10344
10475
|
import {
|
|
10345
10476
|
applyAudioFxChain,
|
|
@@ -10848,7 +10979,7 @@ async function limitDeliveryPeaks(input, deps = {
|
|
|
10848
10979
|
applyChain: applyAudioFxChain,
|
|
10849
10980
|
encode: encodeAac2,
|
|
10850
10981
|
rename: renameWithRetry6,
|
|
10851
|
-
sourceExists:
|
|
10982
|
+
sourceExists: existsSync15
|
|
10852
10983
|
}) {
|
|
10853
10984
|
const { audioPath, sourcePath, signal, timeoutMs } = input;
|
|
10854
10985
|
if (!deps.sourceExists(sourcePath)) return { success: true };
|
|
@@ -10943,7 +11074,7 @@ async function runAudioStageScoped(input) {
|
|
|
10943
11074
|
);
|
|
10944
11075
|
audioLoweredDb = guard.loweredDb;
|
|
10945
11076
|
input.log?.info("[audio] phases", { ...result.audioPhaseMs, guard: Date.now() - guardStart });
|
|
10946
|
-
if (input.keepDeliverySource && !guard.sourceStale &&
|
|
11077
|
+
if (input.keepDeliverySource && !guard.sourceStale && existsSync16(pcmSourcePath)) {
|
|
10947
11078
|
deliverySourcePath = pcmSourcePath;
|
|
10948
11079
|
}
|
|
10949
11080
|
}
|
|
@@ -11071,7 +11202,7 @@ async function mixAudioForDelivery(input, duration, pcmSourcePath) {
|
|
|
11071
11202
|
}
|
|
11072
11203
|
|
|
11073
11204
|
// src/services/render/stages/captureStage.ts
|
|
11074
|
-
import { existsSync as
|
|
11205
|
+
import { existsSync as existsSync17, readdirSync as readdirSync4, statfsSync as statfsSync2 } from "node:fs";
|
|
11075
11206
|
import { join as join25 } from "node:path";
|
|
11076
11207
|
import {
|
|
11077
11208
|
classifyCaptureFailure as classifyCaptureFailure2,
|
|
@@ -11142,7 +11273,7 @@ function diskCaptureShortfallError(needBytes, freeBytes, framesDir, basis, remed
|
|
|
11142
11273
|
}
|
|
11143
11274
|
function measureCaptureFrameBytes(workDir, framesDir) {
|
|
11144
11275
|
let total = sampleDirectoryBytes(framesDir);
|
|
11145
|
-
if (!
|
|
11276
|
+
if (!existsSync17(workDir)) return total;
|
|
11146
11277
|
for (const attempt of readdirSync4(workDir)) {
|
|
11147
11278
|
if (/^retry-\d+-batch-\d+-worker-\d+$/.test(attempt)) {
|
|
11148
11279
|
total += sampleDirectoryBytes(join25(workDir, attempt));
|
|
@@ -11327,7 +11458,11 @@ async function runSequentialDiskCapture(input, captureOptions, captureCfg, check
|
|
|
11327
11458
|
prepareCaptureSessionForReuse3(session, framesDir, videoInjector);
|
|
11328
11459
|
probeSession = null;
|
|
11329
11460
|
}
|
|
11330
|
-
await ensureSessionReady(session
|
|
11461
|
+
await ensureSessionReady(session, {
|
|
11462
|
+
frameIndex: resumeFrom,
|
|
11463
|
+
totalFrames,
|
|
11464
|
+
abortSignal: input.abortSignal
|
|
11465
|
+
});
|
|
11331
11466
|
assertAttemptActive();
|
|
11332
11467
|
lastBrowserConsole = session.browserConsoleBuffer;
|
|
11333
11468
|
await captureSessionFrames(session, {
|
|
@@ -11379,13 +11514,13 @@ function planTransientResume(error, ctx) {
|
|
|
11379
11514
|
if (!firstMissing) return null;
|
|
11380
11515
|
return isTransientCaptureRetryEligible(failure.kind, missing, ctx.retriesUsed) ? firstMissing.startFrame : null;
|
|
11381
11516
|
}
|
|
11382
|
-
async function ensureSessionReady(session) {
|
|
11517
|
+
async function ensureSessionReady(session, budget) {
|
|
11383
11518
|
if (!session.isInitialized) {
|
|
11384
11519
|
await initializeSession5(session);
|
|
11385
11520
|
return;
|
|
11386
11521
|
}
|
|
11387
11522
|
if (process.env.PRODUCER_EXPERIMENTAL_FAST_CAPTURE === "true") {
|
|
11388
|
-
await completeDeferredDrawElementInit3(session);
|
|
11523
|
+
await createSessionInitBudget(budget)(completeDeferredDrawElementInit3(session), session);
|
|
11389
11524
|
}
|
|
11390
11525
|
}
|
|
11391
11526
|
async function captureSessionFrames(session, { input, rangeStart, rangeEnd, resumeFrom, checkDiskProjection }) {
|
|
@@ -11445,7 +11580,7 @@ async function captureSessionFrames(session, { input, rangeStart, rangeEnd, resu
|
|
|
11445
11580
|
}
|
|
11446
11581
|
|
|
11447
11582
|
// src/services/render/stages/captureHdrStage.ts
|
|
11448
|
-
import { existsSync as
|
|
11583
|
+
import { existsSync as existsSync19, mkdirSync as mkdirSync10 } from "node:fs";
|
|
11449
11584
|
import { join as join29 } from "node:path";
|
|
11450
11585
|
import {
|
|
11451
11586
|
closeCaptureSession as closeCaptureSession7,
|
|
@@ -11691,7 +11826,7 @@ import { Worker } from "node:worker_threads";
|
|
|
11691
11826
|
import { fileURLToPath as fileURLToPath3, pathToFileURL } from "node:url";
|
|
11692
11827
|
import { dirname as dirname11, join as join27 } from "node:path";
|
|
11693
11828
|
import { createRequire } from "node:module";
|
|
11694
|
-
import { existsSync as
|
|
11829
|
+
import { existsSync as existsSync18 } from "node:fs";
|
|
11695
11830
|
import { cpus as cpus2 } from "node:os";
|
|
11696
11831
|
function resolveWorkerEntry(explicit) {
|
|
11697
11832
|
if (explicit && explicit.length > 0) {
|
|
@@ -11704,7 +11839,7 @@ function resolveWorkerEntry(explicit) {
|
|
|
11704
11839
|
}
|
|
11705
11840
|
const moduleDir = dirname11(fileURLToPath3(import.meta.url));
|
|
11706
11841
|
const jsPath = join27(moduleDir, "shaderTransitionWorker.js");
|
|
11707
|
-
if (
|
|
11842
|
+
if (existsSync18(jsPath)) return { path: jsPath, isTs: false };
|
|
11708
11843
|
const tsPath = join27(moduleDir, "shaderTransitionWorker.ts");
|
|
11709
11844
|
return { path: tsPath, isTs: true };
|
|
11710
11845
|
}
|
|
@@ -12265,7 +12400,7 @@ async function runCaptureHdrStage(input) {
|
|
|
12265
12400
|
}
|
|
12266
12401
|
const debugDumpEnabled = process.env.KEEP_TEMP === "1";
|
|
12267
12402
|
const debugDumpDir = debugDumpEnabled ? join29(framesDir, "debug-composite") : null;
|
|
12268
|
-
if (debugDumpDir && !
|
|
12403
|
+
if (debugDumpDir && !existsSync19(debugDumpDir)) {
|
|
12269
12404
|
mkdirSync10(debugDumpDir, { recursive: true });
|
|
12270
12405
|
}
|
|
12271
12406
|
const compositeTransfer = resolveCompositeTransfer(hasHdrContent, effectiveHdr);
|
|
@@ -12422,7 +12557,7 @@ async function runCaptureHdrStage(input) {
|
|
|
12422
12557
|
// src/services/render/stages/encodeStage.ts
|
|
12423
12558
|
import {
|
|
12424
12559
|
copyFileSync as copyFileSync4,
|
|
12425
|
-
existsSync as
|
|
12560
|
+
existsSync as existsSync20,
|
|
12426
12561
|
mkdirSync as mkdirSync11,
|
|
12427
12562
|
readdirSync as readdirSync5,
|
|
12428
12563
|
readFileSync as readFileSync8,
|
|
@@ -12556,7 +12691,7 @@ async function encodeGifFromDir(framesDir, framePattern, outputPath, input) {
|
|
|
12556
12691
|
const failure = await reencodeWithClearedWholeFrames(argsInput, run, startTime);
|
|
12557
12692
|
if (failure) return failure;
|
|
12558
12693
|
}
|
|
12559
|
-
const fileSize =
|
|
12694
|
+
const fileSize = existsSync20(outputPath) ? statSync8(outputPath).size : 0;
|
|
12560
12695
|
return {
|
|
12561
12696
|
success: true,
|
|
12562
12697
|
outputPath,
|
|
@@ -12717,7 +12852,7 @@ async function runEncodeStage(input) {
|
|
|
12717
12852
|
const stage5Start = Date.now();
|
|
12718
12853
|
if (isPngSequence) {
|
|
12719
12854
|
const encoded = startEncodeProgress(job, "Writing PNG sequence", onProgress);
|
|
12720
|
-
if (!
|
|
12855
|
+
if (!existsSync20(outputPath)) mkdirSync11(outputPath, { recursive: true });
|
|
12721
12856
|
const captured = readdirSync5(framesDir).filter((name) => name.endsWith(".png")).sort();
|
|
12722
12857
|
if (captured.length === 0) {
|
|
12723
12858
|
throw new Error(
|
|
@@ -12728,7 +12863,7 @@ async function runEncodeStage(input) {
|
|
|
12728
12863
|
const dst = join30(outputPath, formatExportFrameName(i, "png"));
|
|
12729
12864
|
copyFileSync4(join30(framesDir, name), dst);
|
|
12730
12865
|
});
|
|
12731
|
-
if (hasAudio && audioOutputPath &&
|
|
12866
|
+
if (hasAudio && audioOutputPath && existsSync20(audioOutputPath)) {
|
|
12732
12867
|
copyFileSync4(audioOutputPath, join30(outputPath, MIXED_AUDIO_FILENAME2));
|
|
12733
12868
|
log.info(
|
|
12734
12869
|
`[Render] png-sequence: ${MIXED_AUDIO_FILENAME2} sidecar written to ${outputPath}/${MIXED_AUDIO_FILENAME2}`
|
|
@@ -13117,7 +13252,7 @@ function findMissingFrameRanges(totalFrames, framesDir, frameExt) {
|
|
|
13117
13252
|
let rangeStart = null;
|
|
13118
13253
|
for (let frameIndex = 0; frameIndex < totalFrames; frameIndex++) {
|
|
13119
13254
|
const framePath = join31(framesDir, formatCaptureFrameName(frameIndex, frameExt));
|
|
13120
|
-
const missing = !
|
|
13255
|
+
const missing = !existsSync21(framePath) || statSync9(framePath).size <= 8;
|
|
13121
13256
|
if (missing && rangeStart === null) {
|
|
13122
13257
|
rangeStart = frameIndex;
|
|
13123
13258
|
} else if (!missing && rangeStart !== null) {
|
|
@@ -13242,7 +13377,7 @@ function countCapturedFrames(totalFrames, framesDir, frameExt) {
|
|
|
13242
13377
|
let captured = 0;
|
|
13243
13378
|
for (let frameIndex = 0; frameIndex < totalFrames; frameIndex++) {
|
|
13244
13379
|
const framePath = join31(framesDir, formatCaptureFrameName(frameIndex, frameExt));
|
|
13245
|
-
if (
|
|
13380
|
+
if (existsSync21(framePath)) captured++;
|
|
13246
13381
|
}
|
|
13247
13382
|
return captured;
|
|
13248
13383
|
}
|
|
@@ -13907,7 +14042,7 @@ async function executeRenderJob(job, projectDir, outputPath, progressSink, abort
|
|
|
13907
14042
|
validateDeliveryEncoderConfig(job.config);
|
|
13908
14043
|
const debugDir = resolveRenderDebugDir();
|
|
13909
14044
|
const outputDir = dirname13(outputPath);
|
|
13910
|
-
if (!
|
|
14045
|
+
if (!existsSync21(outputDir)) mkdirSync12(outputDir, { recursive: true });
|
|
13911
14046
|
const workDir = job.config.debug ? join31(debugDir, job.id) : createRenderWorkDir(resolveRenderWorkDirPrefix(outputPath, job.id), outputDir);
|
|
13912
14047
|
const pipelineStart = Date.now();
|
|
13913
14048
|
const baseLog = job.config.logger ?? defaultLogger;
|
|
@@ -14049,7 +14184,7 @@ async function executeRenderPipeline(input) {
|
|
|
14049
14184
|
job.startedAt = /* @__PURE__ */ new Date();
|
|
14050
14185
|
assertNotAborted();
|
|
14051
14186
|
assertConfiguredFfmpegBinariesExist();
|
|
14052
|
-
if (!
|
|
14187
|
+
if (!existsSync21(workDir)) mkdirSync12(workDir, { recursive: true });
|
|
14053
14188
|
if (job.config.debug) {
|
|
14054
14189
|
log.info("[Render] Debug artifacts enabled", { workDir, logPath });
|
|
14055
14190
|
}
|
|
@@ -14079,7 +14214,7 @@ async function executeRenderPipeline(input) {
|
|
|
14079
14214
|
});
|
|
14080
14215
|
const entryFile = job.config.entryFile || "index.html";
|
|
14081
14216
|
const htmlPath = join31(projectDir, entryFile);
|
|
14082
|
-
if (!
|
|
14217
|
+
if (!existsSync21(htmlPath)) {
|
|
14083
14218
|
throw new Error(`Entry file not found: ${htmlPath}`);
|
|
14084
14219
|
}
|
|
14085
14220
|
assertNotAborted();
|
|
@@ -14394,9 +14529,11 @@ async function executeRenderPipeline(input) {
|
|
|
14394
14529
|
);
|
|
14395
14530
|
pendingAudio = audioStage.catch(() => void 0);
|
|
14396
14531
|
const stage4Start = Date.now();
|
|
14397
|
-
|
|
14532
|
+
const EFFECT_SCAN_STAGE = "Checking effects";
|
|
14533
|
+
const startingFrameCapture = () => updateJobStatus(job, "rendering", "Starting frame capture", 25, onProgress, {
|
|
14398
14534
|
code: "start_browsers"
|
|
14399
14535
|
});
|
|
14536
|
+
startingFrameCapture();
|
|
14400
14537
|
if (!fileServer) {
|
|
14401
14538
|
const fileServerStart = observability.stageStart("file_server", { reused: false });
|
|
14402
14539
|
try {
|
|
@@ -14421,7 +14558,7 @@ async function executeRenderPipeline(input) {
|
|
|
14421
14558
|
throw new Error("File server failed to initialize before frame capture");
|
|
14422
14559
|
}
|
|
14423
14560
|
const framesDir = join31(workDir, "captured-frames");
|
|
14424
|
-
if (!
|
|
14561
|
+
if (!existsSync21(framesDir)) mkdirSync12(framesDir, { recursive: true });
|
|
14425
14562
|
updateJobStatus(job, "rendering", "Checking browser GPU", 25, onProgress, {
|
|
14426
14563
|
code: "start_browsers"
|
|
14427
14564
|
});
|
|
@@ -14429,6 +14566,7 @@ async function executeRenderPipeline(input) {
|
|
|
14429
14566
|
chromePath: resolveHeadlessShellPath(cfg),
|
|
14430
14567
|
browserTimeout: cfg.browserTimeout
|
|
14431
14568
|
});
|
|
14569
|
+
startingFrameCapture();
|
|
14432
14570
|
captureForceScreenshot = applyConcreteGpuScreenshotClamp(
|
|
14433
14571
|
captureForceScreenshot,
|
|
14434
14572
|
resolvedBrowserGpuMode,
|
|
@@ -14477,10 +14615,13 @@ async function executeRenderPipeline(input) {
|
|
|
14477
14615
|
onEffectScanProgress: (scanned, total) => updateJobStatus(
|
|
14478
14616
|
job,
|
|
14479
14617
|
"rendering",
|
|
14480
|
-
|
|
14618
|
+
`${EFFECT_SCAN_STAGE}, ${scanned} of ${total} frames`,
|
|
14481
14619
|
25,
|
|
14482
14620
|
onProgress
|
|
14483
14621
|
),
|
|
14622
|
+
onEffectScanEnd: () => {
|
|
14623
|
+
if (job.currentStage.startsWith(EFFECT_SCAN_STAGE)) startingFrameCapture();
|
|
14624
|
+
},
|
|
14484
14625
|
requiresWebGpu: compositionRequiresWebGpu2(compiled.html),
|
|
14485
14626
|
// Live route for Chrome memory (spec §5 Phase −1). The aggregate route
|
|
14486
14627
|
// through CapturePerfSummary only exists on success; a mid-capture
|
|
@@ -14748,6 +14889,7 @@ async function executeRenderPipeline(input) {
|
|
|
14748
14889
|
} finally {
|
|
14749
14890
|
calibrationFrames?.release();
|
|
14750
14891
|
}
|
|
14892
|
+
startingFrameCapture();
|
|
14751
14893
|
captureCalibration = outcome.calibration;
|
|
14752
14894
|
calibrationEvaluateTimedOut = outcome.calibrationEvaluateTimedOut;
|
|
14753
14895
|
captureForceScreenshot = outcome.forceScreenshot;
|
|
@@ -15806,7 +15948,7 @@ async function executeRenderPipeline(input) {
|
|
|
15806
15948
|
})
|
|
15807
15949
|
);
|
|
15808
15950
|
const totalElapsed = Date.now() - pipelineStart;
|
|
15809
|
-
const tmpPeakBytes =
|
|
15951
|
+
const tmpPeakBytes = existsSync21(workDir) ? sampleDirectoryBytes(workDir) : 0;
|
|
15810
15952
|
recordTransientRetryObservability();
|
|
15811
15953
|
observability.checkpoint("pipeline", "artifact validated", { totalElapsedMs: totalElapsed });
|
|
15812
15954
|
const observabilitySummary = observability.summary({
|
|
@@ -15887,7 +16029,7 @@ async function executeRenderPipeline(input) {
|
|
|
15887
16029
|
}
|
|
15888
16030
|
}
|
|
15889
16031
|
if (job.config.debug) {
|
|
15890
|
-
if (!isPngSequence && !isHls &&
|
|
16032
|
+
if (!isPngSequence && !isHls && existsSync21(stagedOutputPath)) {
|
|
15891
16033
|
const debugOutput = join31(workDir, `output${videoExt}`);
|
|
15892
16034
|
copyFileSync5(stagedOutputPath, debugOutput);
|
|
15893
16035
|
}
|
|
@@ -16399,13 +16541,13 @@ import { audioFailureDiagnostics as audioFailureDiagnostics3 } from "@hyperframe
|
|
|
16399
16541
|
import {
|
|
16400
16542
|
copyFileSync as copyFileSync6,
|
|
16401
16543
|
cpSync as cpSync2,
|
|
16402
|
-
existsSync as
|
|
16544
|
+
existsSync as existsSync26,
|
|
16403
16545
|
linkSync,
|
|
16404
16546
|
mkdirSync as mkdirSync16,
|
|
16405
16547
|
rmSync as rmSync14,
|
|
16406
16548
|
writeFileSync as writeFileSync10
|
|
16407
16549
|
} from "node:fs";
|
|
16408
|
-
import { extname as extname6, isAbsolute as isAbsolute3, join as join40, relative as relative5, sep as
|
|
16550
|
+
import { extname as extname6, isAbsolute as isAbsolute3, join as join40, relative as relative5, sep as sep3 } from "node:path";
|
|
16409
16551
|
import {
|
|
16410
16552
|
fpsToNumber as fpsToNumber9,
|
|
16411
16553
|
HF_COLOR_GRADING_ATTR as HF_COLOR_GRADING_ATTR2
|
|
@@ -16430,7 +16572,7 @@ import {
|
|
|
16430
16572
|
} from "@hyperframes/engine";
|
|
16431
16573
|
|
|
16432
16574
|
// src/services/distributed/chunkFrames.ts
|
|
16433
|
-
import { existsSync as
|
|
16575
|
+
import { existsSync as existsSync23, readdirSync as readdirSync7 } from "node:fs";
|
|
16434
16576
|
import { extname as extname4, join as join34 } from "node:path";
|
|
16435
16577
|
import {
|
|
16436
16578
|
compositionRequiresWebGpu as compositionRequiresWebGpu3,
|
|
@@ -16440,7 +16582,7 @@ import {
|
|
|
16440
16582
|
// src/services/distributed/shared.ts
|
|
16441
16583
|
import { execFile as execFileCallback } from "node:child_process";
|
|
16442
16584
|
import { dirname as dirname15, join as join33 } from "node:path";
|
|
16443
|
-
import { existsSync as
|
|
16585
|
+
import { existsSync as existsSync22, readFileSync as readFileSync9 } from "node:fs";
|
|
16444
16586
|
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
16445
16587
|
import { promisify as promisify2 } from "node:util";
|
|
16446
16588
|
import {
|
|
@@ -16456,7 +16598,7 @@ function isPlanAudioArtifactPath(path) {
|
|
|
16456
16598
|
function resolvePlanAudioPath(planDir) {
|
|
16457
16599
|
for (const name of [PLAN_AUDIO_RELATIVE_PATH, PLAN_AUDIO_LEGACY_RELATIVE_PATH]) {
|
|
16458
16600
|
const candidate = join33(planDir, name);
|
|
16459
|
-
if (
|
|
16601
|
+
if (existsSync22(candidate)) return candidate;
|
|
16460
16602
|
}
|
|
16461
16603
|
return null;
|
|
16462
16604
|
}
|
|
@@ -16717,7 +16859,7 @@ function readProducerVersion() {
|
|
|
16717
16859
|
let current = startDir;
|
|
16718
16860
|
for (let i = 0; i < 10; i++) {
|
|
16719
16861
|
const candidate = join33(current, "package.json");
|
|
16720
|
-
if (
|
|
16862
|
+
if (existsSync22(candidate)) {
|
|
16721
16863
|
try {
|
|
16722
16864
|
const pkg = JSON.parse(readFileSync9(candidate, "utf-8"));
|
|
16723
16865
|
if (pkg.name === "@hyperframes/producer" && typeof pkg.version === "string") {
|
|
@@ -16771,7 +16913,7 @@ function rebuildExtractedFramesFromPlanDir(planDir, videos, indexMode = "dense-v
|
|
|
16771
16913
|
const result = [];
|
|
16772
16914
|
for (const v of videos) {
|
|
16773
16915
|
const outputDir = join34(planDir, "video-frames", v.videoId);
|
|
16774
|
-
if (!
|
|
16916
|
+
if (!existsSync23(outputDir)) {
|
|
16775
16917
|
throw new Error(
|
|
16776
16918
|
`[renderChunk] planDir missing extracted video frames for ${JSON.stringify(v.videoId)}: ${outputDir} not present. plan() should have written frames here; the planDir is malformed.`
|
|
16777
16919
|
);
|
|
@@ -16870,7 +17012,7 @@ async function extractDeferredVideoFramesForChunk(input) {
|
|
|
16870
17012
|
const range = planned.get(video.videoId).deferredRange;
|
|
16871
17013
|
const remoteUrl = remoteSources[range.sourcePath];
|
|
16872
17014
|
const sourcePath = remoteUrl ?? join34(input.planDir, ...range.sourcePath.split("/"));
|
|
16873
|
-
if (remoteUrl === void 0 && !
|
|
17015
|
+
if (remoteUrl === void 0 && !existsSync23(sourcePath)) {
|
|
16874
17016
|
throw new RenderChunkValidationError(
|
|
16875
17017
|
MISSING_PLAN_ARTIFACT,
|
|
16876
17018
|
`[renderChunk] planDir is missing the source for video ${JSON.stringify(video.videoId)}: ${range.sourcePath}`
|
|
@@ -17070,7 +17212,7 @@ async function readPlanReferenceBackground(input) {
|
|
|
17070
17212
|
}
|
|
17071
17213
|
|
|
17072
17214
|
// src/services/render/stages/freezePlan.ts
|
|
17073
|
-
import { existsSync as
|
|
17215
|
+
import { existsSync as existsSync24, mkdirSync as mkdirSync14, readFileSync as readFileSync10, readdirSync as readdirSync8, writeFileSync as writeFileSync8 } from "node:fs";
|
|
17074
17216
|
import { join as join37, relative as relative3, resolve as resolve9 } from "node:path";
|
|
17075
17217
|
import {
|
|
17076
17218
|
REFERENCE_BACKGROUND_FIELD,
|
|
@@ -17264,7 +17406,7 @@ function planFileSha256(absolutePath, digests) {
|
|
|
17264
17406
|
return sha256;
|
|
17265
17407
|
}
|
|
17266
17408
|
async function prehashPlanFiles(dir, digests) {
|
|
17267
|
-
if (!
|
|
17409
|
+
if (!existsSync24(dir)) return;
|
|
17268
17410
|
for (const { absolutePath } of listPlanFiles(dir)) {
|
|
17269
17411
|
if (!digests.has(absolutePath)) digests.set(absolutePath, await sha256FileAsync(absolutePath));
|
|
17270
17412
|
}
|
|
@@ -17334,15 +17476,15 @@ function writePlanRemoteFiles(planDir, files) {
|
|
|
17334
17476
|
}
|
|
17335
17477
|
function readPlanRemoteFiles(planDir) {
|
|
17336
17478
|
const path = join37(planDir, PLAN_REMOTE_FILES_RELATIVE_PATH);
|
|
17337
|
-
return
|
|
17479
|
+
return existsSync24(path) ? JSON.parse(readFileSync10(path, "utf-8")) : [];
|
|
17338
17480
|
}
|
|
17339
17481
|
function recomputePlanHashFromPlanDir(planDir, digests) {
|
|
17340
17482
|
const planJsonPath = join37(planDir, "plan.json");
|
|
17341
17483
|
const encoderJsonPath = join37(planDir, "meta", "encoder.json");
|
|
17342
|
-
if (!
|
|
17484
|
+
if (!existsSync24(planJsonPath)) {
|
|
17343
17485
|
throw new Error(`[freezePlan] plan.json missing: ${planJsonPath}`);
|
|
17344
17486
|
}
|
|
17345
|
-
if (!
|
|
17487
|
+
if (!existsSync24(encoderJsonPath)) {
|
|
17346
17488
|
throw new Error(`[freezePlan] meta/encoder.json missing: ${encoderJsonPath}`);
|
|
17347
17489
|
}
|
|
17348
17490
|
const planJson = JSON.parse(readFileSync10(planJsonPath, "utf-8"));
|
|
@@ -17372,11 +17514,11 @@ async function freezePlan(input) {
|
|
|
17372
17514
|
hasAudio,
|
|
17373
17515
|
digests
|
|
17374
17516
|
} = input;
|
|
17375
|
-
if (!
|
|
17517
|
+
if (!existsSync24(planDir)) {
|
|
17376
17518
|
throw new Error(`[freezePlan] planDir does not exist: ${planDir}`);
|
|
17377
17519
|
}
|
|
17378
17520
|
const metaDir = join37(planDir, "meta");
|
|
17379
|
-
if (!
|
|
17521
|
+
if (!existsSync24(metaDir)) mkdirSync14(metaDir, { recursive: true });
|
|
17380
17522
|
writeFileSync8(
|
|
17381
17523
|
join37(metaDir, "composition.json"),
|
|
17382
17524
|
`${JSON.stringify(composition, null, 2)}
|
|
@@ -17453,7 +17595,7 @@ function applyRuntimeEnvSnapshot(snapshot, env = process.env) {
|
|
|
17453
17595
|
}
|
|
17454
17596
|
|
|
17455
17597
|
// src/services/distributed/chunkAudio.ts
|
|
17456
|
-
import { closeSync as closeSync6, existsSync as
|
|
17598
|
+
import { closeSync as closeSync6, existsSync as existsSync25, ftruncateSync, mkdirSync as mkdirSync15, openSync as openSync6, readFileSync as readFileSync11 } from "node:fs";
|
|
17457
17599
|
import { writeFileSync as writeFileSync9, writeSync } from "node:fs";
|
|
17458
17600
|
import { join as join38 } from "node:path";
|
|
17459
17601
|
import { appendRenderProvenanceArgs, describeFfmpegFailure as describeFfmpegFailure4, runFfmpeg as runFfmpeg7 } from "@hyperframes/engine";
|
|
@@ -17559,7 +17701,7 @@ async function writeChunkAudioSlices(input) {
|
|
|
17559
17701
|
}
|
|
17560
17702
|
function chunkAudioSource(planDir, chunkIndex, scratchPath) {
|
|
17561
17703
|
const layoutPath = join38(planDir, SLICE_LAYOUT);
|
|
17562
|
-
if (!
|
|
17704
|
+
if (!existsSync25(layoutPath)) return resolvePlanAudioPath(planDir);
|
|
17563
17705
|
const layout = JSON.parse(readFileSync11(layoutPath, "utf-8"));
|
|
17564
17706
|
const frame = readFileSync11(join38(planDir, SLICE_FRAME));
|
|
17565
17707
|
const fd = openSync6(scratchPath, "w");
|
|
@@ -17928,7 +18070,7 @@ function assertPlanSizeWithinLimit(input) {
|
|
|
17928
18070
|
});
|
|
17929
18071
|
throw new PlanTooLargeError(breakdown.totalBytes, input.limitBytes, breakdown, input.observedAt);
|
|
17930
18072
|
}
|
|
17931
|
-
function resolveChunkPlan(totalFrames, configChunkSize, maxParallelChunks, targetChunkFrames) {
|
|
18073
|
+
function resolveChunkPlan(totalFrames, configChunkSize, maxParallelChunks, targetChunkFrames, minParallelChunks = 1) {
|
|
17932
18074
|
assertPositiveInteger2("totalFrames", totalFrames);
|
|
17933
18075
|
assertPositiveInteger2("maxParallelChunks", maxParallelChunks);
|
|
17934
18076
|
if (configChunkSize !== void 0) {
|
|
@@ -17937,7 +18079,11 @@ function resolveChunkPlan(totalFrames, configChunkSize, maxParallelChunks, targe
|
|
|
17937
18079
|
if (targetChunkFrames !== void 0) {
|
|
17938
18080
|
assertPositiveInteger2("targetChunkFrames", targetChunkFrames);
|
|
17939
18081
|
}
|
|
17940
|
-
|
|
18082
|
+
assertPositiveInteger2("minParallelChunks", minParallelChunks);
|
|
18083
|
+
const autoSizeParallel = targetChunkFrames === void 0 ? maxParallelChunks : Math.min(
|
|
18084
|
+
maxParallelChunks,
|
|
18085
|
+
Math.max(minParallelChunks, Math.ceil(totalFrames / targetChunkFrames))
|
|
18086
|
+
);
|
|
17941
18087
|
const resolvedChunkSize = configChunkSize ?? Math.max(MIN_CHUNK_SIZE, Math.ceil(totalFrames / autoSizeParallel));
|
|
17942
18088
|
const naiveCount = Math.ceil(totalFrames / resolvedChunkSize);
|
|
17943
18089
|
const chunkCount = Math.min(maxParallelChunks, Math.max(1, naiveCount));
|
|
@@ -18105,7 +18251,7 @@ function pathInside(root, path) {
|
|
|
18105
18251
|
function stableSourcePath(roots, srcPath) {
|
|
18106
18252
|
for (const [root, prefix] of roots) {
|
|
18107
18253
|
const rel = pathInside(root, srcPath);
|
|
18108
|
-
if (rel !== null) return [prefix, ...rel.split(
|
|
18254
|
+
if (rel !== null) return [prefix, ...rel.split(sep3)].filter(Boolean).join("/");
|
|
18109
18255
|
}
|
|
18110
18256
|
return srcPath;
|
|
18111
18257
|
}
|
|
@@ -18119,8 +18265,8 @@ function createVideoSourceShipper(input) {
|
|
|
18119
18265
|
let result = null;
|
|
18120
18266
|
for (const root of stagedRel === null ? [input.workCompiledDir, input.projectDir] : []) {
|
|
18121
18267
|
const rel = pathInside(root, srcPath);
|
|
18122
|
-
if (rel !== null && (
|
|
18123
|
-
result = ["compiled", ...rel.split(
|
|
18268
|
+
if (rel !== null && (existsSync26(join40(input.planDir, "compiled", rel)) || input.isRemote(srcPath))) {
|
|
18269
|
+
result = ["compiled", ...rel.split(sep3)].join("/");
|
|
18124
18270
|
break;
|
|
18125
18271
|
}
|
|
18126
18272
|
}
|
|
@@ -18147,7 +18293,7 @@ async function compilePlanComposition(projectDir, config, planDir, options) {
|
|
|
18147
18293
|
useGpu: false,
|
|
18148
18294
|
browserGpuMode: "software"
|
|
18149
18295
|
});
|
|
18150
|
-
if (!
|
|
18296
|
+
if (!existsSync26(planDir)) mkdirSync16(planDir, { recursive: true });
|
|
18151
18297
|
const log = config.logger ?? defaultLogger;
|
|
18152
18298
|
const sizeLimitBytes = options.executionPlanSizeLimitBytes ?? config.planDirSizeLimitBytes ?? PLAN_DIR_SIZE_LIMIT_BYTES;
|
|
18153
18299
|
const abortSignal = config.abortSignal;
|
|
@@ -18177,7 +18323,7 @@ async function compilePlanComposition(projectDir, config, planDir, options) {
|
|
|
18177
18323
|
});
|
|
18178
18324
|
const entryFile = config.entryFile ?? "index.html";
|
|
18179
18325
|
const htmlPath = join40(projectDir, entryFile);
|
|
18180
|
-
if (!
|
|
18326
|
+
if (!existsSync26(htmlPath)) {
|
|
18181
18327
|
throw new Error(`[plan] entry file not found: ${htmlPath}`);
|
|
18182
18328
|
}
|
|
18183
18329
|
const workDir = join40(planDir, ".plan-work");
|
|
@@ -18192,7 +18338,7 @@ async function compilePlanComposition(projectDir, config, planDir, options) {
|
|
|
18192
18338
|
filter: (src) => {
|
|
18193
18339
|
const rel = relative5(projectDir, src);
|
|
18194
18340
|
if (rel === "" || rel.startsWith("..")) return true;
|
|
18195
|
-
const firstSegment = rel.split(
|
|
18341
|
+
const firstSegment = rel.split(sep3, 1)[0];
|
|
18196
18342
|
return firstSegment === void 0 || !PLAN_PROJECT_DIR_SKIP_SEGMENTS.has(firstSegment);
|
|
18197
18343
|
}
|
|
18198
18344
|
});
|
|
@@ -18405,15 +18551,15 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
|
|
|
18405
18551
|
logPlanStage(log, "extract", stageStarted);
|
|
18406
18552
|
const stagedVideoFrames = join40(compiledDir, "__hyperframes_video_frames");
|
|
18407
18553
|
const videoFramesDst = join40(planDir, "video-frames");
|
|
18408
|
-
if (
|
|
18554
|
+
if (existsSync26(videoFramesDst)) rmSync14(videoFramesDst, { recursive: true, force: true });
|
|
18409
18555
|
const videoSourcesDir = join40(planDir, "video-sources");
|
|
18410
18556
|
rmSync14(videoSourcesDir, { recursive: true, force: true });
|
|
18411
|
-
if (
|
|
18557
|
+
if (existsSync26(stagedVideoFrames)) {
|
|
18412
18558
|
renameWithRetry8(stagedVideoFrames, videoFramesDst);
|
|
18413
18559
|
} else {
|
|
18414
18560
|
mkdirSync16(videoFramesDst, { recursive: true });
|
|
18415
18561
|
}
|
|
18416
|
-
if (
|
|
18562
|
+
if (existsSync26(finalCompiledDir)) rmSync14(finalCompiledDir, { recursive: true, force: true });
|
|
18417
18563
|
renameWithRetry8(compiledDir, finalCompiledDir);
|
|
18418
18564
|
const shipVideoSource = createVideoSourceShipper({
|
|
18419
18565
|
planDir,
|
|
@@ -18516,7 +18662,8 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
|
|
|
18516
18662
|
totalFrames,
|
|
18517
18663
|
config.chunkSize,
|
|
18518
18664
|
maxParallel,
|
|
18519
|
-
config.targetChunkFrames
|
|
18665
|
+
config.targetChunkFrames,
|
|
18666
|
+
config.minParallelChunks
|
|
18520
18667
|
);
|
|
18521
18668
|
const chunks = buildPlanChunkSlices(
|
|
18522
18669
|
frameWeights,
|
|
@@ -18533,10 +18680,10 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
|
|
|
18533
18680
|
applyDistributedAudioWarningPolicy(job, audioResult.audioError, audioResult.audioFailures, log);
|
|
18534
18681
|
}
|
|
18535
18682
|
const planAudioPath = join40(planDir, PLAN_AUDIO_RELATIVE_PATH);
|
|
18536
|
-
if (audioResult?.hasAudio &&
|
|
18683
|
+
if (audioResult?.hasAudio && existsSync26(audioResult.audioOutputPath)) {
|
|
18537
18684
|
renameWithRetry8(audioResult.audioOutputPath, planAudioPath);
|
|
18538
18685
|
}
|
|
18539
|
-
if (chunksCarryAudio &&
|
|
18686
|
+
if (chunksCarryAudio && existsSync26(planAudioPath)) {
|
|
18540
18687
|
if (options.sliceChunkAudio) {
|
|
18541
18688
|
stageStarted = Date.now();
|
|
18542
18689
|
await writeChunkAudioSlices({
|
|
@@ -18654,7 +18801,7 @@ async function plan(projectDir, config, planDir, options = {}) {
|
|
|
18654
18801
|
|
|
18655
18802
|
// src/services/distributed/renderChunk.ts
|
|
18656
18803
|
import { randomBytes } from "node:crypto";
|
|
18657
|
-
import { existsSync as
|
|
18804
|
+
import { existsSync as existsSync29, mkdirSync as mkdirSync19, readFileSync as readFileSync13, readdirSync as readdirSync11, rmSync as rmSync17, writeFileSync as writeFileSync13 } from "node:fs";
|
|
18658
18805
|
import { cpus as cpus3 } from "node:os";
|
|
18659
18806
|
import { join as join44 } from "node:path";
|
|
18660
18807
|
import {
|
|
@@ -18681,7 +18828,7 @@ import {
|
|
|
18681
18828
|
} from "@hyperframes/engine";
|
|
18682
18829
|
import {
|
|
18683
18830
|
copyFileSync as copyFileSync8,
|
|
18684
|
-
existsSync as
|
|
18831
|
+
existsSync as existsSync28,
|
|
18685
18832
|
lstatSync as lstatSync3,
|
|
18686
18833
|
mkdirSync as mkdirSync18,
|
|
18687
18834
|
mkdtempSync as mkdtempSync6,
|
|
@@ -18692,14 +18839,14 @@ import {
|
|
|
18692
18839
|
writeFileSync as writeFileSync12
|
|
18693
18840
|
} from "node:fs";
|
|
18694
18841
|
import { tmpdir as tmpdir3 } from "node:os";
|
|
18695
|
-
import { dirname as dirname17, isAbsolute as isAbsolute4, join as join43, relative as relative6, resolve as resolve10, sep as
|
|
18842
|
+
import { dirname as dirname17, isAbsolute as isAbsolute4, join as join43, relative as relative6, resolve as resolve10, sep as sep4 } from "node:path";
|
|
18696
18843
|
import { DISTRIBUTED_FPS, isDistributedFps } from "@hyperframes/core";
|
|
18697
18844
|
import { createFrameLookupTable as createFrameLookupTable5, renameWithRetry as renameWithRetry10 } from "@hyperframes/engine";
|
|
18698
18845
|
|
|
18699
18846
|
// src/services/distributed/planV2Publisher.ts
|
|
18700
18847
|
import {
|
|
18701
18848
|
copyFileSync as copyFileSync7,
|
|
18702
|
-
existsSync as
|
|
18849
|
+
existsSync as existsSync27,
|
|
18703
18850
|
linkSync as linkSync2,
|
|
18704
18851
|
mkdirSync as mkdirSync17,
|
|
18705
18852
|
mkdtempSync as mkdtempSync5,
|
|
@@ -18768,7 +18915,7 @@ var LocalPlanV2ArtifactPublisher = class {
|
|
|
18768
18915
|
#linkFile;
|
|
18769
18916
|
#committed = false;
|
|
18770
18917
|
constructor(destinationDir, options = {}) {
|
|
18771
|
-
if (
|
|
18918
|
+
if (existsSync27(destinationDir)) {
|
|
18772
18919
|
throw new PlanV2IntegrityError(`output directory already exists: ${destinationDir}`);
|
|
18773
18920
|
}
|
|
18774
18921
|
this.destinationDir = destinationDir;
|
|
@@ -18785,7 +18932,7 @@ var LocalPlanV2ArtifactPublisher = class {
|
|
|
18785
18932
|
);
|
|
18786
18933
|
}
|
|
18787
18934
|
const destinationPath = planV2BlobPath(this.temporaryDir, digest);
|
|
18788
|
-
if (
|
|
18935
|
+
if (existsSync27(destinationPath)) return;
|
|
18789
18936
|
mkdirSync17(dirname16(destinationPath), { recursive: true });
|
|
18790
18937
|
const stagingDir = mkdtempSync5(join42(dirname16(destinationPath), ".plan-v2-blob-"));
|
|
18791
18938
|
const temporaryPath = join42(stagingDir, "blob");
|
|
@@ -18803,7 +18950,7 @@ var LocalPlanV2ArtifactPublisher = class {
|
|
|
18803
18950
|
}
|
|
18804
18951
|
async commitManifest(manifestBytes) {
|
|
18805
18952
|
for (const digest of new Set(manifestDigests(manifestBytes))) {
|
|
18806
|
-
if (!
|
|
18953
|
+
if (!existsSync27(planV2BlobPath(this.temporaryDir, digest))) {
|
|
18807
18954
|
throw new PlanV2IntegrityError(
|
|
18808
18955
|
`cannot commit manifest before referenced blob is durable: ${digest}`
|
|
18809
18956
|
);
|
|
@@ -18859,7 +19006,7 @@ function listFiles(root) {
|
|
|
18859
19006
|
walk(absolutePath);
|
|
18860
19007
|
} else if (entry.isFile()) {
|
|
18861
19008
|
files.push({
|
|
18862
|
-
path: relative6(rootResolved, absolutePath).split(
|
|
19009
|
+
path: relative6(rootResolved, absolutePath).split(sep4).join("/"),
|
|
18863
19010
|
absolutePath
|
|
18864
19011
|
});
|
|
18865
19012
|
} else {
|
|
@@ -18882,7 +19029,7 @@ function assertValidExtractionCacheCompleteSentinel(path) {
|
|
|
18882
19029
|
}
|
|
18883
19030
|
function validateExtractionCacheCompleteSentinels(executionPlanDir) {
|
|
18884
19031
|
const videoRoot = join43(executionPlanDir, "video-frames");
|
|
18885
|
-
if (!
|
|
19032
|
+
if (!existsSync28(videoRoot)) return;
|
|
18886
19033
|
for (const videoEntry of readdirSync10(videoRoot, { withFileTypes: true })) {
|
|
18887
19034
|
if (!videoEntry.isDirectory()) continue;
|
|
18888
19035
|
const videoDir = join43(videoRoot, videoEntry.name);
|
|
@@ -18899,7 +19046,7 @@ function isExtractionCacheCompleteSentinelPath(path) {
|
|
|
18899
19046
|
function resolveExtractedVideoOutputDir(planDir, videoId) {
|
|
18900
19047
|
const videoRoot = resolve10(planDir, "video-frames");
|
|
18901
19048
|
const outputDir = resolve10(videoRoot, videoId);
|
|
18902
|
-
if (outputDir === videoRoot || !outputDir.startsWith(`${videoRoot}${
|
|
19049
|
+
if (outputDir === videoRoot || !outputDir.startsWith(`${videoRoot}${sep4}`)) {
|
|
18903
19050
|
throw new PlanV2IntegrityError(`unsafe extracted video id: ${JSON.stringify(videoId)}`);
|
|
18904
19051
|
}
|
|
18905
19052
|
return outputDir;
|
|
@@ -18983,7 +19130,7 @@ function parsePlanVideosJson2(value) {
|
|
|
18983
19130
|
}
|
|
18984
19131
|
function materializeExtractedVideoDirectories(planDir) {
|
|
18985
19132
|
const videosPath = join43(planDir, PLAN_VIDEOS_META_RELATIVE_PATH);
|
|
18986
|
-
if (!
|
|
19133
|
+
if (!existsSync28(videosPath)) return;
|
|
18987
19134
|
const videos = parsePlanVideosJson2(readJsonFile(videosPath, PLAN_VIDEOS_META_RELATIVE_PATH));
|
|
18988
19135
|
for (const video of videos.extracted) {
|
|
18989
19136
|
mkdirSync18(resolveExtractedVideoOutputDir(planDir, video.videoId), { recursive: true });
|
|
@@ -19017,9 +19164,9 @@ function parseChunkSlices(value) {
|
|
|
19017
19164
|
}
|
|
19018
19165
|
function buildVideoChunkDependencies(executionPlanDir, dimensions) {
|
|
19019
19166
|
const videoRoot = join43(executionPlanDir, "video-frames");
|
|
19020
|
-
const hasExtractedFrames =
|
|
19167
|
+
const hasExtractedFrames = existsSync28(videoRoot) && listFiles(videoRoot).length > 0;
|
|
19021
19168
|
const videosPath = join43(executionPlanDir, PLAN_VIDEOS_META_RELATIVE_PATH);
|
|
19022
|
-
if (!
|
|
19169
|
+
if (!existsSync28(videosPath)) {
|
|
19023
19170
|
return hasExtractedFrames ? { mode: "full-source-pack", dependencies: null } : { mode: "exact-rendered-frames", dependencies: /* @__PURE__ */ new Map() };
|
|
19024
19171
|
}
|
|
19025
19172
|
const chunksPath = join43(executionPlanDir, "meta", "chunks.json");
|
|
@@ -19059,7 +19206,7 @@ function collectChunkVideoDependencies(input) {
|
|
|
19059
19206
|
for (let frame = chunk.startFrame; frame < chunk.endFrame; frame++) {
|
|
19060
19207
|
const globalTime = frame * input.fpsDen / input.fpsNum;
|
|
19061
19208
|
for (const [videoId, payload] of input.table.getActiveFramePayloads(globalTime)) {
|
|
19062
|
-
const path = input.deferredSources.get(videoId) ?? relative6(resolve10(input.executionPlanDir), payload.framePath).split(
|
|
19209
|
+
const path = input.deferredSources.get(videoId) ?? relative6(resolve10(input.executionPlanDir), payload.framePath).split(sep4).join("/");
|
|
19063
19210
|
assertSafeRelativePath(path);
|
|
19064
19211
|
const indexes = mutable.get(path) ?? /* @__PURE__ */ new Set();
|
|
19065
19212
|
indexes.add(chunk.index);
|
|
@@ -19076,7 +19223,7 @@ function computeManifestHash(manifest) {
|
|
|
19076
19223
|
return sha256Hex(`${PLAN_V2_HASH_PREFIX}${canonicalJsonStringify(manifestPayload(manifest))}`);
|
|
19077
19224
|
}
|
|
19078
19225
|
function writeBlob(sourcePath, destinationPath) {
|
|
19079
|
-
if (
|
|
19226
|
+
if (existsSync28(destinationPath)) return;
|
|
19080
19227
|
mkdirSync18(dirname17(destinationPath), { recursive: true });
|
|
19081
19228
|
const temporaryDir = mkdtempSync6(join43(dirname17(destinationPath), ".plan-v2-blob-"));
|
|
19082
19229
|
const temporaryPath = join43(temporaryDir, "blob");
|
|
@@ -19116,7 +19263,7 @@ async function putTasks(tasks) {
|
|
|
19116
19263
|
}
|
|
19117
19264
|
function buildPlanV2Publication(executionPlanDir, digests) {
|
|
19118
19265
|
const executionPlanPath = join43(executionPlanDir, "plan.json");
|
|
19119
|
-
if (!
|
|
19266
|
+
if (!existsSync28(executionPlanPath)) {
|
|
19120
19267
|
throw new PlanV2IntegrityError(`execution plan is missing plan.json: ${executionPlanPath}`);
|
|
19121
19268
|
}
|
|
19122
19269
|
const executionPlanValue = readJsonFile(executionPlanPath, "execution plan.json");
|
|
@@ -19196,7 +19343,7 @@ function buildPlanV2Publication(executionPlanDir, digests) {
|
|
|
19196
19343
|
};
|
|
19197
19344
|
}
|
|
19198
19345
|
function createPlanV2FromExecutionPlan(executionPlanDir, planV2Dir) {
|
|
19199
|
-
if (
|
|
19346
|
+
if (existsSync28(planV2Dir)) {
|
|
19200
19347
|
throw new PlanV2IntegrityError(`output directory already exists: ${planV2Dir}`);
|
|
19201
19348
|
}
|
|
19202
19349
|
const publication = buildPlanV2Publication(executionPlanDir);
|
|
@@ -19282,7 +19429,7 @@ async function planV2WithPublisher(projectDir, config, publisher, options = {})
|
|
|
19282
19429
|
}
|
|
19283
19430
|
}
|
|
19284
19431
|
async function planV2(projectDir, config, planV2Dir) {
|
|
19285
|
-
if (
|
|
19432
|
+
if (existsSync28(planV2Dir)) {
|
|
19286
19433
|
throw new PlanV2IntegrityError(`output directory already exists: ${planV2Dir}`);
|
|
19287
19434
|
}
|
|
19288
19435
|
const publisher = new LocalPlanV2ArtifactPublisher(planV2Dir);
|
|
@@ -19436,7 +19583,7 @@ function parsePlanV2Manifest(value) {
|
|
|
19436
19583
|
}
|
|
19437
19584
|
function readPlanV2Manifest(planV2Dir) {
|
|
19438
19585
|
const manifestPath = join43(planV2Dir, "plan.json");
|
|
19439
|
-
if (!
|
|
19586
|
+
if (!existsSync28(manifestPath)) {
|
|
19440
19587
|
throw new PlanV2IntegrityError(`missing v2 manifest: ${manifestPath}`);
|
|
19441
19588
|
}
|
|
19442
19589
|
const value = readJsonFile(manifestPath, "v2 manifest");
|
|
@@ -19455,7 +19602,7 @@ function listPlanV2ArtifactsForTarget(manifest, target) {
|
|
|
19455
19602
|
}
|
|
19456
19603
|
function verifyBlob(planV2Dir, artifact) {
|
|
19457
19604
|
const sourcePath = planV2BlobPath(planV2Dir, artifact.sha256);
|
|
19458
|
-
if (!
|
|
19605
|
+
if (!existsSync28(sourcePath)) {
|
|
19459
19606
|
throw new PlanV2IntegrityError(`missing content-addressed artifact ${artifact.sha256}`);
|
|
19460
19607
|
}
|
|
19461
19608
|
const stats = statSync11(sourcePath);
|
|
@@ -19468,7 +19615,7 @@ function verifyBlob(planV2Dir, artifact) {
|
|
|
19468
19615
|
return sourcePath;
|
|
19469
19616
|
}
|
|
19470
19617
|
function materializePlanV2Target(planV2Dir, target, destinationDir, options = {}) {
|
|
19471
|
-
if (
|
|
19618
|
+
if (existsSync28(destinationDir)) {
|
|
19472
19619
|
throw new PlanV2IntegrityError(`materialization destination already exists: ${destinationDir}`);
|
|
19473
19620
|
}
|
|
19474
19621
|
const manifest = readPlanV2Manifest(planV2Dir);
|
|
@@ -19533,7 +19680,7 @@ function validatePlanV2MaterializedTarget(planDir, expectedTarget) {
|
|
|
19533
19680
|
}
|
|
19534
19681
|
function readValidatedPlanV2Materialization(planDir, expectedTarget) {
|
|
19535
19682
|
const markerPath = join43(planDir, PLAN_V2_MATERIALIZATION_MARKER);
|
|
19536
|
-
if (!
|
|
19683
|
+
if (!existsSync28(markerPath)) return null;
|
|
19537
19684
|
const marker = readJsonFile(markerPath, "materialization marker");
|
|
19538
19685
|
if (!isRecord3(marker) || !isRecord3(marker.manifest) || !isRecord3(marker.target)) {
|
|
19539
19686
|
throw new PlanV2IntegrityError("malformed materialization marker");
|
|
@@ -19551,7 +19698,7 @@ function readValidatedPlanV2Materialization(planDir, expectedTarget) {
|
|
|
19551
19698
|
for (const artifact of required) {
|
|
19552
19699
|
if (Object.hasOwn(remoteSources, artifact.path)) continue;
|
|
19553
19700
|
const path = join43(planDir, ...artifact.path.split("/"));
|
|
19554
|
-
if (!
|
|
19701
|
+
if (!existsSync28(path) || statSync11(path).size !== artifact.sizeBytes) {
|
|
19555
19702
|
throw new PlanV2IntegrityError(
|
|
19556
19703
|
`materialized artifact missing or truncated: ${artifact.path}`
|
|
19557
19704
|
);
|
|
@@ -19651,12 +19798,23 @@ async function runCaptureWithScreenshotFallback(input) {
|
|
|
19651
19798
|
try {
|
|
19652
19799
|
return await input.run(input.forceScreenshot);
|
|
19653
19800
|
} catch (error) {
|
|
19654
|
-
if (input.forceScreenshot || await input.canRetry?.() === false)
|
|
19655
|
-
|
|
19801
|
+
if (input.forceScreenshot || await input.canRetry?.() === false)
|
|
19802
|
+
throw imageMissingIn(error) ?? error;
|
|
19803
|
+
if (!shouldRetryChunkCaptureWithScreenshot(error)) throw imageMissingIn(error) ?? error;
|
|
19656
19804
|
input.onFallback?.(error);
|
|
19657
19805
|
await input.resetForScreenshotRetry();
|
|
19658
|
-
return await input.run(true)
|
|
19806
|
+
return await input.run(true).catch((retryError) => {
|
|
19807
|
+
throw imageMissingIn(retryError) ?? retryError;
|
|
19808
|
+
});
|
|
19809
|
+
}
|
|
19810
|
+
}
|
|
19811
|
+
function imageMissingIn(error) {
|
|
19812
|
+
let e = error;
|
|
19813
|
+
for (let depth = 0; depth < 5 && typeof e === "object" && e !== null; depth++) {
|
|
19814
|
+
if (e.name === "ImageMissingFromFrameError") return e;
|
|
19815
|
+
e = e.cause;
|
|
19659
19816
|
}
|
|
19817
|
+
return void 0;
|
|
19660
19818
|
}
|
|
19661
19819
|
async function beginFrameSessionNeedsScreenshotFallback(session, probe = probeBeginFrameLiveness2) {
|
|
19662
19820
|
if (session.launchCaptureMode !== "beginframe") return false;
|
|
@@ -19691,7 +19849,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
19691
19849
|
const planJsonPath = join44(planDir, "plan.json");
|
|
19692
19850
|
const encoderJsonPath = join44(planDir, "meta", "encoder.json");
|
|
19693
19851
|
const chunksJsonPath = join44(planDir, "meta", "chunks.json");
|
|
19694
|
-
if (!
|
|
19852
|
+
if (!existsSync29(planJsonPath)) {
|
|
19695
19853
|
throw new RenderChunkValidationError(
|
|
19696
19854
|
MISSING_PLAN_ARTIFACT,
|
|
19697
19855
|
`[renderChunk] planDir is missing required artifact: ${planJsonPath}`
|
|
@@ -19707,7 +19865,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
19707
19865
|
const v2Manifest = v2Materialization?.manifest ?? null;
|
|
19708
19866
|
const remoteSources = v2Materialization?.remoteSources ?? {};
|
|
19709
19867
|
for (const required of [encoderJsonPath, chunksJsonPath]) {
|
|
19710
|
-
if (!
|
|
19868
|
+
if (!existsSync29(required)) {
|
|
19711
19869
|
throw new RenderChunkValidationError(
|
|
19712
19870
|
MISSING_PLAN_ARTIFACT,
|
|
19713
19871
|
`[renderChunk] planDir is missing required artifact: ${required}`
|
|
@@ -19718,7 +19876,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
19718
19876
|
const chunks = JSON.parse(readFileSync13(chunksJsonPath, "utf-8"));
|
|
19719
19877
|
const videosJsonPath = join44(planDir, PLAN_VIDEOS_META_RELATIVE_PATH);
|
|
19720
19878
|
let planVideos = null;
|
|
19721
|
-
if (
|
|
19879
|
+
if (existsSync29(videosJsonPath)) {
|
|
19722
19880
|
try {
|
|
19723
19881
|
planVideos = validatePlanVideosForChunk(JSON.parse(readFileSync13(videosJsonPath, "utf-8")));
|
|
19724
19882
|
} catch (err) {
|
|
@@ -19749,7 +19907,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
19749
19907
|
const range = resolveChunkFrameRange(slice, chunkIndex, options?.frameRange);
|
|
19750
19908
|
const framesInChunk = range.endFrame - range.startFrame;
|
|
19751
19909
|
const compiledDir = join44(planDir, "compiled");
|
|
19752
|
-
if (!
|
|
19910
|
+
if (!existsSync29(compiledDir)) {
|
|
19753
19911
|
throw new RenderChunkValidationError(
|
|
19754
19912
|
MISSING_PLAN_ARTIFACT,
|
|
19755
19913
|
`[renderChunk] planDir missing compiled/ directory: ${compiledDir}`
|
|
@@ -19999,10 +20157,10 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
19999
20157
|
const effectiveBitrate = encoder.crf != null ? void 0 : encoder.bitrate;
|
|
20000
20158
|
const videoOnlyPath = encoder.chunksCarryAudio ? `${outputChunkPath}.video.mp4` : outputChunkPath;
|
|
20001
20159
|
if (isPngSequence) {
|
|
20002
|
-
if (!
|
|
20160
|
+
if (!existsSync29(outputChunkPath)) mkdirSync19(outputChunkPath, { recursive: true });
|
|
20003
20161
|
} else {
|
|
20004
20162
|
const outDir = join44(outputChunkPath, "..");
|
|
20005
|
-
if (!
|
|
20163
|
+
if (!existsSync29(outDir)) mkdirSync19(outDir, { recursive: true });
|
|
20006
20164
|
}
|
|
20007
20165
|
const encodeInput = {
|
|
20008
20166
|
job,
|
|
@@ -20509,6 +20667,20 @@ function validateDistributedRenderConfig(config) {
|
|
|
20509
20667
|
);
|
|
20510
20668
|
}
|
|
20511
20669
|
}
|
|
20670
|
+
if (config.minParallelChunks !== void 0) {
|
|
20671
|
+
if (!Number.isInteger(config.minParallelChunks) || config.minParallelChunks < 1) {
|
|
20672
|
+
throw new InvalidConfigError(
|
|
20673
|
+
"config.minParallelChunks",
|
|
20674
|
+
`must be a positive integer; got ${config.minParallelChunks}`
|
|
20675
|
+
);
|
|
20676
|
+
}
|
|
20677
|
+
if (config.minParallelChunks > MAX_PARALLEL_CHUNKS_CEILING) {
|
|
20678
|
+
throw new InvalidConfigError(
|
|
20679
|
+
"config.minParallelChunks",
|
|
20680
|
+
`must be <= ${MAX_PARALLEL_CHUNKS_CEILING}; got ${config.minParallelChunks}`
|
|
20681
|
+
);
|
|
20682
|
+
}
|
|
20683
|
+
}
|
|
20512
20684
|
for (const field of ["workerTimeoutSeconds", "workerConcurrency"]) {
|
|
20513
20685
|
const value = config[field];
|
|
20514
20686
|
if (value !== void 0 && (!Number.isInteger(value) || value < 1)) {
|
|
@@ -20678,6 +20850,7 @@ function assertDistributedOptions(value) {
|
|
|
20678
20850
|
"chunkSize",
|
|
20679
20851
|
"maxParallelChunks",
|
|
20680
20852
|
"targetChunkFrames",
|
|
20853
|
+
"minParallelChunks",
|
|
20681
20854
|
"planDirSizeLimitBytes"
|
|
20682
20855
|
]) {
|
|
20683
20856
|
assertOptionalInteger(value, field, 1);
|
|
@@ -20817,6 +20990,7 @@ function distributedConfigFromRequest(request, runtime = {}) {
|
|
|
20817
20990
|
chunkSize: distributed.chunkSize,
|
|
20818
20991
|
maxParallelChunks: distributed.maxParallelChunks,
|
|
20819
20992
|
targetChunkFrames: distributed.targetChunkFrames,
|
|
20993
|
+
minParallelChunks: distributed.minParallelChunks,
|
|
20820
20994
|
runtimeCap: distributed.runtimeCap,
|
|
20821
20995
|
rejectOnSystemFonts: distributed.rejectOnSystemFonts,
|
|
20822
20996
|
failClosedFontFetch: distributed.failClosedFontFetch,
|
|
@@ -20844,6 +21018,7 @@ function renderRequestFromDistributedConfig(input) {
|
|
|
20844
21018
|
...optionalProperty("chunkSize", config.chunkSize),
|
|
20845
21019
|
...optionalProperty("maxParallelChunks", config.maxParallelChunks),
|
|
20846
21020
|
...optionalProperty("targetChunkFrames", config.targetChunkFrames),
|
|
21021
|
+
...optionalProperty("minParallelChunks", config.minParallelChunks),
|
|
20847
21022
|
...optionalProperty("runtimeCap", config.runtimeCap),
|
|
20848
21023
|
...optionalProperty("rejectOnSystemFonts", config.rejectOnSystemFonts),
|
|
20849
21024
|
...optionalProperty("failClosedFontFetch", config.failClosedFontFetch),
|
|
@@ -20902,7 +21077,7 @@ import {
|
|
|
20902
21077
|
|
|
20903
21078
|
// src/server.ts
|
|
20904
21079
|
import {
|
|
20905
|
-
existsSync as
|
|
21080
|
+
existsSync as existsSync32,
|
|
20906
21081
|
mkdirSync as mkdirSync21,
|
|
20907
21082
|
statSync as statSync13,
|
|
20908
21083
|
mkdtempSync as mkdtempSync8,
|
|
@@ -20925,7 +21100,7 @@ import {
|
|
|
20925
21100
|
|
|
20926
21101
|
// src/services/hyperframeLint.ts
|
|
20927
21102
|
import {
|
|
20928
|
-
existsSync as
|
|
21103
|
+
existsSync as existsSync30,
|
|
20929
21104
|
readFileSync as readFileSync15,
|
|
20930
21105
|
statSync as statSync12,
|
|
20931
21106
|
openSync as openSync7,
|
|
@@ -20933,7 +21108,7 @@ import {
|
|
|
20933
21108
|
closeSync as closeSync7,
|
|
20934
21109
|
constants as constants3
|
|
20935
21110
|
} from "node:fs";
|
|
20936
|
-
import { resolve as resolve11, join as join46, relative as relative7, isAbsolute as isAbsolute5, sep as
|
|
21111
|
+
import { resolve as resolve11, join as join46, relative as relative7, isAbsolute as isAbsolute5, sep as sep5 } from "node:path";
|
|
20937
21112
|
import { lintHyperframeHtml } from "@hyperframes/lint";
|
|
20938
21113
|
function isStringRecord(value) {
|
|
20939
21114
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
@@ -20979,7 +21154,7 @@ function readEntryHtml(filePath) {
|
|
|
20979
21154
|
}
|
|
20980
21155
|
function readProjectEntryFile(projectDir, preferredEntryFile) {
|
|
20981
21156
|
const absProjectDir = resolve11(projectDir);
|
|
20982
|
-
if (!
|
|
21157
|
+
if (!existsSync30(absProjectDir) || !statSync12(absProjectDir).isDirectory()) {
|
|
20983
21158
|
return { error: `Project directory not found: ${absProjectDir}` };
|
|
20984
21159
|
}
|
|
20985
21160
|
const entryCandidates = [preferredEntryFile, "index.html", "src/index.html"].filter(
|
|
@@ -20988,7 +21163,7 @@ function readProjectEntryFile(projectDir, preferredEntryFile) {
|
|
|
20988
21163
|
for (const entryFile of entryCandidates) {
|
|
20989
21164
|
const absoluteEntryPath = resolve11(absProjectDir, entryFile);
|
|
20990
21165
|
const entryRelativePath = relative7(absProjectDir, absoluteEntryPath);
|
|
20991
|
-
if (entryRelativePath === ".." || entryRelativePath.startsWith(`..${
|
|
21166
|
+
if (entryRelativePath === ".." || entryRelativePath.startsWith(`..${sep5}`) || isAbsolute5(entryRelativePath)) {
|
|
20992
21167
|
return { error: `Entry file must stay inside project directory: ${entryFile}` };
|
|
20993
21168
|
}
|
|
20994
21169
|
const html = readEntryHtml(absoluteEntryPath);
|
|
@@ -21048,7 +21223,7 @@ async function runHyperframeLint(prepared) {
|
|
|
21048
21223
|
import { Worker as Worker2 } from "node:worker_threads";
|
|
21049
21224
|
import { fileURLToPath as fileURLToPath6 } from "node:url";
|
|
21050
21225
|
import { dirname as dirname18, join as join47 } from "node:path";
|
|
21051
|
-
import { existsSync as
|
|
21226
|
+
import { existsSync as existsSync31 } from "node:fs";
|
|
21052
21227
|
var DEFAULT_HEALTH_PORT = 9848;
|
|
21053
21228
|
async function startHealthWorker(options = {}) {
|
|
21054
21229
|
const log = options.logger ?? defaultLogger2();
|
|
@@ -21125,7 +21300,7 @@ function resolveWorkerEntry2() {
|
|
|
21125
21300
|
const here = dirname18(fileURLToPath6(import.meta.url));
|
|
21126
21301
|
const candidates = [join47(here, "healthWorkerThread.js"), join47(here, "healthWorkerThread.ts")];
|
|
21127
21302
|
for (const candidate of candidates) {
|
|
21128
|
-
if (
|
|
21303
|
+
if (existsSync31(candidate)) return candidate;
|
|
21129
21304
|
}
|
|
21130
21305
|
return void 0;
|
|
21131
21306
|
}
|
|
@@ -21271,7 +21446,7 @@ function resolvePreparedRenderOutput(prepared, rendersDir, log) {
|
|
|
21271
21446
|
const { input, cleanupProjectDir } = prepared;
|
|
21272
21447
|
const absoluteOutputPath = resolveOutputPath(input.projectDir, input.outputPath, rendersDir, log);
|
|
21273
21448
|
const outputDir = dirname19(absoluteOutputPath);
|
|
21274
|
-
if (!
|
|
21449
|
+
if (!existsSync32(outputDir)) mkdirSync21(outputDir, { recursive: true });
|
|
21275
21450
|
return { input, cleanupProjectDir, absoluteOutputPath };
|
|
21276
21451
|
}
|
|
21277
21452
|
function validateRenderOverrides(body) {
|
|
@@ -21312,11 +21487,11 @@ function prepareProjectDirectory(projectDir, options) {
|
|
|
21312
21487
|
const candidate = nonEmptyString(projectDir);
|
|
21313
21488
|
if (!candidate) return null;
|
|
21314
21489
|
const absProjectDir = resolve12(candidate);
|
|
21315
|
-
if (!
|
|
21490
|
+
if (!existsSync32(absProjectDir) || !statSync13(absProjectDir).isDirectory()) {
|
|
21316
21491
|
return { error: `Project directory not found: ${absProjectDir}` };
|
|
21317
21492
|
}
|
|
21318
21493
|
const entry = options.entryFile || "index.html";
|
|
21319
|
-
if (!
|
|
21494
|
+
if (!existsSync32(resolve12(absProjectDir, entry))) {
|
|
21320
21495
|
return { error: `Entry file "${entry}" not found in project directory: ${absProjectDir}` };
|
|
21321
21496
|
}
|
|
21322
21497
|
return { prepared: { input: { projectDir: absProjectDir, ...options } } };
|
|
@@ -21408,7 +21583,7 @@ function cleanupTempDir(dir, log) {
|
|
|
21408
21583
|
}
|
|
21409
21584
|
}
|
|
21410
21585
|
function outputFileSize(path) {
|
|
21411
|
-
return
|
|
21586
|
+
return existsSync32(path) ? statSync13(path).size : 0;
|
|
21412
21587
|
}
|
|
21413
21588
|
function createBlockingProgressReporter(log, requestId) {
|
|
21414
21589
|
let lastLoggedPct = -10;
|
|
@@ -21712,7 +21887,7 @@ function createRenderHandlers(options = {}) {
|
|
|
21712
21887
|
if (!artifact) {
|
|
21713
21888
|
return c.json({ success: false, error: "Output artifact not found or expired" }, 404);
|
|
21714
21889
|
}
|
|
21715
|
-
if (!
|
|
21890
|
+
if (!existsSync32(artifact.path)) {
|
|
21716
21891
|
store.delete(token);
|
|
21717
21892
|
return c.json({ success: false, error: "Output artifact file missing" }, 404);
|
|
21718
21893
|
}
|
|
@@ -21818,7 +21993,7 @@ import {
|
|
|
21818
21993
|
} from "@hyperframes/engine";
|
|
21819
21994
|
import {
|
|
21820
21995
|
cpSync as cpSync3,
|
|
21821
|
-
existsSync as
|
|
21996
|
+
existsSync as existsSync33,
|
|
21822
21997
|
mkdirSync as mkdirSync22,
|
|
21823
21998
|
mkdtempSync as mkdtempSync9,
|
|
21824
21999
|
readFileSync as readFileSync16,
|
|
@@ -21873,7 +22048,7 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
|
|
|
21873
22048
|
}
|
|
21874
22049
|
const format = plan2.dimensions.format;
|
|
21875
22050
|
const cfrEncoder = cfr ? readCfrEncoder(planDir, format) : null;
|
|
21876
|
-
if (!
|
|
22051
|
+
if (!existsSync33(dirname20(outputPath))) {
|
|
21877
22052
|
mkdirSync22(dirname20(outputPath), { recursive: true });
|
|
21878
22053
|
}
|
|
21879
22054
|
const workDir = mkdtempSync9(`${outputPath}.assemble-work-`);
|
|
@@ -21897,7 +22072,7 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
|
|
|
21897
22072
|
try {
|
|
21898
22073
|
const videoSources = cfrEncoder ? [await reencodeCfr(ctx, chunkPaths, cfrEncoder)] : chunkPaths;
|
|
21899
22074
|
const finalPath = join49(workDir, `output.${format}`);
|
|
21900
|
-
const audio = audioPath !== null &&
|
|
22075
|
+
const audio = audioPath !== null && existsSync33(audioPath) ? audioPath : null;
|
|
21901
22076
|
await writeJoined(ctx, videoSources, audio, finalPath);
|
|
21902
22077
|
renameWithRetry11(finalPath, outputPath);
|
|
21903
22078
|
log.info("[assemble] stage timings", {
|
|
@@ -21915,7 +22090,7 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
|
|
|
21915
22090
|
});
|
|
21916
22091
|
}
|
|
21917
22092
|
}
|
|
21918
|
-
const fileSize =
|
|
22093
|
+
const fileSize = existsSync33(outputPath) ? statSync14(outputPath).size : 0;
|
|
21919
22094
|
return {
|
|
21920
22095
|
...referenceBackgroundResult8(plan2[REFERENCE_BACKGROUND_FIELD6]),
|
|
21921
22096
|
outputPath,
|
|
@@ -21927,13 +22102,13 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
|
|
|
21927
22102
|
function readAssemblePlan(planDir, chunkPaths) {
|
|
21928
22103
|
const planJsonPath = join49(planDir, "plan.json");
|
|
21929
22104
|
const chunksJsonPath = join49(planDir, "meta", "chunks.json");
|
|
21930
|
-
if (!
|
|
22105
|
+
if (!existsSync33(planJsonPath)) {
|
|
21931
22106
|
throw new Error(`[assemble] planDir missing plan.json: ${planJsonPath}`);
|
|
21932
22107
|
}
|
|
21933
22108
|
const plan2 = JSON.parse(readFileSync16(planJsonPath, "utf-8"));
|
|
21934
22109
|
readPlanProtocolV1(plan2, DISTRIBUTED_RENDER_CAPABILITIES.roles.assembler);
|
|
21935
22110
|
validatePlanV2MaterializedTarget(planDir, { role: "assembler" });
|
|
21936
|
-
if (!
|
|
22111
|
+
if (!existsSync33(chunksJsonPath)) {
|
|
21937
22112
|
throw new Error(`[assemble] planDir missing meta/chunks.json: ${chunksJsonPath}`);
|
|
21938
22113
|
}
|
|
21939
22114
|
const chunks = JSON.parse(readFileSync16(chunksJsonPath, "utf-8"));
|
|
@@ -21948,7 +22123,7 @@ function readAssemblePlan(planDir, chunkPaths) {
|
|
|
21948
22123
|
}
|
|
21949
22124
|
}
|
|
21950
22125
|
for (const path of chunkPaths.flat()) {
|
|
21951
|
-
if (!
|
|
22126
|
+
if (!existsSync33(path)) {
|
|
21952
22127
|
throw new Error(`[assemble] chunk path does not exist: ${path}`);
|
|
21953
22128
|
}
|
|
21954
22129
|
}
|
|
@@ -22087,7 +22262,7 @@ function planFormat(planDir) {
|
|
|
22087
22262
|
}
|
|
22088
22263
|
function lockedAudio(planDir) {
|
|
22089
22264
|
const encoderJsonPath = join49(planDir, "meta", "encoder.json");
|
|
22090
|
-
if (!
|
|
22265
|
+
if (!existsSync33(encoderJsonPath)) return {};
|
|
22091
22266
|
return JSON.parse(readFileSync16(encoderJsonPath, "utf-8"));
|
|
22092
22267
|
}
|
|
22093
22268
|
function readCfrEncoder(planDir, format) {
|
|
@@ -22097,7 +22272,7 @@ function readCfrEncoder(planDir, format) {
|
|
|
22097
22272
|
);
|
|
22098
22273
|
}
|
|
22099
22274
|
const encoderJsonPath = join49(planDir, "meta", "encoder.json");
|
|
22100
|
-
if (!
|
|
22275
|
+
if (!existsSync33(encoderJsonPath)) {
|
|
22101
22276
|
throw new Error(`[assemble] planDir missing meta/encoder.json: ${encoderJsonPath}`);
|
|
22102
22277
|
}
|
|
22103
22278
|
const encoderJson = JSON.parse(readFileSync16(encoderJsonPath, "utf-8"));
|
|
@@ -22109,7 +22284,7 @@ function readCfrEncoder(planDir, format) {
|
|
|
22109
22284
|
return encoderJson;
|
|
22110
22285
|
}
|
|
22111
22286
|
function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, startTimeMs) {
|
|
22112
|
-
if (
|
|
22287
|
+
if (existsSync33(outputPath)) rmSync20(outputPath, { recursive: true, force: true });
|
|
22113
22288
|
mkdirSync22(outputPath, { recursive: true });
|
|
22114
22289
|
let globalIdx = 0;
|
|
22115
22290
|
for (const chunkDir of chunkPaths) {
|
|
@@ -22133,7 +22308,7 @@ function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, start
|
|
|
22133
22308
|
`[assemble] png-sequence frame count mismatch: merged ${globalIdx} frames vs plan.totalFrames=${totalFrames}. Using on-disk count.`
|
|
22134
22309
|
);
|
|
22135
22310
|
}
|
|
22136
|
-
if (audioPath !== null &&
|
|
22311
|
+
if (audioPath !== null && existsSync33(audioPath)) {
|
|
22137
22312
|
const sidecar = join49(outputPath, MIXED_AUDIO_FILENAME4);
|
|
22138
22313
|
cpSync3(audioPath, sidecar);
|
|
22139
22314
|
}
|