@hyperframes/producer 0.9.16 → 0.9.18

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