@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/public-server.js
CHANGED
|
@@ -3,14 +3,14 @@
|
|
|
3
3
|
// src/server.ts
|
|
4
4
|
import {
|
|
5
5
|
existsSync as existsSync23,
|
|
6
|
-
mkdirSync as
|
|
7
|
-
statSync as
|
|
6
|
+
mkdirSync as mkdirSync13,
|
|
7
|
+
statSync as statSync11,
|
|
8
8
|
mkdtempSync as mkdtempSync4,
|
|
9
9
|
writeFileSync as writeFileSync8,
|
|
10
|
-
rmSync as
|
|
10
|
+
rmSync as rmSync13,
|
|
11
11
|
createReadStream as createReadStream3
|
|
12
12
|
} from "node:fs";
|
|
13
|
-
import { resolve as resolve10, dirname as
|
|
13
|
+
import { resolve as resolve10, dirname as dirname15, join as join35 } from "node:path";
|
|
14
14
|
import { tmpdir as tmpdir3 } from "node:os";
|
|
15
15
|
import { parseArgs } from "node:util";
|
|
16
16
|
import crypto from "node:crypto";
|
|
@@ -27,10 +27,10 @@ import {
|
|
|
27
27
|
import { audioFailureDiagnostics as audioFailureDiagnostics2 } from "@hyperframes/engine";
|
|
28
28
|
import {
|
|
29
29
|
existsSync as existsSync20,
|
|
30
|
-
mkdirSync as
|
|
31
|
-
readdirSync as
|
|
32
|
-
rmSync as
|
|
33
|
-
statSync as
|
|
30
|
+
mkdirSync as mkdirSync12,
|
|
31
|
+
readdirSync as readdirSync6,
|
|
32
|
+
rmSync as rmSync12,
|
|
33
|
+
statSync as statSync9,
|
|
34
34
|
writeFileSync as writeFileSync7,
|
|
35
35
|
copyFileSync as copyFileSync5,
|
|
36
36
|
appendFileSync
|
|
@@ -53,7 +53,7 @@ import {
|
|
|
53
53
|
closeCaptureSession as closeCaptureSession8,
|
|
54
54
|
createCaptureSession as createCaptureSession8,
|
|
55
55
|
initializeSession as initializeSession8,
|
|
56
|
-
createVideoFrameInjector,
|
|
56
|
+
createVideoFrameInjector as createVideoFrameInjector2,
|
|
57
57
|
getEncoderPreset as getEncoderPreset3,
|
|
58
58
|
distributeFrames as distributeFrames2,
|
|
59
59
|
distributeFramesInterleaved as distributeFramesInterleaved2,
|
|
@@ -84,7 +84,7 @@ import {
|
|
|
84
84
|
referenceBackgroundResult,
|
|
85
85
|
createReferenceBackgroundRecorder
|
|
86
86
|
} from "@hyperframes/engine";
|
|
87
|
-
import { join as
|
|
87
|
+
import { join as join31, dirname as dirname13, resolve as resolve8 } from "path";
|
|
88
88
|
import { totalmem as totalmem2 } from "node:os";
|
|
89
89
|
import { randomUUID } from "crypto";
|
|
90
90
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
@@ -2029,6 +2029,7 @@ import {
|
|
|
2029
2029
|
getCapturePerfSummary as getCapturePerfSummary2,
|
|
2030
2030
|
initializeSession as initializeSession2,
|
|
2031
2031
|
prepareCaptureSessionForReuse as prepareCaptureSessionForReuse2,
|
|
2032
|
+
resolveDeVerifyMinDb as resolveDeVerifyMinDb2,
|
|
2032
2033
|
shouldDisableBrowserPoolForParallelWorker,
|
|
2033
2034
|
spawnStreamingEncoder as spawnStreamingEncoder2
|
|
2034
2035
|
} from "@hyperframes/engine";
|
|
@@ -3899,42 +3900,47 @@ function createDrainFrameGuard(args) {
|
|
|
3899
3900
|
sizes.push(buf.length);
|
|
3900
3901
|
if (sizes.length > 60) sizes.shift();
|
|
3901
3902
|
}
|
|
3902
|
-
|
|
3903
|
-
if (truth) {
|
|
3904
|
-
let db;
|
|
3905
|
-
try {
|
|
3906
|
-
db = await psnrDb(buf, truth);
|
|
3907
|
-
} catch (err) {
|
|
3908
|
-
log.warn("[Render] drawElement self-verify sample skipped (psnr infrastructure)", {
|
|
3909
|
-
frame: idx,
|
|
3910
|
-
error: err instanceof Error ? err.message : String(err)
|
|
3911
|
-
});
|
|
3912
|
-
return buf;
|
|
3913
|
-
}
|
|
3914
|
-
if (db < verifyMinDb) {
|
|
3915
|
-
const dumpDir = await mkdtemp(join9(tmpdir(), "hf-de-verify-fail-")).catch(() => null);
|
|
3916
|
-
if (dumpDir) {
|
|
3917
|
-
await Promise.all([
|
|
3918
|
-
writeFile(join9(dumpDir, `frame-${idx}-de.jpg`), buf),
|
|
3919
|
-
writeFile(join9(dumpDir, `frame-${idx}-truth.jpg`), truth)
|
|
3920
|
-
]).catch(() => {
|
|
3921
|
-
});
|
|
3922
|
-
}
|
|
3923
|
-
throw new DrawElementVerificationError(
|
|
3924
|
-
`drawElement self-verify failed at frame ${idx}: ${db.toFixed(1)}dB < ${verifyMinDb}dB vs pre-injection screenshot${dumpDir ? ` (pair: ${dumpDir})` : ""}`,
|
|
3925
|
-
{ kind: "psnr", frameIndex: idx, failedDb: db, verifyThresholdDb: verifyMinDb }
|
|
3926
|
-
);
|
|
3927
|
-
}
|
|
3928
|
-
stats.verifyChecked += 1;
|
|
3929
|
-
stats.verifyMinDb = stats.verifyMinDb === void 0 ? db : Math.min(stats.verifyMinDb, db);
|
|
3930
|
-
log.info("[Render] drawElement self-verify passed", {
|
|
3931
|
-
frame: idx,
|
|
3932
|
-
psnrDb: db === Infinity ? "inf" : Number(db.toFixed(1))
|
|
3933
|
-
});
|
|
3934
|
-
}
|
|
3903
|
+
await verifyDrawElementSample(session, idx, buf, { log, stats, verifyMinDb });
|
|
3935
3904
|
return buf;
|
|
3936
3905
|
};
|
|
3937
3906
|
}
|
|
3907
|
+
async function verifyDrawElementSample(session, idx, buf, ctx) {
|
|
3908
|
+
const { log, stats, verifyMinDb } = ctx;
|
|
3909
|
+
const truth = session.deVerifyFrames?.get(idx);
|
|
3910
|
+
if (!truth) return;
|
|
3911
|
+
let db;
|
|
3912
|
+
try {
|
|
3913
|
+
db = await psnrDb(buf, truth);
|
|
3914
|
+
} catch (err) {
|
|
3915
|
+
log.warn("[Render] drawElement self-verify sample skipped (psnr infrastructure)", {
|
|
3916
|
+
frame: idx,
|
|
3917
|
+
error: err instanceof Error ? err.message : String(err)
|
|
3918
|
+
});
|
|
3919
|
+
return;
|
|
3920
|
+
}
|
|
3921
|
+
if (db < verifyMinDb) {
|
|
3922
|
+
const dumpDir = await mkdtemp(join9(tmpdir(), "hf-de-verify-fail-")).catch(() => null);
|
|
3923
|
+
if (dumpDir) {
|
|
3924
|
+
await Promise.all([
|
|
3925
|
+
writeFile(join9(dumpDir, `frame-${idx}-de.jpg`), buf),
|
|
3926
|
+
writeFile(join9(dumpDir, `frame-${idx}-truth.jpg`), truth)
|
|
3927
|
+
]).catch(() => {
|
|
3928
|
+
});
|
|
3929
|
+
}
|
|
3930
|
+
throw new DrawElementVerificationError(
|
|
3931
|
+
`drawElement self-verify failed at frame ${idx}: ${db.toFixed(1)}dB < ${verifyMinDb}dB vs pre-injection screenshot${dumpDir ? ` (pair: ${dumpDir})` : ""}`,
|
|
3932
|
+
{ kind: "psnr", frameIndex: idx, failedDb: db, verifyThresholdDb: verifyMinDb }
|
|
3933
|
+
);
|
|
3934
|
+
}
|
|
3935
|
+
if (stats) {
|
|
3936
|
+
stats.verifyChecked += 1;
|
|
3937
|
+
stats.verifyMinDb = stats.verifyMinDb === void 0 ? db : Math.min(stats.verifyMinDb, db);
|
|
3938
|
+
}
|
|
3939
|
+
log.info("[Render] drawElement self-verify passed", {
|
|
3940
|
+
frame: idx,
|
|
3941
|
+
psnrDb: db === Infinity ? "inf" : Number(db.toFixed(1))
|
|
3942
|
+
});
|
|
3943
|
+
}
|
|
3938
3944
|
async function runWorkerEncodePipelineLoop(session, totalFrames, job, currentEncoder, reorderBuffer, assertNotAborted, onProgress, log, stats, abortSignal, resume, risk) {
|
|
3939
3945
|
let prev = null;
|
|
3940
3946
|
const frameTime = (i) => i * job.config.fps.den / job.config.fps.num;
|
|
@@ -4398,6 +4404,7 @@ async function runCaptureStreamingStage(input) {
|
|
|
4398
4404
|
);
|
|
4399
4405
|
} else {
|
|
4400
4406
|
const stallTimeoutMs = resolveCaptureStallTimeoutMs();
|
|
4407
|
+
const verifyMinDb = resolveDeVerifyMinDb();
|
|
4401
4408
|
let lastProgressAt = Date.now();
|
|
4402
4409
|
for (let i = resume.from; i < totalFrames; i++) {
|
|
4403
4410
|
assertNotAborted();
|
|
@@ -4414,6 +4421,7 @@ async function runCaptureStreamingStage(input) {
|
|
|
4414
4421
|
{ session, frameIndex: i, totalFrames, stallTimeoutMs: budget },
|
|
4415
4422
|
abortSignal
|
|
4416
4423
|
);
|
|
4424
|
+
await verifyDrawElementSample(session, i, buffer, { log, verifyMinDb });
|
|
4417
4425
|
await reorderBuffer.waitForFrame(i);
|
|
4418
4426
|
ensureFrameWritten(await currentEncoder.writeFrame(buffer), i, currentEncoder);
|
|
4419
4427
|
reorderBuffer.advanceTo(i + 1);
|
|
@@ -4522,6 +4530,10 @@ async function captureSegmentFrames(ctx, segment, encoder) {
|
|
|
4522
4530
|
},
|
|
4523
4531
|
ctx.abortSignal
|
|
4524
4532
|
);
|
|
4533
|
+
await verifyDrawElementSample(ctx.session, i, buffer, {
|
|
4534
|
+
log: ctx.log,
|
|
4535
|
+
verifyMinDb: ctx.verifyMinDb
|
|
4536
|
+
});
|
|
4525
4537
|
ensureFrameWritten(await encoder.writeFrame(buffer), i, encoder);
|
|
4526
4538
|
ctx.job.framesRendered = i + 1;
|
|
4527
4539
|
lastProgressAt = Date.now();
|
|
@@ -4536,6 +4548,25 @@ async function captureSegmentFrames(ctx, segment, encoder) {
|
|
|
4536
4548
|
}
|
|
4537
4549
|
}
|
|
4538
4550
|
async function captureSegment(ctx, segment) {
|
|
4551
|
+
const { frames } = ctx;
|
|
4552
|
+
if (!frames) return captureSegmentToEncoder(ctx, segment);
|
|
4553
|
+
try {
|
|
4554
|
+
reportFrameProgress(
|
|
4555
|
+
ctx.job,
|
|
4556
|
+
`Reading video for segment ${segment.index + 1}/${ctx.segmentCount}`,
|
|
4557
|
+
Math.round(25 + segment.startFrame / ctx.totalFrames * 55),
|
|
4558
|
+
ctx.onProgress,
|
|
4559
|
+
segment.startFrame,
|
|
4560
|
+
ctx.totalFrames
|
|
4561
|
+
);
|
|
4562
|
+
await frames.prepare(segment);
|
|
4563
|
+
ctx.assertNotAborted();
|
|
4564
|
+
return await captureSegmentToEncoder(ctx, segment);
|
|
4565
|
+
} finally {
|
|
4566
|
+
frames.release();
|
|
4567
|
+
}
|
|
4568
|
+
}
|
|
4569
|
+
async function captureSegmentToEncoder(ctx, segment) {
|
|
4539
4570
|
const segmentPath = segmentOutputPath(ctx.segmentDir, segment.index);
|
|
4540
4571
|
let encoder;
|
|
4541
4572
|
try {
|
|
@@ -4588,16 +4619,16 @@ var DEFAULT_DEPS = {
|
|
|
4588
4619
|
function defaultSessionFactory(input) {
|
|
4589
4620
|
let probeSession = input.probeSession;
|
|
4590
4621
|
return {
|
|
4591
|
-
create: async () => {
|
|
4622
|
+
create: async (injector = input.createRenderVideoFrameInjector) => {
|
|
4592
4623
|
const probeCannotCaptureEveryFrame = Boolean(probeSession?.deRiskFrames?.size);
|
|
4593
4624
|
if (probeSession && probeCannotCaptureEveryFrame) {
|
|
4594
4625
|
await closeCaptureSession2(probeSession).catch(() => {
|
|
4595
4626
|
});
|
|
4596
4627
|
probeSession = null;
|
|
4597
4628
|
}
|
|
4598
|
-
const session = probeSession ?? await openSegmentedSession(input);
|
|
4629
|
+
const session = probeSession ?? await openSegmentedSession(input, injector);
|
|
4599
4630
|
if (probeSession) {
|
|
4600
|
-
openSegmentedSessionReuse(input, probeSession);
|
|
4631
|
+
openSegmentedSessionReuse(input, probeSession, injector);
|
|
4601
4632
|
probeSession = null;
|
|
4602
4633
|
}
|
|
4603
4634
|
try {
|
|
@@ -4612,12 +4643,12 @@ function defaultSessionFactory(input) {
|
|
|
4612
4643
|
}
|
|
4613
4644
|
};
|
|
4614
4645
|
}
|
|
4615
|
-
function openSegmentedSessionReuse(input, session) {
|
|
4616
|
-
prepareCaptureSessionForReuse2(session, input.framesDir,
|
|
4646
|
+
function openSegmentedSessionReuse(input, session, injector) {
|
|
4647
|
+
prepareCaptureSessionForReuse2(session, input.framesDir, injector());
|
|
4617
4648
|
}
|
|
4618
|
-
async function openSegmentedSession(input) {
|
|
4649
|
+
async function openSegmentedSession(input, injector) {
|
|
4619
4650
|
const captureCfg = input.cfg.forceScreenshot === input.plan.forceScreenshot ? input.cfg : { ...input.cfg, forceScreenshot: input.plan.forceScreenshot };
|
|
4620
|
-
const videoInjector =
|
|
4651
|
+
const videoInjector = injector();
|
|
4621
4652
|
const captureOptions = { ...input.buildCaptureOptions(), deFrameFallback: false };
|
|
4622
4653
|
const ownBrowser = shouldDisableBrowserPoolForParallelWorker({
|
|
4623
4654
|
parallel: (input.workerCount ?? 1) > 1
|
|
@@ -4630,6 +4661,19 @@ async function openSegmentedSession(input) {
|
|
|
4630
4661
|
ownBrowser ? { ...captureCfg, enableBrowserPool: false } : captureCfg
|
|
4631
4662
|
);
|
|
4632
4663
|
}
|
|
4664
|
+
function removeJoinedSegments(run, segmentPaths) {
|
|
4665
|
+
if (!run.input.segmentVideoFrames || run.input.job.config.keepSegments === true) return;
|
|
4666
|
+
for (const path of segmentPaths) {
|
|
4667
|
+
try {
|
|
4668
|
+
run.deps.removeFile(path);
|
|
4669
|
+
} catch (err) {
|
|
4670
|
+
run.input.log.warn("[Render] could not remove a joined segment file", {
|
|
4671
|
+
path,
|
|
4672
|
+
error: err instanceof Error ? err.message : String(err)
|
|
4673
|
+
});
|
|
4674
|
+
}
|
|
4675
|
+
}
|
|
4676
|
+
}
|
|
4633
4677
|
async function concatSegments(concat, segmentPaths, videoOnlyPath, abortSignal, cfg) {
|
|
4634
4678
|
const result = await concat(segmentPaths, videoOnlyPath, abortSignal, cfg);
|
|
4635
4679
|
if (result.success) return;
|
|
@@ -4641,12 +4685,14 @@ async function concatSegments(concat, segmentPaths, videoOnlyPath, abortSignal,
|
|
|
4641
4685
|
async function createSegmentWorker(run, id) {
|
|
4642
4686
|
const { input, deps } = run;
|
|
4643
4687
|
const factory = input.sessionFactoryForWorker?.(id) ?? run.sessionFactory;
|
|
4688
|
+
const frames = input.segmentVideoFrames?.forWorker();
|
|
4644
4689
|
return {
|
|
4645
4690
|
id,
|
|
4646
4691
|
factory,
|
|
4647
4692
|
sessionSegments: 0,
|
|
4648
4693
|
ctx: {
|
|
4649
|
-
session: await factory.create(),
|
|
4694
|
+
session: await factory.create(frames && (() => frames.hook)),
|
|
4695
|
+
frames,
|
|
4650
4696
|
job: input.job,
|
|
4651
4697
|
cfg: input.cfg,
|
|
4652
4698
|
totalFrames: input.totalFrames,
|
|
@@ -4657,6 +4703,8 @@ async function createSegmentWorker(run, id) {
|
|
|
4657
4703
|
spawnEncoder: deps.spawnEncoder,
|
|
4658
4704
|
captureFrame: deps.captureFrame,
|
|
4659
4705
|
stallTimeoutMs: resolveCaptureStallTimeoutMs(),
|
|
4706
|
+
log: input.log,
|
|
4707
|
+
verifyMinDb: resolveDeVerifyMinDb2(),
|
|
4660
4708
|
abortSignal: input.abortSignal,
|
|
4661
4709
|
assertNotAborted: input.assertNotAborted,
|
|
4662
4710
|
onProgress: input.onProgress,
|
|
@@ -4676,7 +4724,8 @@ async function recycleWorkerSession(run, worker, why) {
|
|
|
4676
4724
|
run.input.dedupPerfs.push(getCapturePerfSummary2(worker.ctx.session));
|
|
4677
4725
|
const memory = worker.ctx.session.chromeMemory?.stats();
|
|
4678
4726
|
await run.deps.closeSession(worker.ctx.session);
|
|
4679
|
-
|
|
4727
|
+
const { frames } = worker.ctx;
|
|
4728
|
+
worker.ctx.session = await worker.factory.create(frames && (() => frames.hook));
|
|
4680
4729
|
worker.sessionSegments = 0;
|
|
4681
4730
|
if (why === "cadence") run.totals.browserRecycles += 1;
|
|
4682
4731
|
run.input.log.info(`[Render] segment browser recycled (${why})`, {
|
|
@@ -4744,6 +4793,7 @@ async function concatResumedRun(run, segmentPaths) {
|
|
|
4744
4793
|
input.abortSignal,
|
|
4745
4794
|
input.cfg
|
|
4746
4795
|
);
|
|
4796
|
+
removeJoinedSegments(run, segmentPaths);
|
|
4747
4797
|
return {
|
|
4748
4798
|
success: true,
|
|
4749
4799
|
encodeMs: 0,
|
|
@@ -4818,6 +4868,7 @@ async function runCaptureSegmentedStage(input) {
|
|
|
4818
4868
|
input.abortSignal,
|
|
4819
4869
|
input.cfg
|
|
4820
4870
|
);
|
|
4871
|
+
removeJoinedSegments(run, segmentPaths);
|
|
4821
4872
|
return {
|
|
4822
4873
|
success: true,
|
|
4823
4874
|
encodeMs: run.totals.encodeMs,
|
|
@@ -5249,10 +5300,273 @@ function checkPageNeverReady(input) {
|
|
|
5249
5300
|
return new PageNeverReadyError(error);
|
|
5250
5301
|
}
|
|
5251
5302
|
|
|
5303
|
+
// src/services/render/deferredVideoFrames.ts
|
|
5304
|
+
import { mkdirSync as mkdirSync4, rmSync as rmSync7 } from "node:fs";
|
|
5305
|
+
import { dirname as dirname5, join as join12 } from "node:path";
|
|
5306
|
+
import {
|
|
5307
|
+
createFrameLookupTable,
|
|
5308
|
+
extractVideoFramesRange,
|
|
5309
|
+
runVideoExtractionWithRetry
|
|
5310
|
+
} from "@hyperframes/engine";
|
|
5311
|
+
function contiguousRuns(sortedIndexes) {
|
|
5312
|
+
const runs = [];
|
|
5313
|
+
for (const index of sortedIndexes) {
|
|
5314
|
+
const last = runs[runs.length - 1];
|
|
5315
|
+
if (last && last.firstFrame + last.frames === index) last.frames++;
|
|
5316
|
+
else runs.push({ firstFrame: index, frames: 1 });
|
|
5317
|
+
}
|
|
5318
|
+
return runs;
|
|
5319
|
+
}
|
|
5320
|
+
function shownDeferredFrameIndexes(source, frames, fps) {
|
|
5321
|
+
const shown = /* @__PURE__ */ new Map();
|
|
5322
|
+
const deferred = source.extracted.filter((video) => video.deferredRange);
|
|
5323
|
+
if (deferred.length === 0) return shown;
|
|
5324
|
+
const table = createFrameLookupTable(
|
|
5325
|
+
source.videos,
|
|
5326
|
+
deferred.map((video) => ({
|
|
5327
|
+
...video,
|
|
5328
|
+
outputDir: "",
|
|
5329
|
+
framePaths: new Map(Array.from({ length: video.totalFrames }, (_, i) => [i, String(i)]))
|
|
5330
|
+
})),
|
|
5331
|
+
resolveRenderFpsConfig(fps).value
|
|
5332
|
+
);
|
|
5333
|
+
for (const frame of frames) {
|
|
5334
|
+
const globalTime = frame * fps.den / fps.num;
|
|
5335
|
+
for (const [videoId, payload] of table.getActiveFramePayloads(globalTime)) {
|
|
5336
|
+
const indexes = shown.get(videoId) ?? /* @__PURE__ */ new Set();
|
|
5337
|
+
indexes.add(payload.frameIndex);
|
|
5338
|
+
shown.set(videoId, indexes);
|
|
5339
|
+
}
|
|
5340
|
+
}
|
|
5341
|
+
return shown;
|
|
5342
|
+
}
|
|
5343
|
+
async function extractDeferredFrames(input) {
|
|
5344
|
+
const deferred = input.extracted.filter((video) => video.deferredRange);
|
|
5345
|
+
if (deferred.length === 0) return [];
|
|
5346
|
+
const shown = shownDeferredFrameIndexes(input, input.frames, input.fps);
|
|
5347
|
+
return Promise.all(
|
|
5348
|
+
deferred.map(async (video) => {
|
|
5349
|
+
const outputDir = join12(input.outputRoot, video.videoId);
|
|
5350
|
+
const indexes = [...shown.get(video.videoId) ?? []].sort((a, b) => a - b);
|
|
5351
|
+
if (indexes.length === 0) {
|
|
5352
|
+
return { ...video, outputDir, framePaths: /* @__PURE__ */ new Map(), ownedByLookup: false };
|
|
5353
|
+
}
|
|
5354
|
+
const range = video.deferredRange;
|
|
5355
|
+
const extracted = await extractVideoFramesRange(
|
|
5356
|
+
input.sourceOf(video),
|
|
5357
|
+
video.videoId,
|
|
5358
|
+
range.startTime,
|
|
5359
|
+
range.durationSeconds,
|
|
5360
|
+
{
|
|
5361
|
+
fps: input.fps,
|
|
5362
|
+
outputDir: input.outputRoot,
|
|
5363
|
+
format: range.format,
|
|
5364
|
+
frameRanges: contiguousRuns(indexes),
|
|
5365
|
+
shownBox: input.shownBoxes?.get(video.videoId)
|
|
5366
|
+
},
|
|
5367
|
+
input.signal,
|
|
5368
|
+
input.cfg
|
|
5369
|
+
);
|
|
5370
|
+
const missing = indexes.find((index) => !extracted.framePaths.has(index));
|
|
5371
|
+
if (missing !== void 0) throw input.onMissingFrame(video, missing);
|
|
5372
|
+
return { ...video, outputDir, framePaths: extracted.framePaths, ownedByLookup: false };
|
|
5373
|
+
})
|
|
5374
|
+
);
|
|
5375
|
+
}
|
|
5376
|
+
async function extractAllDeferredFrames(extracted, input) {
|
|
5377
|
+
const framesTotal = extracted.reduce(
|
|
5378
|
+
(sum, video) => sum + (video.deferredRange ? video.totalFrames : 0),
|
|
5379
|
+
0
|
|
5380
|
+
);
|
|
5381
|
+
let framesDone = 0;
|
|
5382
|
+
for (const video of extracted) {
|
|
5383
|
+
const range = video.deferredRange;
|
|
5384
|
+
if (!range) continue;
|
|
5385
|
+
const { result: filled } = await runVideoExtractionWithRetry(
|
|
5386
|
+
() => extractVideoFramesRange(
|
|
5387
|
+
input.sourceOf?.(video) ?? video.srcPath,
|
|
5388
|
+
video.videoId,
|
|
5389
|
+
range.startTime,
|
|
5390
|
+
range.durationSeconds,
|
|
5391
|
+
{ fps: input.fps, outputDir: dirname5(video.outputDir), format: range.format },
|
|
5392
|
+
input.signal,
|
|
5393
|
+
input.cfg
|
|
5394
|
+
),
|
|
5395
|
+
{
|
|
5396
|
+
signal: input.signal,
|
|
5397
|
+
maxTransientRetries: input.maxTransientRetries,
|
|
5398
|
+
onRetry: () => {
|
|
5399
|
+
rmSync7(video.outputDir, { recursive: true, force: true });
|
|
5400
|
+
mkdirSync4(video.outputDir, { recursive: true });
|
|
5401
|
+
}
|
|
5402
|
+
}
|
|
5403
|
+
);
|
|
5404
|
+
if (filled.framePaths.size < video.totalFrames) {
|
|
5405
|
+
throw new Error(
|
|
5406
|
+
`[Render] video ${JSON.stringify(video.videoId)} has ${filled.framePaths.size} frames; it was expected to have ${video.totalFrames} frames.`
|
|
5407
|
+
);
|
|
5408
|
+
}
|
|
5409
|
+
video.framePaths = filled.framePaths;
|
|
5410
|
+
delete video.deferredRange;
|
|
5411
|
+
framesDone += video.totalFrames;
|
|
5412
|
+
input.onProgress?.(framesDone, framesTotal);
|
|
5413
|
+
}
|
|
5414
|
+
}
|
|
5415
|
+
|
|
5416
|
+
// src/services/render/segmentVideoFrames.ts
|
|
5417
|
+
import { mkdirSync as mkdirSync5, rmSync as rmSync8 } from "node:fs";
|
|
5418
|
+
import { join as join13 } from "node:path";
|
|
5419
|
+
import {
|
|
5420
|
+
createFrameLookupTable as createFrameLookupTable2,
|
|
5421
|
+
createVideoFrameInjector
|
|
5422
|
+
} from "@hyperframes/engine";
|
|
5423
|
+
function segmentFramesDir(rootDir, index) {
|
|
5424
|
+
return join13(rootDir, "segment-frames", `segment_${String(index).padStart(5, "0")}`);
|
|
5425
|
+
}
|
|
5426
|
+
function createSegmentVideoFrames(input) {
|
|
5427
|
+
const extractFrames = input.extractFrames ?? extractDeferredFrames;
|
|
5428
|
+
const sourceOf = input.sourceOf ?? ((video) => video.srcPath);
|
|
5429
|
+
const fullyExtracted = input.extracted.filter((video) => !video.deferredRange);
|
|
5430
|
+
const tableFor = async (frames, dir) => {
|
|
5431
|
+
const sliced = await extractFrames({
|
|
5432
|
+
videos: input.videos,
|
|
5433
|
+
extracted: input.extracted,
|
|
5434
|
+
frames,
|
|
5435
|
+
fps: input.fps,
|
|
5436
|
+
outputRoot: dir,
|
|
5437
|
+
cfg: input.cfg,
|
|
5438
|
+
signal: input.abortSignal,
|
|
5439
|
+
sourceOf,
|
|
5440
|
+
onMissingFrame: (video, index) => new Error(
|
|
5441
|
+
`[Render] video ${JSON.stringify(video.videoId)} has no frame ${index}; it was expected to have ${video.totalFrames} frames.`
|
|
5442
|
+
)
|
|
5443
|
+
});
|
|
5444
|
+
return createFrameLookupTable2(
|
|
5445
|
+
input.videos,
|
|
5446
|
+
[...fullyExtracted, ...sliced],
|
|
5447
|
+
resolveRenderFpsConfig(input.fps).value
|
|
5448
|
+
);
|
|
5449
|
+
};
|
|
5450
|
+
const injectorFor = async (frames, dir) => createVideoFrameInjector(await tableFor(frames, dir), input.injectorOptions);
|
|
5451
|
+
let onDemandCalls = 0;
|
|
5452
|
+
const onDemandHook = () => Object.assign(
|
|
5453
|
+
async (page, time, heldVideoTime) => {
|
|
5454
|
+
const dir = join13(
|
|
5455
|
+
input.rootDir,
|
|
5456
|
+
"segment-frames",
|
|
5457
|
+
`on-demand_${String(onDemandCalls++).padStart(5, "0")}`
|
|
5458
|
+
);
|
|
5459
|
+
try {
|
|
5460
|
+
const frames = [time, heldVideoTime].filter((t) => t !== void 0).map((t) => Math.round(t * input.fps.num / input.fps.den));
|
|
5461
|
+
const injector = await injectorFor(frames, dir);
|
|
5462
|
+
await injector?.(page, time, heldVideoTime);
|
|
5463
|
+
} finally {
|
|
5464
|
+
rmSync8(dir, { recursive: true, force: true });
|
|
5465
|
+
}
|
|
5466
|
+
},
|
|
5467
|
+
{ fresh: onDemandHook }
|
|
5468
|
+
);
|
|
5469
|
+
return {
|
|
5470
|
+
async forFrames(frames, name) {
|
|
5471
|
+
const dir = join13(input.rootDir, "segment-frames", name);
|
|
5472
|
+
const release = () => rmSync8(dir, { recursive: true, force: true });
|
|
5473
|
+
try {
|
|
5474
|
+
release();
|
|
5475
|
+
mkdirSync5(dir, { recursive: true });
|
|
5476
|
+
const table = await tableFor([...frames], dir);
|
|
5477
|
+
return { injector: () => createVideoFrameInjector(table, input.injectorOptions), release };
|
|
5478
|
+
} catch (error) {
|
|
5479
|
+
release();
|
|
5480
|
+
throw error;
|
|
5481
|
+
}
|
|
5482
|
+
},
|
|
5483
|
+
forWorker() {
|
|
5484
|
+
let injector = null;
|
|
5485
|
+
let folder = null;
|
|
5486
|
+
const onDemand = onDemandHook();
|
|
5487
|
+
const release = () => {
|
|
5488
|
+
injector = null;
|
|
5489
|
+
if (folder) rmSync8(folder, { recursive: true, force: true });
|
|
5490
|
+
folder = null;
|
|
5491
|
+
};
|
|
5492
|
+
return {
|
|
5493
|
+
hook: Object.assign(
|
|
5494
|
+
(page, time, heldVideoTime) => (injector ?? onDemand)(page, time, heldVideoTime),
|
|
5495
|
+
{ fresh: onDemandHook }
|
|
5496
|
+
),
|
|
5497
|
+
release,
|
|
5498
|
+
async prepare(segment) {
|
|
5499
|
+
release();
|
|
5500
|
+
const dir = segmentFramesDir(input.rootDir, segment.index);
|
|
5501
|
+
folder = dir;
|
|
5502
|
+
try {
|
|
5503
|
+
rmSync8(dir, { recursive: true, force: true });
|
|
5504
|
+
mkdirSync5(dir, { recursive: true });
|
|
5505
|
+
injector = await injectorFor(
|
|
5506
|
+
Array.from(
|
|
5507
|
+
{ length: segment.endFrame - segment.startFrame },
|
|
5508
|
+
(_, i) => segment.startFrame + i
|
|
5509
|
+
),
|
|
5510
|
+
dir
|
|
5511
|
+
);
|
|
5512
|
+
} catch (error) {
|
|
5513
|
+
release();
|
|
5514
|
+
throw error;
|
|
5515
|
+
}
|
|
5516
|
+
}
|
|
5517
|
+
};
|
|
5518
|
+
}
|
|
5519
|
+
};
|
|
5520
|
+
}
|
|
5521
|
+
|
|
5522
|
+
// src/services/render/videoFrameWindow.ts
|
|
5523
|
+
import { readdirSync as readdirSync3, statSync as statSync4 } from "node:fs";
|
|
5524
|
+
import { join as join14 } from "node:path";
|
|
5525
|
+
function shouldWindowVideoFrames(args) {
|
|
5526
|
+
return args.segmented && args.env.HF_VIDEO_FRAME_WINDOW?.trim().toLowerCase() === "true";
|
|
5527
|
+
}
|
|
5528
|
+
function directoryBytes(path) {
|
|
5529
|
+
let bytes = 0;
|
|
5530
|
+
let entries;
|
|
5531
|
+
try {
|
|
5532
|
+
entries = readdirSync3(path, { withFileTypes: true });
|
|
5533
|
+
} catch {
|
|
5534
|
+
return 0;
|
|
5535
|
+
}
|
|
5536
|
+
for (const entry of entries) {
|
|
5537
|
+
const child2 = join14(path, entry.name);
|
|
5538
|
+
try {
|
|
5539
|
+
bytes += entry.isDirectory() ? directoryBytes(child2) : statSync4(child2).size;
|
|
5540
|
+
} catch {
|
|
5541
|
+
}
|
|
5542
|
+
}
|
|
5543
|
+
return bytes;
|
|
5544
|
+
}
|
|
5545
|
+
function trackSegmentFramePeak(frames, rootDir, onPeak) {
|
|
5546
|
+
let peak = 0;
|
|
5547
|
+
return {
|
|
5548
|
+
forFrames: (frameIndexes, name) => frames.forFrames(frameIndexes, name),
|
|
5549
|
+
forWorker() {
|
|
5550
|
+
const worker = frames.forWorker();
|
|
5551
|
+
return {
|
|
5552
|
+
...worker,
|
|
5553
|
+
async prepare(segment) {
|
|
5554
|
+
await worker.prepare(segment);
|
|
5555
|
+
const held = directoryBytes(join14(rootDir, "segment-frames"));
|
|
5556
|
+
if (held > peak) {
|
|
5557
|
+
peak = held;
|
|
5558
|
+
onPeak(peak);
|
|
5559
|
+
}
|
|
5560
|
+
}
|
|
5561
|
+
};
|
|
5562
|
+
}
|
|
5563
|
+
};
|
|
5564
|
+
}
|
|
5565
|
+
|
|
5252
5566
|
// src/services/render/segmentManifest.ts
|
|
5253
5567
|
import { createHash as createHash2 } from "node:crypto";
|
|
5254
|
-
import { existsSync as existsSync7, mkdirSync as
|
|
5255
|
-
import { join as
|
|
5568
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync6, readFileSync as readFileSync4, statSync as statSync5, writeFileSync as writeFileSync3 } from "node:fs";
|
|
5569
|
+
import { join as join15 } from "node:path";
|
|
5256
5570
|
import { renameWithRetry as renameWithRetry2 } from "@hyperframes/engine";
|
|
5257
5571
|
var MANIFEST_FILENAME = "segments.json";
|
|
5258
5572
|
function computeSegmentPlanHash(input) {
|
|
@@ -5280,7 +5594,7 @@ function computeSegmentPlanHash(input) {
|
|
|
5280
5594
|
return createHash2("sha256").update(ordered.join(" ")).digest("hex").slice(0, 16);
|
|
5281
5595
|
}
|
|
5282
5596
|
function segmentDirFor(projectRendersDir, planHash) {
|
|
5283
|
-
return
|
|
5597
|
+
return join15(projectRendersDir, ".hf-segments", planHash);
|
|
5284
5598
|
}
|
|
5285
5599
|
function hasNumber(value, key) {
|
|
5286
5600
|
return key in value && typeof Reflect.get(value, key) === "number";
|
|
@@ -5298,7 +5612,7 @@ function isManifest(value) {
|
|
|
5298
5612
|
return Reflect.get(value, "version") === 1 && hasString(value, "planHash") && hasNumber(value, "totalFrames") && hasNumber(value, "segmentFrames") && Array.isArray(completed) && completed.every(isEntry);
|
|
5299
5613
|
}
|
|
5300
5614
|
function readSegmentManifest(segmentDir) {
|
|
5301
|
-
const path =
|
|
5615
|
+
const path = join15(segmentDir, MANIFEST_FILENAME);
|
|
5302
5616
|
if (!existsSync7(path)) return null;
|
|
5303
5617
|
try {
|
|
5304
5618
|
const parsed = JSON.parse(readFileSync4(path, "utf-8"));
|
|
@@ -5308,10 +5622,10 @@ function readSegmentManifest(segmentDir) {
|
|
|
5308
5622
|
}
|
|
5309
5623
|
}
|
|
5310
5624
|
function writeSegmentManifest(segmentDir, manifest) {
|
|
5311
|
-
|
|
5312
|
-
const tmp =
|
|
5625
|
+
mkdirSync6(segmentDir, { recursive: true });
|
|
5626
|
+
const tmp = join15(segmentDir, `${MANIFEST_FILENAME}.${process.pid}.tmp`);
|
|
5313
5627
|
writeFileSync3(tmp, JSON.stringify(manifest, null, 2), "utf-8");
|
|
5314
|
-
renameWithRetry2(tmp,
|
|
5628
|
+
renameWithRetry2(tmp, join15(segmentDir, MANIFEST_FILENAME));
|
|
5315
5629
|
}
|
|
5316
5630
|
async function probeSegmentFrameCount(path, probe) {
|
|
5317
5631
|
try {
|
|
@@ -5329,7 +5643,7 @@ async function validateCompletedSegments(manifest, expectedHash, probeFrames) {
|
|
|
5329
5643
|
if (manifest.planHash !== expectedHash) return ok;
|
|
5330
5644
|
for (const entry of manifest.completed) {
|
|
5331
5645
|
if (!existsSync7(entry.path)) continue;
|
|
5332
|
-
const size =
|
|
5646
|
+
const size = statSync5(entry.path).size;
|
|
5333
5647
|
if (size <= 0 || size !== entry.bytes) continue;
|
|
5334
5648
|
const frames = await probeFrames(entry.path);
|
|
5335
5649
|
if (frames !== entry.endFrame - entry.startFrame) continue;
|
|
@@ -5872,7 +6186,7 @@ function computeVideoFrameCoverage(videos, extracted, fps) {
|
|
|
5872
6186
|
for (const video of videos) {
|
|
5873
6187
|
const entry = byId.get(video.id);
|
|
5874
6188
|
const expectedFrames = expectedFramesForVideo(video, entry, fps);
|
|
5875
|
-
const capturedFrames = entry ? entry.framePaths.size : 0;
|
|
6189
|
+
const capturedFrames = entry ? entry.deferredRange ? entry.totalFrames : entry.framePaths.size : 0;
|
|
5876
6190
|
const ratio = expectedFrames === 0 ? 1 : capturedFrames / expectedFrames;
|
|
5877
6191
|
reports.push({
|
|
5878
6192
|
videoId: video.id,
|
|
@@ -5908,7 +6222,7 @@ function countAuthoredTimedClips(html) {
|
|
|
5908
6222
|
|
|
5909
6223
|
// src/services/render/stages/compileStage.ts
|
|
5910
6224
|
import { existsSync as existsSync11, writeFileSync as writeFileSync5 } from "node:fs";
|
|
5911
|
-
import { join as
|
|
6225
|
+
import { join as join19 } from "node:path";
|
|
5912
6226
|
import { suggestMatchingPreset } from "@hyperframes/core";
|
|
5913
6227
|
import { extractStandaloneEntryFromIndex, isTemplateEntry } from "@hyperframes/core/compiler";
|
|
5914
6228
|
import { readTextFile as readTextFile2 } from "@hyperframes/parsers/asset-resolution";
|
|
@@ -5916,8 +6230,8 @@ import { readTextFile as readTextFile2 } from "@hyperframes/parsers/asset-resolu
|
|
|
5916
6230
|
// src/services/htmlCompiler.ts
|
|
5917
6231
|
import { decodedUrlPath } from "@hyperframes/parsers/asset-paths";
|
|
5918
6232
|
import { createHash as createHash6 } from "node:crypto";
|
|
5919
|
-
import { createReadStream as createReadStream2, existsSync as existsSync10, mkdirSync as
|
|
5920
|
-
import { join as
|
|
6233
|
+
import { createReadStream as createReadStream2, existsSync as existsSync10, mkdirSync as mkdirSync9, readFileSync as readFileSync7 } from "fs";
|
|
6234
|
+
import { join as join18, dirname as dirname8, resolve as resolve7, basename as basename4, relative as relative2 } from "path";
|
|
5921
6235
|
import { parseHTML as parseHTML5 } from "linkedom";
|
|
5922
6236
|
import {
|
|
5923
6237
|
COMPOSITION_SOURCE_URL,
|
|
@@ -6163,8 +6477,8 @@ import {
|
|
|
6163
6477
|
|
|
6164
6478
|
// src/services/animatedGifPrep.ts
|
|
6165
6479
|
import { createHash as createHash4 } from "node:crypto";
|
|
6166
|
-
import { copyFileSync as copyFileSync2, existsSync as existsSync8, mkdirSync as
|
|
6167
|
-
import { dirname as
|
|
6480
|
+
import { copyFileSync as copyFileSync2, existsSync as existsSync8, mkdirSync as mkdirSync7, readFileSync as readFileSync5, rmSync as rmSync9, statSync as statSync6 } from "node:fs";
|
|
6481
|
+
import { dirname as dirname6, isAbsolute as isAbsolute2, join as join16, resolve as resolve5 } from "node:path";
|
|
6168
6482
|
import { parseHTML as parseHTML3 } from "linkedom";
|
|
6169
6483
|
import { parseAnimatedGifMetadata } from "@hyperframes/core";
|
|
6170
6484
|
import { DEFAULT_VP9_CPU_USED, runFfmpeg as runFfmpeg2, renameWithRetry as renameWithRetry3 } from "@hyperframes/engine";
|
|
@@ -6213,7 +6527,7 @@ function resolveLocalImageSourcePath(src, options) {
|
|
|
6213
6527
|
}
|
|
6214
6528
|
function isUsableFile(path) {
|
|
6215
6529
|
try {
|
|
6216
|
-
const stat =
|
|
6530
|
+
const stat = statSync6(path);
|
|
6217
6531
|
return stat.isFile() && stat.size > 0;
|
|
6218
6532
|
} catch {
|
|
6219
6533
|
return false;
|
|
@@ -6320,11 +6634,11 @@ async function runAnimatedGifTranscode(request) {
|
|
|
6320
6634
|
throw result.error ?? new Error(`Animated GIF transcode failed (${result.exitCode}): ${result.stderr.slice(-500)}`);
|
|
6321
6635
|
}
|
|
6322
6636
|
async function ensurePreparedWebm(input) {
|
|
6323
|
-
if (!existsSync8(
|
|
6324
|
-
|
|
6637
|
+
if (!existsSync8(dirname6(input.cachePath))) {
|
|
6638
|
+
mkdirSync7(dirname6(input.cachePath), { recursive: true });
|
|
6325
6639
|
}
|
|
6326
|
-
if (!existsSync8(
|
|
6327
|
-
|
|
6640
|
+
if (!existsSync8(dirname6(input.outputPath))) {
|
|
6641
|
+
mkdirSync7(dirname6(input.outputPath), { recursive: true });
|
|
6328
6642
|
}
|
|
6329
6643
|
if (!isUsableFile(input.cachePath)) {
|
|
6330
6644
|
const tmpPath = `${input.cachePath}.tmp-${process.pid}-${Date.now()}`;
|
|
@@ -6348,10 +6662,10 @@ async function ensurePreparedWebm(input) {
|
|
|
6348
6662
|
if (!isUsableFile(input.cachePath)) {
|
|
6349
6663
|
renameWithRetry3(tmpPath, input.cachePath);
|
|
6350
6664
|
} else {
|
|
6351
|
-
|
|
6665
|
+
rmSync9(tmpPath, { force: true });
|
|
6352
6666
|
}
|
|
6353
6667
|
} catch (error) {
|
|
6354
|
-
|
|
6668
|
+
rmSync9(tmpPath, { force: true });
|
|
6355
6669
|
throw error;
|
|
6356
6670
|
}
|
|
6357
6671
|
}
|
|
@@ -6407,7 +6721,7 @@ function replaceImageWithVideo(input) {
|
|
|
6407
6721
|
return video;
|
|
6408
6722
|
}
|
|
6409
6723
|
async function prepareAnimatedGifInputs(html, options) {
|
|
6410
|
-
const outputDir = options.outputDir ??
|
|
6724
|
+
const outputDir = options.outputDir ?? join16(options.downloadDir, PREPARED_GIF_SUBDIR);
|
|
6411
6725
|
const outputSrcPrefix = normalizeRelPath(options.outputSrcPrefix ?? PREPARED_GIF_SUBDIR);
|
|
6412
6726
|
const cacheDir = options.cacheDir ?? outputDir;
|
|
6413
6727
|
const { document: document2 } = parseHTML3(html);
|
|
@@ -6434,8 +6748,8 @@ async function prepareAnimatedGifInputs(html, options) {
|
|
|
6434
6748
|
);
|
|
6435
6749
|
const hash = computePreparedGifHash(bytes, loopIterations, padSeconds);
|
|
6436
6750
|
const filename = `${CACHE_SCHEMA}-${hash.slice(0, 24)}.webm`;
|
|
6437
|
-
const cachePath =
|
|
6438
|
-
const outputPath =
|
|
6751
|
+
const cachePath = join16(cacheDir, filename);
|
|
6752
|
+
const outputPath = join16(outputDir, filename);
|
|
6439
6753
|
const outputSrc = `${outputSrcPrefix}/${filename}`;
|
|
6440
6754
|
await ensurePreparedWebm({
|
|
6441
6755
|
sourcePath,
|
|
@@ -6472,15 +6786,15 @@ import { createHash as createHash5 } from "node:crypto";
|
|
|
6472
6786
|
import {
|
|
6473
6787
|
closeSync as closeSync4,
|
|
6474
6788
|
copyFileSync as copyFileSync3,
|
|
6475
|
-
mkdirSync as
|
|
6789
|
+
mkdirSync as mkdirSync8,
|
|
6476
6790
|
openSync as openSync4,
|
|
6477
6791
|
readFileSync as readFileSync6,
|
|
6478
6792
|
readSync as readSync3,
|
|
6479
|
-
rmSync as
|
|
6480
|
-
statSync as
|
|
6793
|
+
rmSync as rmSync10,
|
|
6794
|
+
statSync as statSync7,
|
|
6481
6795
|
writeFileSync as writeFileSync4
|
|
6482
6796
|
} from "node:fs";
|
|
6483
|
-
import { dirname as
|
|
6797
|
+
import { dirname as dirname7, join as join17, resolve as resolve6 } from "node:path";
|
|
6484
6798
|
import { parseHTML as parseHTML4 } from "linkedom";
|
|
6485
6799
|
import { renameWithRetry as renameWithRetry4, runFfmpeg as runFfmpeg3, runFfprobe } from "@hyperframes/engine";
|
|
6486
6800
|
|
|
@@ -6720,14 +7034,14 @@ function resolveImageSource(src, options) {
|
|
|
6720
7034
|
}
|
|
6721
7035
|
function isUsableFile2(path) {
|
|
6722
7036
|
try {
|
|
6723
|
-
return
|
|
7037
|
+
return statSync7(path).size > 0;
|
|
6724
7038
|
} catch {
|
|
6725
7039
|
return false;
|
|
6726
7040
|
}
|
|
6727
7041
|
}
|
|
6728
7042
|
async function ensurePreparedJpeg(source, cachePath, outputPath, options) {
|
|
6729
|
-
|
|
6730
|
-
|
|
7043
|
+
mkdirSync8(dirname7(cachePath), { recursive: true });
|
|
7044
|
+
mkdirSync8(dirname7(outputPath), { recursive: true });
|
|
6731
7045
|
if (!isUsableFile2(cachePath)) {
|
|
6732
7046
|
const tmpPath = `${cachePath}.tmp-${process.pid}-${Date.now()}`;
|
|
6733
7047
|
const inlinePath = `${tmpPath}.heic`;
|
|
@@ -6745,8 +7059,8 @@ async function ensurePreparedJpeg(source, cachePath, outputPath, options) {
|
|
|
6745
7059
|
const reason = error instanceof Error ? error.message : String(error);
|
|
6746
7060
|
throw TOOL_FAILURE_RE.test(reason) ? new HeifToolUnavailableError(source.label, reason) : new HeifImageConversionError(source.label, reason);
|
|
6747
7061
|
} finally {
|
|
6748
|
-
|
|
6749
|
-
|
|
7062
|
+
rmSync10(tmpPath, { force: true });
|
|
7063
|
+
rmSync10(inlinePath, { force: true });
|
|
6750
7064
|
}
|
|
6751
7065
|
}
|
|
6752
7066
|
if (resolve6(outputPath) !== resolve6(cachePath) && !isUsableFile2(outputPath)) {
|
|
@@ -6754,7 +7068,7 @@ async function ensurePreparedJpeg(source, cachePath, outputPath, options) {
|
|
|
6754
7068
|
}
|
|
6755
7069
|
}
|
|
6756
7070
|
async function prepareHeifImageInputs(html, options) {
|
|
6757
|
-
const outputDir = options.outputDir ??
|
|
7071
|
+
const outputDir = options.outputDir ?? join17(options.downloadDir, PREPARED_HEIF_SUBDIR);
|
|
6758
7072
|
const outputSrcPrefix = normalizeRelPath(options.outputSrcPrefix ?? PREPARED_HEIF_SUBDIR);
|
|
6759
7073
|
const cacheDir = options.cacheDir ?? outputDir;
|
|
6760
7074
|
const preparedAssets = /* @__PURE__ */ new Map();
|
|
@@ -6775,7 +7089,7 @@ async function prepareHeifImageInputs(html, options) {
|
|
|
6775
7089
|
await Promise.all(
|
|
6776
7090
|
[...unique].map(
|
|
6777
7091
|
([filename, source]) => withMediaProbeSlot(
|
|
6778
|
-
() => ensurePreparedJpeg(source,
|
|
7092
|
+
() => ensurePreparedJpeg(source, join17(cacheDir, filename), join17(outputDir, filename), options)
|
|
6779
7093
|
).catch((error) => {
|
|
6780
7094
|
if (!options.onFailure) throw error;
|
|
6781
7095
|
failed.add(filename);
|
|
@@ -6787,8 +7101,8 @@ async function prepareHeifImageInputs(html, options) {
|
|
|
6787
7101
|
if (failed.has(filename)) continue;
|
|
6788
7102
|
const outputSrc = `${outputSrcPrefix}/${filename}`;
|
|
6789
7103
|
img.setAttribute("src", outputSrc);
|
|
6790
|
-
preparedAssets.set(outputSrc,
|
|
6791
|
-
preparedImages.push({ sourceSrc: src, outputSrc, outputPath:
|
|
7104
|
+
preparedAssets.set(outputSrc, join17(outputDir, filename));
|
|
7105
|
+
preparedImages.push({ sourceSrc: src, outputSrc, outputPath: join17(outputDir, filename) });
|
|
6792
7106
|
}
|
|
6793
7107
|
return { html: document2.toString(), preparedAssets, preparedImages };
|
|
6794
7108
|
}
|
|
@@ -7112,8 +7426,8 @@ function detectShaderTransitionUsage(html) {
|
|
|
7112
7426
|
async function resolveMediaDuration(src, mediaStart, playbackRate, baseDir, downloadDir, tagName, elementIdentity, log, urlByAbsentLocalPath, probes) {
|
|
7113
7427
|
let filePath = src;
|
|
7114
7428
|
if (isHttpUrl(src)) {
|
|
7115
|
-
const remoteDir =
|
|
7116
|
-
if (!existsSync10(remoteDir))
|
|
7429
|
+
const remoteDir = join18(downloadDir, REMOTE_MEDIA_SUBDIR);
|
|
7430
|
+
if (!existsSync10(remoteDir)) mkdirSync9(remoteDir, { recursive: true });
|
|
7117
7431
|
try {
|
|
7118
7432
|
filePath = await downloadToTemp(src, remoteDir, void 0, void 0, void 0, {
|
|
7119
7433
|
onTelemetry: logRemoteDownloadTelemetry
|
|
@@ -7123,7 +7437,7 @@ async function resolveMediaDuration(src, mediaStart, playbackRate, baseDir, down
|
|
|
7123
7437
|
}
|
|
7124
7438
|
} else {
|
|
7125
7439
|
filePath = decodedUrlPath(src);
|
|
7126
|
-
if (!filePath.startsWith("/")) filePath =
|
|
7440
|
+
if (!filePath.startsWith("/")) filePath = join18(baseDir, filePath);
|
|
7127
7441
|
filePath = urlByAbsentLocalPath?.get(filePath) ?? filePath;
|
|
7128
7442
|
}
|
|
7129
7443
|
if (!isHttpUrl(filePath) && !existsSync10(filePath)) {
|
|
@@ -7284,7 +7598,7 @@ async function parseSubCompositions(html, projectDir, downloadDir, visited = /*
|
|
|
7284
7598
|
workItems.map(async (item) => {
|
|
7285
7599
|
const { html: compiledSub } = await compileHtmlFile(
|
|
7286
7600
|
rewriteUnresolvableGsapToCdn(item.rawSubHtml, projectDir, item.srcPath),
|
|
7287
|
-
|
|
7601
|
+
dirname8(item.filePath),
|
|
7288
7602
|
downloadDir,
|
|
7289
7603
|
void 0,
|
|
7290
7604
|
urlByAbsentLocalPath,
|
|
@@ -7714,7 +8028,7 @@ var REMOTE_SOURCE_TAG_RE = /<source\b[^>]*?\bsrc\s*=\s*["'](https?:\/\/[^"']+)["
|
|
|
7714
8028
|
var REMOTE_IMG_TAG_RE = /<img\b[^>]*?(?<![\w-])src\s*=\s*["'](https?:\/\/[^"']+)["'][^>]*>/gi;
|
|
7715
8029
|
async function downloadAndRewriteUrls(urlSet, html, remoteDir, warnLabel, logLabel, extraRewrite) {
|
|
7716
8030
|
if (urlSet.size === 0) return { html, remoteMediaAssets: /* @__PURE__ */ new Map() };
|
|
7717
|
-
if (!existsSync10(remoteDir))
|
|
8031
|
+
if (!existsSync10(remoteDir)) mkdirSync9(remoteDir, { recursive: true });
|
|
7718
8032
|
const urlToLocal = /* @__PURE__ */ new Map();
|
|
7719
8033
|
await Promise.all(
|
|
7720
8034
|
[...urlSet].map(async (url) => {
|
|
@@ -7761,7 +8075,7 @@ async function localizeRemoteMediaSources(html, downloadDir) {
|
|
|
7761
8075
|
return downloadAndRewriteUrls(
|
|
7762
8076
|
urlSet,
|
|
7763
8077
|
html,
|
|
7764
|
-
|
|
8078
|
+
join18(downloadDir, REMOTE_MEDIA_SUBDIR),
|
|
7765
8079
|
"Remote media download failed",
|
|
7766
8080
|
"Localized remote media source(s)"
|
|
7767
8081
|
);
|
|
@@ -7776,7 +8090,7 @@ async function localizeRemoteImageSources(html, downloadDir) {
|
|
|
7776
8090
|
return downloadAndRewriteUrls(
|
|
7777
8091
|
urlSet,
|
|
7778
8092
|
html,
|
|
7779
|
-
|
|
8093
|
+
join18(downloadDir, REMOTE_MEDIA_SUBDIR),
|
|
7780
8094
|
"Remote image download failed",
|
|
7781
8095
|
"Localized remote image source(s)"
|
|
7782
8096
|
);
|
|
@@ -7792,7 +8106,7 @@ async function localizeRemoteBackgroundImages(html, downloadDir) {
|
|
|
7792
8106
|
return downloadAndRewriteUrls(
|
|
7793
8107
|
urlSet,
|
|
7794
8108
|
html,
|
|
7795
|
-
|
|
8109
|
+
join18(downloadDir, REMOTE_MEDIA_SUBDIR),
|
|
7796
8110
|
"Remote background-image download failed",
|
|
7797
8111
|
"Localized remote background-image(s)",
|
|
7798
8112
|
// Quoted url('..')/url("..") are rewritten by downloadAndRewriteUrls' default
|
|
@@ -7902,7 +8216,7 @@ function projectStylesheetFontFamilies(html, projectDir) {
|
|
|
7902
8216
|
const family = /font-family\s*:\s*([^;}]+)/i.exec(face)?.[1]?.trim().replace(/^['"]|['"]$/g, "");
|
|
7903
8217
|
const shipsFile = [...face.matchAll(LOCAL_FONTFACE_URL_RE)].some(([, url = ""]) => {
|
|
7904
8218
|
const path = decodedUrlPath(url);
|
|
7905
|
-
const file = path.startsWith("/") ? resolve7(projectDir, `.${path}`) : resolve7(
|
|
8219
|
+
const file = path.startsWith("/") ? resolve7(projectDir, `.${path}`) : resolve7(dirname8(sheet), path);
|
|
7906
8220
|
return isPathInside2(file, projectDir) && existsSync10(file);
|
|
7907
8221
|
});
|
|
7908
8222
|
if (family && shipsFile) families.push(family);
|
|
@@ -7944,7 +8258,7 @@ function localizeFontFaceUrls(html, downloadDir) {
|
|
|
7944
8258
|
return downloadAndRewriteUrls(
|
|
7945
8259
|
collectFontFaceUrls(html),
|
|
7946
8260
|
html,
|
|
7947
|
-
|
|
8261
|
+
join18(downloadDir, REMOTE_MEDIA_SUBDIR),
|
|
7948
8262
|
"Remote font download failed",
|
|
7949
8263
|
"Localized remote font face(s)",
|
|
7950
8264
|
(h, url, relPath) => h.replaceAll(`url(${url})`, `url("${relPath}")`)
|
|
@@ -8618,10 +8932,10 @@ function adaptAspectAgnosticResolution(requested, aspectAgnostic, width, height,
|
|
|
8618
8932
|
}
|
|
8619
8933
|
function resolveEntryHtmlPath(input) {
|
|
8620
8934
|
const { projectDir, workDir, entryFile, log } = input;
|
|
8621
|
-
const htmlPath =
|
|
8935
|
+
const htmlPath = join19(projectDir, entryFile);
|
|
8622
8936
|
const rawEntry = readTextFile2(htmlPath);
|
|
8623
8937
|
if (entryFile === "index.html" || !isTemplateEntry(rawEntry)) return htmlPath;
|
|
8624
|
-
const projectIndexPath =
|
|
8938
|
+
const projectIndexPath = join19(projectDir, "index.html");
|
|
8625
8939
|
if (!existsSync11(projectIndexPath)) {
|
|
8626
8940
|
throw new Error(
|
|
8627
8941
|
`Template entry file "${entryFile}" requires a project index.html to extract its render shell.`
|
|
@@ -8637,7 +8951,7 @@ function resolveEntryHtmlPath(input) {
|
|
|
8637
8951
|
`Entry file "${entryFile}" is not mounted from index.html via data-composition-src, so it cannot be rendered independently.`
|
|
8638
8952
|
);
|
|
8639
8953
|
}
|
|
8640
|
-
const wrapperPath =
|
|
8954
|
+
const wrapperPath = join19(workDir, "standalone-entry.html");
|
|
8641
8955
|
writeFileSync5(wrapperPath, standaloneHtml, "utf-8");
|
|
8642
8956
|
log.info("Extracted standalone entry from index.html host context", { entryFile });
|
|
8643
8957
|
return wrapperPath;
|
|
@@ -8658,7 +8972,7 @@ async function runCompileStage(input) {
|
|
|
8658
8972
|
} = input;
|
|
8659
8973
|
const htmlPath = resolveEntryHtmlPath({ projectDir, workDir, entryFile, log });
|
|
8660
8974
|
const compileStart = Date.now();
|
|
8661
|
-
const compiled = await compileForRender(projectDir, htmlPath,
|
|
8975
|
+
const compiled = await compileForRender(projectDir, htmlPath, join19(workDir, "downloads"), {
|
|
8662
8976
|
log,
|
|
8663
8977
|
failClosedFontFetch: failClosedFontFetch === true,
|
|
8664
8978
|
allowSystemFontCapture,
|
|
@@ -8666,7 +8980,7 @@ async function runCompileStage(input) {
|
|
|
8666
8980
|
variables: input.variables,
|
|
8667
8981
|
urlByAbsentLocalPath: input.urlByAbsentLocalPath,
|
|
8668
8982
|
pageMotionBlur: input.pageMotionBlur,
|
|
8669
|
-
animatedGifCacheDir: cfg.extractCacheDir ?
|
|
8983
|
+
animatedGifCacheDir: cfg.extractCacheDir ? join19(cfg.extractCacheDir, "animated-gif") : void 0,
|
|
8670
8984
|
ffmpegProcessTimeout: cfg.ffmpegProcessTimeout
|
|
8671
8985
|
});
|
|
8672
8986
|
assertNotAborted();
|
|
@@ -8723,7 +9037,7 @@ async function runCompileStage(input) {
|
|
|
8723
9037
|
};
|
|
8724
9038
|
await preflightCompositionAssetMediaTypes({
|
|
8725
9039
|
projectDir,
|
|
8726
|
-
compiledDir:
|
|
9040
|
+
compiledDir: join19(workDir, "compiled"),
|
|
8727
9041
|
composition,
|
|
8728
9042
|
signal: abortSignal
|
|
8729
9043
|
});
|
|
@@ -8769,7 +9083,7 @@ async function runCompileStage(input) {
|
|
|
8769
9083
|
|
|
8770
9084
|
// src/services/render/stages/probeStage.ts
|
|
8771
9085
|
import { createHash as createHash7 } from "node:crypto";
|
|
8772
|
-
import { basename as basename5, join as
|
|
9086
|
+
import { basename as basename5, join as join20 } from "node:path";
|
|
8773
9087
|
import { parseHTML as parseHTML6 } from "linkedom";
|
|
8774
9088
|
import {
|
|
8775
9089
|
closeCaptureSession as closeCaptureSession4,
|
|
@@ -8978,7 +9292,7 @@ async function runProbeStage(input) {
|
|
|
8978
9292
|
});
|
|
8979
9293
|
fileServer = await createFileServer({
|
|
8980
9294
|
projectDir,
|
|
8981
|
-
compiledDir:
|
|
9295
|
+
compiledDir: join20(workDir, "compiled"),
|
|
8982
9296
|
port: 0,
|
|
8983
9297
|
preHeadScripts: [VIRTUAL_TIME_SHIM],
|
|
8984
9298
|
fps: job.config.fps
|
|
@@ -9008,7 +9322,7 @@ async function runProbeStage(input) {
|
|
|
9008
9322
|
log.info("Creating capture session...", { attempt, maxAttempts: PROBE_MAX_ATTEMPTS });
|
|
9009
9323
|
probeSession = await createCaptureSession4(
|
|
9010
9324
|
fileServer.url,
|
|
9011
|
-
|
|
9325
|
+
join20(workDir, "probe"),
|
|
9012
9326
|
captureOpts,
|
|
9013
9327
|
null,
|
|
9014
9328
|
probeCfg
|
|
@@ -9093,7 +9407,7 @@ async function runProbeStage(input) {
|
|
|
9093
9407
|
});
|
|
9094
9408
|
probeSession = await createCaptureSession4(
|
|
9095
9409
|
fileServer.url,
|
|
9096
|
-
|
|
9410
|
+
join20(workDir, "probe-screenshot"),
|
|
9097
9411
|
captureOpts,
|
|
9098
9412
|
null,
|
|
9099
9413
|
{ ...probeCfg, forceScreenshot: true }
|
|
@@ -9131,7 +9445,7 @@ async function runProbeStage(input) {
|
|
|
9131
9445
|
compiled,
|
|
9132
9446
|
resolutions,
|
|
9133
9447
|
projectDir,
|
|
9134
|
-
|
|
9448
|
+
join20(workDir, "downloads")
|
|
9135
9449
|
);
|
|
9136
9450
|
assertNotAborted();
|
|
9137
9451
|
composition.videos = compiled.videos;
|
|
@@ -9322,7 +9636,7 @@ async function runProbeStage(input) {
|
|
|
9322
9636
|
}
|
|
9323
9637
|
await preflightCompositionAssetMediaTypes({
|
|
9324
9638
|
projectDir,
|
|
9325
|
-
compiledDir:
|
|
9639
|
+
compiledDir: join20(workDir, "compiled"),
|
|
9326
9640
|
composition,
|
|
9327
9641
|
signal: abortSignal
|
|
9328
9642
|
});
|
|
@@ -9457,10 +9771,10 @@ function validateRenderDuration(input) {
|
|
|
9457
9771
|
|
|
9458
9772
|
// src/services/render/stages/extractVideosStage.ts
|
|
9459
9773
|
import { existsSync as existsSync12 } from "node:fs";
|
|
9460
|
-
import { join as
|
|
9774
|
+
import { join as join21 } from "node:path";
|
|
9461
9775
|
import {
|
|
9462
9776
|
classifyVideoExtractionError,
|
|
9463
|
-
createFrameLookupTable,
|
|
9777
|
+
createFrameLookupTable as createFrameLookupTable3,
|
|
9464
9778
|
detectTransfer,
|
|
9465
9779
|
extractAllVideoFrames,
|
|
9466
9780
|
extractMediaMetadata as extractMediaMetadata3,
|
|
@@ -9472,7 +9786,7 @@ import {
|
|
|
9472
9786
|
resolveVideoExtractionWindow as resolveVideoExtractionWindow2,
|
|
9473
9787
|
isHttpUrl as isHttpUrl2,
|
|
9474
9788
|
resolveProjectRelativeSrc as resolveProjectRelativeSrc3,
|
|
9475
|
-
runVideoExtractionWithRetry,
|
|
9789
|
+
runVideoExtractionWithRetry as runVideoExtractionWithRetry2,
|
|
9476
9790
|
safeVideoExtractionSourceIdentity,
|
|
9477
9791
|
videoAudioId as videoAudioId2
|
|
9478
9792
|
} from "@hyperframes/engine";
|
|
@@ -9728,7 +10042,7 @@ async function runExtractVideosStage(input) {
|
|
|
9728
10042
|
const videoPath = resolveProjectRelativeSrc3(v.src, projectDir, compiledDir);
|
|
9729
10043
|
if (!existsSync12(videoPath)) return null;
|
|
9730
10044
|
try {
|
|
9731
|
-
const attempted = extractionPolicy.maxTransientRetries === 0 ? { result: await extractMediaMetadata3(videoPath), retries: 0 } : await
|
|
10045
|
+
const attempted = extractionPolicy.maxTransientRetries === 0 ? { result: await extractMediaMetadata3(videoPath), retries: 0 } : await runVideoExtractionWithRetry2(() => extractMediaMetadata3(videoPath), {
|
|
9732
10046
|
signal: abortSignal,
|
|
9733
10047
|
maxTransientRetries: extractionPolicy.maxTransientRetries,
|
|
9734
10048
|
onRetry: () => {
|
|
@@ -9798,14 +10112,14 @@ async function runExtractVideosStage(input) {
|
|
|
9798
10112
|
// because short boundary counts can differ by one frame.
|
|
9799
10113
|
{
|
|
9800
10114
|
fps: job.config.fps,
|
|
9801
|
-
outputDir:
|
|
10115
|
+
outputDir: join21(compiledDir, "__hyperframes_video_frames"),
|
|
9802
10116
|
format: job.config.videoFrameFormat ?? "auto",
|
|
9803
10117
|
toneMapHdrToSdr: rendersSdrOnly(job.config, composition),
|
|
9804
10118
|
timelineEnd: composition.duration,
|
|
9805
10119
|
maxTransientRetries: extractionPolicy.maxTransientRetries,
|
|
9806
10120
|
collectProbeFailures: extractionPolicy.failureMode === "enforce",
|
|
9807
10121
|
deferRangeExtraction,
|
|
9808
|
-
contentKeyedDirs: [
|
|
10122
|
+
contentKeyedDirs: [join21(compiledDir, REMOTE_MEDIA_SUBDIR)],
|
|
9809
10123
|
urlByAbsentLocalPath,
|
|
9810
10124
|
onProgress: ({ planned = 0, done = 0 }) => {
|
|
9811
10125
|
framesPlanned += planned;
|
|
@@ -9825,7 +10139,7 @@ async function runExtractVideosStage(input) {
|
|
|
9825
10139
|
materializeSymlinks
|
|
9826
10140
|
});
|
|
9827
10141
|
if (extractionResult.extracted.length > 0) {
|
|
9828
|
-
frameLookup =
|
|
10142
|
+
frameLookup = createFrameLookupTable3(
|
|
9829
10143
|
composition.videos,
|
|
9830
10144
|
extractionResult.extracted,
|
|
9831
10145
|
resolveRenderFpsConfig(job.config.fps).value
|
|
@@ -9988,7 +10302,7 @@ async function planVideoLayerPassthrough(input) {
|
|
|
9988
10302
|
|
|
9989
10303
|
// src/services/render/stages/audioStage.ts
|
|
9990
10304
|
import { existsSync as existsSync15 } from "node:fs";
|
|
9991
|
-
import { join as
|
|
10305
|
+
import { join as join24 } from "node:path";
|
|
9992
10306
|
import {
|
|
9993
10307
|
MIXED_AUDIO_FILENAME,
|
|
9994
10308
|
audioFailureDiagnostics,
|
|
@@ -9999,7 +10313,7 @@ import {
|
|
|
9999
10313
|
|
|
10000
10314
|
// src/services/render/audioDeliveryLimit.ts
|
|
10001
10315
|
import { existsSync as existsSync14 } from "node:fs";
|
|
10002
|
-
import { dirname as
|
|
10316
|
+
import { dirname as dirname10, join as join23 } from "node:path";
|
|
10003
10317
|
import {
|
|
10004
10318
|
applyAudioFxChain,
|
|
10005
10319
|
describeFfmpegFailure as describeFfmpegFailure3,
|
|
@@ -10010,7 +10324,7 @@ import {
|
|
|
10010
10324
|
// src/services/render/audioPadTrim.ts
|
|
10011
10325
|
import { spawn } from "node:child_process";
|
|
10012
10326
|
import { mkdtempSync as mkdtempSync3 } from "node:fs";
|
|
10013
|
-
import { dirname as
|
|
10327
|
+
import { dirname as dirname9, join as join22 } from "node:path";
|
|
10014
10328
|
import {
|
|
10015
10329
|
DEFAULT_CONFIG,
|
|
10016
10330
|
describeFfmpegFailure as describeFfmpegFailure2,
|
|
@@ -10289,7 +10603,7 @@ async function constrainAacTruePeak(input) {
|
|
|
10289
10603
|
}
|
|
10290
10604
|
async function cutSource(input, scratchDir, attenuationDb) {
|
|
10291
10605
|
if (!input.sourcePath || !input.rewriteSource) return true;
|
|
10292
|
-
const cutPath =
|
|
10606
|
+
const cutPath = join22(scratchDir, "source.wav");
|
|
10293
10607
|
try {
|
|
10294
10608
|
const result = await input.rewriteSource(input.sourcePath, cutPath, gainFilter(attenuationDb));
|
|
10295
10609
|
if (!result.success) throw new Error(result.error);
|
|
@@ -10303,8 +10617,8 @@ async function cutSource(input, scratchDir, attenuationDb) {
|
|
|
10303
10617
|
async function enforceAacTruePeak(input) {
|
|
10304
10618
|
let scratchDir;
|
|
10305
10619
|
try {
|
|
10306
|
-
scratchDir = mkdtempSync3(
|
|
10307
|
-
const correctedPath =
|
|
10620
|
+
scratchDir = mkdtempSync3(join22(dirname9(input.audioPath), ".true-peak-"));
|
|
10621
|
+
const correctedPath = join22(scratchDir, "audio.m4a");
|
|
10308
10622
|
const measurements = [];
|
|
10309
10623
|
let attenuationDb = 0;
|
|
10310
10624
|
let sourcePeakDbfs;
|
|
@@ -10495,11 +10809,13 @@ async function runFfprobeJson(args, signal) {
|
|
|
10495
10809
|
}
|
|
10496
10810
|
|
|
10497
10811
|
// src/services/render/audioDeliveryLimit.ts
|
|
10498
|
-
var
|
|
10499
|
-
var
|
|
10812
|
+
var LIMIT_PASSES = 3;
|
|
10813
|
+
var LOWEST_CEILING_DBTP = AAC_DELIVERY_TRUE_PEAK_DBFS - 2;
|
|
10814
|
+
var PASS_HEADROOM_DB = 0.05;
|
|
10815
|
+
var limitChain = (ceiling) => ({
|
|
10500
10816
|
version: 1,
|
|
10501
|
-
nodes: [{ type: "truepeak", id: "delivery", params: { ceiling
|
|
10502
|
-
};
|
|
10817
|
+
nodes: [{ type: "truepeak", id: "delivery", params: { ceiling } }]
|
|
10818
|
+
});
|
|
10503
10819
|
async function limitDeliveryPeaks(input, deps = {
|
|
10504
10820
|
measure: measureTruePeakDbfs,
|
|
10505
10821
|
applyChain: applyAudioFxChain,
|
|
@@ -10511,15 +10827,39 @@ async function limitDeliveryPeaks(input, deps = {
|
|
|
10511
10827
|
if (!deps.sourceExists(sourcePath)) return { success: true };
|
|
10512
10828
|
const peak = await deps.measure(audioPath, { timeoutMs, signal });
|
|
10513
10829
|
if (peak <= AAC_DELIVERY_TRUE_PEAK_DBFS) return { success: true };
|
|
10514
|
-
const
|
|
10830
|
+
const alternating = ["a", "b"].map(
|
|
10831
|
+
(k) => join23(dirname10(sourcePath), `audio-source-limited-${k}.wav`)
|
|
10832
|
+
);
|
|
10833
|
+
let encodedFrom;
|
|
10834
|
+
let ceiling = AAC_DELIVERY_TRUE_PEAK_DBFS;
|
|
10835
|
+
for (let pass = 0; pass < LIMIT_PASSES; pass++) {
|
|
10836
|
+
const limitedPath = alternating[pass % 2];
|
|
10837
|
+
const result = await limitPass(input, deps, ceiling, limitedPath);
|
|
10838
|
+
if ("encodeFailure" in result) return result.encodeFailure;
|
|
10839
|
+
if ("limitFailed" in result) break;
|
|
10840
|
+
encodedFrom = limitedPath;
|
|
10841
|
+
if (result.peak <= AAC_DELIVERY_TRUE_PEAK_DBFS || ceiling <= LOWEST_CEILING_DBTP) break;
|
|
10842
|
+
ceiling = Math.max(
|
|
10843
|
+
ceiling - (result.peak - AAC_DELIVERY_TRUE_PEAK_DBFS) - PASS_HEADROOM_DB,
|
|
10844
|
+
LOWEST_CEILING_DBTP
|
|
10845
|
+
);
|
|
10846
|
+
}
|
|
10847
|
+
if (encodedFrom) deps.rename(encodedFrom, sourcePath, { copyFallback: true });
|
|
10848
|
+
return { success: true };
|
|
10849
|
+
}
|
|
10850
|
+
async function limitPass(input, deps, ceiling, limitedPath) {
|
|
10851
|
+
const { audioPath, sourcePath, signal, timeoutMs } = input;
|
|
10515
10852
|
try {
|
|
10516
|
-
await deps.applyChain(sourcePath,
|
|
10853
|
+
await deps.applyChain(sourcePath, limitChain(ceiling), limitedPath, {
|
|
10854
|
+
trackId: "delivery",
|
|
10855
|
+
signal
|
|
10856
|
+
});
|
|
10517
10857
|
} catch (error) {
|
|
10518
10858
|
if (signal?.aborted) throw error;
|
|
10519
10859
|
input.warn?.("[audio] true-peak limit failed, the whole-mix cut applies", {
|
|
10520
10860
|
error: error instanceof Error ? error.message : String(error)
|
|
10521
10861
|
});
|
|
10522
|
-
return {
|
|
10862
|
+
return { limitFailed: true };
|
|
10523
10863
|
}
|
|
10524
10864
|
const encoded = await deps.encode(limitedPath, audioPath, input.targetDurationSeconds, {
|
|
10525
10865
|
signal,
|
|
@@ -10527,13 +10867,14 @@ async function limitDeliveryPeaks(input, deps = {
|
|
|
10527
10867
|
});
|
|
10528
10868
|
if (!encoded.success) {
|
|
10529
10869
|
return {
|
|
10530
|
-
|
|
10531
|
-
|
|
10532
|
-
|
|
10870
|
+
encodeFailure: {
|
|
10871
|
+
success: false,
|
|
10872
|
+
error: `[audioDeliveryLimit] ${describeFfmpegFailure3(encoded, timeoutMs)}`,
|
|
10873
|
+
failureReason: encoded.failureReason
|
|
10874
|
+
}
|
|
10533
10875
|
};
|
|
10534
10876
|
}
|
|
10535
|
-
deps.
|
|
10536
|
-
return { success: true };
|
|
10877
|
+
return { peak: await deps.measure(audioPath, { timeoutMs, signal }) };
|
|
10537
10878
|
}
|
|
10538
10879
|
|
|
10539
10880
|
// src/services/render/stages/audioStage.ts
|
|
@@ -10559,7 +10900,7 @@ function runAudioStage(input) {
|
|
|
10559
10900
|
}
|
|
10560
10901
|
async function runAudioStageScoped(input) {
|
|
10561
10902
|
const stageStart = Date.now();
|
|
10562
|
-
const pcmSourcePath =
|
|
10903
|
+
const pcmSourcePath = join24(input.workDir, "audio-source.wav");
|
|
10563
10904
|
const duration = input.totalFrames * input.fps.den / input.fps.num;
|
|
10564
10905
|
let deliverySourcePath;
|
|
10565
10906
|
try {
|
|
@@ -10624,7 +10965,7 @@ async function mixAudioForDelivery(input, duration, pcmSourcePath) {
|
|
|
10624
10965
|
assertNotAborted,
|
|
10625
10966
|
log
|
|
10626
10967
|
} = input;
|
|
10627
|
-
const audioOutputPath =
|
|
10968
|
+
const audioOutputPath = join24(workDir, MIXED_AUDIO_FILENAME);
|
|
10628
10969
|
let hasAudio = false;
|
|
10629
10970
|
let audioError;
|
|
10630
10971
|
let audioFailures;
|
|
@@ -10635,7 +10976,7 @@ async function mixAudioForDelivery(input, duration, pcmSourcePath) {
|
|
|
10635
10976
|
audioResult = await processCompositionAudio(
|
|
10636
10977
|
audios,
|
|
10637
10978
|
projectDir,
|
|
10638
|
-
|
|
10979
|
+
join24(workDir, "audio-work"),
|
|
10639
10980
|
audioOutputPath,
|
|
10640
10981
|
duration,
|
|
10641
10982
|
abortSignal,
|
|
@@ -10703,8 +11044,8 @@ async function mixAudioForDelivery(input, duration, pcmSourcePath) {
|
|
|
10703
11044
|
}
|
|
10704
11045
|
|
|
10705
11046
|
// src/services/render/stages/captureStage.ts
|
|
10706
|
-
import { existsSync as existsSync16, readdirSync as
|
|
10707
|
-
import { join as
|
|
11047
|
+
import { existsSync as existsSync16, readdirSync as readdirSync4, statfsSync as statfsSync2 } from "node:fs";
|
|
11048
|
+
import { join as join25 } from "node:path";
|
|
10708
11049
|
import {
|
|
10709
11050
|
classifyCaptureFailure as classifyCaptureFailure2,
|
|
10710
11051
|
frameFileExtension,
|
|
@@ -10775,15 +11116,15 @@ function diskCaptureShortfallError(needBytes, freeBytes, framesDir, basis, remed
|
|
|
10775
11116
|
function measureCaptureFrameBytes(workDir, framesDir) {
|
|
10776
11117
|
let total = sampleDirectoryBytes(framesDir);
|
|
10777
11118
|
if (!existsSync16(workDir)) return total;
|
|
10778
|
-
for (const attempt of
|
|
11119
|
+
for (const attempt of readdirSync4(workDir)) {
|
|
10779
11120
|
if (/^retry-\d+-batch-\d+-worker-\d+$/.test(attempt)) {
|
|
10780
|
-
total += sampleDirectoryBytes(
|
|
11121
|
+
total += sampleDirectoryBytes(join25(workDir, attempt));
|
|
10781
11122
|
continue;
|
|
10782
11123
|
}
|
|
10783
11124
|
if (!/^capture-attempt-\d+$/.test(attempt)) continue;
|
|
10784
|
-
const attemptDir =
|
|
10785
|
-
for (const name of
|
|
10786
|
-
if (/^worker-\d+$/.test(name)) total += sampleDirectoryBytes(
|
|
11125
|
+
const attemptDir = join25(workDir, attempt);
|
|
11126
|
+
for (const name of readdirSync4(attemptDir)) {
|
|
11127
|
+
if (/^worker-\d+$/.test(name)) total += sampleDirectoryBytes(join25(attemptDir, name));
|
|
10787
11128
|
}
|
|
10788
11129
|
}
|
|
10789
11130
|
return total;
|
|
@@ -11077,8 +11418,8 @@ async function captureSessionFrames(session, { input, rangeStart, rangeEnd, resu
|
|
|
11077
11418
|
}
|
|
11078
11419
|
|
|
11079
11420
|
// src/services/render/stages/captureHdrStage.ts
|
|
11080
|
-
import { existsSync as existsSync18, mkdirSync as
|
|
11081
|
-
import { join as
|
|
11421
|
+
import { existsSync as existsSync18, mkdirSync as mkdirSync10 } from "node:fs";
|
|
11422
|
+
import { join as join29 } from "node:path";
|
|
11082
11423
|
import {
|
|
11083
11424
|
closeCaptureSession as closeCaptureSession7,
|
|
11084
11425
|
cloneCaptureWarnings as cloneCaptureWarnings2,
|
|
@@ -11150,7 +11491,7 @@ function createHdrImageTransferCache(options = {}) {
|
|
|
11150
11491
|
}
|
|
11151
11492
|
|
|
11152
11493
|
// src/services/render/stages/captureHdrSequentialLoop.ts
|
|
11153
|
-
import { join as
|
|
11494
|
+
import { join as join26 } from "node:path";
|
|
11154
11495
|
import {
|
|
11155
11496
|
TRANSITIONS,
|
|
11156
11497
|
crossfade
|
|
@@ -11275,7 +11616,7 @@ async function runSequentialLayeredFrameLoop(input) {
|
|
|
11275
11616
|
);
|
|
11276
11617
|
if (debugDumpEnabled && debugDumpDir && i % 30 === 0) {
|
|
11277
11618
|
writeFileExclusiveSync(
|
|
11278
|
-
|
|
11619
|
+
join26(debugDumpDir, `frame_${String(i).padStart(4, "0")}_final_rgb48le.bin`),
|
|
11279
11620
|
normalCanvas
|
|
11280
11621
|
);
|
|
11281
11622
|
}
|
|
@@ -11307,7 +11648,7 @@ async function runSequentialLayeredFrameLoop(input) {
|
|
|
11307
11648
|
}
|
|
11308
11649
|
|
|
11309
11650
|
// src/services/render/stages/captureHdrHybridLoop.ts
|
|
11310
|
-
import { join as
|
|
11651
|
+
import { join as join28 } from "node:path";
|
|
11311
11652
|
import {
|
|
11312
11653
|
TRANSITIONS as TRANSITIONS2,
|
|
11313
11654
|
closeCaptureSession as closeCaptureSession6,
|
|
@@ -11321,7 +11662,7 @@ import {
|
|
|
11321
11662
|
// src/services/shaderTransitionWorkerPool.ts
|
|
11322
11663
|
import { Worker } from "node:worker_threads";
|
|
11323
11664
|
import { fileURLToPath as fileURLToPath3, pathToFileURL } from "node:url";
|
|
11324
|
-
import { dirname as
|
|
11665
|
+
import { dirname as dirname11, join as join27 } from "node:path";
|
|
11325
11666
|
import { createRequire } from "node:module";
|
|
11326
11667
|
import { existsSync as existsSync17 } from "node:fs";
|
|
11327
11668
|
import { cpus as cpus2 } from "node:os";
|
|
@@ -11334,10 +11675,10 @@ function resolveWorkerEntry(explicit) {
|
|
|
11334
11675
|
const isTs = override.endsWith(".ts");
|
|
11335
11676
|
return { path: override, isTs };
|
|
11336
11677
|
}
|
|
11337
|
-
const moduleDir =
|
|
11338
|
-
const jsPath =
|
|
11678
|
+
const moduleDir = dirname11(fileURLToPath3(import.meta.url));
|
|
11679
|
+
const jsPath = join27(moduleDir, "shaderTransitionWorker.js");
|
|
11339
11680
|
if (existsSync17(jsPath)) return { path: jsPath, isTs: false };
|
|
11340
|
-
const tsPath =
|
|
11681
|
+
const tsPath = join27(moduleDir, "shaderTransitionWorker.ts");
|
|
11341
11682
|
return { path: tsPath, isTs: true };
|
|
11342
11683
|
}
|
|
11343
11684
|
function buildExecArgv(entryIsTs) {
|
|
@@ -11705,7 +12046,7 @@ async function runHybridLayeredFrameLoop(input) {
|
|
|
11705
12046
|
);
|
|
11706
12047
|
if (debugDumpEnabled && debugDumpDir && i % 30 === 0) {
|
|
11707
12048
|
writeFileExclusiveSync(
|
|
11708
|
-
|
|
12049
|
+
join28(debugDumpDir, `frame_${String(i).padStart(4, "0")}_final_rgb48le.bin`),
|
|
11709
12050
|
canvas
|
|
11710
12051
|
);
|
|
11711
12052
|
}
|
|
@@ -11896,9 +12237,9 @@ async function runCaptureHdrStage(input) {
|
|
|
11896
12237
|
if (hdrVideoFrameSources.has(v.id)) hdrVideoEndTimes.set(v.id, v.end);
|
|
11897
12238
|
}
|
|
11898
12239
|
const debugDumpEnabled = process.env.KEEP_TEMP === "1";
|
|
11899
|
-
const debugDumpDir = debugDumpEnabled ?
|
|
12240
|
+
const debugDumpDir = debugDumpEnabled ? join29(framesDir, "debug-composite") : null;
|
|
11900
12241
|
if (debugDumpDir && !existsSync18(debugDumpDir)) {
|
|
11901
|
-
|
|
12242
|
+
mkdirSync10(debugDumpDir, { recursive: true });
|
|
11902
12243
|
}
|
|
11903
12244
|
const compositeTransfer = resolveCompositeTransfer(hasHdrContent, effectiveHdr);
|
|
11904
12245
|
const hdrTargetTransfer = compositeTransfer === "srgb" ? void 0 : compositeTransfer;
|
|
@@ -12055,15 +12396,15 @@ async function runCaptureHdrStage(input) {
|
|
|
12055
12396
|
import {
|
|
12056
12397
|
copyFileSync as copyFileSync4,
|
|
12057
12398
|
existsSync as existsSync19,
|
|
12058
|
-
mkdirSync as
|
|
12059
|
-
readdirSync as
|
|
12399
|
+
mkdirSync as mkdirSync11,
|
|
12400
|
+
readdirSync as readdirSync5,
|
|
12060
12401
|
readFileSync as readFileSync8,
|
|
12061
|
-
rmSync as
|
|
12062
|
-
statSync as
|
|
12402
|
+
rmSync as rmSync11,
|
|
12403
|
+
statSync as statSync8,
|
|
12063
12404
|
writeFileSync as writeFileSync6
|
|
12064
12405
|
} from "node:fs";
|
|
12065
12406
|
import { readFile as readFile2 } from "node:fs/promises";
|
|
12066
|
-
import { dirname as
|
|
12407
|
+
import { dirname as dirname12, join as join30 } from "node:path";
|
|
12067
12408
|
import {
|
|
12068
12409
|
encodeFramesChunkedConcat,
|
|
12069
12410
|
encodeFramesFromDir,
|
|
@@ -12161,7 +12502,7 @@ async function reencodeWithClearedWholeFrames(argsInput, run, startTime) {
|
|
|
12161
12502
|
}
|
|
12162
12503
|
async function encodeGifFromDir(framesDir, framePattern, outputPath, input) {
|
|
12163
12504
|
const startTime = Date.now();
|
|
12164
|
-
const files =
|
|
12505
|
+
const files = readdirSync5(framesDir).filter((file) => file.match(/\.(jpg|jpeg|png)$/i));
|
|
12165
12506
|
const frameCount = files.length;
|
|
12166
12507
|
if (frameCount === 0) {
|
|
12167
12508
|
return gifEncodeFailure(startTime, outputPath, "[FFmpeg] No frame files found in directory");
|
|
@@ -12188,7 +12529,7 @@ async function encodeGifFromDir(framesDir, framePattern, outputPath, input) {
|
|
|
12188
12529
|
const failure = await reencodeWithClearedWholeFrames(argsInput, run, startTime);
|
|
12189
12530
|
if (failure) return failure;
|
|
12190
12531
|
}
|
|
12191
|
-
const fileSize = existsSync19(outputPath) ?
|
|
12532
|
+
const fileSize = existsSync19(outputPath) ? statSync8(outputPath).size : 0;
|
|
12192
12533
|
return {
|
|
12193
12534
|
success: true,
|
|
12194
12535
|
outputPath,
|
|
@@ -12197,7 +12538,7 @@ async function encodeGifFromDir(framesDir, framePattern, outputPath, input) {
|
|
|
12197
12538
|
fileSize
|
|
12198
12539
|
};
|
|
12199
12540
|
} finally {
|
|
12200
|
-
|
|
12541
|
+
rmSync11(input.palettePath, { force: true });
|
|
12201
12542
|
}
|
|
12202
12543
|
}
|
|
12203
12544
|
function startEncodeProgress(job, stage, onProgress) {
|
|
@@ -12274,7 +12615,7 @@ function encodeWhileCapturing(input, totalFrames) {
|
|
|
12274
12615
|
await new Promise((resolve11) => wake = resolve11);
|
|
12275
12616
|
}
|
|
12276
12617
|
if (signal.aborted) return;
|
|
12277
|
-
const mergedPath =
|
|
12618
|
+
const mergedPath = join30(input.framesDir, formatCaptureFrameName(next, "jpg"));
|
|
12278
12619
|
const frame = await readLandedFrame(landed.get(next), mergedPath);
|
|
12279
12620
|
landed.delete(next);
|
|
12280
12621
|
if (!await stream.writeFrame(frame)) {
|
|
@@ -12349,19 +12690,19 @@ async function runEncodeStage(input) {
|
|
|
12349
12690
|
const stage5Start = Date.now();
|
|
12350
12691
|
if (isPngSequence) {
|
|
12351
12692
|
const encoded = startEncodeProgress(job, "Writing PNG sequence", onProgress);
|
|
12352
|
-
if (!existsSync19(outputPath))
|
|
12353
|
-
const captured =
|
|
12693
|
+
if (!existsSync19(outputPath)) mkdirSync11(outputPath, { recursive: true });
|
|
12694
|
+
const captured = readdirSync5(framesDir).filter((name) => name.endsWith(".png")).sort();
|
|
12354
12695
|
if (captured.length === 0) {
|
|
12355
12696
|
throw new Error(
|
|
12356
12697
|
`[Render] png-sequence output requested but no PNGs were captured to ${framesDir}`
|
|
12357
12698
|
);
|
|
12358
12699
|
}
|
|
12359
12700
|
captured.forEach((name, i) => {
|
|
12360
|
-
const dst =
|
|
12361
|
-
copyFileSync4(
|
|
12701
|
+
const dst = join30(outputPath, formatExportFrameName(i, "png"));
|
|
12702
|
+
copyFileSync4(join30(framesDir, name), dst);
|
|
12362
12703
|
});
|
|
12363
12704
|
if (hasAudio && audioOutputPath && existsSync19(audioOutputPath)) {
|
|
12364
|
-
copyFileSync4(audioOutputPath,
|
|
12705
|
+
copyFileSync4(audioOutputPath, join30(outputPath, MIXED_AUDIO_FILENAME2));
|
|
12365
12706
|
log.info(
|
|
12366
12707
|
`[Render] png-sequence: ${MIXED_AUDIO_FILENAME2} sidecar written to ${outputPath}/${MIXED_AUDIO_FILENAME2}`
|
|
12367
12708
|
);
|
|
@@ -12381,7 +12722,7 @@ async function runEncodeStage(input) {
|
|
|
12381
12722
|
const encodeResult2 = await encodeGifFromDir(framesDir, framePattern2, outputPath, {
|
|
12382
12723
|
fps: job.config.fps,
|
|
12383
12724
|
loop,
|
|
12384
|
-
palettePath:
|
|
12725
|
+
palettePath: join30(dirname12(videoOnlyPath), "gif-palette.png"),
|
|
12385
12726
|
preserveAlpha: needsAlpha,
|
|
12386
12727
|
signal: abortSignal,
|
|
12387
12728
|
timeout: engineCfg.ffmpegEncodeTimeout
|
|
@@ -12587,14 +12928,14 @@ function sampleDirectoryBytes(dir) {
|
|
|
12587
12928
|
if (!current) continue;
|
|
12588
12929
|
let entries = [];
|
|
12589
12930
|
try {
|
|
12590
|
-
entries =
|
|
12931
|
+
entries = readdirSync6(current);
|
|
12591
12932
|
} catch {
|
|
12592
12933
|
continue;
|
|
12593
12934
|
}
|
|
12594
12935
|
for (const name of entries) {
|
|
12595
|
-
const full =
|
|
12936
|
+
const full = join31(current, name);
|
|
12596
12937
|
try {
|
|
12597
|
-
const st =
|
|
12938
|
+
const st = statSync9(full);
|
|
12598
12939
|
if (st.isDirectory()) {
|
|
12599
12940
|
stack.push(full);
|
|
12600
12941
|
} else if (st.isFile()) {
|
|
@@ -12748,8 +13089,8 @@ function findMissingFrameRanges(totalFrames, framesDir, frameExt) {
|
|
|
12748
13089
|
const ranges = [];
|
|
12749
13090
|
let rangeStart = null;
|
|
12750
13091
|
for (let frameIndex = 0; frameIndex < totalFrames; frameIndex++) {
|
|
12751
|
-
const framePath =
|
|
12752
|
-
const missing = !existsSync20(framePath) ||
|
|
13092
|
+
const framePath = join31(framesDir, formatCaptureFrameName(frameIndex, frameExt));
|
|
13093
|
+
const missing = !existsSync20(framePath) || statSync9(framePath).size <= 8;
|
|
12753
13094
|
if (missing && rangeStart === null) {
|
|
12754
13095
|
rangeStart = frameIndex;
|
|
12755
13096
|
} else if (!missing && rangeStart !== null) {
|
|
@@ -12765,7 +13106,7 @@ function findMissingFrameRanges(totalFrames, framesDir, frameExt) {
|
|
|
12765
13106
|
function buildMissingFrameRetryBatches(ranges, maxWorkers, workDir, attempt, rangeStart = 0, stride = 1) {
|
|
12766
13107
|
const workersPerBatch = Math.max(1, Math.floor(maxWorkers));
|
|
12767
13108
|
const batches = [];
|
|
12768
|
-
const outputDirFor = (batchIndex, workerId) =>
|
|
13109
|
+
const outputDirFor = (batchIndex, workerId) => join31(workDir, `retry-${attempt}-batch-${batchIndex}-worker-${workerId}`);
|
|
12769
13110
|
if (stride > 1) {
|
|
12770
13111
|
const spans = /* @__PURE__ */ new Map();
|
|
12771
13112
|
for (const range of ranges) {
|
|
@@ -12825,8 +13166,8 @@ function getNextRetryWorkerCount(currentWorkers) {
|
|
|
12825
13166
|
return Math.max(1, Math.floor(currentWorkers / 2));
|
|
12826
13167
|
}
|
|
12827
13168
|
function resolveRenderWorkDirPrefix(outputPath, jobId, platform2 = process.platform, systemTempDir = tmpdir2()) {
|
|
12828
|
-
if (platform2 === "win32" || platform2 === "darwin") return
|
|
12829
|
-
return
|
|
13169
|
+
if (platform2 === "win32" || platform2 === "darwin") return join31(systemTempDir, "hf-render-");
|
|
13170
|
+
return join31(dirname13(outputPath), `work-${jobId}-`);
|
|
12830
13171
|
}
|
|
12831
13172
|
var MAX_TRANSIENT_CAPTURE_RETRIES = 1;
|
|
12832
13173
|
function isTransientCaptureRetryEligible(failureKind, missing, retriesUsed) {
|
|
@@ -12873,7 +13214,7 @@ The composition is too large for the available memory. To reduce memory pressure
|
|
|
12873
13214
|
function countCapturedFrames(totalFrames, framesDir, frameExt) {
|
|
12874
13215
|
let captured = 0;
|
|
12875
13216
|
for (let frameIndex = 0; frameIndex < totalFrames; frameIndex++) {
|
|
12876
|
-
const framePath =
|
|
13217
|
+
const framePath = join31(framesDir, formatCaptureFrameName(frameIndex, frameExt));
|
|
12877
13218
|
if (existsSync20(framePath)) captured++;
|
|
12878
13219
|
}
|
|
12879
13220
|
return captured;
|
|
@@ -12899,7 +13240,7 @@ async function executeDiskCaptureWithAdaptiveRetry(options) {
|
|
|
12899
13240
|
reason: attempt === 0 ? "initial" : pendingTransientRetry ? "transient-retry" : "retry"
|
|
12900
13241
|
});
|
|
12901
13242
|
pendingTransientRetry = false;
|
|
12902
|
-
const attemptWorkDir =
|
|
13243
|
+
const attemptWorkDir = join31(options.workDir, `capture-attempt-${attempt}`);
|
|
12903
13244
|
const batches = missingRanges ? buildMissingFrameRetryBatches(
|
|
12904
13245
|
missingRanges,
|
|
12905
13246
|
currentWorkers,
|
|
@@ -13531,19 +13872,19 @@ function deVerifyFallbackTelemetry(err) {
|
|
|
13531
13872
|
};
|
|
13532
13873
|
}
|
|
13533
13874
|
function resolveRenderDebugDir() {
|
|
13534
|
-
const producerRoot = process.env.PRODUCER_RENDERS_DIR ? resolve8(process.env.PRODUCER_RENDERS_DIR, "..") : resolve8(
|
|
13535
|
-
return
|
|
13875
|
+
const producerRoot = process.env.PRODUCER_RENDERS_DIR ? resolve8(process.env.PRODUCER_RENDERS_DIR, "..") : resolve8(dirname13(fileURLToPath4(import.meta.url)), "../..");
|
|
13876
|
+
return join31(producerRoot, ".debug");
|
|
13536
13877
|
}
|
|
13537
13878
|
async function executeRenderJob(job, projectDir, outputPath, progressSink, abortSignal, assertRenderActive) {
|
|
13538
13879
|
validateHlsRenderConfig(job.config);
|
|
13539
13880
|
validateDeliveryEncoderConfig(job.config);
|
|
13540
13881
|
const debugDir = resolveRenderDebugDir();
|
|
13541
|
-
const outputDir =
|
|
13542
|
-
if (!existsSync20(outputDir))
|
|
13543
|
-
const workDir = job.config.debug ?
|
|
13882
|
+
const outputDir = dirname13(outputPath);
|
|
13883
|
+
if (!existsSync20(outputDir)) mkdirSync12(outputDir, { recursive: true });
|
|
13884
|
+
const workDir = job.config.debug ? join31(debugDir, job.id) : createRenderWorkDir(resolveRenderWorkDirPrefix(outputPath, job.id), outputDir);
|
|
13544
13885
|
const pipelineStart = Date.now();
|
|
13545
13886
|
const baseLog = job.config.logger ?? defaultLogger;
|
|
13546
|
-
const logPath = job.config.debug ?
|
|
13887
|
+
const logPath = job.config.debug ? join31(workDir, "render.log") : null;
|
|
13547
13888
|
const execution = new RenderExecutionContext({
|
|
13548
13889
|
request: { renderJobId: job.id, projectDir, outputPath },
|
|
13549
13890
|
logger: logPath ? createRenderFileLogger(logPath, baseLog) : baseLog,
|
|
@@ -13557,7 +13898,7 @@ async function executeRenderJob(job, projectDir, outputPath, progressSink, abort
|
|
|
13557
13898
|
log.info("KEEP_TEMP=1, leaving workDir on disk for inspection", { workDir });
|
|
13558
13899
|
return;
|
|
13559
13900
|
}
|
|
13560
|
-
|
|
13901
|
+
rmSync12(workDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
|
|
13561
13902
|
});
|
|
13562
13903
|
try {
|
|
13563
13904
|
await executeRenderPipeline({
|
|
@@ -13605,7 +13946,7 @@ async function executeRenderPipeline(input) {
|
|
|
13605
13946
|
imageDecodeFailures: 0
|
|
13606
13947
|
};
|
|
13607
13948
|
let hdrPerf;
|
|
13608
|
-
const perfOutputPath =
|
|
13949
|
+
const perfOutputPath = join31(workDir, "perf-summary.json");
|
|
13609
13950
|
const cfg = { ...job.config.producerConfig ?? resolveConfig2() };
|
|
13610
13951
|
const observability = new RenderObservabilityRecorder({
|
|
13611
13952
|
pipelineStartMs: pipelineStart,
|
|
@@ -13681,7 +14022,7 @@ async function executeRenderPipeline(input) {
|
|
|
13681
14022
|
job.startedAt = /* @__PURE__ */ new Date();
|
|
13682
14023
|
assertNotAborted();
|
|
13683
14024
|
assertConfiguredFfmpegBinariesExist();
|
|
13684
|
-
if (!existsSync20(workDir))
|
|
14025
|
+
if (!existsSync20(workDir)) mkdirSync12(workDir, { recursive: true });
|
|
13685
14026
|
if (job.config.debug) {
|
|
13686
14027
|
log.info("[Render] Debug artifacts enabled", { workDir, logPath });
|
|
13687
14028
|
}
|
|
@@ -13710,7 +14051,7 @@ async function executeRenderPipeline(input) {
|
|
|
13710
14051
|
requestedWorkers: job.config.workers ?? "auto"
|
|
13711
14052
|
});
|
|
13712
14053
|
const entryFile = job.config.entryFile || "index.html";
|
|
13713
|
-
const htmlPath =
|
|
14054
|
+
const htmlPath = join31(projectDir, entryFile);
|
|
13714
14055
|
if (!existsSync20(htmlPath)) {
|
|
13715
14056
|
throw new Error(`Entry file not found: ${htmlPath}`);
|
|
13716
14057
|
}
|
|
@@ -13887,7 +14228,7 @@ async function executeRenderPipeline(input) {
|
|
|
13887
14228
|
canvas: { width, height },
|
|
13888
14229
|
page: probeSession.page,
|
|
13889
14230
|
projectDir,
|
|
13890
|
-
compiledDir:
|
|
14231
|
+
compiledDir: join31(workDir, "compiled")
|
|
13891
14232
|
});
|
|
13892
14233
|
if (videoLayerPlan.videoId === void 0) {
|
|
13893
14234
|
log.info(`[Render] Video layer passthrough off: ${videoLayerPlan.reason}`);
|
|
@@ -13905,7 +14246,22 @@ async function executeRenderPipeline(input) {
|
|
|
13905
14246
|
updateJobStatus(job, "preprocessing", "Extracting video frames", 10, onProgress, {
|
|
13906
14247
|
code: "extract_video"
|
|
13907
14248
|
});
|
|
13908
|
-
const compiledDir =
|
|
14249
|
+
const compiledDir = join31(workDir, "compiled");
|
|
14250
|
+
const segmentsCapture = (layeredOrEffectRoute) => shouldSegmentCapture({
|
|
14251
|
+
env: process.env,
|
|
14252
|
+
durationSeconds: probeResult.duration,
|
|
14253
|
+
outputFormat,
|
|
14254
|
+
layeredOrEffectRoute,
|
|
14255
|
+
streamingOk: shouldUseStreamingEncode(cfg, outputFormat, 1, probeResult.duration)
|
|
14256
|
+
});
|
|
14257
|
+
const segmentedForWindow = segmentsCapture(compiled.hasShaderTransitions);
|
|
14258
|
+
const windowVideoFrames = job.config.motionBlur === void 0 && shouldWindowVideoFrames({ env: process.env, segmented: segmentedForWindow });
|
|
14259
|
+
if (!windowVideoFrames && shouldWindowVideoFrames({ env: process.env, segmented: true })) {
|
|
14260
|
+
const reason = job.config.motionBlur !== void 0 ? "motion blur" : outputFormat !== "mp4" && outputFormat !== "mov" ? `${outputFormat} output` : "capture is not segmented";
|
|
14261
|
+
log.info("[Render] HF_VIDEO_FRAME_WINDOW is set but this render extracts every frame", {
|
|
14262
|
+
reason
|
|
14263
|
+
});
|
|
14264
|
+
}
|
|
13909
14265
|
const extractResult = await observeRenderStage(
|
|
13910
14266
|
observability,
|
|
13911
14267
|
"video_extract",
|
|
@@ -13922,6 +14278,7 @@ async function executeRenderPipeline(input) {
|
|
|
13922
14278
|
assertNotAborted,
|
|
13923
14279
|
// Local staging can use links; distributed plan() alone requires real copies.
|
|
13924
14280
|
materializeSymlinks: false,
|
|
14281
|
+
deferRangeExtraction: windowVideoFrames ? true : void 0,
|
|
13925
14282
|
onProgress
|
|
13926
14283
|
})
|
|
13927
14284
|
);
|
|
@@ -14018,7 +14375,7 @@ async function executeRenderPipeline(input) {
|
|
|
14018
14375
|
try {
|
|
14019
14376
|
fileServer = await createFileServer({
|
|
14020
14377
|
projectDir,
|
|
14021
|
-
compiledDir:
|
|
14378
|
+
compiledDir: join31(workDir, "compiled"),
|
|
14022
14379
|
port: 0,
|
|
14023
14380
|
preHeadScripts: [VIRTUAL_TIME_SHIM],
|
|
14024
14381
|
fps: job.config.fps
|
|
@@ -14036,8 +14393,8 @@ async function executeRenderPipeline(input) {
|
|
|
14036
14393
|
if (!activeFileServer) {
|
|
14037
14394
|
throw new Error("File server failed to initialize before frame capture");
|
|
14038
14395
|
}
|
|
14039
|
-
const framesDir =
|
|
14040
|
-
if (!existsSync20(framesDir))
|
|
14396
|
+
const framesDir = join31(workDir, "captured-frames");
|
|
14397
|
+
if (!existsSync20(framesDir)) mkdirSync12(framesDir, { recursive: true });
|
|
14041
14398
|
updateJobStatus(job, "rendering", "Checking browser GPU", 25, onProgress, {
|
|
14042
14399
|
code: "start_browsers"
|
|
14043
14400
|
});
|
|
@@ -14115,7 +14472,55 @@ async function executeRenderPipeline(input) {
|
|
|
14115
14472
|
});
|
|
14116
14473
|
const buildPrepCaptureOptions = () => stopPrepSessionOnScriptFailure(buildCaptureOptions());
|
|
14117
14474
|
const buildWorkerCaptureOptions = () => withScriptFailureStop(buildCaptureOptions(), job, log);
|
|
14118
|
-
|
|
14475
|
+
let videoFrameWindow = windowVideoFrames && (extractionResult?.extracted.some((video) => video.deferredRange) ?? false);
|
|
14476
|
+
if (windowVideoFrames) {
|
|
14477
|
+
const extracted = extractionResult?.extracted ?? [];
|
|
14478
|
+
log.info("[Render] Video frame window", {
|
|
14479
|
+
windowed: extracted.filter((video) => video.deferredRange).length,
|
|
14480
|
+
fullyExtracted: extracted.filter((video) => !video.deferredRange).length
|
|
14481
|
+
});
|
|
14482
|
+
}
|
|
14483
|
+
const buildSegmentVideoFrames = () => trackSegmentFramePeak(
|
|
14484
|
+
createSegmentVideoFrames({
|
|
14485
|
+
videos: composition.videos,
|
|
14486
|
+
extracted: extractionResult?.extracted ?? [],
|
|
14487
|
+
fps: job.config.fps,
|
|
14488
|
+
rootDir: workDir,
|
|
14489
|
+
cfg,
|
|
14490
|
+
injectorOptions: frameCacheOptions(cfg, workerCount),
|
|
14491
|
+
abortSignal: executionSignal
|
|
14492
|
+
}),
|
|
14493
|
+
workDir,
|
|
14494
|
+
(bytes) => updateCaptureObservability({ videoFrameWindowPeakBytes: bytes })
|
|
14495
|
+
);
|
|
14496
|
+
const createRenderVideoFrameInjector = () => createVideoFrameInjector2(frameLookup, frameCacheOptions(cfg, workerCount));
|
|
14497
|
+
const extractDeferredVideoFrames = async () => {
|
|
14498
|
+
videoFrameWindow = false;
|
|
14499
|
+
updateCaptureObservability({ videoFrameWindow: "off" });
|
|
14500
|
+
const extracted = extractionResult?.extracted ?? [];
|
|
14501
|
+
const videoCount = extracted.filter((video) => video.deferredRange).length;
|
|
14502
|
+
const startedAt = Date.now();
|
|
14503
|
+
log.info("[Render] Extracting the frames of the videos a window would have held back", {
|
|
14504
|
+
videoCount
|
|
14505
|
+
});
|
|
14506
|
+
await observeRenderStage(
|
|
14507
|
+
observability,
|
|
14508
|
+
"video_extract_deferred",
|
|
14509
|
+
{ videoCount },
|
|
14510
|
+
() => extractAllDeferredFrames(extracted, {
|
|
14511
|
+
fps: job.config.fps,
|
|
14512
|
+
cfg,
|
|
14513
|
+
signal: executionSignal,
|
|
14514
|
+
maxTransientRetries: resolveVideoExtractionPolicy().maxTransientRetries,
|
|
14515
|
+
onProgress: (done, total) => reportExtractProgress(job, done, total, onProgress)
|
|
14516
|
+
})
|
|
14517
|
+
);
|
|
14518
|
+
assertNotAborted();
|
|
14519
|
+
log.info("[Render] Extracted the deferred video frames", {
|
|
14520
|
+
videoCount,
|
|
14521
|
+
elapsedMs: Date.now() - startedAt
|
|
14522
|
+
});
|
|
14523
|
+
};
|
|
14119
14524
|
let captureCalibration;
|
|
14120
14525
|
const htmlInCanvasDetected = compiled.renderModeHints.reasons.some(
|
|
14121
14526
|
(r) => r.code === "htmlInCanvas"
|
|
@@ -14281,32 +14686,41 @@ async function executeRenderPipeline(input) {
|
|
|
14281
14686
|
updateJobStatus(job, "rendering", "Measuring capture speed", 25, onProgress, {
|
|
14282
14687
|
code: "start_browsers"
|
|
14283
14688
|
});
|
|
14284
|
-
const
|
|
14285
|
-
|
|
14286
|
-
"
|
|
14287
|
-
|
|
14288
|
-
|
|
14289
|
-
|
|
14290
|
-
|
|
14291
|
-
|
|
14292
|
-
|
|
14293
|
-
|
|
14294
|
-
|
|
14295
|
-
|
|
14296
|
-
|
|
14297
|
-
|
|
14298
|
-
|
|
14299
|
-
|
|
14300
|
-
|
|
14301
|
-
|
|
14302
|
-
|
|
14303
|
-
|
|
14304
|
-
|
|
14305
|
-
|
|
14306
|
-
|
|
14307
|
-
|
|
14308
|
-
|
|
14309
|
-
|
|
14689
|
+
const calibrationFrames = videoFrameWindow ? await buildSegmentVideoFrames().forFrames(
|
|
14690
|
+
selectCaptureCalibrationFrames(totalFrames),
|
|
14691
|
+
"calibration"
|
|
14692
|
+
) : void 0;
|
|
14693
|
+
let outcome;
|
|
14694
|
+
try {
|
|
14695
|
+
outcome = await observeRenderStage(
|
|
14696
|
+
observability,
|
|
14697
|
+
"capture_calibration",
|
|
14698
|
+
captureStageObservationData({
|
|
14699
|
+
forceScreenshot: captureForceScreenshot,
|
|
14700
|
+
// Override the default `capturing` — calibration writes probe
|
|
14701
|
+
// frames only, not `job.framesRendered`, so heartbeats reporting
|
|
14702
|
+
// `framesCompleted: 0` misread as broken. Field signal
|
|
14703
|
+
// ts=1784019503.
|
|
14704
|
+
stagePhase: "calibrating"
|
|
14705
|
+
}),
|
|
14706
|
+
() => runCaptureCalibration({
|
|
14707
|
+
cfg: calibrationCaptureConfig(cfg),
|
|
14708
|
+
fileServer: activeFileServer,
|
|
14709
|
+
workDir,
|
|
14710
|
+
log,
|
|
14711
|
+
job,
|
|
14712
|
+
totalFrames,
|
|
14713
|
+
forceScreenshot: captureForceScreenshot,
|
|
14714
|
+
probeSession,
|
|
14715
|
+
buildCaptureOptions: buildPrepCaptureOptions,
|
|
14716
|
+
createRenderVideoFrameInjector: calibrationFrames ? calibrationFrames.injector : createRenderVideoFrameInjector,
|
|
14717
|
+
assertNotAborted
|
|
14718
|
+
}),
|
|
14719
|
+
{ heartbeatMessage: "browser calibrating (frames not started)" }
|
|
14720
|
+
);
|
|
14721
|
+
} finally {
|
|
14722
|
+
calibrationFrames?.release();
|
|
14723
|
+
}
|
|
14310
14724
|
captureCalibration = outcome.calibration;
|
|
14311
14725
|
calibrationEvaluateTimedOut = outcome.calibrationEvaluateTimedOut;
|
|
14312
14726
|
captureForceScreenshot = outcome.forceScreenshot;
|
|
@@ -14538,7 +14952,7 @@ async function executeRenderPipeline(input) {
|
|
|
14538
14952
|
hls: ".mp4"
|
|
14539
14953
|
};
|
|
14540
14954
|
const videoExt = FORMAT_EXT[outputFormat] ?? ".mp4";
|
|
14541
|
-
const videoOnlyPath =
|
|
14955
|
+
const videoOnlyPath = join31(workDir, `video-only${videoExt}`);
|
|
14542
14956
|
const usePageSideCompositingForTransitions = (cfg.enablePageSideCompositing || isGif) && compiled.hasShaderTransitions && !hasHdrContent && outputSupportsPageSideShaderCompositing(outputFormat);
|
|
14543
14957
|
if (usePageSideCompositingForTransitions) {
|
|
14544
14958
|
activeFileServer.addPreHeadScript(HF_PAGE_SIDE_COMPOSITING_STUB);
|
|
@@ -14639,13 +15053,7 @@ async function executeRenderPipeline(input) {
|
|
|
14639
15053
|
// excluded routes are the ones whose concat-copy or capture loop the
|
|
14640
15054
|
// segment contract does not cover (webm VP9 concat is fragile, HDR and
|
|
14641
15055
|
// shader transitions run their own compositor).
|
|
14642
|
-
useSegmentedCapture:
|
|
14643
|
-
env: process.env,
|
|
14644
|
-
durationSeconds: job.duration,
|
|
14645
|
-
outputFormat,
|
|
14646
|
-
layeredOrEffectRoute: hasHdrContent || compiled.hasShaderTransitions,
|
|
14647
|
-
streamingOk: shouldUseStreamingEncode(cfg, outputFormat, 1, job.duration)
|
|
14648
|
-
}),
|
|
15056
|
+
useSegmentedCapture: segmentsCapture(hasHdrContent || compiled.hasShaderTransitions),
|
|
14649
15057
|
useLayeredComposite,
|
|
14650
15058
|
usePageSideCompositing: usePageSideCompositingForTransitions,
|
|
14651
15059
|
hasHdrContent,
|
|
@@ -14685,10 +15093,14 @@ async function executeRenderPipeline(input) {
|
|
|
14685
15093
|
deWorkerInversion,
|
|
14686
15094
|
deParallelRouter
|
|
14687
15095
|
});
|
|
15096
|
+
if (videoFrameWindow && capturePlan.kind !== "sdr_segmented")
|
|
15097
|
+
await extractDeferredVideoFrames();
|
|
15098
|
+
updateCaptureObservability({ videoFrameWindow: videoFrameWindow ? "on" : "off" });
|
|
14688
15099
|
observability.checkpoint("capture_strategy", "resolved", {
|
|
14689
15100
|
plan: capturePlan.kind,
|
|
14690
15101
|
workerCount: capturePlan.workerCount,
|
|
14691
15102
|
forceScreenshot: capturePlan.forceScreenshot,
|
|
15103
|
+
videoFrameWindow: videoFrameWindow ? "on" : "off",
|
|
14692
15104
|
captureBeyondViewport: resolvedCaptureBeyondViewport ?? null,
|
|
14693
15105
|
useStreamingEncode: capturePlan.kind === "sdr_streaming",
|
|
14694
15106
|
useLayeredComposite: capturePlan.kind === "hdr_layered",
|
|
@@ -14826,7 +15238,7 @@ async function executeRenderPipeline(input) {
|
|
|
14826
15238
|
pageMotionBlur: job.config.pageMotionBlur,
|
|
14827
15239
|
captureQuality: captureOptions.quality
|
|
14828
15240
|
});
|
|
14829
|
-
const segmentDir = segmentDirFor(
|
|
15241
|
+
const segmentDir = segmentDirFor(join31(projectDir, "renders"), segmentPlanHash);
|
|
14830
15242
|
let completedSegments = /* @__PURE__ */ new Set();
|
|
14831
15243
|
if (job.config.resumeSegments === true) {
|
|
14832
15244
|
const existing = readSegmentManifest(segmentDir);
|
|
@@ -14841,10 +15253,10 @@ async function executeRenderPipeline(input) {
|
|
|
14841
15253
|
});
|
|
14842
15254
|
}
|
|
14843
15255
|
if (completedSegments.size === 0) {
|
|
14844
|
-
|
|
15256
|
+
rmSync12(segmentDir, { recursive: true, force: true });
|
|
14845
15257
|
}
|
|
14846
15258
|
} else {
|
|
14847
|
-
|
|
15259
|
+
rmSync12(segmentDir, { recursive: true, force: true });
|
|
14848
15260
|
}
|
|
14849
15261
|
const emptySegmentManifest = () => ({
|
|
14850
15262
|
version: 1,
|
|
@@ -14894,6 +15306,7 @@ async function executeRenderPipeline(input) {
|
|
|
14894
15306
|
},
|
|
14895
15307
|
buildCaptureOptions: buildWorkerCaptureOptions,
|
|
14896
15308
|
createRenderVideoFrameInjector,
|
|
15309
|
+
segmentVideoFrames: videoFrameWindow ? buildSegmentVideoFrames() : void 0,
|
|
14897
15310
|
abortSignal: executionSignal,
|
|
14898
15311
|
assertNotAborted,
|
|
14899
15312
|
onProgress,
|
|
@@ -14945,7 +15358,7 @@ async function executeRenderPipeline(input) {
|
|
|
14945
15358
|
probeSession = null;
|
|
14946
15359
|
await closeOrphanedProbeForRetry(orphaned, closeCaptureSession8, log, "segmented");
|
|
14947
15360
|
}
|
|
14948
|
-
|
|
15361
|
+
rmSync12(segmentDir, { recursive: true, force: true });
|
|
14949
15362
|
completedSegments = /* @__PURE__ */ new Set();
|
|
14950
15363
|
segmentManifest = emptySegmentManifest();
|
|
14951
15364
|
dedupPerfs.length = 0;
|
|
@@ -14984,11 +15397,12 @@ async function executeRenderPipeline(input) {
|
|
|
14984
15397
|
);
|
|
14985
15398
|
updateCaptureObservability({ segmentRetries: segmentedRes.segmentRetries });
|
|
14986
15399
|
if (job.config.keepSegments !== true) {
|
|
14987
|
-
|
|
15400
|
+
rmSync12(segmentDir, { recursive: true, force: true });
|
|
14988
15401
|
}
|
|
14989
15402
|
} else {
|
|
14990
15403
|
capturePlan = replanAfterFailure(capturePlan, { kind: "streaming_unavailable" });
|
|
14991
15404
|
syncCapturePlan();
|
|
15405
|
+
if (videoFrameWindow) await extractDeferredVideoFrames();
|
|
14992
15406
|
probeSession = null;
|
|
14993
15407
|
observability.checkpoint(
|
|
14994
15408
|
"capture_segmented",
|
|
@@ -15259,8 +15673,8 @@ async function executeRenderPipeline(input) {
|
|
|
15259
15673
|
"capture_disk",
|
|
15260
15674
|
"drawElement capture failed; retrying with a fresh screenshot session"
|
|
15261
15675
|
);
|
|
15262
|
-
|
|
15263
|
-
|
|
15676
|
+
rmSync12(framesDir, { recursive: true, force: true });
|
|
15677
|
+
mkdirSync12(framesDir, { recursive: true });
|
|
15264
15678
|
resetCaptureAttemptProgress(job);
|
|
15265
15679
|
dedupPerfs.length = 0;
|
|
15266
15680
|
cfg.useDrawElement = false;
|
|
@@ -15447,7 +15861,7 @@ async function executeRenderPipeline(input) {
|
|
|
15447
15861
|
}
|
|
15448
15862
|
if (job.config.debug) {
|
|
15449
15863
|
if (!isPngSequence && !isHls && existsSync20(stagedOutputPath)) {
|
|
15450
|
-
const debugOutput =
|
|
15864
|
+
const debugOutput = join31(workDir, `output${videoExt}`);
|
|
15451
15865
|
copyFileSync5(stagedOutputPath, debugOutput);
|
|
15452
15866
|
}
|
|
15453
15867
|
}
|
|
@@ -15560,13 +15974,13 @@ async function executeRenderPipeline(input) {
|
|
|
15560
15974
|
import {
|
|
15561
15975
|
existsSync as existsSync21,
|
|
15562
15976
|
readFileSync as readFileSync9,
|
|
15563
|
-
statSync as
|
|
15977
|
+
statSync as statSync10,
|
|
15564
15978
|
openSync as openSync5,
|
|
15565
15979
|
fstatSync as fstatSync3,
|
|
15566
15980
|
closeSync as closeSync5,
|
|
15567
15981
|
constants as constants3
|
|
15568
15982
|
} from "node:fs";
|
|
15569
|
-
import { resolve as resolve9, join as
|
|
15983
|
+
import { resolve as resolve9, join as join32, relative as relative3, isAbsolute as isAbsolute3, sep as sep2 } from "node:path";
|
|
15570
15984
|
import { lintHyperframeHtml } from "@hyperframes/lint";
|
|
15571
15985
|
function isStringRecord(value) {
|
|
15572
15986
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
@@ -15600,7 +16014,7 @@ function readEntryHtml(filePath) {
|
|
|
15600
16014
|
} catch (error) {
|
|
15601
16015
|
if (error instanceof Error && "code" in error && (error.code === "ENOENT" || error.code === "ENOTDIR"))
|
|
15602
16016
|
return null;
|
|
15603
|
-
if (!
|
|
16017
|
+
if (!statSync10(filePath, { throwIfNoEntry: false })?.isFile()) return null;
|
|
15604
16018
|
throw error;
|
|
15605
16019
|
}
|
|
15606
16020
|
try {
|
|
@@ -15612,7 +16026,7 @@ function readEntryHtml(filePath) {
|
|
|
15612
16026
|
}
|
|
15613
16027
|
function readProjectEntryFile(projectDir, preferredEntryFile) {
|
|
15614
16028
|
const absProjectDir = resolve9(projectDir);
|
|
15615
|
-
if (!existsSync21(absProjectDir) || !
|
|
16029
|
+
if (!existsSync21(absProjectDir) || !statSync10(absProjectDir).isDirectory()) {
|
|
15616
16030
|
return { error: `Project directory not found: ${absProjectDir}` };
|
|
15617
16031
|
}
|
|
15618
16032
|
const entryCandidates = [preferredEntryFile, "index.html", "src/index.html"].filter(
|
|
@@ -15634,7 +16048,7 @@ function readProjectEntryFile(projectDir, preferredEntryFile) {
|
|
|
15634
16048
|
}
|
|
15635
16049
|
}
|
|
15636
16050
|
return {
|
|
15637
|
-
error: `No HTML entry file found in project directory: ${
|
|
16051
|
+
error: `No HTML entry file found in project directory: ${join32(absProjectDir, preferredEntryFile || "index.html")}`
|
|
15638
16052
|
};
|
|
15639
16053
|
}
|
|
15640
16054
|
function prepareHyperframeLintBody(body) {
|
|
@@ -15680,7 +16094,7 @@ async function runHyperframeLint(prepared) {
|
|
|
15680
16094
|
// src/services/healthWorker.ts
|
|
15681
16095
|
import { Worker as Worker2 } from "node:worker_threads";
|
|
15682
16096
|
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
15683
|
-
import { dirname as
|
|
16097
|
+
import { dirname as dirname14, join as join33 } from "node:path";
|
|
15684
16098
|
import { existsSync as existsSync22 } from "node:fs";
|
|
15685
16099
|
var DEFAULT_HEALTH_PORT = 9848;
|
|
15686
16100
|
async function startHealthWorker(options = {}) {
|
|
@@ -15755,8 +16169,8 @@ function defaultLogger2() {
|
|
|
15755
16169
|
};
|
|
15756
16170
|
}
|
|
15757
16171
|
function resolveWorkerEntry2() {
|
|
15758
|
-
const here =
|
|
15759
|
-
const candidates = [
|
|
16172
|
+
const here = dirname14(fileURLToPath5(import.meta.url));
|
|
16173
|
+
const candidates = [join33(here, "healthWorkerThread.js"), join33(here, "healthWorkerThread.ts")];
|
|
15760
16174
|
for (const candidate of candidates) {
|
|
15761
16175
|
if (existsSync22(candidate)) return candidate;
|
|
15762
16176
|
}
|
|
@@ -15798,7 +16212,7 @@ import {
|
|
|
15798
16212
|
createReferenceBackgroundRecorder as createReferenceBackgroundRecorder2
|
|
15799
16213
|
} from "@hyperframes/engine";
|
|
15800
16214
|
import { audioFailureDiagnostics as audioFailureDiagnostics3 } from "@hyperframes/engine";
|
|
15801
|
-
import { extname as extname4, isAbsolute as isAbsolute4, join as
|
|
16215
|
+
import { extname as extname4, isAbsolute as isAbsolute4, join as join34, relative as relative4, sep as sep3 } from "node:path";
|
|
15802
16216
|
import {
|
|
15803
16217
|
fpsToNumber as fpsToNumber9,
|
|
15804
16218
|
HF_COLOR_GRADING_ATTR as HF_COLOR_GRADING_ATTR2
|
|
@@ -15817,16 +16231,14 @@ import {
|
|
|
15817
16231
|
import {
|
|
15818
16232
|
closeCaptureSession as closeCaptureSession9,
|
|
15819
16233
|
createCaptureSession as createCaptureSession9,
|
|
15820
|
-
createFrameLookupTable as
|
|
16234
|
+
createFrameLookupTable as createFrameLookupTable4,
|
|
15821
16235
|
initializeSession as initializeSession9
|
|
15822
16236
|
} from "@hyperframes/engine";
|
|
15823
16237
|
|
|
15824
16238
|
// src/services/distributed/chunkFrames.ts
|
|
15825
16239
|
import {
|
|
15826
16240
|
compositionRequiresWebGpu as compositionRequiresWebGpu3,
|
|
15827
|
-
|
|
15828
|
-
createVideoFrameInjector as createVideoFrameInjector2,
|
|
15829
|
-
extractVideoFramesRange
|
|
16241
|
+
createVideoFrameInjector as createVideoFrameInjector3
|
|
15830
16242
|
} from "@hyperframes/engine";
|
|
15831
16243
|
|
|
15832
16244
|
// src/services/distributed/shared.ts
|
|
@@ -15917,9 +16329,9 @@ var PLAN_ROOT_CATEGORY = {
|
|
|
15917
16329
|
var PLAN_DIR_SIZE_LIMIT_BYTES = 2 * 1024 * 1024 * 1024;
|
|
15918
16330
|
var FREEZE_OWNED_PLAN_FILES = [
|
|
15919
16331
|
"plan.json",
|
|
15920
|
-
|
|
15921
|
-
|
|
15922
|
-
|
|
16332
|
+
join34("meta", "composition.json"),
|
|
16333
|
+
join34("meta", "encoder.json"),
|
|
16334
|
+
join34("meta", "chunks.json")
|
|
15923
16335
|
];
|
|
15924
16336
|
|
|
15925
16337
|
// src/services/distributed/renderConfigValidation.ts
|
|
@@ -16307,8 +16719,8 @@ function buildRenderJobConfig(input, outputPath, log) {
|
|
|
16307
16719
|
function resolvePreparedRenderOutput(prepared, rendersDir, log) {
|
|
16308
16720
|
const { input, cleanupProjectDir } = prepared;
|
|
16309
16721
|
const absoluteOutputPath = resolveOutputPath(input.projectDir, input.outputPath, rendersDir, log);
|
|
16310
|
-
const outputDir =
|
|
16311
|
-
if (!existsSync23(outputDir))
|
|
16722
|
+
const outputDir = dirname15(absoluteOutputPath);
|
|
16723
|
+
if (!existsSync23(outputDir)) mkdirSync13(outputDir, { recursive: true });
|
|
16312
16724
|
return { input, cleanupProjectDir, absoluteOutputPath };
|
|
16313
16725
|
}
|
|
16314
16726
|
function validateRenderOverrides(body) {
|
|
@@ -16349,7 +16761,7 @@ function prepareProjectDirectory(projectDir, options) {
|
|
|
16349
16761
|
const candidate = nonEmptyString(projectDir);
|
|
16350
16762
|
if (!candidate) return null;
|
|
16351
16763
|
const absProjectDir = resolve10(candidate);
|
|
16352
|
-
if (!existsSync23(absProjectDir) || !
|
|
16764
|
+
if (!existsSync23(absProjectDir) || !statSync11(absProjectDir).isDirectory()) {
|
|
16353
16765
|
return { error: `Project directory not found: ${absProjectDir}` };
|
|
16354
16766
|
}
|
|
16355
16767
|
const entry = options.entryFile || "index.html";
|
|
@@ -16374,8 +16786,8 @@ async function resolveInlineRenderHtml(body) {
|
|
|
16374
16786
|
}
|
|
16375
16787
|
function materializeInlineProject(html, options) {
|
|
16376
16788
|
const tempRoot = process.env.PRODUCER_TMP_PROJECT_DIR || tmpdir3();
|
|
16377
|
-
const tempProjectDir = mkdtempSync4(
|
|
16378
|
-
writeFileSync8(
|
|
16789
|
+
const tempProjectDir = mkdtempSync4(join35(tempRoot, "producer-project-"));
|
|
16790
|
+
writeFileSync8(join35(tempProjectDir, "index.html"), html, "utf-8");
|
|
16379
16791
|
return {
|
|
16380
16792
|
prepared: {
|
|
16381
16793
|
input: { projectDir: tempProjectDir, ...options },
|
|
@@ -16436,7 +16848,7 @@ function createArtifactStore(ttlMs) {
|
|
|
16436
16848
|
function cleanupTempDir(dir, log) {
|
|
16437
16849
|
if (!dir) return;
|
|
16438
16850
|
try {
|
|
16439
|
-
|
|
16851
|
+
rmSync13(dir, { recursive: true, force: true });
|
|
16440
16852
|
} catch (error) {
|
|
16441
16853
|
log.warn("Failed to cleanup temp project dir", {
|
|
16442
16854
|
cleanupProjectDir: dir,
|
|
@@ -16445,7 +16857,7 @@ function cleanupTempDir(dir, log) {
|
|
|
16445
16857
|
}
|
|
16446
16858
|
}
|
|
16447
16859
|
function outputFileSize(path) {
|
|
16448
|
-
return existsSync23(path) ?
|
|
16860
|
+
return existsSync23(path) ? statSync11(path).size : 0;
|
|
16449
16861
|
}
|
|
16450
16862
|
function createBlockingProgressReporter(log, requestId) {
|
|
16451
16863
|
let lastLoggedPct = -10;
|
|
@@ -16753,7 +17165,7 @@ function createRenderHandlers(options = {}) {
|
|
|
16753
17165
|
store.delete(token);
|
|
16754
17166
|
return c.json({ success: false, error: "Output artifact file missing" }, 404);
|
|
16755
17167
|
}
|
|
16756
|
-
const stats =
|
|
17168
|
+
const stats = statSync11(artifact.path);
|
|
16757
17169
|
return new Response(createReadStream3(artifact.path), {
|
|
16758
17170
|
headers: {
|
|
16759
17171
|
"content-type": "video/mp4",
|