@hyperframes/producer 0.9.16 → 0.9.17
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 +366 -296
- package/dist/hyperframe.manifest.json +1 -1
- package/dist/hyperframe.runtime.iife.js +62 -62
- package/dist/index.js +926 -549
- package/dist/public-server.js +677 -265
- package/dist/services/distributed/chunkFrames.d.ts +1 -2
- package/dist/services/render/deferredVideoFrames.d.ts +42 -0
- package/dist/services/render/observability.d.ts +2 -0
- package/dist/services/render/segmentVideoFrames.d.ts +41 -0
- package/dist/services/render/stages/captureSegmentedStage.d.ts +5 -2
- package/dist/services/render/stages/captureStreamingStage.d.ts +7 -0
- package/dist/services/render/videoFrameWindow.d.ts +12 -0
- package/package.json +7 -7
package/dist/index.js
CHANGED
|
@@ -10,10 +10,10 @@ import {
|
|
|
10
10
|
import { audioFailureDiagnostics as audioFailureDiagnostics2 } from "@hyperframes/engine";
|
|
11
11
|
import {
|
|
12
12
|
existsSync as existsSync20,
|
|
13
|
-
mkdirSync as
|
|
14
|
-
readdirSync as
|
|
15
|
-
rmSync as
|
|
16
|
-
statSync as
|
|
13
|
+
mkdirSync as mkdirSync12,
|
|
14
|
+
readdirSync as readdirSync6,
|
|
15
|
+
rmSync as rmSync12,
|
|
16
|
+
statSync as statSync9,
|
|
17
17
|
writeFileSync as writeFileSync7,
|
|
18
18
|
copyFileSync as copyFileSync5,
|
|
19
19
|
appendFileSync
|
|
@@ -36,7 +36,7 @@ import {
|
|
|
36
36
|
closeCaptureSession as closeCaptureSession8,
|
|
37
37
|
createCaptureSession as createCaptureSession8,
|
|
38
38
|
initializeSession as initializeSession8,
|
|
39
|
-
createVideoFrameInjector,
|
|
39
|
+
createVideoFrameInjector as createVideoFrameInjector2,
|
|
40
40
|
getEncoderPreset as getEncoderPreset3,
|
|
41
41
|
distributeFrames as distributeFrames2,
|
|
42
42
|
distributeFramesInterleaved as distributeFramesInterleaved2,
|
|
@@ -67,7 +67,7 @@ import {
|
|
|
67
67
|
referenceBackgroundResult,
|
|
68
68
|
createReferenceBackgroundRecorder
|
|
69
69
|
} from "@hyperframes/engine";
|
|
70
|
-
import { join as
|
|
70
|
+
import { join as join31, dirname as dirname13, resolve as resolve8 } from "path";
|
|
71
71
|
import { totalmem as totalmem2 } from "node:os";
|
|
72
72
|
import { randomUUID } from "crypto";
|
|
73
73
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
@@ -2016,6 +2016,7 @@ import {
|
|
|
2016
2016
|
getCapturePerfSummary as getCapturePerfSummary2,
|
|
2017
2017
|
initializeSession as initializeSession2,
|
|
2018
2018
|
prepareCaptureSessionForReuse as prepareCaptureSessionForReuse2,
|
|
2019
|
+
resolveDeVerifyMinDb as resolveDeVerifyMinDb2,
|
|
2019
2020
|
shouldDisableBrowserPoolForParallelWorker,
|
|
2020
2021
|
spawnStreamingEncoder as spawnStreamingEncoder2
|
|
2021
2022
|
} from "@hyperframes/engine";
|
|
@@ -3886,42 +3887,47 @@ function createDrainFrameGuard(args) {
|
|
|
3886
3887
|
sizes.push(buf.length);
|
|
3887
3888
|
if (sizes.length > 60) sizes.shift();
|
|
3888
3889
|
}
|
|
3889
|
-
|
|
3890
|
-
if (truth) {
|
|
3891
|
-
let db;
|
|
3892
|
-
try {
|
|
3893
|
-
db = await psnrDb(buf, truth);
|
|
3894
|
-
} catch (err) {
|
|
3895
|
-
log.warn("[Render] drawElement self-verify sample skipped (psnr infrastructure)", {
|
|
3896
|
-
frame: idx,
|
|
3897
|
-
error: err instanceof Error ? err.message : String(err)
|
|
3898
|
-
});
|
|
3899
|
-
return buf;
|
|
3900
|
-
}
|
|
3901
|
-
if (db < verifyMinDb) {
|
|
3902
|
-
const dumpDir = await mkdtemp(join9(tmpdir(), "hf-de-verify-fail-")).catch(() => null);
|
|
3903
|
-
if (dumpDir) {
|
|
3904
|
-
await Promise.all([
|
|
3905
|
-
writeFile(join9(dumpDir, `frame-${idx}-de.jpg`), buf),
|
|
3906
|
-
writeFile(join9(dumpDir, `frame-${idx}-truth.jpg`), truth)
|
|
3907
|
-
]).catch(() => {
|
|
3908
|
-
});
|
|
3909
|
-
}
|
|
3910
|
-
throw new DrawElementVerificationError(
|
|
3911
|
-
`drawElement self-verify failed at frame ${idx}: ${db.toFixed(1)}dB < ${verifyMinDb}dB vs pre-injection screenshot${dumpDir ? ` (pair: ${dumpDir})` : ""}`,
|
|
3912
|
-
{ kind: "psnr", frameIndex: idx, failedDb: db, verifyThresholdDb: verifyMinDb }
|
|
3913
|
-
);
|
|
3914
|
-
}
|
|
3915
|
-
stats.verifyChecked += 1;
|
|
3916
|
-
stats.verifyMinDb = stats.verifyMinDb === void 0 ? db : Math.min(stats.verifyMinDb, db);
|
|
3917
|
-
log.info("[Render] drawElement self-verify passed", {
|
|
3918
|
-
frame: idx,
|
|
3919
|
-
psnrDb: db === Infinity ? "inf" : Number(db.toFixed(1))
|
|
3920
|
-
});
|
|
3921
|
-
}
|
|
3890
|
+
await verifyDrawElementSample(session, idx, buf, { log, stats, verifyMinDb });
|
|
3922
3891
|
return buf;
|
|
3923
3892
|
};
|
|
3924
3893
|
}
|
|
3894
|
+
async function verifyDrawElementSample(session, idx, buf, ctx) {
|
|
3895
|
+
const { log, stats, verifyMinDb } = ctx;
|
|
3896
|
+
const truth = session.deVerifyFrames?.get(idx);
|
|
3897
|
+
if (!truth) return;
|
|
3898
|
+
let db;
|
|
3899
|
+
try {
|
|
3900
|
+
db = await psnrDb(buf, truth);
|
|
3901
|
+
} catch (err) {
|
|
3902
|
+
log.warn("[Render] drawElement self-verify sample skipped (psnr infrastructure)", {
|
|
3903
|
+
frame: idx,
|
|
3904
|
+
error: err instanceof Error ? err.message : String(err)
|
|
3905
|
+
});
|
|
3906
|
+
return;
|
|
3907
|
+
}
|
|
3908
|
+
if (db < verifyMinDb) {
|
|
3909
|
+
const dumpDir = await mkdtemp(join9(tmpdir(), "hf-de-verify-fail-")).catch(() => null);
|
|
3910
|
+
if (dumpDir) {
|
|
3911
|
+
await Promise.all([
|
|
3912
|
+
writeFile(join9(dumpDir, `frame-${idx}-de.jpg`), buf),
|
|
3913
|
+
writeFile(join9(dumpDir, `frame-${idx}-truth.jpg`), truth)
|
|
3914
|
+
]).catch(() => {
|
|
3915
|
+
});
|
|
3916
|
+
}
|
|
3917
|
+
throw new DrawElementVerificationError(
|
|
3918
|
+
`drawElement self-verify failed at frame ${idx}: ${db.toFixed(1)}dB < ${verifyMinDb}dB vs pre-injection screenshot${dumpDir ? ` (pair: ${dumpDir})` : ""}`,
|
|
3919
|
+
{ kind: "psnr", frameIndex: idx, failedDb: db, verifyThresholdDb: verifyMinDb }
|
|
3920
|
+
);
|
|
3921
|
+
}
|
|
3922
|
+
if (stats) {
|
|
3923
|
+
stats.verifyChecked += 1;
|
|
3924
|
+
stats.verifyMinDb = stats.verifyMinDb === void 0 ? db : Math.min(stats.verifyMinDb, db);
|
|
3925
|
+
}
|
|
3926
|
+
log.info("[Render] drawElement self-verify passed", {
|
|
3927
|
+
frame: idx,
|
|
3928
|
+
psnrDb: db === Infinity ? "inf" : Number(db.toFixed(1))
|
|
3929
|
+
});
|
|
3930
|
+
}
|
|
3925
3931
|
async function runWorkerEncodePipelineLoop(session, totalFrames, job, currentEncoder, reorderBuffer, assertNotAborted, onProgress, log, stats, abortSignal, resume, risk) {
|
|
3926
3932
|
let prev = null;
|
|
3927
3933
|
const frameTime = (i) => i * job.config.fps.den / job.config.fps.num;
|
|
@@ -4385,6 +4391,7 @@ async function runCaptureStreamingStage(input) {
|
|
|
4385
4391
|
);
|
|
4386
4392
|
} else {
|
|
4387
4393
|
const stallTimeoutMs = resolveCaptureStallTimeoutMs();
|
|
4394
|
+
const verifyMinDb = resolveDeVerifyMinDb();
|
|
4388
4395
|
let lastProgressAt = Date.now();
|
|
4389
4396
|
for (let i = resume.from; i < totalFrames; i++) {
|
|
4390
4397
|
assertNotAborted();
|
|
@@ -4401,6 +4408,7 @@ async function runCaptureStreamingStage(input) {
|
|
|
4401
4408
|
{ session, frameIndex: i, totalFrames, stallTimeoutMs: budget },
|
|
4402
4409
|
abortSignal
|
|
4403
4410
|
);
|
|
4411
|
+
await verifyDrawElementSample(session, i, buffer, { log, verifyMinDb });
|
|
4404
4412
|
await reorderBuffer.waitForFrame(i);
|
|
4405
4413
|
ensureFrameWritten(await currentEncoder.writeFrame(buffer), i, currentEncoder);
|
|
4406
4414
|
reorderBuffer.advanceTo(i + 1);
|
|
@@ -4509,6 +4517,10 @@ async function captureSegmentFrames(ctx, segment, encoder) {
|
|
|
4509
4517
|
},
|
|
4510
4518
|
ctx.abortSignal
|
|
4511
4519
|
);
|
|
4520
|
+
await verifyDrawElementSample(ctx.session, i, buffer, {
|
|
4521
|
+
log: ctx.log,
|
|
4522
|
+
verifyMinDb: ctx.verifyMinDb
|
|
4523
|
+
});
|
|
4512
4524
|
ensureFrameWritten(await encoder.writeFrame(buffer), i, encoder);
|
|
4513
4525
|
ctx.job.framesRendered = i + 1;
|
|
4514
4526
|
lastProgressAt = Date.now();
|
|
@@ -4523,6 +4535,25 @@ async function captureSegmentFrames(ctx, segment, encoder) {
|
|
|
4523
4535
|
}
|
|
4524
4536
|
}
|
|
4525
4537
|
async function captureSegment(ctx, segment) {
|
|
4538
|
+
const { frames } = ctx;
|
|
4539
|
+
if (!frames) return captureSegmentToEncoder(ctx, segment);
|
|
4540
|
+
try {
|
|
4541
|
+
reportFrameProgress(
|
|
4542
|
+
ctx.job,
|
|
4543
|
+
`Reading video for segment ${segment.index + 1}/${ctx.segmentCount}`,
|
|
4544
|
+
Math.round(25 + segment.startFrame / ctx.totalFrames * 55),
|
|
4545
|
+
ctx.onProgress,
|
|
4546
|
+
segment.startFrame,
|
|
4547
|
+
ctx.totalFrames
|
|
4548
|
+
);
|
|
4549
|
+
await frames.prepare(segment);
|
|
4550
|
+
ctx.assertNotAborted();
|
|
4551
|
+
return await captureSegmentToEncoder(ctx, segment);
|
|
4552
|
+
} finally {
|
|
4553
|
+
frames.release();
|
|
4554
|
+
}
|
|
4555
|
+
}
|
|
4556
|
+
async function captureSegmentToEncoder(ctx, segment) {
|
|
4526
4557
|
const segmentPath = segmentOutputPath(ctx.segmentDir, segment.index);
|
|
4527
4558
|
let encoder;
|
|
4528
4559
|
try {
|
|
@@ -4575,16 +4606,16 @@ var DEFAULT_DEPS = {
|
|
|
4575
4606
|
function defaultSessionFactory(input) {
|
|
4576
4607
|
let probeSession = input.probeSession;
|
|
4577
4608
|
return {
|
|
4578
|
-
create: async () => {
|
|
4609
|
+
create: async (injector = input.createRenderVideoFrameInjector) => {
|
|
4579
4610
|
const probeCannotCaptureEveryFrame = Boolean(probeSession?.deRiskFrames?.size);
|
|
4580
4611
|
if (probeSession && probeCannotCaptureEveryFrame) {
|
|
4581
4612
|
await closeCaptureSession2(probeSession).catch(() => {
|
|
4582
4613
|
});
|
|
4583
4614
|
probeSession = null;
|
|
4584
4615
|
}
|
|
4585
|
-
const session = probeSession ?? await openSegmentedSession(input);
|
|
4616
|
+
const session = probeSession ?? await openSegmentedSession(input, injector);
|
|
4586
4617
|
if (probeSession) {
|
|
4587
|
-
openSegmentedSessionReuse(input, probeSession);
|
|
4618
|
+
openSegmentedSessionReuse(input, probeSession, injector);
|
|
4588
4619
|
probeSession = null;
|
|
4589
4620
|
}
|
|
4590
4621
|
try {
|
|
@@ -4599,12 +4630,12 @@ function defaultSessionFactory(input) {
|
|
|
4599
4630
|
}
|
|
4600
4631
|
};
|
|
4601
4632
|
}
|
|
4602
|
-
function openSegmentedSessionReuse(input, session) {
|
|
4603
|
-
prepareCaptureSessionForReuse2(session, input.framesDir,
|
|
4633
|
+
function openSegmentedSessionReuse(input, session, injector) {
|
|
4634
|
+
prepareCaptureSessionForReuse2(session, input.framesDir, injector());
|
|
4604
4635
|
}
|
|
4605
|
-
async function openSegmentedSession(input) {
|
|
4636
|
+
async function openSegmentedSession(input, injector) {
|
|
4606
4637
|
const captureCfg = input.cfg.forceScreenshot === input.plan.forceScreenshot ? input.cfg : { ...input.cfg, forceScreenshot: input.plan.forceScreenshot };
|
|
4607
|
-
const videoInjector =
|
|
4638
|
+
const videoInjector = injector();
|
|
4608
4639
|
const captureOptions = { ...input.buildCaptureOptions(), deFrameFallback: false };
|
|
4609
4640
|
const ownBrowser = shouldDisableBrowserPoolForParallelWorker({
|
|
4610
4641
|
parallel: (input.workerCount ?? 1) > 1
|
|
@@ -4617,6 +4648,19 @@ async function openSegmentedSession(input) {
|
|
|
4617
4648
|
ownBrowser ? { ...captureCfg, enableBrowserPool: false } : captureCfg
|
|
4618
4649
|
);
|
|
4619
4650
|
}
|
|
4651
|
+
function removeJoinedSegments(run, segmentPaths) {
|
|
4652
|
+
if (!run.input.segmentVideoFrames || run.input.job.config.keepSegments === true) return;
|
|
4653
|
+
for (const path of segmentPaths) {
|
|
4654
|
+
try {
|
|
4655
|
+
run.deps.removeFile(path);
|
|
4656
|
+
} catch (err) {
|
|
4657
|
+
run.input.log.warn("[Render] could not remove a joined segment file", {
|
|
4658
|
+
path,
|
|
4659
|
+
error: err instanceof Error ? err.message : String(err)
|
|
4660
|
+
});
|
|
4661
|
+
}
|
|
4662
|
+
}
|
|
4663
|
+
}
|
|
4620
4664
|
async function concatSegments(concat, segmentPaths, videoOnlyPath, abortSignal, cfg) {
|
|
4621
4665
|
const result = await concat(segmentPaths, videoOnlyPath, abortSignal, cfg);
|
|
4622
4666
|
if (result.success) return;
|
|
@@ -4628,12 +4672,14 @@ async function concatSegments(concat, segmentPaths, videoOnlyPath, abortSignal,
|
|
|
4628
4672
|
async function createSegmentWorker(run, id) {
|
|
4629
4673
|
const { input, deps } = run;
|
|
4630
4674
|
const factory = input.sessionFactoryForWorker?.(id) ?? run.sessionFactory;
|
|
4675
|
+
const frames = input.segmentVideoFrames?.forWorker();
|
|
4631
4676
|
return {
|
|
4632
4677
|
id,
|
|
4633
4678
|
factory,
|
|
4634
4679
|
sessionSegments: 0,
|
|
4635
4680
|
ctx: {
|
|
4636
|
-
session: await factory.create(),
|
|
4681
|
+
session: await factory.create(frames && (() => frames.hook)),
|
|
4682
|
+
frames,
|
|
4637
4683
|
job: input.job,
|
|
4638
4684
|
cfg: input.cfg,
|
|
4639
4685
|
totalFrames: input.totalFrames,
|
|
@@ -4644,6 +4690,8 @@ async function createSegmentWorker(run, id) {
|
|
|
4644
4690
|
spawnEncoder: deps.spawnEncoder,
|
|
4645
4691
|
captureFrame: deps.captureFrame,
|
|
4646
4692
|
stallTimeoutMs: resolveCaptureStallTimeoutMs(),
|
|
4693
|
+
log: input.log,
|
|
4694
|
+
verifyMinDb: resolveDeVerifyMinDb2(),
|
|
4647
4695
|
abortSignal: input.abortSignal,
|
|
4648
4696
|
assertNotAborted: input.assertNotAborted,
|
|
4649
4697
|
onProgress: input.onProgress,
|
|
@@ -4663,7 +4711,8 @@ async function recycleWorkerSession(run, worker, why) {
|
|
|
4663
4711
|
run.input.dedupPerfs.push(getCapturePerfSummary2(worker.ctx.session));
|
|
4664
4712
|
const memory = worker.ctx.session.chromeMemory?.stats();
|
|
4665
4713
|
await run.deps.closeSession(worker.ctx.session);
|
|
4666
|
-
|
|
4714
|
+
const { frames } = worker.ctx;
|
|
4715
|
+
worker.ctx.session = await worker.factory.create(frames && (() => frames.hook));
|
|
4667
4716
|
worker.sessionSegments = 0;
|
|
4668
4717
|
if (why === "cadence") run.totals.browserRecycles += 1;
|
|
4669
4718
|
run.input.log.info(`[Render] segment browser recycled (${why})`, {
|
|
@@ -4731,6 +4780,7 @@ async function concatResumedRun(run, segmentPaths) {
|
|
|
4731
4780
|
input.abortSignal,
|
|
4732
4781
|
input.cfg
|
|
4733
4782
|
);
|
|
4783
|
+
removeJoinedSegments(run, segmentPaths);
|
|
4734
4784
|
return {
|
|
4735
4785
|
success: true,
|
|
4736
4786
|
encodeMs: 0,
|
|
@@ -4805,6 +4855,7 @@ async function runCaptureSegmentedStage(input) {
|
|
|
4805
4855
|
input.abortSignal,
|
|
4806
4856
|
input.cfg
|
|
4807
4857
|
);
|
|
4858
|
+
removeJoinedSegments(run, segmentPaths);
|
|
4808
4859
|
return {
|
|
4809
4860
|
success: true,
|
|
4810
4861
|
encodeMs: run.totals.encodeMs,
|
|
@@ -5236,10 +5287,273 @@ function checkPageNeverReady(input) {
|
|
|
5236
5287
|
return new PageNeverReadyError(error);
|
|
5237
5288
|
}
|
|
5238
5289
|
|
|
5290
|
+
// src/services/render/deferredVideoFrames.ts
|
|
5291
|
+
import { mkdirSync as mkdirSync4, rmSync as rmSync7 } from "node:fs";
|
|
5292
|
+
import { dirname as dirname5, join as join12 } from "node:path";
|
|
5293
|
+
import {
|
|
5294
|
+
createFrameLookupTable,
|
|
5295
|
+
extractVideoFramesRange,
|
|
5296
|
+
runVideoExtractionWithRetry
|
|
5297
|
+
} from "@hyperframes/engine";
|
|
5298
|
+
function contiguousRuns(sortedIndexes) {
|
|
5299
|
+
const runs2 = [];
|
|
5300
|
+
for (const index of sortedIndexes) {
|
|
5301
|
+
const last = runs2[runs2.length - 1];
|
|
5302
|
+
if (last && last.firstFrame + last.frames === index) last.frames++;
|
|
5303
|
+
else runs2.push({ firstFrame: index, frames: 1 });
|
|
5304
|
+
}
|
|
5305
|
+
return runs2;
|
|
5306
|
+
}
|
|
5307
|
+
function shownDeferredFrameIndexes(source, frames, fps) {
|
|
5308
|
+
const shown = /* @__PURE__ */ new Map();
|
|
5309
|
+
const deferred = source.extracted.filter((video) => video.deferredRange);
|
|
5310
|
+
if (deferred.length === 0) return shown;
|
|
5311
|
+
const table = createFrameLookupTable(
|
|
5312
|
+
source.videos,
|
|
5313
|
+
deferred.map((video) => ({
|
|
5314
|
+
...video,
|
|
5315
|
+
outputDir: "",
|
|
5316
|
+
framePaths: new Map(Array.from({ length: video.totalFrames }, (_, i) => [i, String(i)]))
|
|
5317
|
+
})),
|
|
5318
|
+
resolveRenderFpsConfig(fps).value
|
|
5319
|
+
);
|
|
5320
|
+
for (const frame of frames) {
|
|
5321
|
+
const globalTime = frame * fps.den / fps.num;
|
|
5322
|
+
for (const [videoId, payload] of table.getActiveFramePayloads(globalTime)) {
|
|
5323
|
+
const indexes = shown.get(videoId) ?? /* @__PURE__ */ new Set();
|
|
5324
|
+
indexes.add(payload.frameIndex);
|
|
5325
|
+
shown.set(videoId, indexes);
|
|
5326
|
+
}
|
|
5327
|
+
}
|
|
5328
|
+
return shown;
|
|
5329
|
+
}
|
|
5330
|
+
async function extractDeferredFrames(input) {
|
|
5331
|
+
const deferred = input.extracted.filter((video) => video.deferredRange);
|
|
5332
|
+
if (deferred.length === 0) return [];
|
|
5333
|
+
const shown = shownDeferredFrameIndexes(input, input.frames, input.fps);
|
|
5334
|
+
return Promise.all(
|
|
5335
|
+
deferred.map(async (video) => {
|
|
5336
|
+
const outputDir = join12(input.outputRoot, video.videoId);
|
|
5337
|
+
const indexes = [...shown.get(video.videoId) ?? []].sort((a, b) => a - b);
|
|
5338
|
+
if (indexes.length === 0) {
|
|
5339
|
+
return { ...video, outputDir, framePaths: /* @__PURE__ */ new Map(), ownedByLookup: false };
|
|
5340
|
+
}
|
|
5341
|
+
const range = video.deferredRange;
|
|
5342
|
+
const extracted = await extractVideoFramesRange(
|
|
5343
|
+
input.sourceOf(video),
|
|
5344
|
+
video.videoId,
|
|
5345
|
+
range.startTime,
|
|
5346
|
+
range.durationSeconds,
|
|
5347
|
+
{
|
|
5348
|
+
fps: input.fps,
|
|
5349
|
+
outputDir: input.outputRoot,
|
|
5350
|
+
format: range.format,
|
|
5351
|
+
frameRanges: contiguousRuns(indexes),
|
|
5352
|
+
shownBox: input.shownBoxes?.get(video.videoId)
|
|
5353
|
+
},
|
|
5354
|
+
input.signal,
|
|
5355
|
+
input.cfg
|
|
5356
|
+
);
|
|
5357
|
+
const missing = indexes.find((index) => !extracted.framePaths.has(index));
|
|
5358
|
+
if (missing !== void 0) throw input.onMissingFrame(video, missing);
|
|
5359
|
+
return { ...video, outputDir, framePaths: extracted.framePaths, ownedByLookup: false };
|
|
5360
|
+
})
|
|
5361
|
+
);
|
|
5362
|
+
}
|
|
5363
|
+
async function extractAllDeferredFrames(extracted, input) {
|
|
5364
|
+
const framesTotal = extracted.reduce(
|
|
5365
|
+
(sum2, video) => sum2 + (video.deferredRange ? video.totalFrames : 0),
|
|
5366
|
+
0
|
|
5367
|
+
);
|
|
5368
|
+
let framesDone = 0;
|
|
5369
|
+
for (const video of extracted) {
|
|
5370
|
+
const range = video.deferredRange;
|
|
5371
|
+
if (!range) continue;
|
|
5372
|
+
const { result: filled } = await runVideoExtractionWithRetry(
|
|
5373
|
+
() => extractVideoFramesRange(
|
|
5374
|
+
input.sourceOf?.(video) ?? video.srcPath,
|
|
5375
|
+
video.videoId,
|
|
5376
|
+
range.startTime,
|
|
5377
|
+
range.durationSeconds,
|
|
5378
|
+
{ fps: input.fps, outputDir: dirname5(video.outputDir), format: range.format },
|
|
5379
|
+
input.signal,
|
|
5380
|
+
input.cfg
|
|
5381
|
+
),
|
|
5382
|
+
{
|
|
5383
|
+
signal: input.signal,
|
|
5384
|
+
maxTransientRetries: input.maxTransientRetries,
|
|
5385
|
+
onRetry: () => {
|
|
5386
|
+
rmSync7(video.outputDir, { recursive: true, force: true });
|
|
5387
|
+
mkdirSync4(video.outputDir, { recursive: true });
|
|
5388
|
+
}
|
|
5389
|
+
}
|
|
5390
|
+
);
|
|
5391
|
+
if (filled.framePaths.size < video.totalFrames) {
|
|
5392
|
+
throw new Error(
|
|
5393
|
+
`[Render] video ${JSON.stringify(video.videoId)} has ${filled.framePaths.size} frames; it was expected to have ${video.totalFrames} frames.`
|
|
5394
|
+
);
|
|
5395
|
+
}
|
|
5396
|
+
video.framePaths = filled.framePaths;
|
|
5397
|
+
delete video.deferredRange;
|
|
5398
|
+
framesDone += video.totalFrames;
|
|
5399
|
+
input.onProgress?.(framesDone, framesTotal);
|
|
5400
|
+
}
|
|
5401
|
+
}
|
|
5402
|
+
|
|
5403
|
+
// src/services/render/segmentVideoFrames.ts
|
|
5404
|
+
import { mkdirSync as mkdirSync5, rmSync as rmSync8 } from "node:fs";
|
|
5405
|
+
import { join as join13 } from "node:path";
|
|
5406
|
+
import {
|
|
5407
|
+
createFrameLookupTable as createFrameLookupTable2,
|
|
5408
|
+
createVideoFrameInjector
|
|
5409
|
+
} from "@hyperframes/engine";
|
|
5410
|
+
function segmentFramesDir(rootDir, index) {
|
|
5411
|
+
return join13(rootDir, "segment-frames", `segment_${String(index).padStart(5, "0")}`);
|
|
5412
|
+
}
|
|
5413
|
+
function createSegmentVideoFrames(input) {
|
|
5414
|
+
const extractFrames = input.extractFrames ?? extractDeferredFrames;
|
|
5415
|
+
const sourceOf = input.sourceOf ?? ((video) => video.srcPath);
|
|
5416
|
+
const fullyExtracted = input.extracted.filter((video) => !video.deferredRange);
|
|
5417
|
+
const tableFor = async (frames, dir) => {
|
|
5418
|
+
const sliced = await extractFrames({
|
|
5419
|
+
videos: input.videos,
|
|
5420
|
+
extracted: input.extracted,
|
|
5421
|
+
frames,
|
|
5422
|
+
fps: input.fps,
|
|
5423
|
+
outputRoot: dir,
|
|
5424
|
+
cfg: input.cfg,
|
|
5425
|
+
signal: input.abortSignal,
|
|
5426
|
+
sourceOf,
|
|
5427
|
+
onMissingFrame: (video, index) => new Error(
|
|
5428
|
+
`[Render] video ${JSON.stringify(video.videoId)} has no frame ${index}; it was expected to have ${video.totalFrames} frames.`
|
|
5429
|
+
)
|
|
5430
|
+
});
|
|
5431
|
+
return createFrameLookupTable2(
|
|
5432
|
+
input.videos,
|
|
5433
|
+
[...fullyExtracted, ...sliced],
|
|
5434
|
+
resolveRenderFpsConfig(input.fps).value
|
|
5435
|
+
);
|
|
5436
|
+
};
|
|
5437
|
+
const injectorFor = async (frames, dir) => createVideoFrameInjector(await tableFor(frames, dir), input.injectorOptions);
|
|
5438
|
+
let onDemandCalls = 0;
|
|
5439
|
+
const onDemandHook = () => Object.assign(
|
|
5440
|
+
async (page, time, heldVideoTime) => {
|
|
5441
|
+
const dir = join13(
|
|
5442
|
+
input.rootDir,
|
|
5443
|
+
"segment-frames",
|
|
5444
|
+
`on-demand_${String(onDemandCalls++).padStart(5, "0")}`
|
|
5445
|
+
);
|
|
5446
|
+
try {
|
|
5447
|
+
const frames = [time, heldVideoTime].filter((t) => t !== void 0).map((t) => Math.round(t * input.fps.num / input.fps.den));
|
|
5448
|
+
const injector = await injectorFor(frames, dir);
|
|
5449
|
+
await injector?.(page, time, heldVideoTime);
|
|
5450
|
+
} finally {
|
|
5451
|
+
rmSync8(dir, { recursive: true, force: true });
|
|
5452
|
+
}
|
|
5453
|
+
},
|
|
5454
|
+
{ fresh: onDemandHook }
|
|
5455
|
+
);
|
|
5456
|
+
return {
|
|
5457
|
+
async forFrames(frames, name) {
|
|
5458
|
+
const dir = join13(input.rootDir, "segment-frames", name);
|
|
5459
|
+
const release = () => rmSync8(dir, { recursive: true, force: true });
|
|
5460
|
+
try {
|
|
5461
|
+
release();
|
|
5462
|
+
mkdirSync5(dir, { recursive: true });
|
|
5463
|
+
const table = await tableFor([...frames], dir);
|
|
5464
|
+
return { injector: () => createVideoFrameInjector(table, input.injectorOptions), release };
|
|
5465
|
+
} catch (error) {
|
|
5466
|
+
release();
|
|
5467
|
+
throw error;
|
|
5468
|
+
}
|
|
5469
|
+
},
|
|
5470
|
+
forWorker() {
|
|
5471
|
+
let injector = null;
|
|
5472
|
+
let folder = null;
|
|
5473
|
+
const onDemand = onDemandHook();
|
|
5474
|
+
const release = () => {
|
|
5475
|
+
injector = null;
|
|
5476
|
+
if (folder) rmSync8(folder, { recursive: true, force: true });
|
|
5477
|
+
folder = null;
|
|
5478
|
+
};
|
|
5479
|
+
return {
|
|
5480
|
+
hook: Object.assign(
|
|
5481
|
+
(page, time, heldVideoTime) => (injector ?? onDemand)(page, time, heldVideoTime),
|
|
5482
|
+
{ fresh: onDemandHook }
|
|
5483
|
+
),
|
|
5484
|
+
release,
|
|
5485
|
+
async prepare(segment) {
|
|
5486
|
+
release();
|
|
5487
|
+
const dir = segmentFramesDir(input.rootDir, segment.index);
|
|
5488
|
+
folder = dir;
|
|
5489
|
+
try {
|
|
5490
|
+
rmSync8(dir, { recursive: true, force: true });
|
|
5491
|
+
mkdirSync5(dir, { recursive: true });
|
|
5492
|
+
injector = await injectorFor(
|
|
5493
|
+
Array.from(
|
|
5494
|
+
{ length: segment.endFrame - segment.startFrame },
|
|
5495
|
+
(_, i) => segment.startFrame + i
|
|
5496
|
+
),
|
|
5497
|
+
dir
|
|
5498
|
+
);
|
|
5499
|
+
} catch (error) {
|
|
5500
|
+
release();
|
|
5501
|
+
throw error;
|
|
5502
|
+
}
|
|
5503
|
+
}
|
|
5504
|
+
};
|
|
5505
|
+
}
|
|
5506
|
+
};
|
|
5507
|
+
}
|
|
5508
|
+
|
|
5509
|
+
// src/services/render/videoFrameWindow.ts
|
|
5510
|
+
import { readdirSync as readdirSync3, statSync as statSync4 } from "node:fs";
|
|
5511
|
+
import { join as join14 } from "node:path";
|
|
5512
|
+
function shouldWindowVideoFrames(args) {
|
|
5513
|
+
return args.segmented && args.env.HF_VIDEO_FRAME_WINDOW?.trim().toLowerCase() === "true";
|
|
5514
|
+
}
|
|
5515
|
+
function directoryBytes(path) {
|
|
5516
|
+
let bytes = 0;
|
|
5517
|
+
let entries;
|
|
5518
|
+
try {
|
|
5519
|
+
entries = readdirSync3(path, { withFileTypes: true });
|
|
5520
|
+
} catch {
|
|
5521
|
+
return 0;
|
|
5522
|
+
}
|
|
5523
|
+
for (const entry of entries) {
|
|
5524
|
+
const child3 = join14(path, entry.name);
|
|
5525
|
+
try {
|
|
5526
|
+
bytes += entry.isDirectory() ? directoryBytes(child3) : statSync4(child3).size;
|
|
5527
|
+
} catch {
|
|
5528
|
+
}
|
|
5529
|
+
}
|
|
5530
|
+
return bytes;
|
|
5531
|
+
}
|
|
5532
|
+
function trackSegmentFramePeak(frames, rootDir, onPeak) {
|
|
5533
|
+
let peak = 0;
|
|
5534
|
+
return {
|
|
5535
|
+
forFrames: (frameIndexes, name) => frames.forFrames(frameIndexes, name),
|
|
5536
|
+
forWorker() {
|
|
5537
|
+
const worker = frames.forWorker();
|
|
5538
|
+
return {
|
|
5539
|
+
...worker,
|
|
5540
|
+
async prepare(segment) {
|
|
5541
|
+
await worker.prepare(segment);
|
|
5542
|
+
const held = directoryBytes(join14(rootDir, "segment-frames"));
|
|
5543
|
+
if (held > peak) {
|
|
5544
|
+
peak = held;
|
|
5545
|
+
onPeak(peak);
|
|
5546
|
+
}
|
|
5547
|
+
}
|
|
5548
|
+
};
|
|
5549
|
+
}
|
|
5550
|
+
};
|
|
5551
|
+
}
|
|
5552
|
+
|
|
5239
5553
|
// src/services/render/segmentManifest.ts
|
|
5240
5554
|
import { createHash as createHash2 } from "node:crypto";
|
|
5241
|
-
import { existsSync as existsSync7, mkdirSync as
|
|
5242
|
-
import { join as
|
|
5555
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync6, readFileSync as readFileSync4, statSync as statSync5, writeFileSync as writeFileSync3 } from "node:fs";
|
|
5556
|
+
import { join as join15 } from "node:path";
|
|
5243
5557
|
import { renameWithRetry as renameWithRetry2 } from "@hyperframes/engine";
|
|
5244
5558
|
var MANIFEST_FILENAME = "segments.json";
|
|
5245
5559
|
function computeSegmentPlanHash(input) {
|
|
@@ -5267,7 +5581,7 @@ function computeSegmentPlanHash(input) {
|
|
|
5267
5581
|
return createHash2("sha256").update(ordered.join(" ")).digest("hex").slice(0, 16);
|
|
5268
5582
|
}
|
|
5269
5583
|
function segmentDirFor(projectRendersDir, planHash) {
|
|
5270
|
-
return
|
|
5584
|
+
return join15(projectRendersDir, ".hf-segments", planHash);
|
|
5271
5585
|
}
|
|
5272
5586
|
function hasNumber(value, key) {
|
|
5273
5587
|
return key in value && typeof Reflect.get(value, key) === "number";
|
|
@@ -5285,7 +5599,7 @@ function isManifest(value) {
|
|
|
5285
5599
|
return Reflect.get(value, "version") === 1 && hasString(value, "planHash") && hasNumber(value, "totalFrames") && hasNumber(value, "segmentFrames") && Array.isArray(completed) && completed.every(isEntry);
|
|
5286
5600
|
}
|
|
5287
5601
|
function readSegmentManifest(segmentDir) {
|
|
5288
|
-
const path =
|
|
5602
|
+
const path = join15(segmentDir, MANIFEST_FILENAME);
|
|
5289
5603
|
if (!existsSync7(path)) return null;
|
|
5290
5604
|
try {
|
|
5291
5605
|
const parsed = JSON.parse(readFileSync4(path, "utf-8"));
|
|
@@ -5295,10 +5609,10 @@ function readSegmentManifest(segmentDir) {
|
|
|
5295
5609
|
}
|
|
5296
5610
|
}
|
|
5297
5611
|
function writeSegmentManifest(segmentDir, manifest) {
|
|
5298
|
-
|
|
5299
|
-
const tmp =
|
|
5612
|
+
mkdirSync6(segmentDir, { recursive: true });
|
|
5613
|
+
const tmp = join15(segmentDir, `${MANIFEST_FILENAME}.${process.pid}.tmp`);
|
|
5300
5614
|
writeFileSync3(tmp, JSON.stringify(manifest, null, 2), "utf-8");
|
|
5301
|
-
renameWithRetry2(tmp,
|
|
5615
|
+
renameWithRetry2(tmp, join15(segmentDir, MANIFEST_FILENAME));
|
|
5302
5616
|
}
|
|
5303
5617
|
async function probeSegmentFrameCount(path, probe) {
|
|
5304
5618
|
try {
|
|
@@ -5316,7 +5630,7 @@ async function validateCompletedSegments(manifest, expectedHash, probeFrames) {
|
|
|
5316
5630
|
if (manifest.planHash !== expectedHash) return ok;
|
|
5317
5631
|
for (const entry of manifest.completed) {
|
|
5318
5632
|
if (!existsSync7(entry.path)) continue;
|
|
5319
|
-
const size =
|
|
5633
|
+
const size = statSync5(entry.path).size;
|
|
5320
5634
|
if (size <= 0 || size !== entry.bytes) continue;
|
|
5321
5635
|
const frames = await probeFrames(entry.path);
|
|
5322
5636
|
if (frames !== entry.endFrame - entry.startFrame) continue;
|
|
@@ -5859,7 +6173,7 @@ function computeVideoFrameCoverage(videos, extracted, fps) {
|
|
|
5859
6173
|
for (const video of videos) {
|
|
5860
6174
|
const entry = byId.get(video.id);
|
|
5861
6175
|
const expectedFrames = expectedFramesForVideo(video, entry, fps);
|
|
5862
|
-
const capturedFrames = entry ? entry.framePaths.size : 0;
|
|
6176
|
+
const capturedFrames = entry ? entry.deferredRange ? entry.totalFrames : entry.framePaths.size : 0;
|
|
5863
6177
|
const ratio = expectedFrames === 0 ? 1 : capturedFrames / expectedFrames;
|
|
5864
6178
|
reports.push({
|
|
5865
6179
|
videoId: video.id,
|
|
@@ -5895,7 +6209,7 @@ function countAuthoredTimedClips(html) {
|
|
|
5895
6209
|
|
|
5896
6210
|
// src/services/render/stages/compileStage.ts
|
|
5897
6211
|
import { existsSync as existsSync11, writeFileSync as writeFileSync5 } from "node:fs";
|
|
5898
|
-
import { join as
|
|
6212
|
+
import { join as join19 } from "node:path";
|
|
5899
6213
|
import { suggestMatchingPreset } from "@hyperframes/core";
|
|
5900
6214
|
import { extractStandaloneEntryFromIndex, isTemplateEntry } from "@hyperframes/core/compiler";
|
|
5901
6215
|
import { readTextFile as readTextFile2 } from "@hyperframes/parsers/asset-resolution";
|
|
@@ -5903,8 +6217,8 @@ import { readTextFile as readTextFile2 } from "@hyperframes/parsers/asset-resolu
|
|
|
5903
6217
|
// src/services/htmlCompiler.ts
|
|
5904
6218
|
import { decodedUrlPath } from "@hyperframes/parsers/asset-paths";
|
|
5905
6219
|
import { createHash as createHash6 } from "node:crypto";
|
|
5906
|
-
import { createReadStream as createReadStream2, existsSync as existsSync10, mkdirSync as
|
|
5907
|
-
import { join as
|
|
6220
|
+
import { createReadStream as createReadStream2, existsSync as existsSync10, mkdirSync as mkdirSync9, readFileSync as readFileSync7 } from "fs";
|
|
6221
|
+
import { join as join18, dirname as dirname8, resolve as resolve7, basename as basename4, relative as relative2 } from "path";
|
|
5908
6222
|
import { parseHTML as parseHTML5 } from "linkedom";
|
|
5909
6223
|
import {
|
|
5910
6224
|
COMPOSITION_SOURCE_URL,
|
|
@@ -6150,8 +6464,8 @@ import {
|
|
|
6150
6464
|
|
|
6151
6465
|
// src/services/animatedGifPrep.ts
|
|
6152
6466
|
import { createHash as createHash4 } from "node:crypto";
|
|
6153
|
-
import { copyFileSync as copyFileSync2, existsSync as existsSync8, mkdirSync as
|
|
6154
|
-
import { dirname as
|
|
6467
|
+
import { copyFileSync as copyFileSync2, existsSync as existsSync8, mkdirSync as mkdirSync7, readFileSync as readFileSync5, rmSync as rmSync9, statSync as statSync6 } from "node:fs";
|
|
6468
|
+
import { dirname as dirname6, isAbsolute as isAbsolute2, join as join16, resolve as resolve5 } from "node:path";
|
|
6155
6469
|
import { parseHTML as parseHTML3 } from "linkedom";
|
|
6156
6470
|
import { parseAnimatedGifMetadata } from "@hyperframes/core";
|
|
6157
6471
|
import { DEFAULT_VP9_CPU_USED, runFfmpeg as runFfmpeg2, renameWithRetry as renameWithRetry3 } from "@hyperframes/engine";
|
|
@@ -6200,7 +6514,7 @@ function resolveLocalImageSourcePath(src, options) {
|
|
|
6200
6514
|
}
|
|
6201
6515
|
function isUsableFile(path) {
|
|
6202
6516
|
try {
|
|
6203
|
-
const stat =
|
|
6517
|
+
const stat = statSync6(path);
|
|
6204
6518
|
return stat.isFile() && stat.size > 0;
|
|
6205
6519
|
} catch {
|
|
6206
6520
|
return false;
|
|
@@ -6307,11 +6621,11 @@ async function runAnimatedGifTranscode(request) {
|
|
|
6307
6621
|
throw result.error ?? new Error(`Animated GIF transcode failed (${result.exitCode}): ${result.stderr.slice(-500)}`);
|
|
6308
6622
|
}
|
|
6309
6623
|
async function ensurePreparedWebm(input) {
|
|
6310
|
-
if (!existsSync8(
|
|
6311
|
-
|
|
6624
|
+
if (!existsSync8(dirname6(input.cachePath))) {
|
|
6625
|
+
mkdirSync7(dirname6(input.cachePath), { recursive: true });
|
|
6312
6626
|
}
|
|
6313
|
-
if (!existsSync8(
|
|
6314
|
-
|
|
6627
|
+
if (!existsSync8(dirname6(input.outputPath))) {
|
|
6628
|
+
mkdirSync7(dirname6(input.outputPath), { recursive: true });
|
|
6315
6629
|
}
|
|
6316
6630
|
if (!isUsableFile(input.cachePath)) {
|
|
6317
6631
|
const tmpPath = `${input.cachePath}.tmp-${process.pid}-${Date.now()}`;
|
|
@@ -6335,10 +6649,10 @@ async function ensurePreparedWebm(input) {
|
|
|
6335
6649
|
if (!isUsableFile(input.cachePath)) {
|
|
6336
6650
|
renameWithRetry3(tmpPath, input.cachePath);
|
|
6337
6651
|
} else {
|
|
6338
|
-
|
|
6652
|
+
rmSync9(tmpPath, { force: true });
|
|
6339
6653
|
}
|
|
6340
6654
|
} catch (error) {
|
|
6341
|
-
|
|
6655
|
+
rmSync9(tmpPath, { force: true });
|
|
6342
6656
|
throw error;
|
|
6343
6657
|
}
|
|
6344
6658
|
}
|
|
@@ -6394,7 +6708,7 @@ function replaceImageWithVideo(input) {
|
|
|
6394
6708
|
return video;
|
|
6395
6709
|
}
|
|
6396
6710
|
async function prepareAnimatedGifInputs(html, options) {
|
|
6397
|
-
const outputDir = options.outputDir ??
|
|
6711
|
+
const outputDir = options.outputDir ?? join16(options.downloadDir, PREPARED_GIF_SUBDIR);
|
|
6398
6712
|
const outputSrcPrefix = normalizeRelPath(options.outputSrcPrefix ?? PREPARED_GIF_SUBDIR);
|
|
6399
6713
|
const cacheDir = options.cacheDir ?? outputDir;
|
|
6400
6714
|
const { document: document2 } = parseHTML3(html);
|
|
@@ -6421,8 +6735,8 @@ async function prepareAnimatedGifInputs(html, options) {
|
|
|
6421
6735
|
);
|
|
6422
6736
|
const hash = computePreparedGifHash(bytes, loopIterations, padSeconds);
|
|
6423
6737
|
const filename = `${CACHE_SCHEMA}-${hash.slice(0, 24)}.webm`;
|
|
6424
|
-
const cachePath =
|
|
6425
|
-
const outputPath =
|
|
6738
|
+
const cachePath = join16(cacheDir, filename);
|
|
6739
|
+
const outputPath = join16(outputDir, filename);
|
|
6426
6740
|
const outputSrc = `${outputSrcPrefix}/${filename}`;
|
|
6427
6741
|
await ensurePreparedWebm({
|
|
6428
6742
|
sourcePath,
|
|
@@ -6459,15 +6773,15 @@ import { createHash as createHash5 } from "node:crypto";
|
|
|
6459
6773
|
import {
|
|
6460
6774
|
closeSync as closeSync4,
|
|
6461
6775
|
copyFileSync as copyFileSync3,
|
|
6462
|
-
mkdirSync as
|
|
6776
|
+
mkdirSync as mkdirSync8,
|
|
6463
6777
|
openSync as openSync4,
|
|
6464
6778
|
readFileSync as readFileSync6,
|
|
6465
6779
|
readSync as readSync3,
|
|
6466
|
-
rmSync as
|
|
6467
|
-
statSync as
|
|
6780
|
+
rmSync as rmSync10,
|
|
6781
|
+
statSync as statSync7,
|
|
6468
6782
|
writeFileSync as writeFileSync4
|
|
6469
6783
|
} from "node:fs";
|
|
6470
|
-
import { dirname as
|
|
6784
|
+
import { dirname as dirname7, join as join17, resolve as resolve6 } from "node:path";
|
|
6471
6785
|
import { parseHTML as parseHTML4 } from "linkedom";
|
|
6472
6786
|
import { renameWithRetry as renameWithRetry4, runFfmpeg as runFfmpeg3, runFfprobe } from "@hyperframes/engine";
|
|
6473
6787
|
|
|
@@ -6707,14 +7021,14 @@ function resolveImageSource(src, options) {
|
|
|
6707
7021
|
}
|
|
6708
7022
|
function isUsableFile2(path) {
|
|
6709
7023
|
try {
|
|
6710
|
-
return
|
|
7024
|
+
return statSync7(path).size > 0;
|
|
6711
7025
|
} catch {
|
|
6712
7026
|
return false;
|
|
6713
7027
|
}
|
|
6714
7028
|
}
|
|
6715
7029
|
async function ensurePreparedJpeg(source, cachePath, outputPath, options) {
|
|
6716
|
-
|
|
6717
|
-
|
|
7030
|
+
mkdirSync8(dirname7(cachePath), { recursive: true });
|
|
7031
|
+
mkdirSync8(dirname7(outputPath), { recursive: true });
|
|
6718
7032
|
if (!isUsableFile2(cachePath)) {
|
|
6719
7033
|
const tmpPath = `${cachePath}.tmp-${process.pid}-${Date.now()}`;
|
|
6720
7034
|
const inlinePath = `${tmpPath}.heic`;
|
|
@@ -6732,8 +7046,8 @@ async function ensurePreparedJpeg(source, cachePath, outputPath, options) {
|
|
|
6732
7046
|
const reason = error instanceof Error ? error.message : String(error);
|
|
6733
7047
|
throw TOOL_FAILURE_RE.test(reason) ? new HeifToolUnavailableError(source.label, reason) : new HeifImageConversionError(source.label, reason);
|
|
6734
7048
|
} finally {
|
|
6735
|
-
|
|
6736
|
-
|
|
7049
|
+
rmSync10(tmpPath, { force: true });
|
|
7050
|
+
rmSync10(inlinePath, { force: true });
|
|
6737
7051
|
}
|
|
6738
7052
|
}
|
|
6739
7053
|
if (resolve6(outputPath) !== resolve6(cachePath) && !isUsableFile2(outputPath)) {
|
|
@@ -6741,7 +7055,7 @@ async function ensurePreparedJpeg(source, cachePath, outputPath, options) {
|
|
|
6741
7055
|
}
|
|
6742
7056
|
}
|
|
6743
7057
|
async function prepareHeifImageInputs(html, options) {
|
|
6744
|
-
const outputDir = options.outputDir ??
|
|
7058
|
+
const outputDir = options.outputDir ?? join17(options.downloadDir, PREPARED_HEIF_SUBDIR);
|
|
6745
7059
|
const outputSrcPrefix = normalizeRelPath(options.outputSrcPrefix ?? PREPARED_HEIF_SUBDIR);
|
|
6746
7060
|
const cacheDir = options.cacheDir ?? outputDir;
|
|
6747
7061
|
const preparedAssets = /* @__PURE__ */ new Map();
|
|
@@ -6762,7 +7076,7 @@ async function prepareHeifImageInputs(html, options) {
|
|
|
6762
7076
|
await Promise.all(
|
|
6763
7077
|
[...unique].map(
|
|
6764
7078
|
([filename, source]) => withMediaProbeSlot(
|
|
6765
|
-
() => ensurePreparedJpeg(source,
|
|
7079
|
+
() => ensurePreparedJpeg(source, join17(cacheDir, filename), join17(outputDir, filename), options)
|
|
6766
7080
|
).catch((error) => {
|
|
6767
7081
|
if (!options.onFailure) throw error;
|
|
6768
7082
|
failed.add(filename);
|
|
@@ -6774,8 +7088,8 @@ async function prepareHeifImageInputs(html, options) {
|
|
|
6774
7088
|
if (failed.has(filename)) continue;
|
|
6775
7089
|
const outputSrc = `${outputSrcPrefix}/${filename}`;
|
|
6776
7090
|
img.setAttribute("src", outputSrc);
|
|
6777
|
-
preparedAssets.set(outputSrc,
|
|
6778
|
-
preparedImages.push({ sourceSrc: src, outputSrc, outputPath:
|
|
7091
|
+
preparedAssets.set(outputSrc, join17(outputDir, filename));
|
|
7092
|
+
preparedImages.push({ sourceSrc: src, outputSrc, outputPath: join17(outputDir, filename) });
|
|
6779
7093
|
}
|
|
6780
7094
|
return { html: document2.toString(), preparedAssets, preparedImages };
|
|
6781
7095
|
}
|
|
@@ -7099,8 +7413,8 @@ function detectShaderTransitionUsage(html) {
|
|
|
7099
7413
|
async function resolveMediaDuration(src, mediaStart, playbackRate, baseDir, downloadDir, tagName, elementIdentity, log, urlByAbsentLocalPath, probes) {
|
|
7100
7414
|
let filePath = src;
|
|
7101
7415
|
if (isHttpUrl(src)) {
|
|
7102
|
-
const remoteDir =
|
|
7103
|
-
if (!existsSync10(remoteDir))
|
|
7416
|
+
const remoteDir = join18(downloadDir, REMOTE_MEDIA_SUBDIR);
|
|
7417
|
+
if (!existsSync10(remoteDir)) mkdirSync9(remoteDir, { recursive: true });
|
|
7104
7418
|
try {
|
|
7105
7419
|
filePath = await downloadToTemp(src, remoteDir, void 0, void 0, void 0, {
|
|
7106
7420
|
onTelemetry: logRemoteDownloadTelemetry
|
|
@@ -7110,7 +7424,7 @@ async function resolveMediaDuration(src, mediaStart, playbackRate, baseDir, down
|
|
|
7110
7424
|
}
|
|
7111
7425
|
} else {
|
|
7112
7426
|
filePath = decodedUrlPath(src);
|
|
7113
|
-
if (!filePath.startsWith("/")) filePath =
|
|
7427
|
+
if (!filePath.startsWith("/")) filePath = join18(baseDir, filePath);
|
|
7114
7428
|
filePath = urlByAbsentLocalPath?.get(filePath) ?? filePath;
|
|
7115
7429
|
}
|
|
7116
7430
|
if (!isHttpUrl(filePath) && !existsSync10(filePath)) {
|
|
@@ -7271,7 +7585,7 @@ async function parseSubCompositions(html, projectDir, downloadDir, visited = /*
|
|
|
7271
7585
|
workItems.map(async (item) => {
|
|
7272
7586
|
const { html: compiledSub } = await compileHtmlFile(
|
|
7273
7587
|
rewriteUnresolvableGsapToCdn(item.rawSubHtml, projectDir, item.srcPath),
|
|
7274
|
-
|
|
7588
|
+
dirname8(item.filePath),
|
|
7275
7589
|
downloadDir,
|
|
7276
7590
|
void 0,
|
|
7277
7591
|
urlByAbsentLocalPath,
|
|
@@ -7701,7 +8015,7 @@ var REMOTE_SOURCE_TAG_RE = /<source\b[^>]*?\bsrc\s*=\s*["'](https?:\/\/[^"']+)["
|
|
|
7701
8015
|
var REMOTE_IMG_TAG_RE = /<img\b[^>]*?(?<![\w-])src\s*=\s*["'](https?:\/\/[^"']+)["'][^>]*>/gi;
|
|
7702
8016
|
async function downloadAndRewriteUrls(urlSet, html, remoteDir, warnLabel, logLabel, extraRewrite) {
|
|
7703
8017
|
if (urlSet.size === 0) return { html, remoteMediaAssets: /* @__PURE__ */ new Map() };
|
|
7704
|
-
if (!existsSync10(remoteDir))
|
|
8018
|
+
if (!existsSync10(remoteDir)) mkdirSync9(remoteDir, { recursive: true });
|
|
7705
8019
|
const urlToLocal = /* @__PURE__ */ new Map();
|
|
7706
8020
|
await Promise.all(
|
|
7707
8021
|
[...urlSet].map(async (url) => {
|
|
@@ -7748,7 +8062,7 @@ async function localizeRemoteMediaSources(html, downloadDir) {
|
|
|
7748
8062
|
return downloadAndRewriteUrls(
|
|
7749
8063
|
urlSet,
|
|
7750
8064
|
html,
|
|
7751
|
-
|
|
8065
|
+
join18(downloadDir, REMOTE_MEDIA_SUBDIR),
|
|
7752
8066
|
"Remote media download failed",
|
|
7753
8067
|
"Localized remote media source(s)"
|
|
7754
8068
|
);
|
|
@@ -7763,7 +8077,7 @@ async function localizeRemoteImageSources(html, downloadDir) {
|
|
|
7763
8077
|
return downloadAndRewriteUrls(
|
|
7764
8078
|
urlSet,
|
|
7765
8079
|
html,
|
|
7766
|
-
|
|
8080
|
+
join18(downloadDir, REMOTE_MEDIA_SUBDIR),
|
|
7767
8081
|
"Remote image download failed",
|
|
7768
8082
|
"Localized remote image source(s)"
|
|
7769
8083
|
);
|
|
@@ -7779,7 +8093,7 @@ async function localizeRemoteBackgroundImages(html, downloadDir) {
|
|
|
7779
8093
|
return downloadAndRewriteUrls(
|
|
7780
8094
|
urlSet,
|
|
7781
8095
|
html,
|
|
7782
|
-
|
|
8096
|
+
join18(downloadDir, REMOTE_MEDIA_SUBDIR),
|
|
7783
8097
|
"Remote background-image download failed",
|
|
7784
8098
|
"Localized remote background-image(s)",
|
|
7785
8099
|
// Quoted url('..')/url("..") are rewritten by downloadAndRewriteUrls' default
|
|
@@ -7889,7 +8203,7 @@ function projectStylesheetFontFamilies(html, projectDir) {
|
|
|
7889
8203
|
const family = /font-family\s*:\s*([^;}]+)/i.exec(face)?.[1]?.trim().replace(/^['"]|['"]$/g, "");
|
|
7890
8204
|
const shipsFile = [...face.matchAll(LOCAL_FONTFACE_URL_RE)].some(([, url = ""]) => {
|
|
7891
8205
|
const path = decodedUrlPath(url);
|
|
7892
|
-
const file = path.startsWith("/") ? resolve7(projectDir, `.${path}`) : resolve7(
|
|
8206
|
+
const file = path.startsWith("/") ? resolve7(projectDir, `.${path}`) : resolve7(dirname8(sheet), path);
|
|
7893
8207
|
return isPathInside2(file, projectDir) && existsSync10(file);
|
|
7894
8208
|
});
|
|
7895
8209
|
if (family && shipsFile) families.push(family);
|
|
@@ -7934,7 +8248,7 @@ function localizeFontFaceUrls(html, downloadDir) {
|
|
|
7934
8248
|
return downloadAndRewriteUrls(
|
|
7935
8249
|
collectFontFaceUrls(html),
|
|
7936
8250
|
html,
|
|
7937
|
-
|
|
8251
|
+
join18(downloadDir, REMOTE_MEDIA_SUBDIR),
|
|
7938
8252
|
"Remote font download failed",
|
|
7939
8253
|
"Localized remote font face(s)",
|
|
7940
8254
|
(h, url, relPath) => h.replaceAll(`url(${url})`, `url("${relPath}")`)
|
|
@@ -8608,10 +8922,10 @@ function adaptAspectAgnosticResolution(requested, aspectAgnostic, width, height,
|
|
|
8608
8922
|
}
|
|
8609
8923
|
function resolveEntryHtmlPath(input) {
|
|
8610
8924
|
const { projectDir, workDir, entryFile, log } = input;
|
|
8611
|
-
const htmlPath =
|
|
8925
|
+
const htmlPath = join19(projectDir, entryFile);
|
|
8612
8926
|
const rawEntry = readTextFile2(htmlPath);
|
|
8613
8927
|
if (entryFile === "index.html" || !isTemplateEntry(rawEntry)) return htmlPath;
|
|
8614
|
-
const projectIndexPath =
|
|
8928
|
+
const projectIndexPath = join19(projectDir, "index.html");
|
|
8615
8929
|
if (!existsSync11(projectIndexPath)) {
|
|
8616
8930
|
throw new Error(
|
|
8617
8931
|
`Template entry file "${entryFile}" requires a project index.html to extract its render shell.`
|
|
@@ -8627,7 +8941,7 @@ function resolveEntryHtmlPath(input) {
|
|
|
8627
8941
|
`Entry file "${entryFile}" is not mounted from index.html via data-composition-src, so it cannot be rendered independently.`
|
|
8628
8942
|
);
|
|
8629
8943
|
}
|
|
8630
|
-
const wrapperPath =
|
|
8944
|
+
const wrapperPath = join19(workDir, "standalone-entry.html");
|
|
8631
8945
|
writeFileSync5(wrapperPath, standaloneHtml, "utf-8");
|
|
8632
8946
|
log.info("Extracted standalone entry from index.html host context", { entryFile });
|
|
8633
8947
|
return wrapperPath;
|
|
@@ -8648,7 +8962,7 @@ async function runCompileStage(input) {
|
|
|
8648
8962
|
} = input;
|
|
8649
8963
|
const htmlPath = resolveEntryHtmlPath({ projectDir, workDir, entryFile, log });
|
|
8650
8964
|
const compileStart = Date.now();
|
|
8651
|
-
const compiled = await compileForRender(projectDir, htmlPath,
|
|
8965
|
+
const compiled = await compileForRender(projectDir, htmlPath, join19(workDir, "downloads"), {
|
|
8652
8966
|
log,
|
|
8653
8967
|
failClosedFontFetch: failClosedFontFetch === true,
|
|
8654
8968
|
allowSystemFontCapture,
|
|
@@ -8656,7 +8970,7 @@ async function runCompileStage(input) {
|
|
|
8656
8970
|
variables: input.variables,
|
|
8657
8971
|
urlByAbsentLocalPath: input.urlByAbsentLocalPath,
|
|
8658
8972
|
pageMotionBlur: input.pageMotionBlur,
|
|
8659
|
-
animatedGifCacheDir: cfg.extractCacheDir ?
|
|
8973
|
+
animatedGifCacheDir: cfg.extractCacheDir ? join19(cfg.extractCacheDir, "animated-gif") : void 0,
|
|
8660
8974
|
ffmpegProcessTimeout: cfg.ffmpegProcessTimeout
|
|
8661
8975
|
});
|
|
8662
8976
|
assertNotAborted();
|
|
@@ -8713,7 +9027,7 @@ async function runCompileStage(input) {
|
|
|
8713
9027
|
};
|
|
8714
9028
|
await preflightCompositionAssetMediaTypes({
|
|
8715
9029
|
projectDir,
|
|
8716
|
-
compiledDir:
|
|
9030
|
+
compiledDir: join19(workDir, "compiled"),
|
|
8717
9031
|
composition,
|
|
8718
9032
|
signal: abortSignal
|
|
8719
9033
|
});
|
|
@@ -8759,7 +9073,7 @@ async function runCompileStage(input) {
|
|
|
8759
9073
|
|
|
8760
9074
|
// src/services/render/stages/probeStage.ts
|
|
8761
9075
|
import { createHash as createHash7 } from "node:crypto";
|
|
8762
|
-
import { basename as basename5, join as
|
|
9076
|
+
import { basename as basename5, join as join20 } from "node:path";
|
|
8763
9077
|
import { parseHTML as parseHTML6 } from "linkedom";
|
|
8764
9078
|
import {
|
|
8765
9079
|
closeCaptureSession as closeCaptureSession4,
|
|
@@ -8968,7 +9282,7 @@ async function runProbeStage(input) {
|
|
|
8968
9282
|
});
|
|
8969
9283
|
fileServer = await createFileServer({
|
|
8970
9284
|
projectDir,
|
|
8971
|
-
compiledDir:
|
|
9285
|
+
compiledDir: join20(workDir, "compiled"),
|
|
8972
9286
|
port: 0,
|
|
8973
9287
|
preHeadScripts: [VIRTUAL_TIME_SHIM],
|
|
8974
9288
|
fps: job.config.fps
|
|
@@ -8998,7 +9312,7 @@ async function runProbeStage(input) {
|
|
|
8998
9312
|
log.info("Creating capture session...", { attempt, maxAttempts: PROBE_MAX_ATTEMPTS });
|
|
8999
9313
|
probeSession = await createCaptureSession4(
|
|
9000
9314
|
fileServer.url,
|
|
9001
|
-
|
|
9315
|
+
join20(workDir, "probe"),
|
|
9002
9316
|
captureOpts,
|
|
9003
9317
|
null,
|
|
9004
9318
|
probeCfg
|
|
@@ -9083,7 +9397,7 @@ async function runProbeStage(input) {
|
|
|
9083
9397
|
});
|
|
9084
9398
|
probeSession = await createCaptureSession4(
|
|
9085
9399
|
fileServer.url,
|
|
9086
|
-
|
|
9400
|
+
join20(workDir, "probe-screenshot"),
|
|
9087
9401
|
captureOpts,
|
|
9088
9402
|
null,
|
|
9089
9403
|
{ ...probeCfg, forceScreenshot: true }
|
|
@@ -9121,7 +9435,7 @@ async function runProbeStage(input) {
|
|
|
9121
9435
|
compiled,
|
|
9122
9436
|
resolutions,
|
|
9123
9437
|
projectDir,
|
|
9124
|
-
|
|
9438
|
+
join20(workDir, "downloads")
|
|
9125
9439
|
);
|
|
9126
9440
|
assertNotAborted();
|
|
9127
9441
|
composition.videos = compiled.videos;
|
|
@@ -9312,7 +9626,7 @@ async function runProbeStage(input) {
|
|
|
9312
9626
|
}
|
|
9313
9627
|
await preflightCompositionAssetMediaTypes({
|
|
9314
9628
|
projectDir,
|
|
9315
|
-
compiledDir:
|
|
9629
|
+
compiledDir: join20(workDir, "compiled"),
|
|
9316
9630
|
composition,
|
|
9317
9631
|
signal: abortSignal
|
|
9318
9632
|
});
|
|
@@ -9479,10 +9793,10 @@ function validateDistributedDuration(input) {
|
|
|
9479
9793
|
|
|
9480
9794
|
// src/services/render/stages/extractVideosStage.ts
|
|
9481
9795
|
import { existsSync as existsSync12 } from "node:fs";
|
|
9482
|
-
import { join as
|
|
9796
|
+
import { join as join21 } from "node:path";
|
|
9483
9797
|
import {
|
|
9484
9798
|
classifyVideoExtractionError,
|
|
9485
|
-
createFrameLookupTable,
|
|
9799
|
+
createFrameLookupTable as createFrameLookupTable3,
|
|
9486
9800
|
detectTransfer,
|
|
9487
9801
|
extractAllVideoFrames,
|
|
9488
9802
|
extractMediaMetadata as extractMediaMetadata3,
|
|
@@ -9494,7 +9808,7 @@ import {
|
|
|
9494
9808
|
resolveVideoExtractionWindow as resolveVideoExtractionWindow2,
|
|
9495
9809
|
isHttpUrl as isHttpUrl2,
|
|
9496
9810
|
resolveProjectRelativeSrc as resolveProjectRelativeSrc3,
|
|
9497
|
-
runVideoExtractionWithRetry,
|
|
9811
|
+
runVideoExtractionWithRetry as runVideoExtractionWithRetry2,
|
|
9498
9812
|
safeVideoExtractionSourceIdentity,
|
|
9499
9813
|
videoAudioId as videoAudioId2
|
|
9500
9814
|
} from "@hyperframes/engine";
|
|
@@ -9755,7 +10069,7 @@ async function runExtractVideosStage(input) {
|
|
|
9755
10069
|
const videoPath = resolveProjectRelativeSrc3(v.src, projectDir, compiledDir);
|
|
9756
10070
|
if (!existsSync12(videoPath)) return null;
|
|
9757
10071
|
try {
|
|
9758
|
-
const attempted = extractionPolicy.maxTransientRetries === 0 ? { result: await extractMediaMetadata3(videoPath), retries: 0 } : await
|
|
10072
|
+
const attempted = extractionPolicy.maxTransientRetries === 0 ? { result: await extractMediaMetadata3(videoPath), retries: 0 } : await runVideoExtractionWithRetry2(() => extractMediaMetadata3(videoPath), {
|
|
9759
10073
|
signal: abortSignal,
|
|
9760
10074
|
maxTransientRetries: extractionPolicy.maxTransientRetries,
|
|
9761
10075
|
onRetry: () => {
|
|
@@ -9825,14 +10139,14 @@ async function runExtractVideosStage(input) {
|
|
|
9825
10139
|
// because short boundary counts can differ by one frame.
|
|
9826
10140
|
{
|
|
9827
10141
|
fps: job.config.fps,
|
|
9828
|
-
outputDir:
|
|
10142
|
+
outputDir: join21(compiledDir, "__hyperframes_video_frames"),
|
|
9829
10143
|
format: job.config.videoFrameFormat ?? "auto",
|
|
9830
10144
|
toneMapHdrToSdr: rendersSdrOnly(job.config, composition),
|
|
9831
10145
|
timelineEnd: composition.duration,
|
|
9832
10146
|
maxTransientRetries: extractionPolicy.maxTransientRetries,
|
|
9833
10147
|
collectProbeFailures: extractionPolicy.failureMode === "enforce",
|
|
9834
10148
|
deferRangeExtraction,
|
|
9835
|
-
contentKeyedDirs: [
|
|
10149
|
+
contentKeyedDirs: [join21(compiledDir, REMOTE_MEDIA_SUBDIR)],
|
|
9836
10150
|
urlByAbsentLocalPath,
|
|
9837
10151
|
onProgress: ({ planned = 0, done = 0 }) => {
|
|
9838
10152
|
framesPlanned += planned;
|
|
@@ -9852,7 +10166,7 @@ async function runExtractVideosStage(input) {
|
|
|
9852
10166
|
materializeSymlinks
|
|
9853
10167
|
});
|
|
9854
10168
|
if (extractionResult.extracted.length > 0) {
|
|
9855
|
-
frameLookup =
|
|
10169
|
+
frameLookup = createFrameLookupTable3(
|
|
9856
10170
|
composition.videos,
|
|
9857
10171
|
extractionResult.extracted,
|
|
9858
10172
|
resolveRenderFpsConfig(job.config.fps).value
|
|
@@ -10015,7 +10329,7 @@ async function planVideoLayerPassthrough(input) {
|
|
|
10015
10329
|
|
|
10016
10330
|
// src/services/render/stages/audioStage.ts
|
|
10017
10331
|
import { existsSync as existsSync15 } from "node:fs";
|
|
10018
|
-
import { join as
|
|
10332
|
+
import { join as join24 } from "node:path";
|
|
10019
10333
|
import {
|
|
10020
10334
|
MIXED_AUDIO_FILENAME,
|
|
10021
10335
|
audioFailureDiagnostics,
|
|
@@ -10026,7 +10340,7 @@ import {
|
|
|
10026
10340
|
|
|
10027
10341
|
// src/services/render/audioDeliveryLimit.ts
|
|
10028
10342
|
import { existsSync as existsSync14 } from "node:fs";
|
|
10029
|
-
import { dirname as
|
|
10343
|
+
import { dirname as dirname10, join as join23 } from "node:path";
|
|
10030
10344
|
import {
|
|
10031
10345
|
applyAudioFxChain,
|
|
10032
10346
|
describeFfmpegFailure as describeFfmpegFailure3,
|
|
@@ -10037,7 +10351,7 @@ import {
|
|
|
10037
10351
|
// src/services/render/audioPadTrim.ts
|
|
10038
10352
|
import { spawn } from "node:child_process";
|
|
10039
10353
|
import { mkdtempSync as mkdtempSync3 } from "node:fs";
|
|
10040
|
-
import { dirname as
|
|
10354
|
+
import { dirname as dirname9, join as join22 } from "node:path";
|
|
10041
10355
|
import {
|
|
10042
10356
|
DEFAULT_CONFIG,
|
|
10043
10357
|
describeFfmpegFailure as describeFfmpegFailure2,
|
|
@@ -10316,7 +10630,7 @@ async function constrainAacTruePeak(input) {
|
|
|
10316
10630
|
}
|
|
10317
10631
|
async function cutSource(input, scratchDir, attenuationDb) {
|
|
10318
10632
|
if (!input.sourcePath || !input.rewriteSource) return true;
|
|
10319
|
-
const cutPath =
|
|
10633
|
+
const cutPath = join22(scratchDir, "source.wav");
|
|
10320
10634
|
try {
|
|
10321
10635
|
const result = await input.rewriteSource(input.sourcePath, cutPath, gainFilter(attenuationDb));
|
|
10322
10636
|
if (!result.success) throw new Error(result.error);
|
|
@@ -10330,8 +10644,8 @@ async function cutSource(input, scratchDir, attenuationDb) {
|
|
|
10330
10644
|
async function enforceAacTruePeak(input) {
|
|
10331
10645
|
let scratchDir;
|
|
10332
10646
|
try {
|
|
10333
|
-
scratchDir = mkdtempSync3(
|
|
10334
|
-
const correctedPath =
|
|
10647
|
+
scratchDir = mkdtempSync3(join22(dirname9(input.audioPath), ".true-peak-"));
|
|
10648
|
+
const correctedPath = join22(scratchDir, "audio.m4a");
|
|
10335
10649
|
const measurements = [];
|
|
10336
10650
|
let attenuationDb = 0;
|
|
10337
10651
|
let sourcePeakDbfs;
|
|
@@ -10522,11 +10836,13 @@ async function runFfprobeJson(args, signal) {
|
|
|
10522
10836
|
}
|
|
10523
10837
|
|
|
10524
10838
|
// src/services/render/audioDeliveryLimit.ts
|
|
10525
|
-
var
|
|
10526
|
-
var
|
|
10839
|
+
var LIMIT_PASSES = 3;
|
|
10840
|
+
var LOWEST_CEILING_DBTP = AAC_DELIVERY_TRUE_PEAK_DBFS - 2;
|
|
10841
|
+
var PASS_HEADROOM_DB = 0.05;
|
|
10842
|
+
var limitChain = (ceiling) => ({
|
|
10527
10843
|
version: 1,
|
|
10528
|
-
nodes: [{ type: "truepeak", id: "delivery", params: { ceiling
|
|
10529
|
-
};
|
|
10844
|
+
nodes: [{ type: "truepeak", id: "delivery", params: { ceiling } }]
|
|
10845
|
+
});
|
|
10530
10846
|
async function limitDeliveryPeaks(input, deps = {
|
|
10531
10847
|
measure: measureTruePeakDbfs,
|
|
10532
10848
|
applyChain: applyAudioFxChain,
|
|
@@ -10538,15 +10854,39 @@ async function limitDeliveryPeaks(input, deps = {
|
|
|
10538
10854
|
if (!deps.sourceExists(sourcePath)) return { success: true };
|
|
10539
10855
|
const peak = await deps.measure(audioPath, { timeoutMs, signal });
|
|
10540
10856
|
if (peak <= AAC_DELIVERY_TRUE_PEAK_DBFS) return { success: true };
|
|
10541
|
-
const
|
|
10857
|
+
const alternating = ["a", "b"].map(
|
|
10858
|
+
(k) => join23(dirname10(sourcePath), `audio-source-limited-${k}.wav`)
|
|
10859
|
+
);
|
|
10860
|
+
let encodedFrom;
|
|
10861
|
+
let ceiling = AAC_DELIVERY_TRUE_PEAK_DBFS;
|
|
10862
|
+
for (let pass = 0; pass < LIMIT_PASSES; pass++) {
|
|
10863
|
+
const limitedPath = alternating[pass % 2];
|
|
10864
|
+
const result = await limitPass(input, deps, ceiling, limitedPath);
|
|
10865
|
+
if ("encodeFailure" in result) return result.encodeFailure;
|
|
10866
|
+
if ("limitFailed" in result) break;
|
|
10867
|
+
encodedFrom = limitedPath;
|
|
10868
|
+
if (result.peak <= AAC_DELIVERY_TRUE_PEAK_DBFS || ceiling <= LOWEST_CEILING_DBTP) break;
|
|
10869
|
+
ceiling = Math.max(
|
|
10870
|
+
ceiling - (result.peak - AAC_DELIVERY_TRUE_PEAK_DBFS) - PASS_HEADROOM_DB,
|
|
10871
|
+
LOWEST_CEILING_DBTP
|
|
10872
|
+
);
|
|
10873
|
+
}
|
|
10874
|
+
if (encodedFrom) deps.rename(encodedFrom, sourcePath, { copyFallback: true });
|
|
10875
|
+
return { success: true };
|
|
10876
|
+
}
|
|
10877
|
+
async function limitPass(input, deps, ceiling, limitedPath) {
|
|
10878
|
+
const { audioPath, sourcePath, signal, timeoutMs } = input;
|
|
10542
10879
|
try {
|
|
10543
|
-
await deps.applyChain(sourcePath,
|
|
10880
|
+
await deps.applyChain(sourcePath, limitChain(ceiling), limitedPath, {
|
|
10881
|
+
trackId: "delivery",
|
|
10882
|
+
signal
|
|
10883
|
+
});
|
|
10544
10884
|
} catch (error) {
|
|
10545
10885
|
if (signal?.aborted) throw error;
|
|
10546
10886
|
input.warn?.("[audio] true-peak limit failed, the whole-mix cut applies", {
|
|
10547
10887
|
error: error instanceof Error ? error.message : String(error)
|
|
10548
10888
|
});
|
|
10549
|
-
return {
|
|
10889
|
+
return { limitFailed: true };
|
|
10550
10890
|
}
|
|
10551
10891
|
const encoded = await deps.encode(limitedPath, audioPath, input.targetDurationSeconds, {
|
|
10552
10892
|
signal,
|
|
@@ -10554,13 +10894,14 @@ async function limitDeliveryPeaks(input, deps = {
|
|
|
10554
10894
|
});
|
|
10555
10895
|
if (!encoded.success) {
|
|
10556
10896
|
return {
|
|
10557
|
-
|
|
10558
|
-
|
|
10559
|
-
|
|
10897
|
+
encodeFailure: {
|
|
10898
|
+
success: false,
|
|
10899
|
+
error: `[audioDeliveryLimit] ${describeFfmpegFailure3(encoded, timeoutMs)}`,
|
|
10900
|
+
failureReason: encoded.failureReason
|
|
10901
|
+
}
|
|
10560
10902
|
};
|
|
10561
10903
|
}
|
|
10562
|
-
deps.
|
|
10563
|
-
return { success: true };
|
|
10904
|
+
return { peak: await deps.measure(audioPath, { timeoutMs, signal }) };
|
|
10564
10905
|
}
|
|
10565
10906
|
|
|
10566
10907
|
// src/services/render/stages/audioStage.ts
|
|
@@ -10586,7 +10927,7 @@ function runAudioStage(input) {
|
|
|
10586
10927
|
}
|
|
10587
10928
|
async function runAudioStageScoped(input) {
|
|
10588
10929
|
const stageStart = Date.now();
|
|
10589
|
-
const pcmSourcePath =
|
|
10930
|
+
const pcmSourcePath = join24(input.workDir, "audio-source.wav");
|
|
10590
10931
|
const duration = input.totalFrames * input.fps.den / input.fps.num;
|
|
10591
10932
|
let deliverySourcePath;
|
|
10592
10933
|
try {
|
|
@@ -10651,7 +10992,7 @@ async function mixAudioForDelivery(input, duration, pcmSourcePath) {
|
|
|
10651
10992
|
assertNotAborted,
|
|
10652
10993
|
log
|
|
10653
10994
|
} = input;
|
|
10654
|
-
const audioOutputPath =
|
|
10995
|
+
const audioOutputPath = join24(workDir, MIXED_AUDIO_FILENAME);
|
|
10655
10996
|
let hasAudio = false;
|
|
10656
10997
|
let audioError;
|
|
10657
10998
|
let audioFailures;
|
|
@@ -10662,7 +11003,7 @@ async function mixAudioForDelivery(input, duration, pcmSourcePath) {
|
|
|
10662
11003
|
audioResult = await processCompositionAudio(
|
|
10663
11004
|
audios,
|
|
10664
11005
|
projectDir,
|
|
10665
|
-
|
|
11006
|
+
join24(workDir, "audio-work"),
|
|
10666
11007
|
audioOutputPath,
|
|
10667
11008
|
duration,
|
|
10668
11009
|
abortSignal,
|
|
@@ -10730,8 +11071,8 @@ async function mixAudioForDelivery(input, duration, pcmSourcePath) {
|
|
|
10730
11071
|
}
|
|
10731
11072
|
|
|
10732
11073
|
// src/services/render/stages/captureStage.ts
|
|
10733
|
-
import { existsSync as existsSync16, readdirSync as
|
|
10734
|
-
import { join as
|
|
11074
|
+
import { existsSync as existsSync16, readdirSync as readdirSync4, statfsSync as statfsSync2 } from "node:fs";
|
|
11075
|
+
import { join as join25 } from "node:path";
|
|
10735
11076
|
import {
|
|
10736
11077
|
classifyCaptureFailure as classifyCaptureFailure2,
|
|
10737
11078
|
frameFileExtension,
|
|
@@ -10802,15 +11143,15 @@ function diskCaptureShortfallError(needBytes, freeBytes, framesDir, basis, remed
|
|
|
10802
11143
|
function measureCaptureFrameBytes(workDir, framesDir) {
|
|
10803
11144
|
let total = sampleDirectoryBytes(framesDir);
|
|
10804
11145
|
if (!existsSync16(workDir)) return total;
|
|
10805
|
-
for (const attempt of
|
|
11146
|
+
for (const attempt of readdirSync4(workDir)) {
|
|
10806
11147
|
if (/^retry-\d+-batch-\d+-worker-\d+$/.test(attempt)) {
|
|
10807
|
-
total += sampleDirectoryBytes(
|
|
11148
|
+
total += sampleDirectoryBytes(join25(workDir, attempt));
|
|
10808
11149
|
continue;
|
|
10809
11150
|
}
|
|
10810
11151
|
if (!/^capture-attempt-\d+$/.test(attempt)) continue;
|
|
10811
|
-
const attemptDir =
|
|
10812
|
-
for (const name of
|
|
10813
|
-
if (/^worker-\d+$/.test(name)) total += sampleDirectoryBytes(
|
|
11152
|
+
const attemptDir = join25(workDir, attempt);
|
|
11153
|
+
for (const name of readdirSync4(attemptDir)) {
|
|
11154
|
+
if (/^worker-\d+$/.test(name)) total += sampleDirectoryBytes(join25(attemptDir, name));
|
|
10814
11155
|
}
|
|
10815
11156
|
}
|
|
10816
11157
|
return total;
|
|
@@ -11104,8 +11445,8 @@ async function captureSessionFrames(session, { input, rangeStart, rangeEnd, resu
|
|
|
11104
11445
|
}
|
|
11105
11446
|
|
|
11106
11447
|
// src/services/render/stages/captureHdrStage.ts
|
|
11107
|
-
import { existsSync as existsSync18, mkdirSync as
|
|
11108
|
-
import { join as
|
|
11448
|
+
import { existsSync as existsSync18, mkdirSync as mkdirSync10 } from "node:fs";
|
|
11449
|
+
import { join as join29 } from "node:path";
|
|
11109
11450
|
import {
|
|
11110
11451
|
closeCaptureSession as closeCaptureSession7,
|
|
11111
11452
|
cloneCaptureWarnings as cloneCaptureWarnings2,
|
|
@@ -11177,7 +11518,7 @@ function createHdrImageTransferCache(options = {}) {
|
|
|
11177
11518
|
}
|
|
11178
11519
|
|
|
11179
11520
|
// src/services/render/stages/captureHdrSequentialLoop.ts
|
|
11180
|
-
import { join as
|
|
11521
|
+
import { join as join26 } from "node:path";
|
|
11181
11522
|
import {
|
|
11182
11523
|
TRANSITIONS,
|
|
11183
11524
|
crossfade
|
|
@@ -11302,7 +11643,7 @@ async function runSequentialLayeredFrameLoop(input) {
|
|
|
11302
11643
|
);
|
|
11303
11644
|
if (debugDumpEnabled && debugDumpDir && i % 30 === 0) {
|
|
11304
11645
|
writeFileExclusiveSync(
|
|
11305
|
-
|
|
11646
|
+
join26(debugDumpDir, `frame_${String(i).padStart(4, "0")}_final_rgb48le.bin`),
|
|
11306
11647
|
normalCanvas
|
|
11307
11648
|
);
|
|
11308
11649
|
}
|
|
@@ -11334,7 +11675,7 @@ async function runSequentialLayeredFrameLoop(input) {
|
|
|
11334
11675
|
}
|
|
11335
11676
|
|
|
11336
11677
|
// src/services/render/stages/captureHdrHybridLoop.ts
|
|
11337
|
-
import { join as
|
|
11678
|
+
import { join as join28 } from "node:path";
|
|
11338
11679
|
import {
|
|
11339
11680
|
TRANSITIONS as TRANSITIONS2,
|
|
11340
11681
|
closeCaptureSession as closeCaptureSession6,
|
|
@@ -11348,7 +11689,7 @@ import {
|
|
|
11348
11689
|
// src/services/shaderTransitionWorkerPool.ts
|
|
11349
11690
|
import { Worker } from "node:worker_threads";
|
|
11350
11691
|
import { fileURLToPath as fileURLToPath3, pathToFileURL } from "node:url";
|
|
11351
|
-
import { dirname as
|
|
11692
|
+
import { dirname as dirname11, join as join27 } from "node:path";
|
|
11352
11693
|
import { createRequire } from "node:module";
|
|
11353
11694
|
import { existsSync as existsSync17 } from "node:fs";
|
|
11354
11695
|
import { cpus as cpus2 } from "node:os";
|
|
@@ -11361,10 +11702,10 @@ function resolveWorkerEntry(explicit) {
|
|
|
11361
11702
|
const isTs = override.endsWith(".ts");
|
|
11362
11703
|
return { path: override, isTs };
|
|
11363
11704
|
}
|
|
11364
|
-
const moduleDir =
|
|
11365
|
-
const jsPath =
|
|
11705
|
+
const moduleDir = dirname11(fileURLToPath3(import.meta.url));
|
|
11706
|
+
const jsPath = join27(moduleDir, "shaderTransitionWorker.js");
|
|
11366
11707
|
if (existsSync17(jsPath)) return { path: jsPath, isTs: false };
|
|
11367
|
-
const tsPath =
|
|
11708
|
+
const tsPath = join27(moduleDir, "shaderTransitionWorker.ts");
|
|
11368
11709
|
return { path: tsPath, isTs: true };
|
|
11369
11710
|
}
|
|
11370
11711
|
function buildExecArgv(entryIsTs) {
|
|
@@ -11732,7 +12073,7 @@ async function runHybridLayeredFrameLoop(input) {
|
|
|
11732
12073
|
);
|
|
11733
12074
|
if (debugDumpEnabled && debugDumpDir && i % 30 === 0) {
|
|
11734
12075
|
writeFileExclusiveSync(
|
|
11735
|
-
|
|
12076
|
+
join28(debugDumpDir, `frame_${String(i).padStart(4, "0")}_final_rgb48le.bin`),
|
|
11736
12077
|
canvas
|
|
11737
12078
|
);
|
|
11738
12079
|
}
|
|
@@ -11923,9 +12264,9 @@ async function runCaptureHdrStage(input) {
|
|
|
11923
12264
|
if (hdrVideoFrameSources.has(v.id)) hdrVideoEndTimes.set(v.id, v.end);
|
|
11924
12265
|
}
|
|
11925
12266
|
const debugDumpEnabled = process.env.KEEP_TEMP === "1";
|
|
11926
|
-
const debugDumpDir = debugDumpEnabled ?
|
|
12267
|
+
const debugDumpDir = debugDumpEnabled ? join29(framesDir, "debug-composite") : null;
|
|
11927
12268
|
if (debugDumpDir && !existsSync18(debugDumpDir)) {
|
|
11928
|
-
|
|
12269
|
+
mkdirSync10(debugDumpDir, { recursive: true });
|
|
11929
12270
|
}
|
|
11930
12271
|
const compositeTransfer = resolveCompositeTransfer(hasHdrContent, effectiveHdr);
|
|
11931
12272
|
const hdrTargetTransfer = compositeTransfer === "srgb" ? void 0 : compositeTransfer;
|
|
@@ -12082,15 +12423,15 @@ async function runCaptureHdrStage(input) {
|
|
|
12082
12423
|
import {
|
|
12083
12424
|
copyFileSync as copyFileSync4,
|
|
12084
12425
|
existsSync as existsSync19,
|
|
12085
|
-
mkdirSync as
|
|
12086
|
-
readdirSync as
|
|
12426
|
+
mkdirSync as mkdirSync11,
|
|
12427
|
+
readdirSync as readdirSync5,
|
|
12087
12428
|
readFileSync as readFileSync8,
|
|
12088
|
-
rmSync as
|
|
12089
|
-
statSync as
|
|
12429
|
+
rmSync as rmSync11,
|
|
12430
|
+
statSync as statSync8,
|
|
12090
12431
|
writeFileSync as writeFileSync6
|
|
12091
12432
|
} from "node:fs";
|
|
12092
12433
|
import { readFile as readFile2 } from "node:fs/promises";
|
|
12093
|
-
import { dirname as
|
|
12434
|
+
import { dirname as dirname12, join as join30 } from "node:path";
|
|
12094
12435
|
import {
|
|
12095
12436
|
encodeFramesChunkedConcat,
|
|
12096
12437
|
encodeFramesFromDir,
|
|
@@ -12188,7 +12529,7 @@ async function reencodeWithClearedWholeFrames(argsInput, run, startTime) {
|
|
|
12188
12529
|
}
|
|
12189
12530
|
async function encodeGifFromDir(framesDir, framePattern, outputPath, input) {
|
|
12190
12531
|
const startTime = Date.now();
|
|
12191
|
-
const files =
|
|
12532
|
+
const files = readdirSync5(framesDir).filter((file) => file.match(/\.(jpg|jpeg|png)$/i));
|
|
12192
12533
|
const frameCount = files.length;
|
|
12193
12534
|
if (frameCount === 0) {
|
|
12194
12535
|
return gifEncodeFailure(startTime, outputPath, "[FFmpeg] No frame files found in directory");
|
|
@@ -12215,7 +12556,7 @@ async function encodeGifFromDir(framesDir, framePattern, outputPath, input) {
|
|
|
12215
12556
|
const failure = await reencodeWithClearedWholeFrames(argsInput, run, startTime);
|
|
12216
12557
|
if (failure) return failure;
|
|
12217
12558
|
}
|
|
12218
|
-
const fileSize = existsSync19(outputPath) ?
|
|
12559
|
+
const fileSize = existsSync19(outputPath) ? statSync8(outputPath).size : 0;
|
|
12219
12560
|
return {
|
|
12220
12561
|
success: true,
|
|
12221
12562
|
outputPath,
|
|
@@ -12224,7 +12565,7 @@ async function encodeGifFromDir(framesDir, framePattern, outputPath, input) {
|
|
|
12224
12565
|
fileSize
|
|
12225
12566
|
};
|
|
12226
12567
|
} finally {
|
|
12227
|
-
|
|
12568
|
+
rmSync11(input.palettePath, { force: true });
|
|
12228
12569
|
}
|
|
12229
12570
|
}
|
|
12230
12571
|
function startEncodeProgress(job, stage, onProgress) {
|
|
@@ -12301,7 +12642,7 @@ function encodeWhileCapturing(input, totalFrames) {
|
|
|
12301
12642
|
await new Promise((resolve13) => wake = resolve13);
|
|
12302
12643
|
}
|
|
12303
12644
|
if (signal.aborted) return;
|
|
12304
|
-
const mergedPath =
|
|
12645
|
+
const mergedPath = join30(input.framesDir, formatCaptureFrameName(next, "jpg"));
|
|
12305
12646
|
const frame = await readLandedFrame(landed.get(next), mergedPath);
|
|
12306
12647
|
landed.delete(next);
|
|
12307
12648
|
if (!await stream.writeFrame(frame)) {
|
|
@@ -12376,19 +12717,19 @@ async function runEncodeStage(input) {
|
|
|
12376
12717
|
const stage5Start = Date.now();
|
|
12377
12718
|
if (isPngSequence) {
|
|
12378
12719
|
const encoded = startEncodeProgress(job, "Writing PNG sequence", onProgress);
|
|
12379
|
-
if (!existsSync19(outputPath))
|
|
12380
|
-
const captured =
|
|
12720
|
+
if (!existsSync19(outputPath)) mkdirSync11(outputPath, { recursive: true });
|
|
12721
|
+
const captured = readdirSync5(framesDir).filter((name) => name.endsWith(".png")).sort();
|
|
12381
12722
|
if (captured.length === 0) {
|
|
12382
12723
|
throw new Error(
|
|
12383
12724
|
`[Render] png-sequence output requested but no PNGs were captured to ${framesDir}`
|
|
12384
12725
|
);
|
|
12385
12726
|
}
|
|
12386
12727
|
captured.forEach((name, i) => {
|
|
12387
|
-
const dst =
|
|
12388
|
-
copyFileSync4(
|
|
12728
|
+
const dst = join30(outputPath, formatExportFrameName(i, "png"));
|
|
12729
|
+
copyFileSync4(join30(framesDir, name), dst);
|
|
12389
12730
|
});
|
|
12390
12731
|
if (hasAudio && audioOutputPath && existsSync19(audioOutputPath)) {
|
|
12391
|
-
copyFileSync4(audioOutputPath,
|
|
12732
|
+
copyFileSync4(audioOutputPath, join30(outputPath, MIXED_AUDIO_FILENAME2));
|
|
12392
12733
|
log.info(
|
|
12393
12734
|
`[Render] png-sequence: ${MIXED_AUDIO_FILENAME2} sidecar written to ${outputPath}/${MIXED_AUDIO_FILENAME2}`
|
|
12394
12735
|
);
|
|
@@ -12408,7 +12749,7 @@ async function runEncodeStage(input) {
|
|
|
12408
12749
|
const encodeResult2 = await encodeGifFromDir(framesDir, framePattern2, outputPath, {
|
|
12409
12750
|
fps: job.config.fps,
|
|
12410
12751
|
loop,
|
|
12411
|
-
palettePath:
|
|
12752
|
+
palettePath: join30(dirname12(videoOnlyPath), "gif-palette.png"),
|
|
12412
12753
|
preserveAlpha: needsAlpha,
|
|
12413
12754
|
signal: abortSignal,
|
|
12414
12755
|
timeout: engineCfg.ffmpegEncodeTimeout
|
|
@@ -12614,14 +12955,14 @@ function sampleDirectoryBytes(dir) {
|
|
|
12614
12955
|
if (!current) continue;
|
|
12615
12956
|
let entries = [];
|
|
12616
12957
|
try {
|
|
12617
|
-
entries =
|
|
12958
|
+
entries = readdirSync6(current);
|
|
12618
12959
|
} catch {
|
|
12619
12960
|
continue;
|
|
12620
12961
|
}
|
|
12621
12962
|
for (const name of entries) {
|
|
12622
|
-
const full =
|
|
12963
|
+
const full = join31(current, name);
|
|
12623
12964
|
try {
|
|
12624
|
-
const st =
|
|
12965
|
+
const st = statSync9(full);
|
|
12625
12966
|
if (st.isDirectory()) {
|
|
12626
12967
|
stack.push(full);
|
|
12627
12968
|
} else if (st.isFile()) {
|
|
@@ -12775,8 +13116,8 @@ function findMissingFrameRanges(totalFrames, framesDir, frameExt) {
|
|
|
12775
13116
|
const ranges = [];
|
|
12776
13117
|
let rangeStart = null;
|
|
12777
13118
|
for (let frameIndex = 0; frameIndex < totalFrames; frameIndex++) {
|
|
12778
|
-
const framePath =
|
|
12779
|
-
const missing = !existsSync20(framePath) ||
|
|
13119
|
+
const framePath = join31(framesDir, formatCaptureFrameName(frameIndex, frameExt));
|
|
13120
|
+
const missing = !existsSync20(framePath) || statSync9(framePath).size <= 8;
|
|
12780
13121
|
if (missing && rangeStart === null) {
|
|
12781
13122
|
rangeStart = frameIndex;
|
|
12782
13123
|
} else if (!missing && rangeStart !== null) {
|
|
@@ -12792,7 +13133,7 @@ function findMissingFrameRanges(totalFrames, framesDir, frameExt) {
|
|
|
12792
13133
|
function buildMissingFrameRetryBatches(ranges, maxWorkers, workDir, attempt, rangeStart = 0, stride = 1) {
|
|
12793
13134
|
const workersPerBatch = Math.max(1, Math.floor(maxWorkers));
|
|
12794
13135
|
const batches = [];
|
|
12795
|
-
const outputDirFor = (batchIndex, workerId) =>
|
|
13136
|
+
const outputDirFor = (batchIndex, workerId) => join31(workDir, `retry-${attempt}-batch-${batchIndex}-worker-${workerId}`);
|
|
12796
13137
|
if (stride > 1) {
|
|
12797
13138
|
const spans = /* @__PURE__ */ new Map();
|
|
12798
13139
|
for (const range of ranges) {
|
|
@@ -12852,8 +13193,8 @@ function getNextRetryWorkerCount(currentWorkers) {
|
|
|
12852
13193
|
return Math.max(1, Math.floor(currentWorkers / 2));
|
|
12853
13194
|
}
|
|
12854
13195
|
function resolveRenderWorkDirPrefix(outputPath, jobId, platform2 = process.platform, systemTempDir = tmpdir2()) {
|
|
12855
|
-
if (platform2 === "win32" || platform2 === "darwin") return
|
|
12856
|
-
return
|
|
13196
|
+
if (platform2 === "win32" || platform2 === "darwin") return join31(systemTempDir, "hf-render-");
|
|
13197
|
+
return join31(dirname13(outputPath), `work-${jobId}-`);
|
|
12857
13198
|
}
|
|
12858
13199
|
var MAX_TRANSIENT_CAPTURE_RETRIES = 1;
|
|
12859
13200
|
function isTransientCaptureRetryEligible(failureKind, missing, retriesUsed) {
|
|
@@ -12900,7 +13241,7 @@ The composition is too large for the available memory. To reduce memory pressure
|
|
|
12900
13241
|
function countCapturedFrames(totalFrames, framesDir, frameExt) {
|
|
12901
13242
|
let captured = 0;
|
|
12902
13243
|
for (let frameIndex = 0; frameIndex < totalFrames; frameIndex++) {
|
|
12903
|
-
const framePath =
|
|
13244
|
+
const framePath = join31(framesDir, formatCaptureFrameName(frameIndex, frameExt));
|
|
12904
13245
|
if (existsSync20(framePath)) captured++;
|
|
12905
13246
|
}
|
|
12906
13247
|
return captured;
|
|
@@ -12926,7 +13267,7 @@ async function executeDiskCaptureWithAdaptiveRetry(options) {
|
|
|
12926
13267
|
reason: attempt === 0 ? "initial" : pendingTransientRetry ? "transient-retry" : "retry"
|
|
12927
13268
|
});
|
|
12928
13269
|
pendingTransientRetry = false;
|
|
12929
|
-
const attemptWorkDir =
|
|
13270
|
+
const attemptWorkDir = join31(options.workDir, `capture-attempt-${attempt}`);
|
|
12930
13271
|
const batches = missingRanges ? buildMissingFrameRetryBatches(
|
|
12931
13272
|
missingRanges,
|
|
12932
13273
|
currentWorkers,
|
|
@@ -13558,19 +13899,19 @@ function deVerifyFallbackTelemetry(err) {
|
|
|
13558
13899
|
};
|
|
13559
13900
|
}
|
|
13560
13901
|
function resolveRenderDebugDir() {
|
|
13561
|
-
const producerRoot = process.env.PRODUCER_RENDERS_DIR ? resolve8(process.env.PRODUCER_RENDERS_DIR, "..") : resolve8(
|
|
13562
|
-
return
|
|
13902
|
+
const producerRoot = process.env.PRODUCER_RENDERS_DIR ? resolve8(process.env.PRODUCER_RENDERS_DIR, "..") : resolve8(dirname13(fileURLToPath4(import.meta.url)), "../..");
|
|
13903
|
+
return join31(producerRoot, ".debug");
|
|
13563
13904
|
}
|
|
13564
13905
|
async function executeRenderJob(job, projectDir, outputPath, progressSink, abortSignal, assertRenderActive) {
|
|
13565
13906
|
validateHlsRenderConfig(job.config);
|
|
13566
13907
|
validateDeliveryEncoderConfig(job.config);
|
|
13567
13908
|
const debugDir = resolveRenderDebugDir();
|
|
13568
|
-
const outputDir =
|
|
13569
|
-
if (!existsSync20(outputDir))
|
|
13570
|
-
const workDir = job.config.debug ?
|
|
13909
|
+
const outputDir = dirname13(outputPath);
|
|
13910
|
+
if (!existsSync20(outputDir)) mkdirSync12(outputDir, { recursive: true });
|
|
13911
|
+
const workDir = job.config.debug ? join31(debugDir, job.id) : createRenderWorkDir(resolveRenderWorkDirPrefix(outputPath, job.id), outputDir);
|
|
13571
13912
|
const pipelineStart = Date.now();
|
|
13572
13913
|
const baseLog = job.config.logger ?? defaultLogger;
|
|
13573
|
-
const logPath = job.config.debug ?
|
|
13914
|
+
const logPath = job.config.debug ? join31(workDir, "render.log") : null;
|
|
13574
13915
|
const execution = new RenderExecutionContext({
|
|
13575
13916
|
request: { renderJobId: job.id, projectDir, outputPath },
|
|
13576
13917
|
logger: logPath ? createRenderFileLogger(logPath, baseLog) : baseLog,
|
|
@@ -13584,7 +13925,7 @@ async function executeRenderJob(job, projectDir, outputPath, progressSink, abort
|
|
|
13584
13925
|
log.info("KEEP_TEMP=1, leaving workDir on disk for inspection", { workDir });
|
|
13585
13926
|
return;
|
|
13586
13927
|
}
|
|
13587
|
-
|
|
13928
|
+
rmSync12(workDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
|
|
13588
13929
|
});
|
|
13589
13930
|
try {
|
|
13590
13931
|
await executeRenderPipeline({
|
|
@@ -13632,7 +13973,7 @@ async function executeRenderPipeline(input) {
|
|
|
13632
13973
|
imageDecodeFailures: 0
|
|
13633
13974
|
};
|
|
13634
13975
|
let hdrPerf;
|
|
13635
|
-
const perfOutputPath =
|
|
13976
|
+
const perfOutputPath = join31(workDir, "perf-summary.json");
|
|
13636
13977
|
const cfg = { ...job.config.producerConfig ?? resolveConfig2() };
|
|
13637
13978
|
const observability = new RenderObservabilityRecorder({
|
|
13638
13979
|
pipelineStartMs: pipelineStart,
|
|
@@ -13708,7 +14049,7 @@ async function executeRenderPipeline(input) {
|
|
|
13708
14049
|
job.startedAt = /* @__PURE__ */ new Date();
|
|
13709
14050
|
assertNotAborted();
|
|
13710
14051
|
assertConfiguredFfmpegBinariesExist();
|
|
13711
|
-
if (!existsSync20(workDir))
|
|
14052
|
+
if (!existsSync20(workDir)) mkdirSync12(workDir, { recursive: true });
|
|
13712
14053
|
if (job.config.debug) {
|
|
13713
14054
|
log.info("[Render] Debug artifacts enabled", { workDir, logPath });
|
|
13714
14055
|
}
|
|
@@ -13737,7 +14078,7 @@ async function executeRenderPipeline(input) {
|
|
|
13737
14078
|
requestedWorkers: job.config.workers ?? "auto"
|
|
13738
14079
|
});
|
|
13739
14080
|
const entryFile = job.config.entryFile || "index.html";
|
|
13740
|
-
const htmlPath =
|
|
14081
|
+
const htmlPath = join31(projectDir, entryFile);
|
|
13741
14082
|
if (!existsSync20(htmlPath)) {
|
|
13742
14083
|
throw new Error(`Entry file not found: ${htmlPath}`);
|
|
13743
14084
|
}
|
|
@@ -13914,7 +14255,7 @@ async function executeRenderPipeline(input) {
|
|
|
13914
14255
|
canvas: { width, height },
|
|
13915
14256
|
page: probeSession.page,
|
|
13916
14257
|
projectDir,
|
|
13917
|
-
compiledDir:
|
|
14258
|
+
compiledDir: join31(workDir, "compiled")
|
|
13918
14259
|
});
|
|
13919
14260
|
if (videoLayerPlan.videoId === void 0) {
|
|
13920
14261
|
log.info(`[Render] Video layer passthrough off: ${videoLayerPlan.reason}`);
|
|
@@ -13932,7 +14273,22 @@ async function executeRenderPipeline(input) {
|
|
|
13932
14273
|
updateJobStatus(job, "preprocessing", "Extracting video frames", 10, onProgress, {
|
|
13933
14274
|
code: "extract_video"
|
|
13934
14275
|
});
|
|
13935
|
-
const compiledDir =
|
|
14276
|
+
const compiledDir = join31(workDir, "compiled");
|
|
14277
|
+
const segmentsCapture = (layeredOrEffectRoute) => shouldSegmentCapture({
|
|
14278
|
+
env: process.env,
|
|
14279
|
+
durationSeconds: probeResult.duration,
|
|
14280
|
+
outputFormat,
|
|
14281
|
+
layeredOrEffectRoute,
|
|
14282
|
+
streamingOk: shouldUseStreamingEncode(cfg, outputFormat, 1, probeResult.duration)
|
|
14283
|
+
});
|
|
14284
|
+
const segmentedForWindow = segmentsCapture(compiled.hasShaderTransitions);
|
|
14285
|
+
const windowVideoFrames = job.config.motionBlur === void 0 && shouldWindowVideoFrames({ env: process.env, segmented: segmentedForWindow });
|
|
14286
|
+
if (!windowVideoFrames && shouldWindowVideoFrames({ env: process.env, segmented: true })) {
|
|
14287
|
+
const reason = job.config.motionBlur !== void 0 ? "motion blur" : outputFormat !== "mp4" && outputFormat !== "mov" ? `${outputFormat} output` : "capture is not segmented";
|
|
14288
|
+
log.info("[Render] HF_VIDEO_FRAME_WINDOW is set but this render extracts every frame", {
|
|
14289
|
+
reason
|
|
14290
|
+
});
|
|
14291
|
+
}
|
|
13936
14292
|
const extractResult = await observeRenderStage(
|
|
13937
14293
|
observability,
|
|
13938
14294
|
"video_extract",
|
|
@@ -13949,6 +14305,7 @@ async function executeRenderPipeline(input) {
|
|
|
13949
14305
|
assertNotAborted,
|
|
13950
14306
|
// Local staging can use links; distributed plan() alone requires real copies.
|
|
13951
14307
|
materializeSymlinks: false,
|
|
14308
|
+
deferRangeExtraction: windowVideoFrames ? true : void 0,
|
|
13952
14309
|
onProgress
|
|
13953
14310
|
})
|
|
13954
14311
|
);
|
|
@@ -14045,7 +14402,7 @@ async function executeRenderPipeline(input) {
|
|
|
14045
14402
|
try {
|
|
14046
14403
|
fileServer = await createFileServer({
|
|
14047
14404
|
projectDir,
|
|
14048
|
-
compiledDir:
|
|
14405
|
+
compiledDir: join31(workDir, "compiled"),
|
|
14049
14406
|
port: 0,
|
|
14050
14407
|
preHeadScripts: [VIRTUAL_TIME_SHIM],
|
|
14051
14408
|
fps: job.config.fps
|
|
@@ -14063,8 +14420,8 @@ async function executeRenderPipeline(input) {
|
|
|
14063
14420
|
if (!activeFileServer) {
|
|
14064
14421
|
throw new Error("File server failed to initialize before frame capture");
|
|
14065
14422
|
}
|
|
14066
|
-
const framesDir =
|
|
14067
|
-
if (!existsSync20(framesDir))
|
|
14423
|
+
const framesDir = join31(workDir, "captured-frames");
|
|
14424
|
+
if (!existsSync20(framesDir)) mkdirSync12(framesDir, { recursive: true });
|
|
14068
14425
|
updateJobStatus(job, "rendering", "Checking browser GPU", 25, onProgress, {
|
|
14069
14426
|
code: "start_browsers"
|
|
14070
14427
|
});
|
|
@@ -14142,7 +14499,55 @@ async function executeRenderPipeline(input) {
|
|
|
14142
14499
|
});
|
|
14143
14500
|
const buildPrepCaptureOptions = () => stopPrepSessionOnScriptFailure(buildCaptureOptions());
|
|
14144
14501
|
const buildWorkerCaptureOptions = () => withScriptFailureStop(buildCaptureOptions(), job, log);
|
|
14145
|
-
|
|
14502
|
+
let videoFrameWindow = windowVideoFrames && (extractionResult?.extracted.some((video) => video.deferredRange) ?? false);
|
|
14503
|
+
if (windowVideoFrames) {
|
|
14504
|
+
const extracted = extractionResult?.extracted ?? [];
|
|
14505
|
+
log.info("[Render] Video frame window", {
|
|
14506
|
+
windowed: extracted.filter((video) => video.deferredRange).length,
|
|
14507
|
+
fullyExtracted: extracted.filter((video) => !video.deferredRange).length
|
|
14508
|
+
});
|
|
14509
|
+
}
|
|
14510
|
+
const buildSegmentVideoFrames = () => trackSegmentFramePeak(
|
|
14511
|
+
createSegmentVideoFrames({
|
|
14512
|
+
videos: composition.videos,
|
|
14513
|
+
extracted: extractionResult?.extracted ?? [],
|
|
14514
|
+
fps: job.config.fps,
|
|
14515
|
+
rootDir: workDir,
|
|
14516
|
+
cfg,
|
|
14517
|
+
injectorOptions: frameCacheOptions(cfg, workerCount),
|
|
14518
|
+
abortSignal: executionSignal
|
|
14519
|
+
}),
|
|
14520
|
+
workDir,
|
|
14521
|
+
(bytes) => updateCaptureObservability({ videoFrameWindowPeakBytes: bytes })
|
|
14522
|
+
);
|
|
14523
|
+
const createRenderVideoFrameInjector = () => createVideoFrameInjector2(frameLookup, frameCacheOptions(cfg, workerCount));
|
|
14524
|
+
const extractDeferredVideoFrames = async () => {
|
|
14525
|
+
videoFrameWindow = false;
|
|
14526
|
+
updateCaptureObservability({ videoFrameWindow: "off" });
|
|
14527
|
+
const extracted = extractionResult?.extracted ?? [];
|
|
14528
|
+
const videoCount = extracted.filter((video) => video.deferredRange).length;
|
|
14529
|
+
const startedAt = Date.now();
|
|
14530
|
+
log.info("[Render] Extracting the frames of the videos a window would have held back", {
|
|
14531
|
+
videoCount
|
|
14532
|
+
});
|
|
14533
|
+
await observeRenderStage(
|
|
14534
|
+
observability,
|
|
14535
|
+
"video_extract_deferred",
|
|
14536
|
+
{ videoCount },
|
|
14537
|
+
() => extractAllDeferredFrames(extracted, {
|
|
14538
|
+
fps: job.config.fps,
|
|
14539
|
+
cfg,
|
|
14540
|
+
signal: executionSignal,
|
|
14541
|
+
maxTransientRetries: resolveVideoExtractionPolicy().maxTransientRetries,
|
|
14542
|
+
onProgress: (done, total) => reportExtractProgress(job, done, total, onProgress)
|
|
14543
|
+
})
|
|
14544
|
+
);
|
|
14545
|
+
assertNotAborted();
|
|
14546
|
+
log.info("[Render] Extracted the deferred video frames", {
|
|
14547
|
+
videoCount,
|
|
14548
|
+
elapsedMs: Date.now() - startedAt
|
|
14549
|
+
});
|
|
14550
|
+
};
|
|
14146
14551
|
let captureCalibration;
|
|
14147
14552
|
const htmlInCanvasDetected = compiled.renderModeHints.reasons.some(
|
|
14148
14553
|
(r) => r.code === "htmlInCanvas"
|
|
@@ -14308,32 +14713,41 @@ async function executeRenderPipeline(input) {
|
|
|
14308
14713
|
updateJobStatus(job, "rendering", "Measuring capture speed", 25, onProgress, {
|
|
14309
14714
|
code: "start_browsers"
|
|
14310
14715
|
});
|
|
14311
|
-
const
|
|
14312
|
-
|
|
14313
|
-
"
|
|
14314
|
-
|
|
14315
|
-
|
|
14316
|
-
|
|
14317
|
-
|
|
14318
|
-
|
|
14319
|
-
|
|
14320
|
-
|
|
14321
|
-
|
|
14322
|
-
|
|
14323
|
-
|
|
14324
|
-
|
|
14325
|
-
|
|
14326
|
-
|
|
14327
|
-
|
|
14328
|
-
|
|
14329
|
-
|
|
14330
|
-
|
|
14331
|
-
|
|
14332
|
-
|
|
14333
|
-
|
|
14334
|
-
|
|
14335
|
-
|
|
14336
|
-
|
|
14716
|
+
const calibrationFrames = videoFrameWindow ? await buildSegmentVideoFrames().forFrames(
|
|
14717
|
+
selectCaptureCalibrationFrames(totalFrames),
|
|
14718
|
+
"calibration"
|
|
14719
|
+
) : void 0;
|
|
14720
|
+
let outcome;
|
|
14721
|
+
try {
|
|
14722
|
+
outcome = await observeRenderStage(
|
|
14723
|
+
observability,
|
|
14724
|
+
"capture_calibration",
|
|
14725
|
+
captureStageObservationData({
|
|
14726
|
+
forceScreenshot: captureForceScreenshot,
|
|
14727
|
+
// Override the default `capturing` — calibration writes probe
|
|
14728
|
+
// frames only, not `job.framesRendered`, so heartbeats reporting
|
|
14729
|
+
// `framesCompleted: 0` misread as broken. Field signal
|
|
14730
|
+
// ts=1784019503.
|
|
14731
|
+
stagePhase: "calibrating"
|
|
14732
|
+
}),
|
|
14733
|
+
() => runCaptureCalibration({
|
|
14734
|
+
cfg: calibrationCaptureConfig(cfg),
|
|
14735
|
+
fileServer: activeFileServer,
|
|
14736
|
+
workDir,
|
|
14737
|
+
log,
|
|
14738
|
+
job,
|
|
14739
|
+
totalFrames,
|
|
14740
|
+
forceScreenshot: captureForceScreenshot,
|
|
14741
|
+
probeSession,
|
|
14742
|
+
buildCaptureOptions: buildPrepCaptureOptions,
|
|
14743
|
+
createRenderVideoFrameInjector: calibrationFrames ? calibrationFrames.injector : createRenderVideoFrameInjector,
|
|
14744
|
+
assertNotAborted
|
|
14745
|
+
}),
|
|
14746
|
+
{ heartbeatMessage: "browser calibrating (frames not started)" }
|
|
14747
|
+
);
|
|
14748
|
+
} finally {
|
|
14749
|
+
calibrationFrames?.release();
|
|
14750
|
+
}
|
|
14337
14751
|
captureCalibration = outcome.calibration;
|
|
14338
14752
|
calibrationEvaluateTimedOut = outcome.calibrationEvaluateTimedOut;
|
|
14339
14753
|
captureForceScreenshot = outcome.forceScreenshot;
|
|
@@ -14565,7 +14979,7 @@ async function executeRenderPipeline(input) {
|
|
|
14565
14979
|
hls: ".mp4"
|
|
14566
14980
|
};
|
|
14567
14981
|
const videoExt = FORMAT_EXT[outputFormat] ?? ".mp4";
|
|
14568
|
-
const videoOnlyPath =
|
|
14982
|
+
const videoOnlyPath = join31(workDir, `video-only${videoExt}`);
|
|
14569
14983
|
const usePageSideCompositingForTransitions = (cfg.enablePageSideCompositing || isGif) && compiled.hasShaderTransitions && !hasHdrContent && outputSupportsPageSideShaderCompositing(outputFormat);
|
|
14570
14984
|
if (usePageSideCompositingForTransitions) {
|
|
14571
14985
|
activeFileServer.addPreHeadScript(HF_PAGE_SIDE_COMPOSITING_STUB);
|
|
@@ -14666,13 +15080,7 @@ async function executeRenderPipeline(input) {
|
|
|
14666
15080
|
// excluded routes are the ones whose concat-copy or capture loop the
|
|
14667
15081
|
// segment contract does not cover (webm VP9 concat is fragile, HDR and
|
|
14668
15082
|
// shader transitions run their own compositor).
|
|
14669
|
-
useSegmentedCapture:
|
|
14670
|
-
env: process.env,
|
|
14671
|
-
durationSeconds: job.duration,
|
|
14672
|
-
outputFormat,
|
|
14673
|
-
layeredOrEffectRoute: hasHdrContent || compiled.hasShaderTransitions,
|
|
14674
|
-
streamingOk: shouldUseStreamingEncode(cfg, outputFormat, 1, job.duration)
|
|
14675
|
-
}),
|
|
15083
|
+
useSegmentedCapture: segmentsCapture(hasHdrContent || compiled.hasShaderTransitions),
|
|
14676
15084
|
useLayeredComposite,
|
|
14677
15085
|
usePageSideCompositing: usePageSideCompositingForTransitions,
|
|
14678
15086
|
hasHdrContent,
|
|
@@ -14712,10 +15120,14 @@ async function executeRenderPipeline(input) {
|
|
|
14712
15120
|
deWorkerInversion,
|
|
14713
15121
|
deParallelRouter
|
|
14714
15122
|
});
|
|
15123
|
+
if (videoFrameWindow && capturePlan.kind !== "sdr_segmented")
|
|
15124
|
+
await extractDeferredVideoFrames();
|
|
15125
|
+
updateCaptureObservability({ videoFrameWindow: videoFrameWindow ? "on" : "off" });
|
|
14715
15126
|
observability.checkpoint("capture_strategy", "resolved", {
|
|
14716
15127
|
plan: capturePlan.kind,
|
|
14717
15128
|
workerCount: capturePlan.workerCount,
|
|
14718
15129
|
forceScreenshot: capturePlan.forceScreenshot,
|
|
15130
|
+
videoFrameWindow: videoFrameWindow ? "on" : "off",
|
|
14719
15131
|
captureBeyondViewport: resolvedCaptureBeyondViewport ?? null,
|
|
14720
15132
|
useStreamingEncode: capturePlan.kind === "sdr_streaming",
|
|
14721
15133
|
useLayeredComposite: capturePlan.kind === "hdr_layered",
|
|
@@ -14853,7 +15265,7 @@ async function executeRenderPipeline(input) {
|
|
|
14853
15265
|
pageMotionBlur: job.config.pageMotionBlur,
|
|
14854
15266
|
captureQuality: captureOptions.quality
|
|
14855
15267
|
});
|
|
14856
|
-
const segmentDir = segmentDirFor(
|
|
15268
|
+
const segmentDir = segmentDirFor(join31(projectDir, "renders"), segmentPlanHash);
|
|
14857
15269
|
let completedSegments = /* @__PURE__ */ new Set();
|
|
14858
15270
|
if (job.config.resumeSegments === true) {
|
|
14859
15271
|
const existing = readSegmentManifest(segmentDir);
|
|
@@ -14868,10 +15280,10 @@ async function executeRenderPipeline(input) {
|
|
|
14868
15280
|
});
|
|
14869
15281
|
}
|
|
14870
15282
|
if (completedSegments.size === 0) {
|
|
14871
|
-
|
|
15283
|
+
rmSync12(segmentDir, { recursive: true, force: true });
|
|
14872
15284
|
}
|
|
14873
15285
|
} else {
|
|
14874
|
-
|
|
15286
|
+
rmSync12(segmentDir, { recursive: true, force: true });
|
|
14875
15287
|
}
|
|
14876
15288
|
const emptySegmentManifest = () => ({
|
|
14877
15289
|
version: 1,
|
|
@@ -14921,6 +15333,7 @@ async function executeRenderPipeline(input) {
|
|
|
14921
15333
|
},
|
|
14922
15334
|
buildCaptureOptions: buildWorkerCaptureOptions,
|
|
14923
15335
|
createRenderVideoFrameInjector,
|
|
15336
|
+
segmentVideoFrames: videoFrameWindow ? buildSegmentVideoFrames() : void 0,
|
|
14924
15337
|
abortSignal: executionSignal,
|
|
14925
15338
|
assertNotAborted,
|
|
14926
15339
|
onProgress,
|
|
@@ -14972,7 +15385,7 @@ async function executeRenderPipeline(input) {
|
|
|
14972
15385
|
probeSession = null;
|
|
14973
15386
|
await closeOrphanedProbeForRetry(orphaned, closeCaptureSession8, log, "segmented");
|
|
14974
15387
|
}
|
|
14975
|
-
|
|
15388
|
+
rmSync12(segmentDir, { recursive: true, force: true });
|
|
14976
15389
|
completedSegments = /* @__PURE__ */ new Set();
|
|
14977
15390
|
segmentManifest = emptySegmentManifest();
|
|
14978
15391
|
dedupPerfs.length = 0;
|
|
@@ -15011,11 +15424,12 @@ async function executeRenderPipeline(input) {
|
|
|
15011
15424
|
);
|
|
15012
15425
|
updateCaptureObservability({ segmentRetries: segmentedRes.segmentRetries });
|
|
15013
15426
|
if (job.config.keepSegments !== true) {
|
|
15014
|
-
|
|
15427
|
+
rmSync12(segmentDir, { recursive: true, force: true });
|
|
15015
15428
|
}
|
|
15016
15429
|
} else {
|
|
15017
15430
|
capturePlan = replanAfterFailure(capturePlan, { kind: "streaming_unavailable" });
|
|
15018
15431
|
syncCapturePlan();
|
|
15432
|
+
if (videoFrameWindow) await extractDeferredVideoFrames();
|
|
15019
15433
|
probeSession = null;
|
|
15020
15434
|
observability.checkpoint(
|
|
15021
15435
|
"capture_segmented",
|
|
@@ -15286,8 +15700,8 @@ async function executeRenderPipeline(input) {
|
|
|
15286
15700
|
"capture_disk",
|
|
15287
15701
|
"drawElement capture failed; retrying with a fresh screenshot session"
|
|
15288
15702
|
);
|
|
15289
|
-
|
|
15290
|
-
|
|
15703
|
+
rmSync12(framesDir, { recursive: true, force: true });
|
|
15704
|
+
mkdirSync12(framesDir, { recursive: true });
|
|
15291
15705
|
resetCaptureAttemptProgress(job);
|
|
15292
15706
|
dedupPerfs.length = 0;
|
|
15293
15707
|
cfg.useDrawElement = false;
|
|
@@ -15474,7 +15888,7 @@ async function executeRenderPipeline(input) {
|
|
|
15474
15888
|
}
|
|
15475
15889
|
if (job.config.debug) {
|
|
15476
15890
|
if (!isPngSequence && !isHls && existsSync20(stagedOutputPath)) {
|
|
15477
|
-
const debugOutput =
|
|
15891
|
+
const debugOutput = join31(workDir, `output${videoExt}`);
|
|
15478
15892
|
copyFileSync5(stagedOutputPath, debugOutput);
|
|
15479
15893
|
}
|
|
15480
15894
|
}
|
|
@@ -15588,15 +16002,15 @@ import {
|
|
|
15588
16002
|
REFERENCE_BACKGROUND_FIELD as REFERENCE_BACKGROUND_FIELD4,
|
|
15589
16003
|
referenceBackgroundResult as referenceBackgroundResult6
|
|
15590
16004
|
} from "@hyperframes/engine";
|
|
15591
|
-
import { mkdirSync as
|
|
15592
|
-
import { join as
|
|
16005
|
+
import { mkdirSync as mkdirSync20, mkdtempSync as mkdtempSync7, readFileSync as readFileSync14, rmSync as rmSync18 } from "node:fs";
|
|
16006
|
+
import { join as join45 } from "node:path";
|
|
15593
16007
|
import { tmpdir as tmpdir4 } from "node:os";
|
|
15594
16008
|
|
|
15595
16009
|
// src/services/distributed/mp4Stitch.ts
|
|
15596
|
-
import { createReadStream as createReadStream3, createWriteStream, mkdirSync as
|
|
16010
|
+
import { createReadStream as createReadStream3, createWriteStream, mkdirSync as mkdirSync13, mkdtempSync as mkdtempSync4, rmSync as rmSync13 } from "node:fs";
|
|
15597
16011
|
import { open } from "node:fs/promises";
|
|
15598
16012
|
import { renameWithRetry as renameWithRetry7 } from "@hyperframes/engine";
|
|
15599
|
-
import { dirname as
|
|
16013
|
+
import { dirname as dirname14, join as join32 } from "node:path";
|
|
15600
16014
|
import { finished, pipeline } from "node:stream/promises";
|
|
15601
16015
|
function boxAt(src, off, at, limit) {
|
|
15602
16016
|
let size = src.readUInt32BE(off);
|
|
@@ -15960,18 +16374,18 @@ async function writeStitchedMp4(chunkPaths, outputPath, audioPath) {
|
|
|
15960
16374
|
const sourcePaths = audioPath ? [audioPath, ...chunkPaths] : chunkPaths;
|
|
15961
16375
|
const layouts = await Promise.all(sourcePaths.map((path) => readMp4Layout(fileRange(path))));
|
|
15962
16376
|
const header = audioPath ? buildStitchedHeader(layouts.slice(1), layouts[0]) : buildStitchedHeader(layouts);
|
|
15963
|
-
|
|
16377
|
+
mkdirSync13(dirname14(outputPath), { recursive: true });
|
|
15964
16378
|
const workDir = mkdtempSync4(`${outputPath}.stitch-`);
|
|
15965
|
-
const out = createWriteStream(
|
|
16379
|
+
const out = createWriteStream(join32(workDir, "out.mp4"));
|
|
15966
16380
|
try {
|
|
15967
16381
|
out.write(header);
|
|
15968
16382
|
for (const [i, path] of sourcePaths.entries()) await copyPayload(path, layouts[i], i, out);
|
|
15969
16383
|
out.end();
|
|
15970
16384
|
await finished(out);
|
|
15971
|
-
renameWithRetry7(
|
|
16385
|
+
renameWithRetry7(join32(workDir, "out.mp4"), outputPath);
|
|
15972
16386
|
} finally {
|
|
15973
16387
|
out.destroy();
|
|
15974
|
-
|
|
16388
|
+
rmSync13(workDir, { recursive: true, force: true });
|
|
15975
16389
|
}
|
|
15976
16390
|
}
|
|
15977
16391
|
|
|
@@ -15987,11 +16401,11 @@ import {
|
|
|
15987
16401
|
cpSync as cpSync2,
|
|
15988
16402
|
existsSync as existsSync25,
|
|
15989
16403
|
linkSync,
|
|
15990
|
-
mkdirSync as
|
|
15991
|
-
rmSync as
|
|
16404
|
+
mkdirSync as mkdirSync16,
|
|
16405
|
+
rmSync as rmSync14,
|
|
15992
16406
|
writeFileSync as writeFileSync10
|
|
15993
16407
|
} from "node:fs";
|
|
15994
|
-
import { extname as extname6, isAbsolute as isAbsolute3, join as
|
|
16408
|
+
import { extname as extname6, isAbsolute as isAbsolute3, join as join40, relative as relative5, sep as sep2 } from "node:path";
|
|
15995
16409
|
import {
|
|
15996
16410
|
fpsToNumber as fpsToNumber9,
|
|
15997
16411
|
HF_COLOR_GRADING_ATTR as HF_COLOR_GRADING_ATTR2
|
|
@@ -16007,27 +16421,25 @@ import {
|
|
|
16007
16421
|
} from "@hyperframes/engine";
|
|
16008
16422
|
|
|
16009
16423
|
// src/services/distributed/planFrameCost.ts
|
|
16010
|
-
import { join as
|
|
16424
|
+
import { join as join35 } from "node:path";
|
|
16011
16425
|
import {
|
|
16012
16426
|
closeCaptureSession as closeCaptureSession9,
|
|
16013
16427
|
createCaptureSession as createCaptureSession9,
|
|
16014
|
-
createFrameLookupTable as
|
|
16428
|
+
createFrameLookupTable as createFrameLookupTable4,
|
|
16015
16429
|
initializeSession as initializeSession9
|
|
16016
16430
|
} from "@hyperframes/engine";
|
|
16017
16431
|
|
|
16018
16432
|
// src/services/distributed/chunkFrames.ts
|
|
16019
|
-
import { existsSync as existsSync22, readdirSync as
|
|
16020
|
-
import { extname as extname4, join as
|
|
16433
|
+
import { existsSync as existsSync22, readdirSync as readdirSync7 } from "node:fs";
|
|
16434
|
+
import { extname as extname4, join as join34 } from "node:path";
|
|
16021
16435
|
import {
|
|
16022
16436
|
compositionRequiresWebGpu as compositionRequiresWebGpu3,
|
|
16023
|
-
|
|
16024
|
-
createVideoFrameInjector as createVideoFrameInjector2,
|
|
16025
|
-
extractVideoFramesRange
|
|
16437
|
+
createVideoFrameInjector as createVideoFrameInjector3
|
|
16026
16438
|
} from "@hyperframes/engine";
|
|
16027
16439
|
|
|
16028
16440
|
// src/services/distributed/shared.ts
|
|
16029
16441
|
import { execFile as execFileCallback } from "node:child_process";
|
|
16030
|
-
import { dirname as
|
|
16442
|
+
import { dirname as dirname15, join as join33 } from "node:path";
|
|
16031
16443
|
import { existsSync as existsSync21, readFileSync as readFileSync9 } from "node:fs";
|
|
16032
16444
|
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
16033
16445
|
import { promisify as promisify2 } from "node:util";
|
|
@@ -16043,7 +16455,7 @@ function isPlanAudioArtifactPath(path) {
|
|
|
16043
16455
|
}
|
|
16044
16456
|
function resolvePlanAudioPath(planDir) {
|
|
16045
16457
|
for (const name of [PLAN_AUDIO_RELATIVE_PATH, PLAN_AUDIO_LEGACY_RELATIVE_PATH]) {
|
|
16046
|
-
const candidate =
|
|
16458
|
+
const candidate = join33(planDir, name);
|
|
16047
16459
|
if (existsSync21(candidate)) return candidate;
|
|
16048
16460
|
}
|
|
16049
16461
|
return null;
|
|
@@ -16301,10 +16713,10 @@ function buildSyntheticRenderJob(input) {
|
|
|
16301
16713
|
var cachedProducerVersion = null;
|
|
16302
16714
|
function readProducerVersion() {
|
|
16303
16715
|
if (cachedProducerVersion !== null) return cachedProducerVersion;
|
|
16304
|
-
const startDir =
|
|
16716
|
+
const startDir = dirname15(fileURLToPath5(import.meta.url));
|
|
16305
16717
|
let current = startDir;
|
|
16306
16718
|
for (let i = 0; i < 10; i++) {
|
|
16307
|
-
const candidate =
|
|
16719
|
+
const candidate = join33(current, "package.json");
|
|
16308
16720
|
if (existsSync21(candidate)) {
|
|
16309
16721
|
try {
|
|
16310
16722
|
const pkg = JSON.parse(readFileSync9(candidate, "utf-8"));
|
|
@@ -16315,7 +16727,7 @@ function readProducerVersion() {
|
|
|
16315
16727
|
} catch {
|
|
16316
16728
|
}
|
|
16317
16729
|
}
|
|
16318
|
-
const parent =
|
|
16730
|
+
const parent = dirname15(current);
|
|
16319
16731
|
if (parent === current) break;
|
|
16320
16732
|
current = parent;
|
|
16321
16733
|
}
|
|
@@ -16340,11 +16752,11 @@ var RenderChunkValidationError = class extends Error {
|
|
|
16340
16752
|
};
|
|
16341
16753
|
function createChunkVideoFrameInjectorFactory(frameLookup, options) {
|
|
16342
16754
|
if (!(frameLookup instanceof Promise))
|
|
16343
|
-
return () =>
|
|
16755
|
+
return () => createVideoFrameInjector3(frameLookup, options);
|
|
16344
16756
|
return () => {
|
|
16345
16757
|
let hook;
|
|
16346
16758
|
return async (page, time, heldVideoTime) => {
|
|
16347
|
-
hook ??=
|
|
16759
|
+
hook ??= createVideoFrameInjector3(await frameLookup, options);
|
|
16348
16760
|
await hook?.(page, time, heldVideoTime);
|
|
16349
16761
|
};
|
|
16350
16762
|
};
|
|
@@ -16358,14 +16770,14 @@ function frameNumberFromFileName(name) {
|
|
|
16358
16770
|
function rebuildExtractedFramesFromPlanDir(planDir, videos, indexMode = "dense-v1") {
|
|
16359
16771
|
const result = [];
|
|
16360
16772
|
for (const v of videos) {
|
|
16361
|
-
const outputDir =
|
|
16773
|
+
const outputDir = join34(planDir, "video-frames", v.videoId);
|
|
16362
16774
|
if (!existsSync22(outputDir)) {
|
|
16363
16775
|
throw new Error(
|
|
16364
16776
|
`[renderChunk] planDir missing extracted video frames for ${JSON.stringify(v.videoId)}: ${outputDir} not present. plan() should have written frames here; the planDir is malformed.`
|
|
16365
16777
|
);
|
|
16366
16778
|
}
|
|
16367
16779
|
const ext = (extname4(v.framePattern) || ".jpg").toLowerCase();
|
|
16368
|
-
const frames =
|
|
16780
|
+
const frames = readdirSync7(outputDir).filter((name) => name.toLowerCase().endsWith(ext)).sort((left, right) => {
|
|
16369
16781
|
const leftNumber = frameNumberFromFileName(left);
|
|
16370
16782
|
const rightNumber = frameNumberFromFileName(right);
|
|
16371
16783
|
if (leftNumber === null || rightNumber === null) return left.localeCompare(right);
|
|
@@ -16377,7 +16789,7 @@ function rebuildExtractedFramesFromPlanDir(planDir, videos, indexMode = "dense-v
|
|
|
16377
16789
|
if (!frameName) continue;
|
|
16378
16790
|
const frameNumber = indexMode === "sparse-v2" ? frameNumberFromFileName(frameName) : null;
|
|
16379
16791
|
const frameIndex = frameNumber === null ? i : frameNumber - 1;
|
|
16380
|
-
framePaths.set(frameIndex,
|
|
16792
|
+
framePaths.set(frameIndex, join34(outputDir, frameName));
|
|
16381
16793
|
}
|
|
16382
16794
|
result.push({
|
|
16383
16795
|
videoId: v.videoId,
|
|
@@ -16394,15 +16806,6 @@ function rebuildExtractedFramesFromPlanDir(planDir, videos, indexMode = "dense-v
|
|
|
16394
16806
|
}
|
|
16395
16807
|
return result;
|
|
16396
16808
|
}
|
|
16397
|
-
function contiguousRuns(sortedIndexes) {
|
|
16398
|
-
const runs2 = [];
|
|
16399
|
-
for (const index of sortedIndexes) {
|
|
16400
|
-
const last = runs2[runs2.length - 1];
|
|
16401
|
-
if (last && last.firstFrame + last.frames === index) last.frames++;
|
|
16402
|
-
else runs2.push({ firstFrame: index, frames: 1 });
|
|
16403
|
-
}
|
|
16404
|
-
return runs2;
|
|
16405
|
-
}
|
|
16406
16809
|
function buildChunkCaptureOptions(input) {
|
|
16407
16810
|
const { dimensions, encoder } = input;
|
|
16408
16811
|
const videoCaptureBeyondViewport = resolveVideoCaptureBeyondViewport(input.videoCount);
|
|
@@ -16425,28 +16828,19 @@ function buildChunkCaptureOptions(input) {
|
|
|
16425
16828
|
requiresWebGpu: compositionRequiresWebGpu3(input.html)
|
|
16426
16829
|
};
|
|
16427
16830
|
}
|
|
16428
|
-
function
|
|
16429
|
-
|
|
16430
|
-
|
|
16431
|
-
|
|
16432
|
-
|
|
16433
|
-
|
|
16434
|
-
|
|
16435
|
-
|
|
16436
|
-
|
|
16437
|
-
|
|
16438
|
-
|
|
16439
|
-
|
|
16831
|
+
function planExtractedFrames(planVideos) {
|
|
16832
|
+
return planVideos.extracted.map((video) => ({
|
|
16833
|
+
...video,
|
|
16834
|
+
outputDir: "",
|
|
16835
|
+
framePaths: /* @__PURE__ */ new Map()
|
|
16836
|
+
}));
|
|
16837
|
+
}
|
|
16838
|
+
function shownPlanFrameIndexes(planVideos, frames, fps) {
|
|
16839
|
+
return shownDeferredFrameIndexes(
|
|
16840
|
+
{ videos: planVideos.videos, extracted: planExtractedFrames(planVideos) },
|
|
16841
|
+
frames,
|
|
16842
|
+
fps
|
|
16440
16843
|
);
|
|
16441
|
-
for (const frame of frames) {
|
|
16442
|
-
const globalTime = frame * fps.den / fps.num;
|
|
16443
|
-
for (const [videoId, payload] of table.getActiveFramePayloads(globalTime)) {
|
|
16444
|
-
const indexes = shown.get(videoId) ?? /* @__PURE__ */ new Set();
|
|
16445
|
-
indexes.add(payload.frameIndex);
|
|
16446
|
-
shown.set(videoId, indexes);
|
|
16447
|
-
}
|
|
16448
|
-
}
|
|
16449
|
-
return shown;
|
|
16450
16844
|
}
|
|
16451
16845
|
async function extractDeferredVideoFramesForChunk(input) {
|
|
16452
16846
|
const deferred = input.planVideos.extracted.filter((video) => video.deferredRange);
|
|
@@ -16463,49 +16857,32 @@ async function extractDeferredVideoFramesForChunk(input) {
|
|
|
16463
16857
|
url: remoteSources[path].split("?")[0]
|
|
16464
16858
|
});
|
|
16465
16859
|
}
|
|
16466
|
-
|
|
16467
|
-
|
|
16468
|
-
|
|
16469
|
-
|
|
16470
|
-
|
|
16471
|
-
|
|
16472
|
-
|
|
16473
|
-
|
|
16474
|
-
|
|
16475
|
-
|
|
16860
|
+
const planned = new Map(input.planVideos.extracted.map((video) => [video.videoId, video]));
|
|
16861
|
+
return extractDeferredFrames({
|
|
16862
|
+
videos: input.planVideos.videos,
|
|
16863
|
+
extracted: planExtractedFrames(input.planVideos),
|
|
16864
|
+
frames: input.frames,
|
|
16865
|
+
fps: input.fps,
|
|
16866
|
+
outputRoot: input.outputRoot,
|
|
16867
|
+
cfg: input.cfg,
|
|
16868
|
+
shownBoxes: input.shownBoxes,
|
|
16869
|
+
sourceOf: (video) => {
|
|
16870
|
+
const range = planned.get(video.videoId).deferredRange;
|
|
16476
16871
|
const remoteUrl = remoteSources[range.sourcePath];
|
|
16477
|
-
const sourcePath = remoteUrl ??
|
|
16872
|
+
const sourcePath = remoteUrl ?? join34(input.planDir, ...range.sourcePath.split("/"));
|
|
16478
16873
|
if (remoteUrl === void 0 && !existsSync22(sourcePath)) {
|
|
16479
16874
|
throw new RenderChunkValidationError(
|
|
16480
16875
|
MISSING_PLAN_ARTIFACT,
|
|
16481
16876
|
`[renderChunk] planDir is missing the source for video ${JSON.stringify(video.videoId)}: ${range.sourcePath}`
|
|
16482
16877
|
);
|
|
16483
16878
|
}
|
|
16484
|
-
|
|
16485
|
-
|
|
16486
|
-
|
|
16487
|
-
|
|
16488
|
-
|
|
16489
|
-
|
|
16490
|
-
|
|
16491
|
-
outputDir: input.outputRoot,
|
|
16492
|
-
format: range.format,
|
|
16493
|
-
frameRanges: contiguousRuns(indexes),
|
|
16494
|
-
shownBox: input.shownBoxes?.get(video.videoId)
|
|
16495
|
-
},
|
|
16496
|
-
void 0,
|
|
16497
|
-
input.cfg
|
|
16498
|
-
);
|
|
16499
|
-
const missing = indexes.find((index) => !extracted.framePaths.has(index));
|
|
16500
|
-
if (missing !== void 0) {
|
|
16501
|
-
throw new RenderChunkValidationError(
|
|
16502
|
-
INVALID_VIDEO_METADATA,
|
|
16503
|
-
`[renderChunk] video ${JSON.stringify(video.videoId)} has no frame ${missing}; the plan expected ${video.totalFrames} frames from ${range.sourcePath}.`
|
|
16504
|
-
);
|
|
16505
|
-
}
|
|
16506
|
-
return { ...video, outputDir, framePaths: extracted.framePaths, ownedByLookup: false };
|
|
16507
|
-
})
|
|
16508
|
-
);
|
|
16879
|
+
return sourcePath;
|
|
16880
|
+
},
|
|
16881
|
+
onMissingFrame: (video, index) => new RenderChunkValidationError(
|
|
16882
|
+
INVALID_VIDEO_METADATA,
|
|
16883
|
+
`[renderChunk] video ${JSON.stringify(video.videoId)} has no frame ${index}; the plan expected ${video.totalFrames} frames from ${planned.get(video.videoId).deferredRange.sourcePath}.`
|
|
16884
|
+
)
|
|
16885
|
+
});
|
|
16509
16886
|
}
|
|
16510
16887
|
|
|
16511
16888
|
// src/services/distributed/planFrameCost.ts
|
|
@@ -16523,12 +16900,12 @@ async function measurePlanSecondsPerFrame(input) {
|
|
|
16523
16900
|
planVideos,
|
|
16524
16901
|
frames: sampled,
|
|
16525
16902
|
fps: fpsRatio,
|
|
16526
|
-
outputRoot:
|
|
16903
|
+
outputRoot: join35(workDir, "frame-cost-video-frames"),
|
|
16527
16904
|
cfg: input.cfg
|
|
16528
16905
|
})
|
|
16529
16906
|
];
|
|
16530
|
-
const lookup = extracted.length > 0 ?
|
|
16531
|
-
const compiledDir =
|
|
16907
|
+
const lookup = extracted.length > 0 ? createFrameLookupTable4(planVideos.videos, extracted, resolveRenderFpsConfig(fpsRatio).value) : null;
|
|
16908
|
+
const compiledDir = join35(planDir, "compiled");
|
|
16532
16909
|
const fileServer = await createFileServer({
|
|
16533
16910
|
projectDir: compiledDir,
|
|
16534
16911
|
compiledDir,
|
|
@@ -16540,7 +16917,7 @@ async function measurePlanSecondsPerFrame(input) {
|
|
|
16540
16917
|
try {
|
|
16541
16918
|
session = await createCaptureSession9(
|
|
16542
16919
|
fileServer.url,
|
|
16543
|
-
|
|
16920
|
+
join35(workDir, "frame-cost"),
|
|
16544
16921
|
buildChunkCaptureOptions({
|
|
16545
16922
|
dimensions: { ...input, fpsNum: fps, fpsDen: 1 },
|
|
16546
16923
|
encoder: input,
|
|
@@ -16637,7 +17014,7 @@ function buildWeightedChunkSlices(weights, chunkCount, minFrames, maxFrames) {
|
|
|
16637
17014
|
}
|
|
16638
17015
|
|
|
16639
17016
|
// src/services/render/planReferenceBackground.ts
|
|
16640
|
-
import { join as
|
|
17017
|
+
import { join as join36 } from "node:path";
|
|
16641
17018
|
import {
|
|
16642
17019
|
closeCaptureSession as closeCaptureSession10,
|
|
16643
17020
|
createCaptureSession as createCaptureSession10,
|
|
@@ -16650,14 +17027,14 @@ async function readPlanReferenceBackground(input) {
|
|
|
16650
17027
|
try {
|
|
16651
17028
|
server = await createFileServer({
|
|
16652
17029
|
projectDir: input.projectDir,
|
|
16653
|
-
compiledDir:
|
|
17030
|
+
compiledDir: join36(input.workDir, "compiled"),
|
|
16654
17031
|
port: 0,
|
|
16655
17032
|
preHeadScripts: [VIRTUAL_TIME_SHIM],
|
|
16656
17033
|
fps: input.options.fps
|
|
16657
17034
|
});
|
|
16658
17035
|
session = await createCaptureSession10(
|
|
16659
17036
|
server.url,
|
|
16660
|
-
|
|
17037
|
+
join36(input.workDir, "reference"),
|
|
16661
17038
|
{
|
|
16662
17039
|
...input.options,
|
|
16663
17040
|
requiresWebGpu: false
|
|
@@ -16693,8 +17070,8 @@ async function readPlanReferenceBackground(input) {
|
|
|
16693
17070
|
}
|
|
16694
17071
|
|
|
16695
17072
|
// src/services/render/stages/freezePlan.ts
|
|
16696
|
-
import { existsSync as existsSync23, mkdirSync as
|
|
16697
|
-
import { join as
|
|
17073
|
+
import { existsSync as existsSync23, mkdirSync as mkdirSync14, readFileSync as readFileSync10, readdirSync as readdirSync8, writeFileSync as writeFileSync8 } from "node:fs";
|
|
17074
|
+
import { join as join37, relative as relative3, resolve as resolve9 } from "node:path";
|
|
16698
17075
|
import {
|
|
16699
17076
|
REFERENCE_BACKGROUND_FIELD,
|
|
16700
17077
|
referenceBackgroundResult as referenceBackgroundResult3
|
|
@@ -16910,9 +17287,9 @@ function listPlanFiles(planDir) {
|
|
|
16910
17287
|
const results = [];
|
|
16911
17288
|
const rootResolved = resolve9(planDir);
|
|
16912
17289
|
function walk(dir) {
|
|
16913
|
-
const entries =
|
|
17290
|
+
const entries = readdirSync8(dir, { withFileTypes: true });
|
|
16914
17291
|
for (const entry of entries) {
|
|
16915
|
-
const full =
|
|
17292
|
+
const full = join37(dir, entry.name);
|
|
16916
17293
|
if (entry.isDirectory()) {
|
|
16917
17294
|
walk(full);
|
|
16918
17295
|
} else if (entry.isFile()) {
|
|
@@ -16953,15 +17330,15 @@ var PLAN_REMOTE_FILES_RELATIVE_PATH = "meta/remote-files.json";
|
|
|
16953
17330
|
function writePlanRemoteFiles(planDir, files) {
|
|
16954
17331
|
if (files.length === 0) return;
|
|
16955
17332
|
const sorted = [...files].sort((a, b) => a.path < b.path ? -1 : 1);
|
|
16956
|
-
writeFileSync8(
|
|
17333
|
+
writeFileSync8(join37(planDir, PLAN_REMOTE_FILES_RELATIVE_PATH), JSON.stringify(sorted), "utf-8");
|
|
16957
17334
|
}
|
|
16958
17335
|
function readPlanRemoteFiles(planDir) {
|
|
16959
|
-
const path =
|
|
17336
|
+
const path = join37(planDir, PLAN_REMOTE_FILES_RELATIVE_PATH);
|
|
16960
17337
|
return existsSync23(path) ? JSON.parse(readFileSync10(path, "utf-8")) : [];
|
|
16961
17338
|
}
|
|
16962
17339
|
function recomputePlanHashFromPlanDir(planDir, digests) {
|
|
16963
|
-
const planJsonPath =
|
|
16964
|
-
const encoderJsonPath =
|
|
17340
|
+
const planJsonPath = join37(planDir, "plan.json");
|
|
17341
|
+
const encoderJsonPath = join37(planDir, "meta", "encoder.json");
|
|
16965
17342
|
if (!existsSync23(planJsonPath)) {
|
|
16966
17343
|
throw new Error(`[freezePlan] plan.json missing: ${planJsonPath}`);
|
|
16967
17344
|
}
|
|
@@ -16998,18 +17375,18 @@ async function freezePlan(input) {
|
|
|
16998
17375
|
if (!existsSync23(planDir)) {
|
|
16999
17376
|
throw new Error(`[freezePlan] planDir does not exist: ${planDir}`);
|
|
17000
17377
|
}
|
|
17001
|
-
const metaDir =
|
|
17002
|
-
if (!existsSync23(metaDir))
|
|
17378
|
+
const metaDir = join37(planDir, "meta");
|
|
17379
|
+
if (!existsSync23(metaDir)) mkdirSync14(metaDir, { recursive: true });
|
|
17003
17380
|
writeFileSync8(
|
|
17004
|
-
|
|
17381
|
+
join37(metaDir, "composition.json"),
|
|
17005
17382
|
`${JSON.stringify(composition, null, 2)}
|
|
17006
17383
|
`,
|
|
17007
17384
|
"utf-8"
|
|
17008
17385
|
);
|
|
17009
17386
|
const encoderForCanonical = stripUndefined(encoder);
|
|
17010
17387
|
const encoderConfigCanonicalJson = canonicalJsonStringify(encoderForCanonical);
|
|
17011
|
-
writeFileSync8(
|
|
17012
|
-
writeFileSync8(
|
|
17388
|
+
writeFileSync8(join37(metaDir, "encoder.json"), encoderConfigCanonicalJson, "utf-8");
|
|
17389
|
+
writeFileSync8(join37(metaDir, "chunks.json"), `${JSON.stringify(chunks, null, 2)}
|
|
17013
17390
|
`, "utf-8");
|
|
17014
17391
|
const { compositionHtml, assets } = collectPlanAssetShas(planDir, digests);
|
|
17015
17392
|
const planHash = computePlanHash({
|
|
@@ -17034,7 +17411,7 @@ async function freezePlan(input) {
|
|
|
17034
17411
|
duration: durationSeconds,
|
|
17035
17412
|
hasAudio
|
|
17036
17413
|
};
|
|
17037
|
-
const planJsonPath =
|
|
17414
|
+
const planJsonPath = join37(planDir, "plan.json");
|
|
17038
17415
|
writeFileSync8(planJsonPath, `${JSON.stringify(planJson, null, 2)}
|
|
17039
17416
|
`, "utf-8");
|
|
17040
17417
|
return { planJsonPath, planHash };
|
|
@@ -17076,9 +17453,9 @@ function applyRuntimeEnvSnapshot(snapshot, env = process.env) {
|
|
|
17076
17453
|
}
|
|
17077
17454
|
|
|
17078
17455
|
// src/services/distributed/chunkAudio.ts
|
|
17079
|
-
import { closeSync as closeSync6, existsSync as existsSync24, ftruncateSync, mkdirSync as
|
|
17456
|
+
import { closeSync as closeSync6, existsSync as existsSync24, ftruncateSync, mkdirSync as mkdirSync15, openSync as openSync6, readFileSync as readFileSync11 } from "node:fs";
|
|
17080
17457
|
import { writeFileSync as writeFileSync9, writeSync } from "node:fs";
|
|
17081
|
-
import { join as
|
|
17458
|
+
import { join as join38 } from "node:path";
|
|
17082
17459
|
import { appendRenderProvenanceArgs, describeFfmpegFailure as describeFfmpegFailure4, runFfmpeg as runFfmpeg7 } from "@hyperframes/engine";
|
|
17083
17460
|
function audioCutForChunk(grid, chunks, index, fps) {
|
|
17084
17461
|
const firstPacketAt = (frame) => Math.min(
|
|
@@ -17166,7 +17543,7 @@ async function writeChunkAudioSlices(input) {
|
|
|
17166
17543
|
});
|
|
17167
17544
|
const bytes = readFileSync11(input.audioPath);
|
|
17168
17545
|
const layout = { sizeBytes: bytes.length, payloadStart, payloadEnd, partStarts: {} };
|
|
17169
|
-
|
|
17546
|
+
mkdirSync15(join38(input.planDir, SLICE_DIR), { recursive: true });
|
|
17170
17547
|
input.chunks.forEach((_, index) => {
|
|
17171
17548
|
const cut = audioCutForChunk(grid, input.chunks, index, input.fps);
|
|
17172
17549
|
if (!cut) return;
|
|
@@ -17174,17 +17551,17 @@ async function writeChunkAudioSlices(input) {
|
|
|
17174
17551
|
const start = offsets[cut.firstPacket];
|
|
17175
17552
|
layout.partStarts[index] = start;
|
|
17176
17553
|
const end = offsets[last] + track.sizes[last];
|
|
17177
|
-
writeFileSync9(
|
|
17554
|
+
writeFileSync9(join38(input.planDir, chunkAudioSlicePath(index)), bytes.subarray(start, end));
|
|
17178
17555
|
});
|
|
17179
17556
|
const frame = [bytes.subarray(0, payloadStart), bytes.subarray(payloadEnd)];
|
|
17180
|
-
writeFileSync9(
|
|
17181
|
-
writeFileSync9(
|
|
17557
|
+
writeFileSync9(join38(input.planDir, SLICE_FRAME), Buffer.concat(frame));
|
|
17558
|
+
writeFileSync9(join38(input.planDir, SLICE_LAYOUT), JSON.stringify(layout));
|
|
17182
17559
|
}
|
|
17183
17560
|
function chunkAudioSource(planDir, chunkIndex, scratchPath) {
|
|
17184
|
-
const layoutPath =
|
|
17561
|
+
const layoutPath = join38(planDir, SLICE_LAYOUT);
|
|
17185
17562
|
if (!existsSync24(layoutPath)) return resolvePlanAudioPath(planDir);
|
|
17186
17563
|
const layout = JSON.parse(readFileSync11(layoutPath, "utf-8"));
|
|
17187
|
-
const frame = readFileSync11(
|
|
17564
|
+
const frame = readFileSync11(join38(planDir, SLICE_FRAME));
|
|
17188
17565
|
const fd = openSync6(scratchPath, "w");
|
|
17189
17566
|
try {
|
|
17190
17567
|
writeSync(fd, frame, 0, layout.payloadStart, 0);
|
|
@@ -17197,7 +17574,7 @@ function chunkAudioSource(planDir, chunkIndex, scratchPath) {
|
|
|
17197
17574
|
);
|
|
17198
17575
|
const start = layout.partStarts[chunkIndex];
|
|
17199
17576
|
if (start !== void 0) {
|
|
17200
|
-
const part = readFileSync11(
|
|
17577
|
+
const part = readFileSync11(join38(planDir, chunkAudioSlicePath(chunkIndex)));
|
|
17201
17578
|
writeSync(fd, part, 0, part.length, start);
|
|
17202
17579
|
}
|
|
17203
17580
|
ftruncateSync(fd, layout.sizeBytes);
|
|
@@ -17252,8 +17629,8 @@ async function muxChunkWithAudio(input) {
|
|
|
17252
17629
|
|
|
17253
17630
|
// src/services/distributed/planSize.ts
|
|
17254
17631
|
import { createHash as createHash9 } from "node:crypto";
|
|
17255
|
-
import { lstatSync as lstatSync2, readdirSync as
|
|
17256
|
-
import { extname as extname5, join as
|
|
17632
|
+
import { lstatSync as lstatSync2, readdirSync as readdirSync9 } from "node:fs";
|
|
17633
|
+
import { extname as extname5, join as join39, relative as relative4 } from "node:path";
|
|
17257
17634
|
var MAX_TOP_COMPONENTS = 10;
|
|
17258
17635
|
var SOURCE_MEDIA_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
17259
17636
|
".aac",
|
|
@@ -17360,12 +17737,12 @@ function recordFile(result, components, rootDir, rootKind, filePath) {
|
|
|
17360
17737
|
function walkRegularFiles(dir, visit) {
|
|
17361
17738
|
let entries;
|
|
17362
17739
|
try {
|
|
17363
|
-
entries =
|
|
17740
|
+
entries = readdirSync9(dir, { withFileTypes: true });
|
|
17364
17741
|
} catch {
|
|
17365
17742
|
return;
|
|
17366
17743
|
}
|
|
17367
17744
|
for (const entry of entries) {
|
|
17368
|
-
const full =
|
|
17745
|
+
const full = join39(dir, entry.name);
|
|
17369
17746
|
if (entry.isDirectory()) {
|
|
17370
17747
|
walkRegularFiles(full, visit);
|
|
17371
17748
|
} else if (entry.isFile()) {
|
|
@@ -17577,13 +17954,13 @@ function assertPositiveInteger2(name, value) {
|
|
|
17577
17954
|
}
|
|
17578
17955
|
var FREEZE_OWNED_PLAN_FILES = [
|
|
17579
17956
|
"plan.json",
|
|
17580
|
-
|
|
17581
|
-
|
|
17582
|
-
|
|
17957
|
+
join40("meta", "composition.json"),
|
|
17958
|
+
join40("meta", "encoder.json"),
|
|
17959
|
+
join40("meta", "chunks.json")
|
|
17583
17960
|
];
|
|
17584
17961
|
function removeFreezeOwnedPlanFiles(planDir) {
|
|
17585
17962
|
for (const relativePath of FREEZE_OWNED_PLAN_FILES) {
|
|
17586
|
-
|
|
17963
|
+
rmSync14(join40(planDir, relativePath), { force: true });
|
|
17587
17964
|
}
|
|
17588
17965
|
}
|
|
17589
17966
|
function buildChunkSlices(totalFrames, chunkCount, effectiveChunkSize) {
|
|
@@ -17738,19 +18115,19 @@ function createVideoSourceShipper(input) {
|
|
|
17738
18115
|
const known = shipped.get(srcPath);
|
|
17739
18116
|
if (known) return known;
|
|
17740
18117
|
const stagedRel = pathInside(input.workVideoFramesDir, srcPath);
|
|
17741
|
-
const current = stagedRel === null ? srcPath :
|
|
18118
|
+
const current = stagedRel === null ? srcPath : join40(input.planDir, "video-frames", stagedRel);
|
|
17742
18119
|
let result = null;
|
|
17743
18120
|
for (const root of stagedRel === null ? [input.workCompiledDir, input.projectDir] : []) {
|
|
17744
18121
|
const rel = pathInside(root, srcPath);
|
|
17745
|
-
if (rel !== null && (existsSync25(
|
|
18122
|
+
if (rel !== null && (existsSync25(join40(input.planDir, "compiled", rel)) || input.isRemote(srcPath))) {
|
|
17746
18123
|
result = ["compiled", ...rel.split(sep2)].join("/");
|
|
17747
18124
|
break;
|
|
17748
18125
|
}
|
|
17749
18126
|
}
|
|
17750
18127
|
if (result === null) {
|
|
17751
18128
|
result = `video-sources/${shipped.size}${extname6(current)}`;
|
|
17752
|
-
const destination =
|
|
17753
|
-
|
|
18129
|
+
const destination = join40(input.planDir, ...result.split("/"));
|
|
18130
|
+
mkdirSync16(join40(input.planDir, "video-sources"), { recursive: true });
|
|
17754
18131
|
try {
|
|
17755
18132
|
linkSync(current, destination);
|
|
17756
18133
|
} catch {
|
|
@@ -17770,7 +18147,7 @@ async function compilePlanComposition(projectDir, config, planDir, options) {
|
|
|
17770
18147
|
useGpu: false,
|
|
17771
18148
|
browserGpuMode: "software"
|
|
17772
18149
|
});
|
|
17773
|
-
if (!existsSync25(planDir))
|
|
18150
|
+
if (!existsSync25(planDir)) mkdirSync16(planDir, { recursive: true });
|
|
17774
18151
|
const log = config.logger ?? defaultLogger;
|
|
17775
18152
|
const sizeLimitBytes = options.executionPlanSizeLimitBytes ?? config.planDirSizeLimitBytes ?? PLAN_DIR_SIZE_LIMIT_BYTES;
|
|
17776
18153
|
const abortSignal = config.abortSignal;
|
|
@@ -17799,15 +18176,15 @@ async function compilePlanComposition(projectDir, config, planDir, options) {
|
|
|
17799
18176
|
variables: config.variables
|
|
17800
18177
|
});
|
|
17801
18178
|
const entryFile = config.entryFile ?? "index.html";
|
|
17802
|
-
const htmlPath =
|
|
18179
|
+
const htmlPath = join40(projectDir, entryFile);
|
|
17803
18180
|
if (!existsSync25(htmlPath)) {
|
|
17804
18181
|
throw new Error(`[plan] entry file not found: ${htmlPath}`);
|
|
17805
18182
|
}
|
|
17806
|
-
const workDir =
|
|
17807
|
-
|
|
17808
|
-
|
|
17809
|
-
const compiledDir =
|
|
17810
|
-
|
|
18183
|
+
const workDir = join40(planDir, ".plan-work");
|
|
18184
|
+
rmSync14(workDir, { recursive: true, force: true });
|
|
18185
|
+
mkdirSync16(workDir, { recursive: true });
|
|
18186
|
+
const compiledDir = join40(workDir, "compiled");
|
|
18187
|
+
mkdirSync16(compiledDir, { recursive: true });
|
|
17811
18188
|
let stageStarted = Date.now();
|
|
17812
18189
|
cpSync2(projectDir, compiledDir, {
|
|
17813
18190
|
recursive: true,
|
|
@@ -17820,10 +18197,10 @@ async function compilePlanComposition(projectDir, config, planDir, options) {
|
|
|
17820
18197
|
}
|
|
17821
18198
|
});
|
|
17822
18199
|
logPlanStage(log, "copy", stageStarted);
|
|
17823
|
-
const finalCompiledDir =
|
|
18200
|
+
const finalCompiledDir = join40(planDir, "compiled");
|
|
17824
18201
|
const needsAlpha = outputNeedsAlpha(config.format);
|
|
17825
18202
|
const remoteFiles = Object.entries(options.remoteFiles ?? {});
|
|
17826
|
-
const remoteLocalPath = (rel) =>
|
|
18203
|
+
const remoteLocalPath = (rel) => join40(projectDir, ...rel.split("/"));
|
|
17827
18204
|
for (const [, file] of remoteFiles) {
|
|
17828
18205
|
declareContentAddressedUrl(file.url, file.sha256);
|
|
17829
18206
|
if (file.probeFactsUrl) declareProbeFactsUrl(file.url, file.probeFactsUrl);
|
|
@@ -18026,17 +18403,17 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
|
|
|
18026
18403
|
let stageStarted = Date.now();
|
|
18027
18404
|
const extractResult = await extractPlanVideos(c, options.deferVideoExtraction);
|
|
18028
18405
|
logPlanStage(log, "extract", stageStarted);
|
|
18029
|
-
const stagedVideoFrames =
|
|
18030
|
-
const videoFramesDst =
|
|
18031
|
-
if (existsSync25(videoFramesDst))
|
|
18032
|
-
const videoSourcesDir =
|
|
18033
|
-
|
|
18406
|
+
const stagedVideoFrames = join40(compiledDir, "__hyperframes_video_frames");
|
|
18407
|
+
const videoFramesDst = join40(planDir, "video-frames");
|
|
18408
|
+
if (existsSync25(videoFramesDst)) rmSync14(videoFramesDst, { recursive: true, force: true });
|
|
18409
|
+
const videoSourcesDir = join40(planDir, "video-sources");
|
|
18410
|
+
rmSync14(videoSourcesDir, { recursive: true, force: true });
|
|
18034
18411
|
if (existsSync25(stagedVideoFrames)) {
|
|
18035
18412
|
renameWithRetry8(stagedVideoFrames, videoFramesDst);
|
|
18036
18413
|
} else {
|
|
18037
|
-
|
|
18414
|
+
mkdirSync16(videoFramesDst, { recursive: true });
|
|
18038
18415
|
}
|
|
18039
|
-
if (existsSync25(finalCompiledDir))
|
|
18416
|
+
if (existsSync25(finalCompiledDir)) rmSync14(finalCompiledDir, { recursive: true, force: true });
|
|
18040
18417
|
renameWithRetry8(compiledDir, finalCompiledDir);
|
|
18041
18418
|
const shipVideoSource = createVideoSourceShipper({
|
|
18042
18419
|
planDir,
|
|
@@ -18066,7 +18443,7 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
|
|
|
18066
18443
|
};
|
|
18067
18444
|
})
|
|
18068
18445
|
});
|
|
18069
|
-
|
|
18446
|
+
mkdirSync16(join40(planDir, "meta"), { recursive: true });
|
|
18070
18447
|
writePlanRemoteFiles(
|
|
18071
18448
|
planDir,
|
|
18072
18449
|
remoteFiles.map(([rel, { sha256, sizeBytes }]) => ({
|
|
@@ -18076,7 +18453,7 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
|
|
|
18076
18453
|
}))
|
|
18077
18454
|
);
|
|
18078
18455
|
writeFileSync10(
|
|
18079
|
-
|
|
18456
|
+
join40(planDir, PLAN_VIDEOS_META_RELATIVE_PATH),
|
|
18080
18457
|
JSON.stringify(planVideosJson, null, 2),
|
|
18081
18458
|
"utf-8"
|
|
18082
18459
|
);
|
|
@@ -18155,7 +18532,7 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
|
|
|
18155
18532
|
if (audioResult?.audioError) {
|
|
18156
18533
|
applyDistributedAudioWarningPolicy(job, audioResult.audioError, audioResult.audioFailures, log);
|
|
18157
18534
|
}
|
|
18158
|
-
const planAudioPath =
|
|
18535
|
+
const planAudioPath = join40(planDir, PLAN_AUDIO_RELATIVE_PATH);
|
|
18159
18536
|
if (audioResult?.hasAudio && existsSync25(audioResult.audioOutputPath)) {
|
|
18160
18537
|
renameWithRetry8(audioResult.audioOutputPath, planAudioPath);
|
|
18161
18538
|
}
|
|
@@ -18205,7 +18582,7 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
|
|
|
18205
18582
|
format: config.format
|
|
18206
18583
|
};
|
|
18207
18584
|
try {
|
|
18208
|
-
|
|
18585
|
+
rmSync14(workDir, { recursive: true, force: true });
|
|
18209
18586
|
} catch (err) {
|
|
18210
18587
|
log.warn("[plan] failed to remove temp work dir", {
|
|
18211
18588
|
workDir,
|
|
@@ -18277,9 +18654,9 @@ async function plan(projectDir, config, planDir, options = {}) {
|
|
|
18277
18654
|
|
|
18278
18655
|
// src/services/distributed/renderChunk.ts
|
|
18279
18656
|
import { randomBytes } from "node:crypto";
|
|
18280
|
-
import { existsSync as existsSync28, mkdirSync as
|
|
18657
|
+
import { existsSync as existsSync28, mkdirSync as mkdirSync19, readFileSync as readFileSync13, readdirSync as readdirSync11, rmSync as rmSync17, writeFileSync as writeFileSync13 } from "node:fs";
|
|
18281
18658
|
import { cpus as cpus3 } from "node:os";
|
|
18282
|
-
import { join as
|
|
18659
|
+
import { join as join44 } from "node:path";
|
|
18283
18660
|
import {
|
|
18284
18661
|
assertSwiftShader,
|
|
18285
18662
|
BROWSER_GPU_NOT_SOFTWARE as BROWSER_GPU_NOT_SOFTWARE4,
|
|
@@ -18287,7 +18664,7 @@ import {
|
|
|
18287
18664
|
classifyCaptureFailure as classifyCaptureFailure4,
|
|
18288
18665
|
closeCaptureSession as closeCaptureSession11,
|
|
18289
18666
|
createCaptureSession as createCaptureSession11,
|
|
18290
|
-
createFrameLookupTable as
|
|
18667
|
+
createFrameLookupTable as createFrameLookupTable6,
|
|
18291
18668
|
deriveBeginFrameProbeTimeTicks as deriveBeginFrameProbeTimeTicks2,
|
|
18292
18669
|
getEncoderPreset as getEncoderPreset5,
|
|
18293
18670
|
initializeSession as initializeSession10,
|
|
@@ -18306,32 +18683,32 @@ import {
|
|
|
18306
18683
|
copyFileSync as copyFileSync8,
|
|
18307
18684
|
existsSync as existsSync27,
|
|
18308
18685
|
lstatSync as lstatSync3,
|
|
18309
|
-
mkdirSync as
|
|
18686
|
+
mkdirSync as mkdirSync18,
|
|
18310
18687
|
mkdtempSync as mkdtempSync6,
|
|
18311
18688
|
readFileSync as readFileSync12,
|
|
18312
|
-
readdirSync as
|
|
18313
|
-
rmSync as
|
|
18314
|
-
statSync as
|
|
18689
|
+
readdirSync as readdirSync10,
|
|
18690
|
+
rmSync as rmSync16,
|
|
18691
|
+
statSync as statSync11,
|
|
18315
18692
|
writeFileSync as writeFileSync12
|
|
18316
18693
|
} from "node:fs";
|
|
18317
18694
|
import { tmpdir as tmpdir3 } from "node:os";
|
|
18318
|
-
import { dirname as
|
|
18695
|
+
import { dirname as dirname17, isAbsolute as isAbsolute4, join as join43, relative as relative6, resolve as resolve10, sep as sep3 } from "node:path";
|
|
18319
18696
|
import { DISTRIBUTED_FPS, isDistributedFps } from "@hyperframes/core";
|
|
18320
|
-
import { createFrameLookupTable as
|
|
18697
|
+
import { createFrameLookupTable as createFrameLookupTable5, renameWithRetry as renameWithRetry10 } from "@hyperframes/engine";
|
|
18321
18698
|
|
|
18322
18699
|
// src/services/distributed/planV2Publisher.ts
|
|
18323
18700
|
import {
|
|
18324
18701
|
copyFileSync as copyFileSync7,
|
|
18325
18702
|
existsSync as existsSync26,
|
|
18326
18703
|
linkSync as linkSync2,
|
|
18327
|
-
mkdirSync as
|
|
18704
|
+
mkdirSync as mkdirSync17,
|
|
18328
18705
|
mkdtempSync as mkdtempSync5,
|
|
18329
|
-
rmSync as
|
|
18330
|
-
statSync as
|
|
18706
|
+
rmSync as rmSync15,
|
|
18707
|
+
statSync as statSync10,
|
|
18331
18708
|
writeFileSync as writeFileSync11
|
|
18332
18709
|
} from "node:fs";
|
|
18333
18710
|
import { renameWithRetry as renameWithRetry9 } from "@hyperframes/engine";
|
|
18334
|
-
import { dirname as
|
|
18711
|
+
import { dirname as dirname16, join as join42 } from "node:path";
|
|
18335
18712
|
|
|
18336
18713
|
// src/services/distributed/planV2Errors.ts
|
|
18337
18714
|
var PLAN_V2_INTEGRITY_UNRECOVERABLE = "PLAN_V2_INTEGRITY_UNRECOVERABLE";
|
|
@@ -18346,7 +18723,7 @@ var PlanV2IntegrityError = class extends Error {
|
|
|
18346
18723
|
};
|
|
18347
18724
|
|
|
18348
18725
|
// src/services/distributed/planV2Layout.ts
|
|
18349
|
-
import { join as
|
|
18726
|
+
import { join as join41 } from "node:path";
|
|
18350
18727
|
var SHA256_PATTERN = /^[0-9a-f]{64}$/;
|
|
18351
18728
|
function assertPlanV2Sha256(value, field) {
|
|
18352
18729
|
if (typeof value !== "string" || !SHA256_PATTERN.test(value)) {
|
|
@@ -18356,7 +18733,7 @@ function assertPlanV2Sha256(value, field) {
|
|
|
18356
18733
|
}
|
|
18357
18734
|
function planV2BlobPath(root, sha256) {
|
|
18358
18735
|
const digest = assertPlanV2Sha256(sha256, "artifact sha256");
|
|
18359
|
-
return
|
|
18736
|
+
return join41(root, "artifacts", "sha256", digest.slice(0, 2), digest);
|
|
18360
18737
|
}
|
|
18361
18738
|
|
|
18362
18739
|
// src/services/distributed/planV2Publisher.ts
|
|
@@ -18396,12 +18773,12 @@ var LocalPlanV2ArtifactPublisher = class {
|
|
|
18396
18773
|
}
|
|
18397
18774
|
this.destinationDir = destinationDir;
|
|
18398
18775
|
this.#linkFile = options.linkFile ?? linkSync2;
|
|
18399
|
-
|
|
18400
|
-
this.temporaryDir = mkdtempSync5(
|
|
18776
|
+
mkdirSync17(dirname16(destinationDir), { recursive: true });
|
|
18777
|
+
this.temporaryDir = mkdtempSync5(join42(dirname16(destinationDir), ".plan-v2-publish-"));
|
|
18401
18778
|
}
|
|
18402
18779
|
async putBlob(blob) {
|
|
18403
18780
|
const digest = assertPlanV2Sha256(blob.sha256, "published blob sha256");
|
|
18404
|
-
const sourceSize =
|
|
18781
|
+
const sourceSize = statSync10(blob.sourcePath).size;
|
|
18405
18782
|
if (sourceSize !== blob.sizeBytes) {
|
|
18406
18783
|
throw new PlanV2IntegrityError(
|
|
18407
18784
|
`published blob size changed for ${digest}: expected ${blob.sizeBytes}, got ${sourceSize}`
|
|
@@ -18409,9 +18786,9 @@ var LocalPlanV2ArtifactPublisher = class {
|
|
|
18409
18786
|
}
|
|
18410
18787
|
const destinationPath = planV2BlobPath(this.temporaryDir, digest);
|
|
18411
18788
|
if (existsSync26(destinationPath)) return;
|
|
18412
|
-
|
|
18413
|
-
const stagingDir = mkdtempSync5(
|
|
18414
|
-
const temporaryPath =
|
|
18789
|
+
mkdirSync17(dirname16(destinationPath), { recursive: true });
|
|
18790
|
+
const stagingDir = mkdtempSync5(join42(dirname16(destinationPath), ".plan-v2-blob-"));
|
|
18791
|
+
const temporaryPath = join42(stagingDir, "blob");
|
|
18415
18792
|
try {
|
|
18416
18793
|
try {
|
|
18417
18794
|
this.#linkFile(blob.sourcePath, temporaryPath);
|
|
@@ -18421,7 +18798,7 @@ var LocalPlanV2ArtifactPublisher = class {
|
|
|
18421
18798
|
}
|
|
18422
18799
|
renameWithRetry9(temporaryPath, destinationPath);
|
|
18423
18800
|
} finally {
|
|
18424
|
-
|
|
18801
|
+
rmSync15(stagingDir, { recursive: true, force: true });
|
|
18425
18802
|
}
|
|
18426
18803
|
}
|
|
18427
18804
|
async commitManifest(manifestBytes) {
|
|
@@ -18432,13 +18809,13 @@ var LocalPlanV2ArtifactPublisher = class {
|
|
|
18432
18809
|
);
|
|
18433
18810
|
}
|
|
18434
18811
|
}
|
|
18435
|
-
writeFileSync11(
|
|
18812
|
+
writeFileSync11(join42(this.temporaryDir, "plan.json"), manifestBytes, "utf-8");
|
|
18436
18813
|
renameWithRetry9(this.temporaryDir, this.destinationDir);
|
|
18437
18814
|
this.#committed = true;
|
|
18438
18815
|
}
|
|
18439
18816
|
async abort() {
|
|
18440
18817
|
if (!this.#committed) {
|
|
18441
|
-
|
|
18818
|
+
rmSync15(this.temporaryDir, { recursive: true, force: true });
|
|
18442
18819
|
}
|
|
18443
18820
|
}
|
|
18444
18821
|
};
|
|
@@ -18476,8 +18853,8 @@ function listFiles(root) {
|
|
|
18476
18853
|
const files = [];
|
|
18477
18854
|
const rootResolved = resolve10(root);
|
|
18478
18855
|
function walk(dir) {
|
|
18479
|
-
for (const entry of
|
|
18480
|
-
const absolutePath =
|
|
18856
|
+
for (const entry of readdirSync10(dir, { withFileTypes: true })) {
|
|
18857
|
+
const absolutePath = join43(dir, entry.name);
|
|
18481
18858
|
if (entry.isDirectory()) {
|
|
18482
18859
|
walk(absolutePath);
|
|
18483
18860
|
} else if (entry.isFile()) {
|
|
@@ -18504,13 +18881,13 @@ function assertValidExtractionCacheCompleteSentinel(path) {
|
|
|
18504
18881
|
}
|
|
18505
18882
|
}
|
|
18506
18883
|
function validateExtractionCacheCompleteSentinels(executionPlanDir) {
|
|
18507
|
-
const videoRoot =
|
|
18884
|
+
const videoRoot = join43(executionPlanDir, "video-frames");
|
|
18508
18885
|
if (!existsSync27(videoRoot)) return;
|
|
18509
|
-
for (const videoEntry of
|
|
18886
|
+
for (const videoEntry of readdirSync10(videoRoot, { withFileTypes: true })) {
|
|
18510
18887
|
if (!videoEntry.isDirectory()) continue;
|
|
18511
|
-
const videoDir =
|
|
18512
|
-
if (
|
|
18513
|
-
const sentinelPath =
|
|
18888
|
+
const videoDir = join43(videoRoot, videoEntry.name);
|
|
18889
|
+
if (readdirSync10(videoDir).includes(EXTRACTION_CACHE_COMPLETE_SENTINEL)) {
|
|
18890
|
+
const sentinelPath = join43(videoDir, EXTRACTION_CACHE_COMPLETE_SENTINEL);
|
|
18514
18891
|
assertValidExtractionCacheCompleteSentinel(sentinelPath);
|
|
18515
18892
|
}
|
|
18516
18893
|
}
|
|
@@ -18557,15 +18934,15 @@ function listVideoFramePaths(executionPlanDir, videos) {
|
|
|
18557
18934
|
if (video.deferredRange) {
|
|
18558
18935
|
const framePaths2 = /* @__PURE__ */ new Map();
|
|
18559
18936
|
for (let index = 0; index < video.totalFrames; index++) {
|
|
18560
|
-
framePaths2.set(index,
|
|
18937
|
+
framePaths2.set(index, join43(outputDir, String(index)));
|
|
18561
18938
|
}
|
|
18562
18939
|
return { ...video, outputDir, framePaths: framePaths2, ownedByLookup: false };
|
|
18563
18940
|
}
|
|
18564
|
-
const frameNames =
|
|
18941
|
+
const frameNames = readdirSync10(outputDir).sort();
|
|
18565
18942
|
const framePaths = /* @__PURE__ */ new Map();
|
|
18566
18943
|
for (const frameName of frameNames) {
|
|
18567
18944
|
if (frameName === EXTRACTION_CACHE_COMPLETE_SENTINEL) {
|
|
18568
|
-
assertValidExtractionCacheCompleteSentinel(
|
|
18945
|
+
assertValidExtractionCacheCompleteSentinel(join43(outputDir, frameName));
|
|
18569
18946
|
continue;
|
|
18570
18947
|
}
|
|
18571
18948
|
const match = /(\d+)(?=\.[^.]+$)/.exec(frameName);
|
|
@@ -18584,7 +18961,7 @@ function listVideoFramePaths(executionPlanDir, videos) {
|
|
|
18584
18961
|
`duplicate extracted frame index ${frameIndex} in ${outputDir}`
|
|
18585
18962
|
);
|
|
18586
18963
|
}
|
|
18587
|
-
framePaths.set(frameIndex,
|
|
18964
|
+
framePaths.set(frameIndex, join43(outputDir, frameName));
|
|
18588
18965
|
}
|
|
18589
18966
|
return {
|
|
18590
18967
|
...video,
|
|
@@ -18605,11 +18982,11 @@ function parsePlanVideosJson2(value) {
|
|
|
18605
18982
|
}
|
|
18606
18983
|
}
|
|
18607
18984
|
function materializeExtractedVideoDirectories(planDir) {
|
|
18608
|
-
const videosPath =
|
|
18985
|
+
const videosPath = join43(planDir, PLAN_VIDEOS_META_RELATIVE_PATH);
|
|
18609
18986
|
if (!existsSync27(videosPath)) return;
|
|
18610
18987
|
const videos = parsePlanVideosJson2(readJsonFile(videosPath, PLAN_VIDEOS_META_RELATIVE_PATH));
|
|
18611
18988
|
for (const video of videos.extracted) {
|
|
18612
|
-
|
|
18989
|
+
mkdirSync18(resolveExtractedVideoOutputDir(planDir, video.videoId), { recursive: true });
|
|
18613
18990
|
}
|
|
18614
18991
|
}
|
|
18615
18992
|
function parseChunkSlices(value) {
|
|
@@ -18639,13 +19016,13 @@ function parseChunkSlices(value) {
|
|
|
18639
19016
|
});
|
|
18640
19017
|
}
|
|
18641
19018
|
function buildVideoChunkDependencies(executionPlanDir, dimensions) {
|
|
18642
|
-
const videoRoot =
|
|
19019
|
+
const videoRoot = join43(executionPlanDir, "video-frames");
|
|
18643
19020
|
const hasExtractedFrames = existsSync27(videoRoot) && listFiles(videoRoot).length > 0;
|
|
18644
|
-
const videosPath =
|
|
19021
|
+
const videosPath = join43(executionPlanDir, PLAN_VIDEOS_META_RELATIVE_PATH);
|
|
18645
19022
|
if (!existsSync27(videosPath)) {
|
|
18646
19023
|
return hasExtractedFrames ? { mode: "full-source-pack", dependencies: null } : { mode: "exact-rendered-frames", dependencies: /* @__PURE__ */ new Map() };
|
|
18647
19024
|
}
|
|
18648
|
-
const chunksPath =
|
|
19025
|
+
const chunksPath = join43(executionPlanDir, "meta", "chunks.json");
|
|
18649
19026
|
const videos = readJsonFile(videosPath, PLAN_VIDEOS_META_RELATIVE_PATH);
|
|
18650
19027
|
const chunks = readJsonFile(chunksPath, "meta/chunks.json");
|
|
18651
19028
|
const parsedVideos = parsePlanVideosJson2(videos);
|
|
@@ -18653,7 +19030,7 @@ function buildVideoChunkDependencies(executionPlanDir, dimensions) {
|
|
|
18653
19030
|
const extracted = listVideoFramePaths(executionPlanDir, parsedVideos);
|
|
18654
19031
|
const fpsNum = readPositiveInteger2(dimensions.fpsNum, "dimensions.fpsNum");
|
|
18655
19032
|
const fpsDen = readPositiveInteger2(dimensions.fpsDen, "dimensions.fpsDen");
|
|
18656
|
-
const table =
|
|
19033
|
+
const table = createFrameLookupTable5(
|
|
18657
19034
|
parsedVideos.videos,
|
|
18658
19035
|
extracted,
|
|
18659
19036
|
resolveRenderFpsConfig({ num: fpsNum, den: fpsDen }).value
|
|
@@ -18700,21 +19077,21 @@ function computeManifestHash(manifest) {
|
|
|
18700
19077
|
}
|
|
18701
19078
|
function writeBlob(sourcePath, destinationPath) {
|
|
18702
19079
|
if (existsSync27(destinationPath)) return;
|
|
18703
|
-
|
|
18704
|
-
const temporaryDir = mkdtempSync6(
|
|
18705
|
-
const temporaryPath =
|
|
19080
|
+
mkdirSync18(dirname17(destinationPath), { recursive: true });
|
|
19081
|
+
const temporaryDir = mkdtempSync6(join43(dirname17(destinationPath), ".plan-v2-blob-"));
|
|
19082
|
+
const temporaryPath = join43(temporaryDir, "blob");
|
|
18706
19083
|
try {
|
|
18707
19084
|
copyFileSync8(sourcePath, temporaryPath);
|
|
18708
19085
|
renameWithRetry10(temporaryPath, destinationPath);
|
|
18709
19086
|
} finally {
|
|
18710
|
-
|
|
19087
|
+
rmSync16(temporaryDir, { recursive: true, force: true });
|
|
18711
19088
|
}
|
|
18712
19089
|
}
|
|
18713
19090
|
function publishBlobFor(absolutePath, digests) {
|
|
18714
19091
|
return {
|
|
18715
19092
|
sourcePath: absolutePath,
|
|
18716
19093
|
sha256: planFileSha256(absolutePath, digests),
|
|
18717
|
-
sizeBytes:
|
|
19094
|
+
sizeBytes: statSync11(absolutePath).size
|
|
18718
19095
|
};
|
|
18719
19096
|
}
|
|
18720
19097
|
async function putBlobs(publisher, blobs) {
|
|
@@ -18738,7 +19115,7 @@ async function putTasks(tasks) {
|
|
|
18738
19115
|
if (failure) throw failure.reason;
|
|
18739
19116
|
}
|
|
18740
19117
|
function buildPlanV2Publication(executionPlanDir, digests) {
|
|
18741
|
-
const executionPlanPath =
|
|
19118
|
+
const executionPlanPath = join43(executionPlanDir, "plan.json");
|
|
18742
19119
|
if (!existsSync27(executionPlanPath)) {
|
|
18743
19120
|
throw new PlanV2IntegrityError(`execution plan is missing plan.json: ${executionPlanPath}`);
|
|
18744
19121
|
}
|
|
@@ -18766,7 +19143,7 @@ function buildPlanV2Publication(executionPlanDir, digests) {
|
|
|
18766
19143
|
}
|
|
18767
19144
|
validateExtractionCacheCompleteSentinels(executionPlanDir);
|
|
18768
19145
|
const videoDependencyPlan = buildVideoChunkDependencies(executionPlanDir, dimensions);
|
|
18769
|
-
const encoder = readJsonFile(
|
|
19146
|
+
const encoder = readJsonFile(join43(executionPlanDir, "meta", "encoder.json"), "meta/encoder.json");
|
|
18770
19147
|
const files = listFiles(executionPlanDir);
|
|
18771
19148
|
const chunkAudio = !isRecord3(encoder) || encoder.chunksCarryAudio !== true ? "none" : files.some((file) => chunkAudioSliceTarget(file.path) !== null) ? "sliced" : "whole";
|
|
18772
19149
|
for (const file of files) {
|
|
@@ -18826,21 +19203,21 @@ function createPlanV2FromExecutionPlan(executionPlanDir, planV2Dir) {
|
|
|
18826
19203
|
if (publication.storedBlobs.length > 0) {
|
|
18827
19204
|
throw new PlanV2IntegrityError("a local plan v2 directory cannot reference remote files");
|
|
18828
19205
|
}
|
|
18829
|
-
|
|
18830
|
-
const tempDir = mkdtempSync6(
|
|
19206
|
+
mkdirSync18(dirname17(planV2Dir), { recursive: true });
|
|
19207
|
+
const tempDir = mkdtempSync6(join43(dirname17(planV2Dir), ".plan-v2-build-"));
|
|
18831
19208
|
try {
|
|
18832
19209
|
for (const blob of publication.blobs) {
|
|
18833
19210
|
writeBlob(blob.sourcePath, planV2BlobPath(tempDir, blob.sha256));
|
|
18834
19211
|
}
|
|
18835
19212
|
writeFileSync12(
|
|
18836
|
-
|
|
19213
|
+
join43(tempDir, "plan.json"),
|
|
18837
19214
|
canonicalJsonStringify(publication.manifest),
|
|
18838
19215
|
"utf-8"
|
|
18839
19216
|
);
|
|
18840
19217
|
renameWithRetry10(tempDir, planV2Dir);
|
|
18841
19218
|
return resultFromManifest(planV2Dir, publication.manifest);
|
|
18842
19219
|
} catch (error) {
|
|
18843
|
-
|
|
19220
|
+
rmSync16(tempDir, { recursive: true, force: true });
|
|
18844
19221
|
throw error;
|
|
18845
19222
|
}
|
|
18846
19223
|
}
|
|
@@ -18874,7 +19251,7 @@ async function publishExecutionPlan(executionPlanDir, publisher, { digests, log
|
|
|
18874
19251
|
}
|
|
18875
19252
|
}
|
|
18876
19253
|
async function planV2WithPublisher(projectDir, config, publisher, options = {}) {
|
|
18877
|
-
const stagingRoot = mkdtempSync6(
|
|
19254
|
+
const stagingRoot = mkdtempSync6(join43(options.stagingParentDir ?? tmpdir3(), ".execution-plan-"));
|
|
18878
19255
|
const digests = /* @__PURE__ */ new Map();
|
|
18879
19256
|
try {
|
|
18880
19257
|
await buildLocalExecutionPlan(projectDir, config, stagingRoot, {
|
|
@@ -18885,7 +19262,7 @@ async function planV2WithPublisher(projectDir, config, publisher, options = {})
|
|
|
18885
19262
|
// artifactTargets ships every compiled/ file to all chunks, so each is in the manifest.
|
|
18886
19263
|
whileAudioMixes: () => putBlobs(
|
|
18887
19264
|
publisher,
|
|
18888
|
-
listFiles(
|
|
19265
|
+
listFiles(join43(stagingRoot, "compiled")).map(
|
|
18889
19266
|
(file) => publishBlobFor(file.absolutePath, digests)
|
|
18890
19267
|
)
|
|
18891
19268
|
),
|
|
@@ -18901,7 +19278,7 @@ async function planV2WithPublisher(projectDir, config, publisher, options = {})
|
|
|
18901
19278
|
}
|
|
18902
19279
|
throw error;
|
|
18903
19280
|
} finally {
|
|
18904
|
-
|
|
19281
|
+
rmSync16(stagingRoot, { recursive: true, force: true });
|
|
18905
19282
|
}
|
|
18906
19283
|
}
|
|
18907
19284
|
async function planV2(projectDir, config, planV2Dir) {
|
|
@@ -18910,7 +19287,7 @@ async function planV2(projectDir, config, planV2Dir) {
|
|
|
18910
19287
|
}
|
|
18911
19288
|
const publisher = new LocalPlanV2ArtifactPublisher(planV2Dir);
|
|
18912
19289
|
const manifest = await planV2WithPublisher(projectDir, config, publisher, {
|
|
18913
|
-
stagingParentDir:
|
|
19290
|
+
stagingParentDir: dirname17(planV2Dir)
|
|
18914
19291
|
});
|
|
18915
19292
|
return resultFromManifest(planV2Dir, manifest);
|
|
18916
19293
|
}
|
|
@@ -18922,7 +19299,7 @@ function resultFromManifest(planV2Dir, manifest) {
|
|
|
18922
19299
|
isRecord3(executionPlan) ? executionPlan[REFERENCE_BACKGROUND_FIELD3] : void 0
|
|
18923
19300
|
),
|
|
18924
19301
|
planDir: planV2Dir,
|
|
18925
|
-
manifestPath:
|
|
19302
|
+
manifestPath: join43(planV2Dir, "plan.json"),
|
|
18926
19303
|
planProtocol: PLAN_PROTOCOL_V2,
|
|
18927
19304
|
planHash: manifest.planHash,
|
|
18928
19305
|
sourcePlanV1Hash: getPlanV2ExecutionPlanHash(manifest),
|
|
@@ -19058,7 +19435,7 @@ function parsePlanV2Manifest(value) {
|
|
|
19058
19435
|
return parsed;
|
|
19059
19436
|
}
|
|
19060
19437
|
function readPlanV2Manifest(planV2Dir) {
|
|
19061
|
-
const manifestPath =
|
|
19438
|
+
const manifestPath = join43(planV2Dir, "plan.json");
|
|
19062
19439
|
if (!existsSync27(manifestPath)) {
|
|
19063
19440
|
throw new PlanV2IntegrityError(`missing v2 manifest: ${manifestPath}`);
|
|
19064
19441
|
}
|
|
@@ -19081,7 +19458,7 @@ function verifyBlob(planV2Dir, artifact) {
|
|
|
19081
19458
|
if (!existsSync27(sourcePath)) {
|
|
19082
19459
|
throw new PlanV2IntegrityError(`missing content-addressed artifact ${artifact.sha256}`);
|
|
19083
19460
|
}
|
|
19084
|
-
const stats =
|
|
19461
|
+
const stats = statSync11(sourcePath);
|
|
19085
19462
|
if (!stats.isFile() || stats.size !== artifact.sizeBytes) {
|
|
19086
19463
|
throw new PlanV2IntegrityError(`artifact size mismatch for ${artifact.path}`);
|
|
19087
19464
|
}
|
|
@@ -19103,17 +19480,17 @@ function materializePlanV2Target(planV2Dir, target, destinationDir, options = {}
|
|
|
19103
19480
|
artifact,
|
|
19104
19481
|
sourcePath: verifyBlob(planV2Dir, artifact)
|
|
19105
19482
|
}));
|
|
19106
|
-
|
|
19107
|
-
const tempDir = mkdtempSync6(
|
|
19483
|
+
mkdirSync18(dirname17(destinationDir), { recursive: true });
|
|
19484
|
+
const tempDir = mkdtempSync6(join43(dirname17(destinationDir), ".plan-v2-materialize-"));
|
|
19108
19485
|
try {
|
|
19109
19486
|
for (const { artifact, sourcePath } of verified) {
|
|
19110
|
-
const destinationPath =
|
|
19111
|
-
|
|
19487
|
+
const destinationPath = join43(tempDir, ...artifact.path.split("/"));
|
|
19488
|
+
mkdirSync18(dirname17(destinationPath), { recursive: true });
|
|
19112
19489
|
copyFileSync8(sourcePath, destinationPath);
|
|
19113
19490
|
}
|
|
19114
19491
|
if (target.role === "chunk") materializeExtractedVideoDirectories(tempDir);
|
|
19115
19492
|
writeFileSync12(
|
|
19116
|
-
|
|
19493
|
+
join43(tempDir, PLAN_V2_MATERIALIZATION_MARKER),
|
|
19117
19494
|
canonicalJsonStringify({
|
|
19118
19495
|
manifest,
|
|
19119
19496
|
target,
|
|
@@ -19123,7 +19500,7 @@ function materializePlanV2Target(planV2Dir, target, destinationDir, options = {}
|
|
|
19123
19500
|
);
|
|
19124
19501
|
renameWithRetry10(tempDir, destinationDir);
|
|
19125
19502
|
} catch (error) {
|
|
19126
|
-
|
|
19503
|
+
rmSync16(tempDir, { recursive: true, force: true });
|
|
19127
19504
|
throw error;
|
|
19128
19505
|
}
|
|
19129
19506
|
return {
|
|
@@ -19136,7 +19513,7 @@ function materializePlanV2Target(planV2Dir, target, destinationDir, options = {}
|
|
|
19136
19513
|
// Join the artifact's OWN name, not the current constant: a plan written
|
|
19137
19514
|
// before the container change carries the legacy name, and materializing it
|
|
19138
19515
|
// under the new one would point at a file that was never written.
|
|
19139
|
-
audioPath: target.role === "assembler" ? audioArtifact ?
|
|
19516
|
+
audioPath: target.role === "assembler" ? audioArtifact ? join43(destinationDir, audioArtifact.path) : null : null
|
|
19140
19517
|
};
|
|
19141
19518
|
}
|
|
19142
19519
|
function assertRemoteSources(remoteSources, artifacts) {
|
|
@@ -19155,7 +19532,7 @@ function validatePlanV2MaterializedTarget(planDir, expectedTarget) {
|
|
|
19155
19532
|
return readValidatedPlanV2Materialization(planDir, expectedTarget)?.manifest ?? null;
|
|
19156
19533
|
}
|
|
19157
19534
|
function readValidatedPlanV2Materialization(planDir, expectedTarget) {
|
|
19158
|
-
const markerPath =
|
|
19535
|
+
const markerPath = join43(planDir, PLAN_V2_MATERIALIZATION_MARKER);
|
|
19159
19536
|
if (!existsSync27(markerPath)) return null;
|
|
19160
19537
|
const marker = readJsonFile(markerPath, "materialization marker");
|
|
19161
19538
|
if (!isRecord3(marker) || !isRecord3(marker.manifest) || !isRecord3(marker.target)) {
|
|
@@ -19173,8 +19550,8 @@ function readValidatedPlanV2Materialization(planDir, expectedTarget) {
|
|
|
19173
19550
|
assertRemoteSources(remoteSources, required);
|
|
19174
19551
|
for (const artifact of required) {
|
|
19175
19552
|
if (Object.hasOwn(remoteSources, artifact.path)) continue;
|
|
19176
|
-
const path =
|
|
19177
|
-
if (!existsSync27(path) ||
|
|
19553
|
+
const path = join43(planDir, ...artifact.path.split("/"));
|
|
19554
|
+
if (!existsSync27(path) || statSync11(path).size !== artifact.sizeBytes) {
|
|
19178
19555
|
throw new PlanV2IntegrityError(
|
|
19179
19556
|
`materialized artifact missing or truncated: ${artifact.path}`
|
|
19180
19557
|
);
|
|
@@ -19292,9 +19669,9 @@ async function beginFrameSessionNeedsScreenshotFallback(session, probe = probeBe
|
|
|
19292
19669
|
}
|
|
19293
19670
|
function hashChunkOutput(outputPath, kind) {
|
|
19294
19671
|
if (kind === "file") return sha256Hex(readFileSync13(outputPath));
|
|
19295
|
-
const entries =
|
|
19672
|
+
const entries = readdirSync11(outputPath).filter((name) => /\.(png|jpg|jpeg)$/i.test(name)).sort();
|
|
19296
19673
|
const lines = entries.map(
|
|
19297
|
-
(name) => `${name}\0${sha256Hex(readFileSync13(
|
|
19674
|
+
(name) => `${name}\0${sha256Hex(readFileSync13(join44(outputPath, name)))}`
|
|
19298
19675
|
);
|
|
19299
19676
|
return sha256Hex(lines.join("\0"));
|
|
19300
19677
|
}
|
|
@@ -19311,9 +19688,9 @@ function resolveLockedVp9CpuUsed(lockedEncoder) {
|
|
|
19311
19688
|
async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
19312
19689
|
const start = Date.now();
|
|
19313
19690
|
const log = defaultLogger;
|
|
19314
|
-
const planJsonPath =
|
|
19315
|
-
const encoderJsonPath =
|
|
19316
|
-
const chunksJsonPath =
|
|
19691
|
+
const planJsonPath = join44(planDir, "plan.json");
|
|
19692
|
+
const encoderJsonPath = join44(planDir, "meta", "encoder.json");
|
|
19693
|
+
const chunksJsonPath = join44(planDir, "meta", "chunks.json");
|
|
19317
19694
|
if (!existsSync28(planJsonPath)) {
|
|
19318
19695
|
throw new RenderChunkValidationError(
|
|
19319
19696
|
MISSING_PLAN_ARTIFACT,
|
|
@@ -19339,7 +19716,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
19339
19716
|
}
|
|
19340
19717
|
const encoder = JSON.parse(readFileSync13(encoderJsonPath, "utf-8"));
|
|
19341
19718
|
const chunks = JSON.parse(readFileSync13(chunksJsonPath, "utf-8"));
|
|
19342
|
-
const videosJsonPath =
|
|
19719
|
+
const videosJsonPath = join44(planDir, PLAN_VIDEOS_META_RELATIVE_PATH);
|
|
19343
19720
|
let planVideos = null;
|
|
19344
19721
|
if (existsSync28(videosJsonPath)) {
|
|
19345
19722
|
try {
|
|
@@ -19371,7 +19748,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
19371
19748
|
}
|
|
19372
19749
|
const range = resolveChunkFrameRange(slice, chunkIndex, options?.frameRange);
|
|
19373
19750
|
const framesInChunk = range.endFrame - range.startFrame;
|
|
19374
|
-
const compiledDir =
|
|
19751
|
+
const compiledDir = join44(planDir, "compiled");
|
|
19375
19752
|
if (!existsSync28(compiledDir)) {
|
|
19376
19753
|
throw new RenderChunkValidationError(
|
|
19377
19754
|
MISSING_PLAN_ARTIFACT,
|
|
@@ -19428,14 +19805,14 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
19428
19805
|
forceScreenshotExplicitlyOptedOut: !encoder.forceScreenshot
|
|
19429
19806
|
};
|
|
19430
19807
|
const workDir = `${outputChunkPath}.work.${process.pid}.${randomBytes(4).toString("hex")}`;
|
|
19431
|
-
|
|
19432
|
-
const framesDir =
|
|
19433
|
-
|
|
19808
|
+
mkdirSync19(workDir, { recursive: true });
|
|
19809
|
+
const framesDir = join44(workDir, "captured-frames");
|
|
19810
|
+
mkdirSync19(framesDir, { recursive: true });
|
|
19434
19811
|
const videoFramesDir = `${workDir}.video-frames`;
|
|
19435
19812
|
const chunkFrameNumbers = Array.from({ length: framesInChunk }, (_, i) => range.startFrame + i);
|
|
19436
19813
|
const chunkFps = { num: plan2.dimensions.fpsNum, den: plan2.dimensions.fpsDen };
|
|
19437
19814
|
const deferredVideos = planVideos?.extracted.filter((video) => video.deferredRange) ?? [];
|
|
19438
|
-
const shownFrames = planVideos ?
|
|
19815
|
+
const shownFrames = planVideos ? shownPlanFrameIndexes(planVideos, chunkFrameNumbers, chunkFps) : /* @__PURE__ */ new Map();
|
|
19439
19816
|
const remoteVideoIds = new Set(
|
|
19440
19817
|
planVideos?.extracted.filter(
|
|
19441
19818
|
(video) => video.deferredRange && Object.hasOwn(remoteSources, video.deferredRange.sourcePath)
|
|
@@ -19454,7 +19831,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
19454
19831
|
dimensions: plan2.dimensions,
|
|
19455
19832
|
encoder,
|
|
19456
19833
|
videoCount: planVideos?.videos.length ?? 0,
|
|
19457
|
-
html: readFileSync13(
|
|
19834
|
+
html: readFileSync13(join44(compiledDir, "index.html"), "utf-8"),
|
|
19458
19835
|
// The plan ships a deferred source only to chunks that show it; don't wait on the rest.
|
|
19459
19836
|
skipReadinessVideoIds: deferredVideos.filter(
|
|
19460
19837
|
(video) => !shownFrames.get(video.videoId)?.size || remoteVideoIds.has(video.videoId)
|
|
@@ -19589,7 +19966,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
19589
19966
|
deferredVideoFrames.catch(() => {
|
|
19590
19967
|
});
|
|
19591
19968
|
const videoFrameLookup = planVideos && planVideos.extracted.length > 0 ? deferredVideoFrames.then(
|
|
19592
|
-
(deferred) =>
|
|
19969
|
+
(deferred) => createFrameLookupTable6(
|
|
19593
19970
|
planVideos.videos,
|
|
19594
19971
|
[
|
|
19595
19972
|
...rebuildExtractedFramesFromPlanDir(
|
|
@@ -19622,10 +19999,10 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
19622
19999
|
const effectiveBitrate = encoder.crf != null ? void 0 : encoder.bitrate;
|
|
19623
20000
|
const videoOnlyPath = encoder.chunksCarryAudio ? `${outputChunkPath}.video.mp4` : outputChunkPath;
|
|
19624
20001
|
if (isPngSequence) {
|
|
19625
|
-
if (!existsSync28(outputChunkPath))
|
|
20002
|
+
if (!existsSync28(outputChunkPath)) mkdirSync19(outputChunkPath, { recursive: true });
|
|
19626
20003
|
} else {
|
|
19627
|
-
const outDir =
|
|
19628
|
-
if (!existsSync28(outDir))
|
|
20004
|
+
const outDir = join44(outputChunkPath, "..");
|
|
20005
|
+
if (!existsSync28(outDir)) mkdirSync19(outDir, { recursive: true });
|
|
19629
20006
|
}
|
|
19630
20007
|
const encodeInput = {
|
|
19631
20008
|
job,
|
|
@@ -19741,9 +20118,9 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
19741
20118
|
return observedChunkCaptureMode(capturePerfs);
|
|
19742
20119
|
},
|
|
19743
20120
|
resetForScreenshotRetry: () => {
|
|
19744
|
-
|
|
20121
|
+
rmSync17(workDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
|
|
19745
20122
|
boxesMeasured();
|
|
19746
|
-
|
|
20123
|
+
mkdirSync19(framesDir, { recursive: true });
|
|
19747
20124
|
capturePerfs.length = 0;
|
|
19748
20125
|
captureAttempts.length = 0;
|
|
19749
20126
|
job.framesRendered = 0;
|
|
@@ -19787,8 +20164,8 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
19787
20164
|
fps: { num: plan2.dimensions.fpsNum, den: plan2.dimensions.fpsDen },
|
|
19788
20165
|
timeoutMs: cfg.ffmpegProcessTimeout
|
|
19789
20166
|
});
|
|
19790
|
-
|
|
19791
|
-
|
|
20167
|
+
rmSync17(videoOnlyPath, { force: true });
|
|
20168
|
+
rmSync17(audioScratchPath, { force: true });
|
|
19792
20169
|
}
|
|
19793
20170
|
encodeStageMs = Date.now() - encodeStarted;
|
|
19794
20171
|
} finally {
|
|
@@ -19838,8 +20215,8 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
19838
20215
|
writeFileSync13(perfPath, `${JSON.stringify(perfPayload, null, 2)}
|
|
19839
20216
|
`, "utf-8");
|
|
19840
20217
|
try {
|
|
19841
|
-
|
|
19842
|
-
|
|
20218
|
+
rmSync17(workDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
|
|
20219
|
+
rmSync17(videoFramesDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
|
|
19843
20220
|
} catch (err) {
|
|
19844
20221
|
log.warn("[renderChunk] failed to remove work dir", {
|
|
19845
20222
|
workDir,
|
|
@@ -19898,11 +20275,11 @@ async function renderSpan(projectDir, config, span, outputPath) {
|
|
|
19898
20275
|
if (config.cfr === true) {
|
|
19899
20276
|
throw new RenderSpanUnsupportedError("--start/--end cannot be combined with a cfr re-encode.");
|
|
19900
20277
|
}
|
|
19901
|
-
const workDir = mkdtempSync7(
|
|
20278
|
+
const workDir = mkdtempSync7(join45(tmpdir4(), "hf-render-span-"));
|
|
19902
20279
|
try {
|
|
19903
|
-
const planDir =
|
|
19904
|
-
const partsDir =
|
|
19905
|
-
|
|
20280
|
+
const planDir = join45(workDir, "plan");
|
|
20281
|
+
const partsDir = join45(workDir, "parts");
|
|
20282
|
+
mkdirSync20(partsDir, { recursive: true });
|
|
19906
20283
|
const planned = await plan(
|
|
19907
20284
|
projectDir,
|
|
19908
20285
|
{ ...config, rejectOnSystemFonts: false, failClosedFontFetch: false },
|
|
@@ -19916,7 +20293,7 @@ async function renderSpan(projectDir, config, span, outputPath) {
|
|
|
19916
20293
|
planned.totalFrames
|
|
19917
20294
|
);
|
|
19918
20295
|
const slices = JSON.parse(
|
|
19919
|
-
readFileSync14(
|
|
20296
|
+
readFileSync14(join45(planDir, "meta", "chunks.json"), "utf-8")
|
|
19920
20297
|
);
|
|
19921
20298
|
const parts = [];
|
|
19922
20299
|
for (const slice of slices) {
|
|
@@ -19926,7 +20303,7 @@ async function renderSpan(projectDir, config, span, outputPath) {
|
|
|
19926
20303
|
};
|
|
19927
20304
|
if (range.endFrame <= range.startFrame) continue;
|
|
19928
20305
|
throwIfAborted(config.abortSignal);
|
|
19929
|
-
const part =
|
|
20306
|
+
const part = join45(partsDir, `part-${String(parts.length).padStart(4, "0")}.mp4`);
|
|
19930
20307
|
await renderChunk(planDir, slice.index, part, { frameRange: range });
|
|
19931
20308
|
parts.push(part);
|
|
19932
20309
|
}
|
|
@@ -19939,7 +20316,7 @@ async function renderSpan(projectDir, config, span, outputPath) {
|
|
|
19939
20316
|
framesEncoded: endFrame - startFrame
|
|
19940
20317
|
};
|
|
19941
20318
|
} finally {
|
|
19942
|
-
|
|
20319
|
+
rmSync18(workDir, { recursive: true, force: true });
|
|
19943
20320
|
}
|
|
19944
20321
|
}
|
|
19945
20322
|
|
|
@@ -20515,7 +20892,7 @@ import {
|
|
|
20515
20892
|
} from "@hyperframes/engine";
|
|
20516
20893
|
|
|
20517
20894
|
// src/index.ts
|
|
20518
|
-
import { createVideoFrameInjector as
|
|
20895
|
+
import { createVideoFrameInjector as createVideoFrameInjector4 } from "@hyperframes/engine";
|
|
20519
20896
|
|
|
20520
20897
|
// src/config.ts
|
|
20521
20898
|
import {
|
|
@@ -20526,14 +20903,14 @@ import {
|
|
|
20526
20903
|
// src/server.ts
|
|
20527
20904
|
import {
|
|
20528
20905
|
existsSync as existsSync31,
|
|
20529
|
-
mkdirSync as
|
|
20530
|
-
statSync as
|
|
20906
|
+
mkdirSync as mkdirSync21,
|
|
20907
|
+
statSync as statSync13,
|
|
20531
20908
|
mkdtempSync as mkdtempSync8,
|
|
20532
20909
|
writeFileSync as writeFileSync14,
|
|
20533
|
-
rmSync as
|
|
20910
|
+
rmSync as rmSync19,
|
|
20534
20911
|
createReadStream as createReadStream5
|
|
20535
20912
|
} from "node:fs";
|
|
20536
|
-
import { resolve as resolve12, dirname as
|
|
20913
|
+
import { resolve as resolve12, dirname as dirname19, join as join48 } from "node:path";
|
|
20537
20914
|
import { tmpdir as tmpdir5 } from "node:os";
|
|
20538
20915
|
import { parseArgs } from "node:util";
|
|
20539
20916
|
import crypto from "node:crypto";
|
|
@@ -20550,13 +20927,13 @@ import {
|
|
|
20550
20927
|
import {
|
|
20551
20928
|
existsSync as existsSync29,
|
|
20552
20929
|
readFileSync as readFileSync15,
|
|
20553
|
-
statSync as
|
|
20930
|
+
statSync as statSync12,
|
|
20554
20931
|
openSync as openSync7,
|
|
20555
20932
|
fstatSync as fstatSync3,
|
|
20556
20933
|
closeSync as closeSync7,
|
|
20557
20934
|
constants as constants3
|
|
20558
20935
|
} from "node:fs";
|
|
20559
|
-
import { resolve as resolve11, join as
|
|
20936
|
+
import { resolve as resolve11, join as join46, relative as relative7, isAbsolute as isAbsolute5, sep as sep4 } from "node:path";
|
|
20560
20937
|
import { lintHyperframeHtml } from "@hyperframes/lint";
|
|
20561
20938
|
function isStringRecord(value) {
|
|
20562
20939
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
@@ -20590,7 +20967,7 @@ function readEntryHtml(filePath) {
|
|
|
20590
20967
|
} catch (error) {
|
|
20591
20968
|
if (error instanceof Error && "code" in error && (error.code === "ENOENT" || error.code === "ENOTDIR"))
|
|
20592
20969
|
return null;
|
|
20593
|
-
if (!
|
|
20970
|
+
if (!statSync12(filePath, { throwIfNoEntry: false })?.isFile()) return null;
|
|
20594
20971
|
throw error;
|
|
20595
20972
|
}
|
|
20596
20973
|
try {
|
|
@@ -20602,7 +20979,7 @@ function readEntryHtml(filePath) {
|
|
|
20602
20979
|
}
|
|
20603
20980
|
function readProjectEntryFile(projectDir, preferredEntryFile) {
|
|
20604
20981
|
const absProjectDir = resolve11(projectDir);
|
|
20605
|
-
if (!existsSync29(absProjectDir) || !
|
|
20982
|
+
if (!existsSync29(absProjectDir) || !statSync12(absProjectDir).isDirectory()) {
|
|
20606
20983
|
return { error: `Project directory not found: ${absProjectDir}` };
|
|
20607
20984
|
}
|
|
20608
20985
|
const entryCandidates = [preferredEntryFile, "index.html", "src/index.html"].filter(
|
|
@@ -20624,7 +21001,7 @@ function readProjectEntryFile(projectDir, preferredEntryFile) {
|
|
|
20624
21001
|
}
|
|
20625
21002
|
}
|
|
20626
21003
|
return {
|
|
20627
|
-
error: `No HTML entry file found in project directory: ${
|
|
21004
|
+
error: `No HTML entry file found in project directory: ${join46(absProjectDir, preferredEntryFile || "index.html")}`
|
|
20628
21005
|
};
|
|
20629
21006
|
}
|
|
20630
21007
|
function prepareHyperframeLintBody(body) {
|
|
@@ -20670,7 +21047,7 @@ async function runHyperframeLint(prepared) {
|
|
|
20670
21047
|
// src/services/healthWorker.ts
|
|
20671
21048
|
import { Worker as Worker2 } from "node:worker_threads";
|
|
20672
21049
|
import { fileURLToPath as fileURLToPath6 } from "node:url";
|
|
20673
|
-
import { dirname as
|
|
21050
|
+
import { dirname as dirname18, join as join47 } from "node:path";
|
|
20674
21051
|
import { existsSync as existsSync30 } from "node:fs";
|
|
20675
21052
|
var DEFAULT_HEALTH_PORT = 9848;
|
|
20676
21053
|
async function startHealthWorker(options = {}) {
|
|
@@ -20745,8 +21122,8 @@ function defaultLogger2() {
|
|
|
20745
21122
|
};
|
|
20746
21123
|
}
|
|
20747
21124
|
function resolveWorkerEntry2() {
|
|
20748
|
-
const here =
|
|
20749
|
-
const candidates = [
|
|
21125
|
+
const here = dirname18(fileURLToPath6(import.meta.url));
|
|
21126
|
+
const candidates = [join47(here, "healthWorkerThread.js"), join47(here, "healthWorkerThread.ts")];
|
|
20750
21127
|
for (const candidate of candidates) {
|
|
20751
21128
|
if (existsSync30(candidate)) return candidate;
|
|
20752
21129
|
}
|
|
@@ -20893,8 +21270,8 @@ function buildRenderJobConfig(input, outputPath, log) {
|
|
|
20893
21270
|
function resolvePreparedRenderOutput(prepared, rendersDir, log) {
|
|
20894
21271
|
const { input, cleanupProjectDir } = prepared;
|
|
20895
21272
|
const absoluteOutputPath = resolveOutputPath(input.projectDir, input.outputPath, rendersDir, log);
|
|
20896
|
-
const outputDir =
|
|
20897
|
-
if (!existsSync31(outputDir))
|
|
21273
|
+
const outputDir = dirname19(absoluteOutputPath);
|
|
21274
|
+
if (!existsSync31(outputDir)) mkdirSync21(outputDir, { recursive: true });
|
|
20898
21275
|
return { input, cleanupProjectDir, absoluteOutputPath };
|
|
20899
21276
|
}
|
|
20900
21277
|
function validateRenderOverrides(body) {
|
|
@@ -20935,7 +21312,7 @@ function prepareProjectDirectory(projectDir, options) {
|
|
|
20935
21312
|
const candidate = nonEmptyString(projectDir);
|
|
20936
21313
|
if (!candidate) return null;
|
|
20937
21314
|
const absProjectDir = resolve12(candidate);
|
|
20938
|
-
if (!existsSync31(absProjectDir) || !
|
|
21315
|
+
if (!existsSync31(absProjectDir) || !statSync13(absProjectDir).isDirectory()) {
|
|
20939
21316
|
return { error: `Project directory not found: ${absProjectDir}` };
|
|
20940
21317
|
}
|
|
20941
21318
|
const entry = options.entryFile || "index.html";
|
|
@@ -20960,8 +21337,8 @@ async function resolveInlineRenderHtml(body) {
|
|
|
20960
21337
|
}
|
|
20961
21338
|
function materializeInlineProject(html, options) {
|
|
20962
21339
|
const tempRoot = process.env.PRODUCER_TMP_PROJECT_DIR || tmpdir5();
|
|
20963
|
-
const tempProjectDir = mkdtempSync8(
|
|
20964
|
-
writeFileSync14(
|
|
21340
|
+
const tempProjectDir = mkdtempSync8(join48(tempRoot, "producer-project-"));
|
|
21341
|
+
writeFileSync14(join48(tempProjectDir, "index.html"), html, "utf-8");
|
|
20965
21342
|
return {
|
|
20966
21343
|
prepared: {
|
|
20967
21344
|
input: { projectDir: tempProjectDir, ...options },
|
|
@@ -21022,7 +21399,7 @@ function createArtifactStore(ttlMs) {
|
|
|
21022
21399
|
function cleanupTempDir(dir, log) {
|
|
21023
21400
|
if (!dir) return;
|
|
21024
21401
|
try {
|
|
21025
|
-
|
|
21402
|
+
rmSync19(dir, { recursive: true, force: true });
|
|
21026
21403
|
} catch (error) {
|
|
21027
21404
|
log.warn("Failed to cleanup temp project dir", {
|
|
21028
21405
|
cleanupProjectDir: dir,
|
|
@@ -21031,7 +21408,7 @@ function cleanupTempDir(dir, log) {
|
|
|
21031
21408
|
}
|
|
21032
21409
|
}
|
|
21033
21410
|
function outputFileSize(path) {
|
|
21034
|
-
return existsSync31(path) ?
|
|
21411
|
+
return existsSync31(path) ? statSync13(path).size : 0;
|
|
21035
21412
|
}
|
|
21036
21413
|
function createBlockingProgressReporter(log, requestId) {
|
|
21037
21414
|
let lastLoggedPct = -10;
|
|
@@ -21339,7 +21716,7 @@ function createRenderHandlers(options = {}) {
|
|
|
21339
21716
|
store.delete(token);
|
|
21340
21717
|
return c.json({ success: false, error: "Output artifact file missing" }, 404);
|
|
21341
21718
|
}
|
|
21342
|
-
const stats =
|
|
21719
|
+
const stats = statSync13(artifact.path);
|
|
21343
21720
|
return new Response(createReadStream5(artifact.path), {
|
|
21344
21721
|
headers: {
|
|
21345
21722
|
"content-type": "video/mp4",
|
|
@@ -21430,9 +21807,9 @@ import {
|
|
|
21430
21807
|
import { quantizeTimeToFrame, MEDIA_VISUAL_STYLE_PROPERTIES } from "@hyperframes/engine";
|
|
21431
21808
|
|
|
21432
21809
|
// src/services/distributed/planV2Execution.ts
|
|
21433
|
-
import { mkdtempSync as mkdtempSync10, rmSync as
|
|
21810
|
+
import { mkdtempSync as mkdtempSync10, rmSync as rmSync21 } from "node:fs";
|
|
21434
21811
|
import { tmpdir as tmpdir6 } from "node:os";
|
|
21435
|
-
import { join as
|
|
21812
|
+
import { join as join50 } from "node:path";
|
|
21436
21813
|
|
|
21437
21814
|
// src/services/distributed/assemble.ts
|
|
21438
21815
|
import {
|
|
@@ -21442,15 +21819,15 @@ import {
|
|
|
21442
21819
|
import {
|
|
21443
21820
|
cpSync as cpSync3,
|
|
21444
21821
|
existsSync as existsSync32,
|
|
21445
|
-
mkdirSync as
|
|
21822
|
+
mkdirSync as mkdirSync22,
|
|
21446
21823
|
mkdtempSync as mkdtempSync9,
|
|
21447
21824
|
readFileSync as readFileSync16,
|
|
21448
|
-
readdirSync as
|
|
21449
|
-
rmSync as
|
|
21450
|
-
statSync as
|
|
21825
|
+
readdirSync as readdirSync12,
|
|
21826
|
+
rmSync as rmSync20,
|
|
21827
|
+
statSync as statSync14,
|
|
21451
21828
|
writeFileSync as writeFileSync15
|
|
21452
21829
|
} from "node:fs";
|
|
21453
|
-
import { dirname as
|
|
21830
|
+
import { dirname as dirname20, join as join49 } from "node:path";
|
|
21454
21831
|
import {
|
|
21455
21832
|
appendRateCapArgs as appendRateCapArgs2,
|
|
21456
21833
|
appendRenderProvenanceArgs as appendRenderProvenanceArgs2,
|
|
@@ -21491,13 +21868,13 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
|
|
|
21491
21868
|
outputPath,
|
|
21492
21869
|
durationMs: Date.now() - start,
|
|
21493
21870
|
framesEncoded: plan2.totalFrames,
|
|
21494
|
-
fileSize:
|
|
21871
|
+
fileSize: statSync14(outputPath).size
|
|
21495
21872
|
};
|
|
21496
21873
|
}
|
|
21497
21874
|
const format = plan2.dimensions.format;
|
|
21498
21875
|
const cfrEncoder = cfr ? readCfrEncoder(planDir, format) : null;
|
|
21499
|
-
if (!existsSync32(
|
|
21500
|
-
|
|
21876
|
+
if (!existsSync32(dirname20(outputPath))) {
|
|
21877
|
+
mkdirSync22(dirname20(outputPath), { recursive: true });
|
|
21501
21878
|
}
|
|
21502
21879
|
const workDir = mkdtempSync9(`${outputPath}.assemble-work-`);
|
|
21503
21880
|
const fps = { num: plan2.dimensions.fpsNum, den: plan2.dimensions.fpsDen };
|
|
@@ -21519,7 +21896,7 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
|
|
|
21519
21896
|
};
|
|
21520
21897
|
try {
|
|
21521
21898
|
const videoSources = cfrEncoder ? [await reencodeCfr(ctx, chunkPaths, cfrEncoder)] : chunkPaths;
|
|
21522
|
-
const finalPath =
|
|
21899
|
+
const finalPath = join49(workDir, `output.${format}`);
|
|
21523
21900
|
const audio = audioPath !== null && existsSync32(audioPath) ? audioPath : null;
|
|
21524
21901
|
await writeJoined(ctx, videoSources, audio, finalPath);
|
|
21525
21902
|
renameWithRetry11(finalPath, outputPath);
|
|
@@ -21530,7 +21907,7 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
|
|
|
21530
21907
|
});
|
|
21531
21908
|
} finally {
|
|
21532
21909
|
try {
|
|
21533
|
-
|
|
21910
|
+
rmSync20(workDir, { recursive: true, force: true });
|
|
21534
21911
|
} catch (err) {
|
|
21535
21912
|
log.warn("[assemble] failed to remove work dir", {
|
|
21536
21913
|
workDir,
|
|
@@ -21538,7 +21915,7 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
|
|
|
21538
21915
|
});
|
|
21539
21916
|
}
|
|
21540
21917
|
}
|
|
21541
|
-
const fileSize = existsSync32(outputPath) ?
|
|
21918
|
+
const fileSize = existsSync32(outputPath) ? statSync14(outputPath).size : 0;
|
|
21542
21919
|
return {
|
|
21543
21920
|
...referenceBackgroundResult8(plan2[REFERENCE_BACKGROUND_FIELD6]),
|
|
21544
21921
|
outputPath,
|
|
@@ -21548,8 +21925,8 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
|
|
|
21548
21925
|
};
|
|
21549
21926
|
}
|
|
21550
21927
|
function readAssemblePlan(planDir, chunkPaths) {
|
|
21551
|
-
const planJsonPath =
|
|
21552
|
-
const chunksJsonPath =
|
|
21928
|
+
const planJsonPath = join49(planDir, "plan.json");
|
|
21929
|
+
const chunksJsonPath = join49(planDir, "meta", "chunks.json");
|
|
21553
21930
|
if (!existsSync32(planJsonPath)) {
|
|
21554
21931
|
throw new Error(`[assemble] planDir missing plan.json: ${planJsonPath}`);
|
|
21555
21932
|
}
|
|
@@ -21600,7 +21977,7 @@ function cfrEncodeArgs(inputArgs, encoder, fpsArg) {
|
|
|
21600
21977
|
return args;
|
|
21601
21978
|
}
|
|
21602
21979
|
async function reencodeCfr(ctx, chunkPaths, encoder) {
|
|
21603
|
-
const cfrOutputPath =
|
|
21980
|
+
const cfrOutputPath = join49(ctx.workDir, `cfr.${ctx.format}`);
|
|
21604
21981
|
const cfrArgs = cfrEncodeArgs(videoInputArgs(ctx, chunkPaths), encoder, ctx.fpsArg);
|
|
21605
21982
|
appendRenderProvenanceArgs2(cfrArgs, cfrOutputPath);
|
|
21606
21983
|
cfrArgs.push("-y", cfrOutputPath);
|
|
@@ -21627,7 +22004,7 @@ async function writeJoined(ctx, videoSources, audioPath, finalPath) {
|
|
|
21627
22004
|
);
|
|
21628
22005
|
return;
|
|
21629
22006
|
}
|
|
21630
|
-
const paddedAudioPath =
|
|
22007
|
+
const paddedAudioPath = join49(ctx.workDir, "audio-padded.m4a");
|
|
21631
22008
|
const padTrimResult = await ctx.timed(
|
|
21632
22009
|
"audioMs",
|
|
21633
22010
|
() => padOrTrimAudioToVideoFrameCount({
|
|
@@ -21667,7 +22044,7 @@ async function writeJoined(ctx, videoSources, audioPath, finalPath) {
|
|
|
21667
22044
|
}
|
|
21668
22045
|
}
|
|
21669
22046
|
function concatDemuxerInput(chunkPaths, workDir, fpsArg) {
|
|
21670
|
-
const listPath =
|
|
22047
|
+
const listPath = join49(workDir, "concat-list.txt");
|
|
21671
22048
|
const body = chunkPaths.map((path) => `file '${path.replace(/'/g, "'\\''")}'`).join("\n");
|
|
21672
22049
|
writeFileSync15(listPath, `${body}
|
|
21673
22050
|
`, "utf-8");
|
|
@@ -21705,11 +22082,11 @@ function stitchesSeparateAudio(planDir) {
|
|
|
21705
22082
|
return lockedAudio(planDir).separateAudio === true;
|
|
21706
22083
|
}
|
|
21707
22084
|
function planFormat(planDir) {
|
|
21708
|
-
const plan2 = JSON.parse(readFileSync16(
|
|
22085
|
+
const plan2 = JSON.parse(readFileSync16(join49(planDir, "plan.json"), "utf-8"));
|
|
21709
22086
|
return plan2.dimensions.format;
|
|
21710
22087
|
}
|
|
21711
22088
|
function lockedAudio(planDir) {
|
|
21712
|
-
const encoderJsonPath =
|
|
22089
|
+
const encoderJsonPath = join49(planDir, "meta", "encoder.json");
|
|
21713
22090
|
if (!existsSync32(encoderJsonPath)) return {};
|
|
21714
22091
|
return JSON.parse(readFileSync16(encoderJsonPath, "utf-8"));
|
|
21715
22092
|
}
|
|
@@ -21719,7 +22096,7 @@ function readCfrEncoder(planDir, format) {
|
|
|
21719
22096
|
`[assemble] cfr=true is only supported for format="mp4" (got "${format}"). Stream-copy paths for webm and mov already produce exact avg_frame_rate; cfr re-encode is not needed.`
|
|
21720
22097
|
);
|
|
21721
22098
|
}
|
|
21722
|
-
const encoderJsonPath =
|
|
22099
|
+
const encoderJsonPath = join49(planDir, "meta", "encoder.json");
|
|
21723
22100
|
if (!existsSync32(encoderJsonPath)) {
|
|
21724
22101
|
throw new Error(`[assemble] planDir missing meta/encoder.json: ${encoderJsonPath}`);
|
|
21725
22102
|
}
|
|
@@ -21732,22 +22109,22 @@ function readCfrEncoder(planDir, format) {
|
|
|
21732
22109
|
return encoderJson;
|
|
21733
22110
|
}
|
|
21734
22111
|
function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, startTimeMs) {
|
|
21735
|
-
if (existsSync32(outputPath))
|
|
21736
|
-
|
|
22112
|
+
if (existsSync32(outputPath)) rmSync20(outputPath, { recursive: true, force: true });
|
|
22113
|
+
mkdirSync22(outputPath, { recursive: true });
|
|
21737
22114
|
let globalIdx = 0;
|
|
21738
22115
|
for (const chunkDir of chunkPaths) {
|
|
21739
|
-
if (!
|
|
22116
|
+
if (!statSync14(chunkDir).isDirectory()) {
|
|
21740
22117
|
throw new Error(
|
|
21741
22118
|
`[assemble] png-sequence chunk must be a directory: ${chunkDir} (got a file)`
|
|
21742
22119
|
);
|
|
21743
22120
|
}
|
|
21744
|
-
const frames =
|
|
22121
|
+
const frames = readdirSync12(chunkDir).filter((name) => name.endsWith(".png")).sort();
|
|
21745
22122
|
if (frames.length === 0) {
|
|
21746
22123
|
throw new Error(`[assemble] png-sequence chunk has no frames: ${chunkDir}`);
|
|
21747
22124
|
}
|
|
21748
22125
|
for (const frame of frames) {
|
|
21749
|
-
const dst =
|
|
21750
|
-
cpSync3(
|
|
22126
|
+
const dst = join49(outputPath, formatExportFrameName(globalIdx, "png"));
|
|
22127
|
+
cpSync3(join49(chunkDir, frame), dst);
|
|
21751
22128
|
globalIdx += 1;
|
|
21752
22129
|
}
|
|
21753
22130
|
}
|
|
@@ -21757,13 +22134,13 @@ function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, start
|
|
|
21757
22134
|
);
|
|
21758
22135
|
}
|
|
21759
22136
|
if (audioPath !== null && existsSync32(audioPath)) {
|
|
21760
|
-
const sidecar =
|
|
22137
|
+
const sidecar = join49(outputPath, MIXED_AUDIO_FILENAME4);
|
|
21761
22138
|
cpSync3(audioPath, sidecar);
|
|
21762
22139
|
}
|
|
21763
22140
|
let fileSize = 0;
|
|
21764
|
-
for (const name of
|
|
22141
|
+
for (const name of readdirSync12(outputPath)) {
|
|
21765
22142
|
try {
|
|
21766
|
-
fileSize +=
|
|
22143
|
+
fileSize += statSync14(join49(outputPath, name)).size;
|
|
21767
22144
|
} catch {
|
|
21768
22145
|
}
|
|
21769
22146
|
}
|
|
@@ -21777,18 +22154,18 @@ function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, start
|
|
|
21777
22154
|
|
|
21778
22155
|
// src/services/distributed/planV2Execution.ts
|
|
21779
22156
|
async function renderChunkV2(planV2Dir, chunkIndex, outputChunkPath, options) {
|
|
21780
|
-
const workRoot = mkdtempSync10(
|
|
21781
|
-
const materializedPlanDir =
|
|
22157
|
+
const workRoot = mkdtempSync10(join50(tmpdir6(), "hf-plan-v2-chunk-"));
|
|
22158
|
+
const materializedPlanDir = join50(workRoot, "plan");
|
|
21782
22159
|
try {
|
|
21783
22160
|
materializePlanV2Target(planV2Dir, { role: "chunk", chunkIndex }, materializedPlanDir);
|
|
21784
22161
|
return await renderChunk(materializedPlanDir, chunkIndex, outputChunkPath, options);
|
|
21785
22162
|
} finally {
|
|
21786
|
-
|
|
22163
|
+
rmSync21(workRoot, { recursive: true, force: true });
|
|
21787
22164
|
}
|
|
21788
22165
|
}
|
|
21789
22166
|
async function assembleV2(planV2Dir, chunkPaths, outputPath, options) {
|
|
21790
|
-
const workRoot = mkdtempSync10(
|
|
21791
|
-
const materializedPlanDir =
|
|
22167
|
+
const workRoot = mkdtempSync10(join50(tmpdir6(), "hf-plan-v2-assembler-"));
|
|
22168
|
+
const materializedPlanDir = join50(workRoot, "plan");
|
|
21792
22169
|
try {
|
|
21793
22170
|
const materialized = materializePlanV2Target(
|
|
21794
22171
|
planV2Dir,
|
|
@@ -21803,7 +22180,7 @@ async function assembleV2(planV2Dir, chunkPaths, outputPath, options) {
|
|
|
21803
22180
|
options
|
|
21804
22181
|
);
|
|
21805
22182
|
} finally {
|
|
21806
|
-
|
|
22183
|
+
rmSync21(workRoot, { recursive: true, force: true });
|
|
21807
22184
|
}
|
|
21808
22185
|
}
|
|
21809
22186
|
|
|
@@ -21854,7 +22231,7 @@ export {
|
|
|
21854
22231
|
createRenderHandlers,
|
|
21855
22232
|
createRenderJob,
|
|
21856
22233
|
createRenderRequest,
|
|
21857
|
-
|
|
22234
|
+
createVideoFrameInjector4 as createVideoFrameInjector,
|
|
21858
22235
|
defaultLogger,
|
|
21859
22236
|
distributedConfigFromRequest,
|
|
21860
22237
|
executeRenderJob,
|