@hyperframes/producer 0.9.15 → 0.9.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -9,12 +9,12 @@ import {
9
9
  // src/services/renderOrchestrator.ts
10
10
  import { audioFailureDiagnostics as audioFailureDiagnostics2 } from "@hyperframes/engine";
11
11
  import {
12
- existsSync as existsSync19,
13
- mkdirSync as mkdirSync10,
14
- readdirSync as readdirSync5,
15
- rmSync as rmSync10,
16
- statSync as statSync8,
17
- writeFileSync as writeFileSync6,
12
+ existsSync as existsSync20,
13
+ mkdirSync as mkdirSync12,
14
+ readdirSync as readdirSync6,
15
+ rmSync as rmSync12,
16
+ statSync as statSync9,
17
+ writeFileSync as writeFileSync7,
18
18
  copyFileSync as copyFileSync5,
19
19
  appendFileSync
20
20
  } from "fs";
@@ -29,16 +29,14 @@ import {
29
29
  toFps as toFps2
30
30
  } from "@hyperframes/core";
31
31
  import { HF_AUDIO_GROUP_TAG } from "@hyperframes/core/audio-groups";
32
- import { extractStandaloneEntryFromIndex, isTemplateEntry } from "@hyperframes/core/compiler";
33
32
  import { HTML_BODY_CSS_HEIGHT_FIRST_RE, HTML_BODY_CSS_WIDTH_FIRST_RE } from "@hyperframes/parsers";
34
- import { readTextFile as readTextFile2 } from "@hyperframes/parsers/asset-resolution";
35
33
  import {
36
34
  extractMediaMetadata as extractMediaMetadata4,
37
35
  resolveConfig as resolveConfig2,
38
36
  closeCaptureSession as closeCaptureSession8,
39
37
  createCaptureSession as createCaptureSession8,
40
38
  initializeSession as initializeSession8,
41
- createVideoFrameInjector,
39
+ createVideoFrameInjector as createVideoFrameInjector2,
42
40
  getEncoderPreset as getEncoderPreset3,
43
41
  distributeFrames as distributeFrames2,
44
42
  distributeFramesInterleaved as distributeFramesInterleaved2,
@@ -62,13 +60,14 @@ import {
62
60
  isDrawElementCaptureError,
63
61
  getDrawElementVerificationDetails,
64
62
  augmentProtocolTimeoutError,
65
- augmentPageNavigationTimeoutError
63
+ augmentPageNavigationTimeoutError,
64
+ displaySize
66
65
  } from "@hyperframes/engine";
67
66
  import {
68
67
  referenceBackgroundResult,
69
68
  createReferenceBackgroundRecorder
70
69
  } from "@hyperframes/engine";
71
- 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";
72
71
  import { totalmem as totalmem2 } from "node:os";
73
72
  import { randomUUID } from "crypto";
74
73
  import { fileURLToPath as fileURLToPath4 } from "url";
@@ -2017,6 +2016,7 @@ import {
2017
2016
  getCapturePerfSummary as getCapturePerfSummary2,
2018
2017
  initializeSession as initializeSession2,
2019
2018
  prepareCaptureSessionForReuse as prepareCaptureSessionForReuse2,
2019
+ resolveDeVerifyMinDb as resolveDeVerifyMinDb2,
2020
2020
  shouldDisableBrowserPoolForParallelWorker,
2021
2021
  spawnStreamingEncoder as spawnStreamingEncoder2
2022
2022
  } from "@hyperframes/engine";
@@ -3887,42 +3887,47 @@ function createDrainFrameGuard(args) {
3887
3887
  sizes.push(buf.length);
3888
3888
  if (sizes.length > 60) sizes.shift();
3889
3889
  }
3890
- const truth = session.deVerifyFrames?.get(idx);
3891
- if (truth) {
3892
- let db;
3893
- try {
3894
- db = await psnrDb(buf, truth);
3895
- } catch (err) {
3896
- log.warn("[Render] drawElement self-verify sample skipped (psnr infrastructure)", {
3897
- frame: idx,
3898
- error: err instanceof Error ? err.message : String(err)
3899
- });
3900
- return buf;
3901
- }
3902
- if (db < verifyMinDb) {
3903
- const dumpDir = await mkdtemp(join9(tmpdir(), "hf-de-verify-fail-")).catch(() => null);
3904
- if (dumpDir) {
3905
- await Promise.all([
3906
- writeFile(join9(dumpDir, `frame-${idx}-de.jpg`), buf),
3907
- writeFile(join9(dumpDir, `frame-${idx}-truth.jpg`), truth)
3908
- ]).catch(() => {
3909
- });
3910
- }
3911
- throw new DrawElementVerificationError(
3912
- `drawElement self-verify failed at frame ${idx}: ${db.toFixed(1)}dB < ${verifyMinDb}dB vs pre-injection screenshot${dumpDir ? ` (pair: ${dumpDir})` : ""}`,
3913
- { kind: "psnr", frameIndex: idx, failedDb: db, verifyThresholdDb: verifyMinDb }
3914
- );
3915
- }
3916
- stats.verifyChecked += 1;
3917
- stats.verifyMinDb = stats.verifyMinDb === void 0 ? db : Math.min(stats.verifyMinDb, db);
3918
- log.info("[Render] drawElement self-verify passed", {
3919
- frame: idx,
3920
- psnrDb: db === Infinity ? "inf" : Number(db.toFixed(1))
3921
- });
3922
- }
3890
+ await verifyDrawElementSample(session, idx, buf, { log, stats, verifyMinDb });
3923
3891
  return buf;
3924
3892
  };
3925
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
+ }
3926
3931
  async function runWorkerEncodePipelineLoop(session, totalFrames, job, currentEncoder, reorderBuffer, assertNotAborted, onProgress, log, stats, abortSignal, resume, risk) {
3927
3932
  let prev = null;
3928
3933
  const frameTime = (i) => i * job.config.fps.den / job.config.fps.num;
@@ -4386,6 +4391,7 @@ async function runCaptureStreamingStage(input) {
4386
4391
  );
4387
4392
  } else {
4388
4393
  const stallTimeoutMs = resolveCaptureStallTimeoutMs();
4394
+ const verifyMinDb = resolveDeVerifyMinDb();
4389
4395
  let lastProgressAt = Date.now();
4390
4396
  for (let i = resume.from; i < totalFrames; i++) {
4391
4397
  assertNotAborted();
@@ -4402,6 +4408,7 @@ async function runCaptureStreamingStage(input) {
4402
4408
  { session, frameIndex: i, totalFrames, stallTimeoutMs: budget },
4403
4409
  abortSignal
4404
4410
  );
4411
+ await verifyDrawElementSample(session, i, buffer, { log, verifyMinDb });
4405
4412
  await reorderBuffer.waitForFrame(i);
4406
4413
  ensureFrameWritten(await currentEncoder.writeFrame(buffer), i, currentEncoder);
4407
4414
  reorderBuffer.advanceTo(i + 1);
@@ -4510,6 +4517,10 @@ async function captureSegmentFrames(ctx, segment, encoder) {
4510
4517
  },
4511
4518
  ctx.abortSignal
4512
4519
  );
4520
+ await verifyDrawElementSample(ctx.session, i, buffer, {
4521
+ log: ctx.log,
4522
+ verifyMinDb: ctx.verifyMinDb
4523
+ });
4513
4524
  ensureFrameWritten(await encoder.writeFrame(buffer), i, encoder);
4514
4525
  ctx.job.framesRendered = i + 1;
4515
4526
  lastProgressAt = Date.now();
@@ -4524,6 +4535,25 @@ async function captureSegmentFrames(ctx, segment, encoder) {
4524
4535
  }
4525
4536
  }
4526
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) {
4527
4557
  const segmentPath = segmentOutputPath(ctx.segmentDir, segment.index);
4528
4558
  let encoder;
4529
4559
  try {
@@ -4576,16 +4606,16 @@ var DEFAULT_DEPS = {
4576
4606
  function defaultSessionFactory(input) {
4577
4607
  let probeSession = input.probeSession;
4578
4608
  return {
4579
- create: async () => {
4609
+ create: async (injector = input.createRenderVideoFrameInjector) => {
4580
4610
  const probeCannotCaptureEveryFrame = Boolean(probeSession?.deRiskFrames?.size);
4581
4611
  if (probeSession && probeCannotCaptureEveryFrame) {
4582
4612
  await closeCaptureSession2(probeSession).catch(() => {
4583
4613
  });
4584
4614
  probeSession = null;
4585
4615
  }
4586
- const session = probeSession ?? await openSegmentedSession(input);
4616
+ const session = probeSession ?? await openSegmentedSession(input, injector);
4587
4617
  if (probeSession) {
4588
- openSegmentedSessionReuse(input, probeSession);
4618
+ openSegmentedSessionReuse(input, probeSession, injector);
4589
4619
  probeSession = null;
4590
4620
  }
4591
4621
  try {
@@ -4600,12 +4630,12 @@ function defaultSessionFactory(input) {
4600
4630
  }
4601
4631
  };
4602
4632
  }
4603
- function openSegmentedSessionReuse(input, session) {
4604
- prepareCaptureSessionForReuse2(session, input.framesDir, input.createRenderVideoFrameInjector());
4633
+ function openSegmentedSessionReuse(input, session, injector) {
4634
+ prepareCaptureSessionForReuse2(session, input.framesDir, injector());
4605
4635
  }
4606
- async function openSegmentedSession(input) {
4636
+ async function openSegmentedSession(input, injector) {
4607
4637
  const captureCfg = input.cfg.forceScreenshot === input.plan.forceScreenshot ? input.cfg : { ...input.cfg, forceScreenshot: input.plan.forceScreenshot };
4608
- const videoInjector = input.createRenderVideoFrameInjector();
4638
+ const videoInjector = injector();
4609
4639
  const captureOptions = { ...input.buildCaptureOptions(), deFrameFallback: false };
4610
4640
  const ownBrowser = shouldDisableBrowserPoolForParallelWorker({
4611
4641
  parallel: (input.workerCount ?? 1) > 1
@@ -4618,6 +4648,19 @@ async function openSegmentedSession(input) {
4618
4648
  ownBrowser ? { ...captureCfg, enableBrowserPool: false } : captureCfg
4619
4649
  );
4620
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
+ }
4621
4664
  async function concatSegments(concat, segmentPaths, videoOnlyPath, abortSignal, cfg) {
4622
4665
  const result = await concat(segmentPaths, videoOnlyPath, abortSignal, cfg);
4623
4666
  if (result.success) return;
@@ -4629,12 +4672,14 @@ async function concatSegments(concat, segmentPaths, videoOnlyPath, abortSignal,
4629
4672
  async function createSegmentWorker(run, id) {
4630
4673
  const { input, deps } = run;
4631
4674
  const factory = input.sessionFactoryForWorker?.(id) ?? run.sessionFactory;
4675
+ const frames = input.segmentVideoFrames?.forWorker();
4632
4676
  return {
4633
4677
  id,
4634
4678
  factory,
4635
4679
  sessionSegments: 0,
4636
4680
  ctx: {
4637
- session: await factory.create(),
4681
+ session: await factory.create(frames && (() => frames.hook)),
4682
+ frames,
4638
4683
  job: input.job,
4639
4684
  cfg: input.cfg,
4640
4685
  totalFrames: input.totalFrames,
@@ -4645,6 +4690,8 @@ async function createSegmentWorker(run, id) {
4645
4690
  spawnEncoder: deps.spawnEncoder,
4646
4691
  captureFrame: deps.captureFrame,
4647
4692
  stallTimeoutMs: resolveCaptureStallTimeoutMs(),
4693
+ log: input.log,
4694
+ verifyMinDb: resolveDeVerifyMinDb2(),
4648
4695
  abortSignal: input.abortSignal,
4649
4696
  assertNotAborted: input.assertNotAborted,
4650
4697
  onProgress: input.onProgress,
@@ -4664,7 +4711,8 @@ async function recycleWorkerSession(run, worker, why) {
4664
4711
  run.input.dedupPerfs.push(getCapturePerfSummary2(worker.ctx.session));
4665
4712
  const memory = worker.ctx.session.chromeMemory?.stats();
4666
4713
  await run.deps.closeSession(worker.ctx.session);
4667
- worker.ctx.session = await worker.factory.create();
4714
+ const { frames } = worker.ctx;
4715
+ worker.ctx.session = await worker.factory.create(frames && (() => frames.hook));
4668
4716
  worker.sessionSegments = 0;
4669
4717
  if (why === "cadence") run.totals.browserRecycles += 1;
4670
4718
  run.input.log.info(`[Render] segment browser recycled (${why})`, {
@@ -4732,6 +4780,7 @@ async function concatResumedRun(run, segmentPaths) {
4732
4780
  input.abortSignal,
4733
4781
  input.cfg
4734
4782
  );
4783
+ removeJoinedSegments(run, segmentPaths);
4735
4784
  return {
4736
4785
  success: true,
4737
4786
  encodeMs: 0,
@@ -4806,6 +4855,7 @@ async function runCaptureSegmentedStage(input) {
4806
4855
  input.abortSignal,
4807
4856
  input.cfg
4808
4857
  );
4858
+ removeJoinedSegments(run, segmentPaths);
4809
4859
  return {
4810
4860
  success: true,
4811
4861
  encodeMs: run.totals.encodeMs,
@@ -5237,10 +5287,273 @@ function checkPageNeverReady(input) {
5237
5287
  return new PageNeverReadyError(error);
5238
5288
  }
5239
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
+
5240
5553
  // src/services/render/segmentManifest.ts
5241
5554
  import { createHash as createHash2 } from "node:crypto";
5242
- import { existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync4, statSync as statSync4, writeFileSync as writeFileSync3 } from "node:fs";
5243
- 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";
5244
5557
  import { renameWithRetry as renameWithRetry2 } from "@hyperframes/engine";
5245
5558
  var MANIFEST_FILENAME = "segments.json";
5246
5559
  function computeSegmentPlanHash(input) {
@@ -5268,7 +5581,7 @@ function computeSegmentPlanHash(input) {
5268
5581
  return createHash2("sha256").update(ordered.join(" ")).digest("hex").slice(0, 16);
5269
5582
  }
5270
5583
  function segmentDirFor(projectRendersDir, planHash) {
5271
- return join12(projectRendersDir, ".hf-segments", planHash);
5584
+ return join15(projectRendersDir, ".hf-segments", planHash);
5272
5585
  }
5273
5586
  function hasNumber(value, key) {
5274
5587
  return key in value && typeof Reflect.get(value, key) === "number";
@@ -5286,7 +5599,7 @@ function isManifest(value) {
5286
5599
  return Reflect.get(value, "version") === 1 && hasString(value, "planHash") && hasNumber(value, "totalFrames") && hasNumber(value, "segmentFrames") && Array.isArray(completed) && completed.every(isEntry);
5287
5600
  }
5288
5601
  function readSegmentManifest(segmentDir) {
5289
- const path = join12(segmentDir, MANIFEST_FILENAME);
5602
+ const path = join15(segmentDir, MANIFEST_FILENAME);
5290
5603
  if (!existsSync7(path)) return null;
5291
5604
  try {
5292
5605
  const parsed = JSON.parse(readFileSync4(path, "utf-8"));
@@ -5296,10 +5609,10 @@ function readSegmentManifest(segmentDir) {
5296
5609
  }
5297
5610
  }
5298
5611
  function writeSegmentManifest(segmentDir, manifest) {
5299
- mkdirSync4(segmentDir, { recursive: true });
5300
- const tmp = join12(segmentDir, `${MANIFEST_FILENAME}.${process.pid}.tmp`);
5612
+ mkdirSync6(segmentDir, { recursive: true });
5613
+ const tmp = join15(segmentDir, `${MANIFEST_FILENAME}.${process.pid}.tmp`);
5301
5614
  writeFileSync3(tmp, JSON.stringify(manifest, null, 2), "utf-8");
5302
- renameWithRetry2(tmp, join12(segmentDir, MANIFEST_FILENAME));
5615
+ renameWithRetry2(tmp, join15(segmentDir, MANIFEST_FILENAME));
5303
5616
  }
5304
5617
  async function probeSegmentFrameCount(path, probe) {
5305
5618
  try {
@@ -5317,7 +5630,7 @@ async function validateCompletedSegments(manifest, expectedHash, probeFrames) {
5317
5630
  if (manifest.planHash !== expectedHash) return ok;
5318
5631
  for (const entry of manifest.completed) {
5319
5632
  if (!existsSync7(entry.path)) continue;
5320
- const size = statSync4(entry.path).size;
5633
+ const size = statSync5(entry.path).size;
5321
5634
  if (size <= 0 || size !== entry.bytes) continue;
5322
5635
  const frames = await probeFrames(entry.path);
5323
5636
  if (frames !== entry.endFrame - entry.startFrame) continue;
@@ -5860,7 +6173,7 @@ function computeVideoFrameCoverage(videos, extracted, fps) {
5860
6173
  for (const video of videos) {
5861
6174
  const entry = byId.get(video.id);
5862
6175
  const expectedFrames = expectedFramesForVideo(video, entry, fps);
5863
- const capturedFrames = entry ? entry.framePaths.size : 0;
6176
+ const capturedFrames = entry ? entry.deferredRange ? entry.totalFrames : entry.framePaths.size : 0;
5864
6177
  const ratio = expectedFrames === 0 ? 1 : capturedFrames / expectedFrames;
5865
6178
  reports.push({
5866
6179
  videoId: video.id,
@@ -5895,14 +6208,17 @@ function countAuthoredTimedClips(html) {
5895
6208
  }
5896
6209
 
5897
6210
  // src/services/render/stages/compileStage.ts
5898
- import { join as join16 } from "node:path";
6211
+ import { existsSync as existsSync11, writeFileSync as writeFileSync5 } from "node:fs";
6212
+ import { join as join19 } from "node:path";
5899
6213
  import { suggestMatchingPreset } from "@hyperframes/core";
6214
+ import { extractStandaloneEntryFromIndex, isTemplateEntry } from "@hyperframes/core/compiler";
6215
+ import { readTextFile as readTextFile2 } from "@hyperframes/parsers/asset-resolution";
5900
6216
 
5901
6217
  // src/services/htmlCompiler.ts
5902
6218
  import { decodedUrlPath } from "@hyperframes/parsers/asset-paths";
5903
6219
  import { createHash as createHash6 } from "node:crypto";
5904
- import { createReadStream as createReadStream2, existsSync as existsSync10, mkdirSync as mkdirSync7, readFileSync as readFileSync7 } from "fs";
5905
- 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";
5906
6222
  import { parseHTML as parseHTML5 } from "linkedom";
5907
6223
  import {
5908
6224
  COMPOSITION_SOURCE_URL,
@@ -6148,8 +6464,8 @@ import {
6148
6464
 
6149
6465
  // src/services/animatedGifPrep.ts
6150
6466
  import { createHash as createHash4 } from "node:crypto";
6151
- import { copyFileSync as copyFileSync2, existsSync as existsSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync5, rmSync as rmSync7, statSync as statSync5 } from "node:fs";
6152
- 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";
6153
6469
  import { parseHTML as parseHTML3 } from "linkedom";
6154
6470
  import { parseAnimatedGifMetadata } from "@hyperframes/core";
6155
6471
  import { DEFAULT_VP9_CPU_USED, runFfmpeg as runFfmpeg2, renameWithRetry as renameWithRetry3 } from "@hyperframes/engine";
@@ -6198,7 +6514,7 @@ function resolveLocalImageSourcePath(src, options) {
6198
6514
  }
6199
6515
  function isUsableFile(path) {
6200
6516
  try {
6201
- const stat = statSync5(path);
6517
+ const stat = statSync6(path);
6202
6518
  return stat.isFile() && stat.size > 0;
6203
6519
  } catch {
6204
6520
  return false;
@@ -6305,11 +6621,11 @@ async function runAnimatedGifTranscode(request) {
6305
6621
  throw result.error ?? new Error(`Animated GIF transcode failed (${result.exitCode}): ${result.stderr.slice(-500)}`);
6306
6622
  }
6307
6623
  async function ensurePreparedWebm(input) {
6308
- if (!existsSync8(dirname5(input.cachePath))) {
6309
- mkdirSync5(dirname5(input.cachePath), { recursive: true });
6624
+ if (!existsSync8(dirname6(input.cachePath))) {
6625
+ mkdirSync7(dirname6(input.cachePath), { recursive: true });
6310
6626
  }
6311
- if (!existsSync8(dirname5(input.outputPath))) {
6312
- mkdirSync5(dirname5(input.outputPath), { recursive: true });
6627
+ if (!existsSync8(dirname6(input.outputPath))) {
6628
+ mkdirSync7(dirname6(input.outputPath), { recursive: true });
6313
6629
  }
6314
6630
  if (!isUsableFile(input.cachePath)) {
6315
6631
  const tmpPath = `${input.cachePath}.tmp-${process.pid}-${Date.now()}`;
@@ -6333,10 +6649,10 @@ async function ensurePreparedWebm(input) {
6333
6649
  if (!isUsableFile(input.cachePath)) {
6334
6650
  renameWithRetry3(tmpPath, input.cachePath);
6335
6651
  } else {
6336
- rmSync7(tmpPath, { force: true });
6652
+ rmSync9(tmpPath, { force: true });
6337
6653
  }
6338
6654
  } catch (error) {
6339
- rmSync7(tmpPath, { force: true });
6655
+ rmSync9(tmpPath, { force: true });
6340
6656
  throw error;
6341
6657
  }
6342
6658
  }
@@ -6392,7 +6708,7 @@ function replaceImageWithVideo(input) {
6392
6708
  return video;
6393
6709
  }
6394
6710
  async function prepareAnimatedGifInputs(html, options) {
6395
- const outputDir = options.outputDir ?? join13(options.downloadDir, PREPARED_GIF_SUBDIR);
6711
+ const outputDir = options.outputDir ?? join16(options.downloadDir, PREPARED_GIF_SUBDIR);
6396
6712
  const outputSrcPrefix = normalizeRelPath(options.outputSrcPrefix ?? PREPARED_GIF_SUBDIR);
6397
6713
  const cacheDir = options.cacheDir ?? outputDir;
6398
6714
  const { document: document2 } = parseHTML3(html);
@@ -6419,8 +6735,8 @@ async function prepareAnimatedGifInputs(html, options) {
6419
6735
  );
6420
6736
  const hash = computePreparedGifHash(bytes, loopIterations, padSeconds);
6421
6737
  const filename = `${CACHE_SCHEMA}-${hash.slice(0, 24)}.webm`;
6422
- const cachePath = join13(cacheDir, filename);
6423
- const outputPath = join13(outputDir, filename);
6738
+ const cachePath = join16(cacheDir, filename);
6739
+ const outputPath = join16(outputDir, filename);
6424
6740
  const outputSrc = `${outputSrcPrefix}/${filename}`;
6425
6741
  await ensurePreparedWebm({
6426
6742
  sourcePath,
@@ -6457,15 +6773,15 @@ import { createHash as createHash5 } from "node:crypto";
6457
6773
  import {
6458
6774
  closeSync as closeSync4,
6459
6775
  copyFileSync as copyFileSync3,
6460
- mkdirSync as mkdirSync6,
6776
+ mkdirSync as mkdirSync8,
6461
6777
  openSync as openSync4,
6462
6778
  readFileSync as readFileSync6,
6463
6779
  readSync as readSync3,
6464
- rmSync as rmSync8,
6465
- statSync as statSync6,
6780
+ rmSync as rmSync10,
6781
+ statSync as statSync7,
6466
6782
  writeFileSync as writeFileSync4
6467
6783
  } from "node:fs";
6468
- 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";
6469
6785
  import { parseHTML as parseHTML4 } from "linkedom";
6470
6786
  import { renameWithRetry as renameWithRetry4, runFfmpeg as runFfmpeg3, runFfprobe } from "@hyperframes/engine";
6471
6787
 
@@ -6705,14 +7021,14 @@ function resolveImageSource(src, options) {
6705
7021
  }
6706
7022
  function isUsableFile2(path) {
6707
7023
  try {
6708
- return statSync6(path).size > 0;
7024
+ return statSync7(path).size > 0;
6709
7025
  } catch {
6710
7026
  return false;
6711
7027
  }
6712
7028
  }
6713
7029
  async function ensurePreparedJpeg(source, cachePath, outputPath, options) {
6714
- mkdirSync6(dirname6(cachePath), { recursive: true });
6715
- mkdirSync6(dirname6(outputPath), { recursive: true });
7030
+ mkdirSync8(dirname7(cachePath), { recursive: true });
7031
+ mkdirSync8(dirname7(outputPath), { recursive: true });
6716
7032
  if (!isUsableFile2(cachePath)) {
6717
7033
  const tmpPath = `${cachePath}.tmp-${process.pid}-${Date.now()}`;
6718
7034
  const inlinePath = `${tmpPath}.heic`;
@@ -6730,8 +7046,8 @@ async function ensurePreparedJpeg(source, cachePath, outputPath, options) {
6730
7046
  const reason = error instanceof Error ? error.message : String(error);
6731
7047
  throw TOOL_FAILURE_RE.test(reason) ? new HeifToolUnavailableError(source.label, reason) : new HeifImageConversionError(source.label, reason);
6732
7048
  } finally {
6733
- rmSync8(tmpPath, { force: true });
6734
- rmSync8(inlinePath, { force: true });
7049
+ rmSync10(tmpPath, { force: true });
7050
+ rmSync10(inlinePath, { force: true });
6735
7051
  }
6736
7052
  }
6737
7053
  if (resolve6(outputPath) !== resolve6(cachePath) && !isUsableFile2(outputPath)) {
@@ -6739,7 +7055,7 @@ async function ensurePreparedJpeg(source, cachePath, outputPath, options) {
6739
7055
  }
6740
7056
  }
6741
7057
  async function prepareHeifImageInputs(html, options) {
6742
- const outputDir = options.outputDir ?? join14(options.downloadDir, PREPARED_HEIF_SUBDIR);
7058
+ const outputDir = options.outputDir ?? join17(options.downloadDir, PREPARED_HEIF_SUBDIR);
6743
7059
  const outputSrcPrefix = normalizeRelPath(options.outputSrcPrefix ?? PREPARED_HEIF_SUBDIR);
6744
7060
  const cacheDir = options.cacheDir ?? outputDir;
6745
7061
  const preparedAssets = /* @__PURE__ */ new Map();
@@ -6760,7 +7076,7 @@ async function prepareHeifImageInputs(html, options) {
6760
7076
  await Promise.all(
6761
7077
  [...unique].map(
6762
7078
  ([filename, source]) => withMediaProbeSlot(
6763
- () => ensurePreparedJpeg(source, join14(cacheDir, filename), join14(outputDir, filename), options)
7079
+ () => ensurePreparedJpeg(source, join17(cacheDir, filename), join17(outputDir, filename), options)
6764
7080
  ).catch((error) => {
6765
7081
  if (!options.onFailure) throw error;
6766
7082
  failed.add(filename);
@@ -6772,8 +7088,8 @@ async function prepareHeifImageInputs(html, options) {
6772
7088
  if (failed.has(filename)) continue;
6773
7089
  const outputSrc = `${outputSrcPrefix}/${filename}`;
6774
7090
  img.setAttribute("src", outputSrc);
6775
- preparedAssets.set(outputSrc, join14(outputDir, filename));
6776
- 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) });
6777
7093
  }
6778
7094
  return { html: document2.toString(), preparedAssets, preparedImages };
6779
7095
  }
@@ -7097,8 +7413,8 @@ function detectShaderTransitionUsage(html) {
7097
7413
  async function resolveMediaDuration(src, mediaStart, playbackRate, baseDir, downloadDir, tagName, elementIdentity, log, urlByAbsentLocalPath, probes) {
7098
7414
  let filePath = src;
7099
7415
  if (isHttpUrl(src)) {
7100
- const remoteDir = join15(downloadDir, REMOTE_MEDIA_SUBDIR);
7101
- if (!existsSync10(remoteDir)) mkdirSync7(remoteDir, { recursive: true });
7416
+ const remoteDir = join18(downloadDir, REMOTE_MEDIA_SUBDIR);
7417
+ if (!existsSync10(remoteDir)) mkdirSync9(remoteDir, { recursive: true });
7102
7418
  try {
7103
7419
  filePath = await downloadToTemp(src, remoteDir, void 0, void 0, void 0, {
7104
7420
  onTelemetry: logRemoteDownloadTelemetry
@@ -7108,7 +7424,7 @@ async function resolveMediaDuration(src, mediaStart, playbackRate, baseDir, down
7108
7424
  }
7109
7425
  } else {
7110
7426
  filePath = decodedUrlPath(src);
7111
- if (!filePath.startsWith("/")) filePath = join15(baseDir, filePath);
7427
+ if (!filePath.startsWith("/")) filePath = join18(baseDir, filePath);
7112
7428
  filePath = urlByAbsentLocalPath?.get(filePath) ?? filePath;
7113
7429
  }
7114
7430
  if (!isHttpUrl(filePath) && !existsSync10(filePath)) {
@@ -7269,7 +7585,7 @@ async function parseSubCompositions(html, projectDir, downloadDir, visited = /*
7269
7585
  workItems.map(async (item) => {
7270
7586
  const { html: compiledSub } = await compileHtmlFile(
7271
7587
  rewriteUnresolvableGsapToCdn(item.rawSubHtml, projectDir, item.srcPath),
7272
- dirname7(item.filePath),
7588
+ dirname8(item.filePath),
7273
7589
  downloadDir,
7274
7590
  void 0,
7275
7591
  urlByAbsentLocalPath,
@@ -7699,7 +8015,7 @@ var REMOTE_SOURCE_TAG_RE = /<source\b[^>]*?\bsrc\s*=\s*["'](https?:\/\/[^"']+)["
7699
8015
  var REMOTE_IMG_TAG_RE = /<img\b[^>]*?(?<![\w-])src\s*=\s*["'](https?:\/\/[^"']+)["'][^>]*>/gi;
7700
8016
  async function downloadAndRewriteUrls(urlSet, html, remoteDir, warnLabel, logLabel, extraRewrite) {
7701
8017
  if (urlSet.size === 0) return { html, remoteMediaAssets: /* @__PURE__ */ new Map() };
7702
- if (!existsSync10(remoteDir)) mkdirSync7(remoteDir, { recursive: true });
8018
+ if (!existsSync10(remoteDir)) mkdirSync9(remoteDir, { recursive: true });
7703
8019
  const urlToLocal = /* @__PURE__ */ new Map();
7704
8020
  await Promise.all(
7705
8021
  [...urlSet].map(async (url) => {
@@ -7746,7 +8062,7 @@ async function localizeRemoteMediaSources(html, downloadDir) {
7746
8062
  return downloadAndRewriteUrls(
7747
8063
  urlSet,
7748
8064
  html,
7749
- join15(downloadDir, REMOTE_MEDIA_SUBDIR),
8065
+ join18(downloadDir, REMOTE_MEDIA_SUBDIR),
7750
8066
  "Remote media download failed",
7751
8067
  "Localized remote media source(s)"
7752
8068
  );
@@ -7761,7 +8077,7 @@ async function localizeRemoteImageSources(html, downloadDir) {
7761
8077
  return downloadAndRewriteUrls(
7762
8078
  urlSet,
7763
8079
  html,
7764
- join15(downloadDir, REMOTE_MEDIA_SUBDIR),
8080
+ join18(downloadDir, REMOTE_MEDIA_SUBDIR),
7765
8081
  "Remote image download failed",
7766
8082
  "Localized remote image source(s)"
7767
8083
  );
@@ -7777,7 +8093,7 @@ async function localizeRemoteBackgroundImages(html, downloadDir) {
7777
8093
  return downloadAndRewriteUrls(
7778
8094
  urlSet,
7779
8095
  html,
7780
- join15(downloadDir, REMOTE_MEDIA_SUBDIR),
8096
+ join18(downloadDir, REMOTE_MEDIA_SUBDIR),
7781
8097
  "Remote background-image download failed",
7782
8098
  "Localized remote background-image(s)",
7783
8099
  // Quoted url('..')/url("..") are rewritten by downloadAndRewriteUrls' default
@@ -7887,7 +8203,7 @@ function projectStylesheetFontFamilies(html, projectDir) {
7887
8203
  const family = /font-family\s*:\s*([^;}]+)/i.exec(face)?.[1]?.trim().replace(/^['"]|['"]$/g, "");
7888
8204
  const shipsFile = [...face.matchAll(LOCAL_FONTFACE_URL_RE)].some(([, url = ""]) => {
7889
8205
  const path = decodedUrlPath(url);
7890
- const file = path.startsWith("/") ? resolve7(projectDir, `.${path}`) : resolve7(dirname7(sheet), path);
8206
+ const file = path.startsWith("/") ? resolve7(projectDir, `.${path}`) : resolve7(dirname8(sheet), path);
7891
8207
  return isPathInside2(file, projectDir) && existsSync10(file);
7892
8208
  });
7893
8209
  if (family && shipsFile) families.push(family);
@@ -7932,7 +8248,7 @@ function localizeFontFaceUrls(html, downloadDir) {
7932
8248
  return downloadAndRewriteUrls(
7933
8249
  collectFontFaceUrls(html),
7934
8250
  html,
7935
- join15(downloadDir, REMOTE_MEDIA_SUBDIR),
8251
+ join18(downloadDir, REMOTE_MEDIA_SUBDIR),
7936
8252
  "Remote font download failed",
7937
8253
  "Localized remote font face(s)",
7938
8254
  (h, url, relPath) => h.replaceAll(`url(${url})`, `url("${relPath}")`)
@@ -8604,11 +8920,36 @@ function adaptAspectAgnosticResolution(requested, aspectAgnostic, width, height,
8604
8920
  });
8605
8921
  return flipped;
8606
8922
  }
8923
+ function resolveEntryHtmlPath(input) {
8924
+ const { projectDir, workDir, entryFile, log } = input;
8925
+ const htmlPath = join19(projectDir, entryFile);
8926
+ const rawEntry = readTextFile2(htmlPath);
8927
+ if (entryFile === "index.html" || !isTemplateEntry(rawEntry)) return htmlPath;
8928
+ const projectIndexPath = join19(projectDir, "index.html");
8929
+ if (!existsSync11(projectIndexPath)) {
8930
+ throw new Error(
8931
+ `Template entry file "${entryFile}" requires a project index.html to extract its render shell.`
8932
+ );
8933
+ }
8934
+ const standaloneHtml = extractStandaloneEntryFromIndex(
8935
+ readTextFile2(projectIndexPath),
8936
+ entryFile,
8937
+ rawEntry
8938
+ );
8939
+ if (!standaloneHtml) {
8940
+ throw new Error(
8941
+ `Entry file "${entryFile}" is not mounted from index.html via data-composition-src, so it cannot be rendered independently.`
8942
+ );
8943
+ }
8944
+ const wrapperPath = join19(workDir, "standalone-entry.html");
8945
+ writeFileSync5(wrapperPath, standaloneHtml, "utf-8");
8946
+ log.info("Extracted standalone entry from index.html host context", { entryFile });
8947
+ return wrapperPath;
8948
+ }
8607
8949
  async function runCompileStage(input) {
8608
8950
  const {
8609
8951
  projectDir,
8610
8952
  workDir,
8611
- htmlPath,
8612
8953
  entryFile,
8613
8954
  job,
8614
8955
  cfg,
@@ -8619,8 +8960,9 @@ async function runCompileStage(input) {
8619
8960
  allowSystemFontCapture,
8620
8961
  abortSignal
8621
8962
  } = input;
8963
+ const htmlPath = resolveEntryHtmlPath({ projectDir, workDir, entryFile, log });
8622
8964
  const compileStart = Date.now();
8623
- const compiled = await compileForRender(projectDir, htmlPath, join16(workDir, "downloads"), {
8965
+ const compiled = await compileForRender(projectDir, htmlPath, join19(workDir, "downloads"), {
8624
8966
  log,
8625
8967
  failClosedFontFetch: failClosedFontFetch === true,
8626
8968
  allowSystemFontCapture,
@@ -8628,7 +8970,7 @@ async function runCompileStage(input) {
8628
8970
  variables: input.variables,
8629
8971
  urlByAbsentLocalPath: input.urlByAbsentLocalPath,
8630
8972
  pageMotionBlur: input.pageMotionBlur,
8631
- animatedGifCacheDir: cfg.extractCacheDir ? join16(cfg.extractCacheDir, "animated-gif") : void 0,
8973
+ animatedGifCacheDir: cfg.extractCacheDir ? join19(cfg.extractCacheDir, "animated-gif") : void 0,
8632
8974
  ffmpegProcessTimeout: cfg.ffmpegProcessTimeout
8633
8975
  });
8634
8976
  assertNotAborted();
@@ -8685,7 +9027,7 @@ async function runCompileStage(input) {
8685
9027
  };
8686
9028
  await preflightCompositionAssetMediaTypes({
8687
9029
  projectDir,
8688
- compiledDir: join16(workDir, "compiled"),
9030
+ compiledDir: join19(workDir, "compiled"),
8689
9031
  composition,
8690
9032
  signal: abortSignal
8691
9033
  });
@@ -8731,7 +9073,7 @@ async function runCompileStage(input) {
8731
9073
 
8732
9074
  // src/services/render/stages/probeStage.ts
8733
9075
  import { createHash as createHash7 } from "node:crypto";
8734
- import { basename as basename5, join as join17 } from "node:path";
9076
+ import { basename as basename5, join as join20 } from "node:path";
8735
9077
  import { parseHTML as parseHTML6 } from "linkedom";
8736
9078
  import {
8737
9079
  closeCaptureSession as closeCaptureSession4,
@@ -8940,7 +9282,7 @@ async function runProbeStage(input) {
8940
9282
  });
8941
9283
  fileServer = await createFileServer({
8942
9284
  projectDir,
8943
- compiledDir: join17(workDir, "compiled"),
9285
+ compiledDir: join20(workDir, "compiled"),
8944
9286
  port: 0,
8945
9287
  preHeadScripts: [VIRTUAL_TIME_SHIM],
8946
9288
  fps: job.config.fps
@@ -8970,7 +9312,7 @@ async function runProbeStage(input) {
8970
9312
  log.info("Creating capture session...", { attempt, maxAttempts: PROBE_MAX_ATTEMPTS });
8971
9313
  probeSession = await createCaptureSession4(
8972
9314
  fileServer.url,
8973
- join17(workDir, "probe"),
9315
+ join20(workDir, "probe"),
8974
9316
  captureOpts,
8975
9317
  null,
8976
9318
  probeCfg
@@ -9055,7 +9397,7 @@ async function runProbeStage(input) {
9055
9397
  });
9056
9398
  probeSession = await createCaptureSession4(
9057
9399
  fileServer.url,
9058
- join17(workDir, "probe-screenshot"),
9400
+ join20(workDir, "probe-screenshot"),
9059
9401
  captureOpts,
9060
9402
  null,
9061
9403
  { ...probeCfg, forceScreenshot: true }
@@ -9093,7 +9435,7 @@ async function runProbeStage(input) {
9093
9435
  compiled,
9094
9436
  resolutions,
9095
9437
  projectDir,
9096
- join17(workDir, "downloads")
9438
+ join20(workDir, "downloads")
9097
9439
  );
9098
9440
  assertNotAborted();
9099
9441
  composition.videos = compiled.videos;
@@ -9284,7 +9626,7 @@ async function runProbeStage(input) {
9284
9626
  }
9285
9627
  await preflightCompositionAssetMediaTypes({
9286
9628
  projectDir,
9287
- compiledDir: join17(workDir, "compiled"),
9629
+ compiledDir: join20(workDir, "compiled"),
9288
9630
  composition,
9289
9631
  signal: abortSignal
9290
9632
  });
@@ -9450,11 +9792,11 @@ function validateDistributedDuration(input) {
9450
9792
  }
9451
9793
 
9452
9794
  // src/services/render/stages/extractVideosStage.ts
9453
- import { existsSync as existsSync11 } from "node:fs";
9454
- import { join as join18 } from "node:path";
9795
+ import { existsSync as existsSync12 } from "node:fs";
9796
+ import { join as join21 } from "node:path";
9455
9797
  import {
9456
9798
  classifyVideoExtractionError,
9457
- createFrameLookupTable,
9799
+ createFrameLookupTable as createFrameLookupTable3,
9458
9800
  detectTransfer,
9459
9801
  extractAllVideoFrames,
9460
9802
  extractMediaMetadata as extractMediaMetadata3,
@@ -9466,7 +9808,7 @@ import {
9466
9808
  resolveVideoExtractionWindow as resolveVideoExtractionWindow2,
9467
9809
  isHttpUrl as isHttpUrl2,
9468
9810
  resolveProjectRelativeSrc as resolveProjectRelativeSrc3,
9469
- runVideoExtractionWithRetry,
9811
+ runVideoExtractionWithRetry as runVideoExtractionWithRetry2,
9470
9812
  safeVideoExtractionSourceIdentity,
9471
9813
  videoAudioId as videoAudioId2
9472
9814
  } from "@hyperframes/engine";
@@ -9725,9 +10067,9 @@ async function runExtractVideosStage(input) {
9725
10067
  const probeFailures = await Promise.all(
9726
10068
  composition.videos.map(async (v) => {
9727
10069
  const videoPath = resolveProjectRelativeSrc3(v.src, projectDir, compiledDir);
9728
- if (!existsSync11(videoPath)) return null;
10070
+ if (!existsSync12(videoPath)) return null;
9729
10071
  try {
9730
- 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), {
9731
10073
  signal: abortSignal,
9732
10074
  maxTransientRetries: extractionPolicy.maxTransientRetries,
9733
10075
  onRetry: () => {
@@ -9763,7 +10105,7 @@ async function runExtractVideosStage(input) {
9763
10105
  const probed = await Promise.all(
9764
10106
  composition.images.map(async (img) => {
9765
10107
  const imgPath = resolveProjectRelativeSrc3(img.src, projectDir, compiledDir);
9766
- if (!existsSync11(imgPath)) return null;
10108
+ if (!existsSync12(imgPath)) return null;
9767
10109
  const colorSpace = await probeColorSpaceSafely(imgPath, log);
9768
10110
  if (isHdrColorSpace2(colorSpace)) {
9769
10111
  nativeHdrImageIds.add(img.id);
@@ -9797,14 +10139,14 @@ async function runExtractVideosStage(input) {
9797
10139
  // because short boundary counts can differ by one frame.
9798
10140
  {
9799
10141
  fps: job.config.fps,
9800
- outputDir: join18(compiledDir, "__hyperframes_video_frames"),
10142
+ outputDir: join21(compiledDir, "__hyperframes_video_frames"),
9801
10143
  format: job.config.videoFrameFormat ?? "auto",
9802
10144
  toneMapHdrToSdr: rendersSdrOnly(job.config, composition),
9803
10145
  timelineEnd: composition.duration,
9804
10146
  maxTransientRetries: extractionPolicy.maxTransientRetries,
9805
10147
  collectProbeFailures: extractionPolicy.failureMode === "enforce",
9806
10148
  deferRangeExtraction,
9807
- contentKeyedDirs: [join18(compiledDir, REMOTE_MEDIA_SUBDIR)],
10149
+ contentKeyedDirs: [join21(compiledDir, REMOTE_MEDIA_SUBDIR)],
9808
10150
  urlByAbsentLocalPath,
9809
10151
  onProgress: ({ planned = 0, done = 0 }) => {
9810
10152
  framesPlanned += planned;
@@ -9824,7 +10166,7 @@ async function runExtractVideosStage(input) {
9824
10166
  materializeSymlinks
9825
10167
  });
9826
10168
  if (extractionResult.extracted.length > 0) {
9827
- frameLookup = createFrameLookupTable(
10169
+ frameLookup = createFrameLookupTable3(
9828
10170
  composition.videos,
9829
10171
  extractionResult.extracted,
9830
10172
  resolveRenderFpsConfig(job.config.fps).value
@@ -9883,7 +10225,7 @@ async function appendVideoAudioFromProbe(input) {
9883
10225
  videos.map(async (video) => {
9884
10226
  const path = isHttpUrl2(video.src) ? video.src : resolveProjectRelativeSrc3(video.src, projectDir, compiledDir);
9885
10227
  const readFrom = urlByAbsentLocalPath?.get(path) ?? path;
9886
- if (!isHttpUrl2(readFrom) && !existsSync11(readFrom)) return null;
10228
+ if (!isHttpUrl2(readFrom) && !existsSync12(readFrom)) return null;
9887
10229
  const metadata = await extractMediaMetadata3(readFrom);
9888
10230
  const window2 = await resolveFinalFrameExtractionWindow2(
9889
10231
  readFrom,
@@ -9903,7 +10245,7 @@ async function appendVideoAudioFromProbe(input) {
9903
10245
  }
9904
10246
 
9905
10247
  // src/services/render/videoLayerPassthroughPlan.ts
9906
- import { existsSync as existsSync12 } from "node:fs";
10248
+ import { existsSync as existsSync13 } from "node:fs";
9907
10249
  import {
9908
10250
  buildVideoLayerInput,
9909
10251
  isHttpUrl as isHttpUrl3,
@@ -9971,7 +10313,7 @@ async function planVideoLayerPassthrough(input) {
9971
10313
  const { video } = q;
9972
10314
  if (isHttpUrl3(video.src)) return { reason: "remote_source" };
9973
10315
  const path = resolveProjectRelativeSrc4(video.src, input.projectDir, input.compiledDir);
9974
- if (!existsSync12(path)) return { reason: "source_missing" };
10316
+ if (!existsSync13(path)) return { reason: "source_missing" };
9975
10317
  const layout = await probeVideoLayout(input.page, video.id, input.canvas, input.duration);
9976
10318
  if ("reason" in layout) return layout;
9977
10319
  const layer = await buildVideoLayerInput(
@@ -9986,8 +10328,8 @@ async function planVideoLayerPassthrough(input) {
9986
10328
  }
9987
10329
 
9988
10330
  // src/services/render/stages/audioStage.ts
9989
- import { existsSync as existsSync14 } from "node:fs";
9990
- import { join as join21 } from "node:path";
10331
+ import { existsSync as existsSync15 } from "node:fs";
10332
+ import { join as join24 } from "node:path";
9991
10333
  import {
9992
10334
  MIXED_AUDIO_FILENAME,
9993
10335
  audioFailureDiagnostics,
@@ -9997,8 +10339,8 @@ import {
9997
10339
  } from "@hyperframes/engine";
9998
10340
 
9999
10341
  // src/services/render/audioDeliveryLimit.ts
10000
- import { existsSync as existsSync13 } from "node:fs";
10001
- import { dirname as dirname9, join as join20 } from "node:path";
10342
+ import { existsSync as existsSync14 } from "node:fs";
10343
+ import { dirname as dirname10, join as join23 } from "node:path";
10002
10344
  import {
10003
10345
  applyAudioFxChain,
10004
10346
  describeFfmpegFailure as describeFfmpegFailure3,
@@ -10009,7 +10351,7 @@ import {
10009
10351
  // src/services/render/audioPadTrim.ts
10010
10352
  import { spawn } from "node:child_process";
10011
10353
  import { mkdtempSync as mkdtempSync3 } from "node:fs";
10012
- import { dirname as dirname8, join as join19 } from "node:path";
10354
+ import { dirname as dirname9, join as join22 } from "node:path";
10013
10355
  import {
10014
10356
  DEFAULT_CONFIG,
10015
10357
  describeFfmpegFailure as describeFfmpegFailure2,
@@ -10288,7 +10630,7 @@ async function constrainAacTruePeak(input) {
10288
10630
  }
10289
10631
  async function cutSource(input, scratchDir, attenuationDb) {
10290
10632
  if (!input.sourcePath || !input.rewriteSource) return true;
10291
- const cutPath = join19(scratchDir, "source.wav");
10633
+ const cutPath = join22(scratchDir, "source.wav");
10292
10634
  try {
10293
10635
  const result = await input.rewriteSource(input.sourcePath, cutPath, gainFilter(attenuationDb));
10294
10636
  if (!result.success) throw new Error(result.error);
@@ -10302,8 +10644,8 @@ async function cutSource(input, scratchDir, attenuationDb) {
10302
10644
  async function enforceAacTruePeak(input) {
10303
10645
  let scratchDir;
10304
10646
  try {
10305
- scratchDir = mkdtempSync3(join19(dirname8(input.audioPath), ".true-peak-"));
10306
- const correctedPath = join19(scratchDir, "audio.m4a");
10647
+ scratchDir = mkdtempSync3(join22(dirname9(input.audioPath), ".true-peak-"));
10648
+ const correctedPath = join22(scratchDir, "audio.m4a");
10307
10649
  const measurements = [];
10308
10650
  let attenuationDb = 0;
10309
10651
  let sourcePeakDbfs;
@@ -10494,31 +10836,57 @@ async function runFfprobeJson(args, signal) {
10494
10836
  }
10495
10837
 
10496
10838
  // src/services/render/audioDeliveryLimit.ts
10497
- var LIMIT_CEILING_DBTP = AAC_DELIVERY_TRUE_PEAK_DBFS - 2;
10498
- 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) => ({
10499
10843
  version: 1,
10500
- nodes: [{ type: "truepeak", id: "delivery", params: { ceiling: LIMIT_CEILING_DBTP } }]
10501
- };
10844
+ nodes: [{ type: "truepeak", id: "delivery", params: { ceiling } }]
10845
+ });
10502
10846
  async function limitDeliveryPeaks(input, deps = {
10503
10847
  measure: measureTruePeakDbfs,
10504
10848
  applyChain: applyAudioFxChain,
10505
10849
  encode: encodeAac2,
10506
10850
  rename: renameWithRetry6,
10507
- sourceExists: existsSync13
10851
+ sourceExists: existsSync14
10508
10852
  }) {
10509
10853
  const { audioPath, sourcePath, signal, timeoutMs } = input;
10510
10854
  if (!deps.sourceExists(sourcePath)) return { success: true };
10511
10855
  const peak = await deps.measure(audioPath, { timeoutMs, signal });
10512
10856
  if (peak <= AAC_DELIVERY_TRUE_PEAK_DBFS) return { success: true };
10513
- 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;
10514
10879
  try {
10515
- await deps.applyChain(sourcePath, LIMIT_CHAIN, limitedPath, { trackId: "delivery", signal });
10880
+ await deps.applyChain(sourcePath, limitChain(ceiling), limitedPath, {
10881
+ trackId: "delivery",
10882
+ signal
10883
+ });
10516
10884
  } catch (error) {
10517
10885
  if (signal?.aborted) throw error;
10518
10886
  input.warn?.("[audio] true-peak limit failed, the whole-mix cut applies", {
10519
10887
  error: error instanceof Error ? error.message : String(error)
10520
10888
  });
10521
- return { success: true };
10889
+ return { limitFailed: true };
10522
10890
  }
10523
10891
  const encoded = await deps.encode(limitedPath, audioPath, input.targetDurationSeconds, {
10524
10892
  signal,
@@ -10526,13 +10894,14 @@ async function limitDeliveryPeaks(input, deps = {
10526
10894
  });
10527
10895
  if (!encoded.success) {
10528
10896
  return {
10529
- success: false,
10530
- error: `[audioDeliveryLimit] ${describeFfmpegFailure3(encoded, timeoutMs)}`,
10531
- failureReason: encoded.failureReason
10897
+ encodeFailure: {
10898
+ success: false,
10899
+ error: `[audioDeliveryLimit] ${describeFfmpegFailure3(encoded, timeoutMs)}`,
10900
+ failureReason: encoded.failureReason
10901
+ }
10532
10902
  };
10533
10903
  }
10534
- deps.rename(limitedPath, sourcePath, { copyFallback: true });
10535
- return { success: true };
10904
+ return { peak: await deps.measure(audioPath, { timeoutMs, signal }) };
10536
10905
  }
10537
10906
 
10538
10907
  // src/services/render/stages/audioStage.ts
@@ -10558,7 +10927,7 @@ function runAudioStage(input) {
10558
10927
  }
10559
10928
  async function runAudioStageScoped(input) {
10560
10929
  const stageStart = Date.now();
10561
- const pcmSourcePath = join21(input.workDir, "audio-source.wav");
10930
+ const pcmSourcePath = join24(input.workDir, "audio-source.wav");
10562
10931
  const duration = input.totalFrames * input.fps.den / input.fps.num;
10563
10932
  let deliverySourcePath;
10564
10933
  try {
@@ -10574,7 +10943,7 @@ async function runAudioStageScoped(input) {
10574
10943
  );
10575
10944
  audioLoweredDb = guard.loweredDb;
10576
10945
  input.log?.info("[audio] phases", { ...result.audioPhaseMs, guard: Date.now() - guardStart });
10577
- if (input.keepDeliverySource && !guard.sourceStale && existsSync14(pcmSourcePath)) {
10946
+ if (input.keepDeliverySource && !guard.sourceStale && existsSync15(pcmSourcePath)) {
10578
10947
  deliverySourcePath = pcmSourcePath;
10579
10948
  }
10580
10949
  }
@@ -10623,7 +10992,7 @@ async function mixAudioForDelivery(input, duration, pcmSourcePath) {
10623
10992
  assertNotAborted,
10624
10993
  log
10625
10994
  } = input;
10626
- const audioOutputPath = join21(workDir, MIXED_AUDIO_FILENAME);
10995
+ const audioOutputPath = join24(workDir, MIXED_AUDIO_FILENAME);
10627
10996
  let hasAudio = false;
10628
10997
  let audioError;
10629
10998
  let audioFailures;
@@ -10634,7 +11003,7 @@ async function mixAudioForDelivery(input, duration, pcmSourcePath) {
10634
11003
  audioResult = await processCompositionAudio(
10635
11004
  audios,
10636
11005
  projectDir,
10637
- join21(workDir, "audio-work"),
11006
+ join24(workDir, "audio-work"),
10638
11007
  audioOutputPath,
10639
11008
  duration,
10640
11009
  abortSignal,
@@ -10702,8 +11071,8 @@ async function mixAudioForDelivery(input, duration, pcmSourcePath) {
10702
11071
  }
10703
11072
 
10704
11073
  // src/services/render/stages/captureStage.ts
10705
- import { existsSync as existsSync15, readdirSync as readdirSync3, statfsSync as statfsSync2 } from "node:fs";
10706
- 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";
10707
11076
  import {
10708
11077
  classifyCaptureFailure as classifyCaptureFailure2,
10709
11078
  frameFileExtension,
@@ -10773,16 +11142,16 @@ function diskCaptureShortfallError(needBytes, freeBytes, framesDir, basis, remed
10773
11142
  }
10774
11143
  function measureCaptureFrameBytes(workDir, framesDir) {
10775
11144
  let total = sampleDirectoryBytes(framesDir);
10776
- if (!existsSync15(workDir)) return total;
10777
- for (const attempt of readdirSync3(workDir)) {
11145
+ if (!existsSync16(workDir)) return total;
11146
+ for (const attempt of readdirSync4(workDir)) {
10778
11147
  if (/^retry-\d+-batch-\d+-worker-\d+$/.test(attempt)) {
10779
- total += sampleDirectoryBytes(join22(workDir, attempt));
11148
+ total += sampleDirectoryBytes(join25(workDir, attempt));
10780
11149
  continue;
10781
11150
  }
10782
11151
  if (!/^capture-attempt-\d+$/.test(attempt)) continue;
10783
- const attemptDir = join22(workDir, attempt);
10784
- for (const name of readdirSync3(attemptDir)) {
10785
- 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));
10786
11155
  }
10787
11156
  }
10788
11157
  return total;
@@ -11076,8 +11445,8 @@ async function captureSessionFrames(session, { input, rangeStart, rangeEnd, resu
11076
11445
  }
11077
11446
 
11078
11447
  // src/services/render/stages/captureHdrStage.ts
11079
- import { existsSync as existsSync17, mkdirSync as mkdirSync8 } from "node:fs";
11080
- 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";
11081
11450
  import {
11082
11451
  closeCaptureSession as closeCaptureSession7,
11083
11452
  cloneCaptureWarnings as cloneCaptureWarnings2,
@@ -11149,7 +11518,7 @@ function createHdrImageTransferCache(options = {}) {
11149
11518
  }
11150
11519
 
11151
11520
  // src/services/render/stages/captureHdrSequentialLoop.ts
11152
- import { join as join23 } from "node:path";
11521
+ import { join as join26 } from "node:path";
11153
11522
  import {
11154
11523
  TRANSITIONS,
11155
11524
  crossfade
@@ -11274,7 +11643,7 @@ async function runSequentialLayeredFrameLoop(input) {
11274
11643
  );
11275
11644
  if (debugDumpEnabled && debugDumpDir && i % 30 === 0) {
11276
11645
  writeFileExclusiveSync(
11277
- join23(debugDumpDir, `frame_${String(i).padStart(4, "0")}_final_rgb48le.bin`),
11646
+ join26(debugDumpDir, `frame_${String(i).padStart(4, "0")}_final_rgb48le.bin`),
11278
11647
  normalCanvas
11279
11648
  );
11280
11649
  }
@@ -11306,7 +11675,7 @@ async function runSequentialLayeredFrameLoop(input) {
11306
11675
  }
11307
11676
 
11308
11677
  // src/services/render/stages/captureHdrHybridLoop.ts
11309
- import { join as join25 } from "node:path";
11678
+ import { join as join28 } from "node:path";
11310
11679
  import {
11311
11680
  TRANSITIONS as TRANSITIONS2,
11312
11681
  closeCaptureSession as closeCaptureSession6,
@@ -11320,9 +11689,9 @@ import {
11320
11689
  // src/services/shaderTransitionWorkerPool.ts
11321
11690
  import { Worker } from "node:worker_threads";
11322
11691
  import { fileURLToPath as fileURLToPath3, pathToFileURL } from "node:url";
11323
- import { dirname as dirname10, join as join24 } from "node:path";
11692
+ import { dirname as dirname11, join as join27 } from "node:path";
11324
11693
  import { createRequire } from "node:module";
11325
- import { existsSync as existsSync16 } from "node:fs";
11694
+ import { existsSync as existsSync17 } from "node:fs";
11326
11695
  import { cpus as cpus2 } from "node:os";
11327
11696
  function resolveWorkerEntry(explicit) {
11328
11697
  if (explicit && explicit.length > 0) {
@@ -11333,10 +11702,10 @@ function resolveWorkerEntry(explicit) {
11333
11702
  const isTs = override.endsWith(".ts");
11334
11703
  return { path: override, isTs };
11335
11704
  }
11336
- const moduleDir = dirname10(fileURLToPath3(import.meta.url));
11337
- const jsPath = join24(moduleDir, "shaderTransitionWorker.js");
11338
- if (existsSync16(jsPath)) return { path: jsPath, isTs: false };
11339
- const tsPath = join24(moduleDir, "shaderTransitionWorker.ts");
11705
+ const moduleDir = dirname11(fileURLToPath3(import.meta.url));
11706
+ const jsPath = join27(moduleDir, "shaderTransitionWorker.js");
11707
+ if (existsSync17(jsPath)) return { path: jsPath, isTs: false };
11708
+ const tsPath = join27(moduleDir, "shaderTransitionWorker.ts");
11340
11709
  return { path: tsPath, isTs: true };
11341
11710
  }
11342
11711
  function buildExecArgv(entryIsTs) {
@@ -11704,7 +12073,7 @@ async function runHybridLayeredFrameLoop(input) {
11704
12073
  );
11705
12074
  if (debugDumpEnabled && debugDumpDir && i % 30 === 0) {
11706
12075
  writeFileExclusiveSync(
11707
- join25(debugDumpDir, `frame_${String(i).padStart(4, "0")}_final_rgb48le.bin`),
12076
+ join28(debugDumpDir, `frame_${String(i).padStart(4, "0")}_final_rgb48le.bin`),
11708
12077
  canvas
11709
12078
  );
11710
12079
  }
@@ -11895,9 +12264,9 @@ async function runCaptureHdrStage(input) {
11895
12264
  if (hdrVideoFrameSources.has(v.id)) hdrVideoEndTimes.set(v.id, v.end);
11896
12265
  }
11897
12266
  const debugDumpEnabled = process.env.KEEP_TEMP === "1";
11898
- const debugDumpDir = debugDumpEnabled ? join26(framesDir, "debug-composite") : null;
11899
- if (debugDumpDir && !existsSync17(debugDumpDir)) {
11900
- mkdirSync8(debugDumpDir, { recursive: true });
12267
+ const debugDumpDir = debugDumpEnabled ? join29(framesDir, "debug-composite") : null;
12268
+ if (debugDumpDir && !existsSync18(debugDumpDir)) {
12269
+ mkdirSync10(debugDumpDir, { recursive: true });
11901
12270
  }
11902
12271
  const compositeTransfer = resolveCompositeTransfer(hasHdrContent, effectiveHdr);
11903
12272
  const hdrTargetTransfer = compositeTransfer === "srgb" ? void 0 : compositeTransfer;
@@ -12053,16 +12422,16 @@ async function runCaptureHdrStage(input) {
12053
12422
  // src/services/render/stages/encodeStage.ts
12054
12423
  import {
12055
12424
  copyFileSync as copyFileSync4,
12056
- existsSync as existsSync18,
12057
- mkdirSync as mkdirSync9,
12058
- readdirSync as readdirSync4,
12425
+ existsSync as existsSync19,
12426
+ mkdirSync as mkdirSync11,
12427
+ readdirSync as readdirSync5,
12059
12428
  readFileSync as readFileSync8,
12060
- rmSync as rmSync9,
12061
- statSync as statSync7,
12062
- writeFileSync as writeFileSync5
12429
+ rmSync as rmSync11,
12430
+ statSync as statSync8,
12431
+ writeFileSync as writeFileSync6
12063
12432
  } from "node:fs";
12064
12433
  import { readFile as readFile2 } from "node:fs/promises";
12065
- import { dirname as dirname11, join as join27 } from "node:path";
12434
+ import { dirname as dirname12, join as join30 } from "node:path";
12066
12435
  import {
12067
12436
  encodeFramesChunkedConcat,
12068
12437
  encodeFramesFromDir,
@@ -12155,12 +12524,12 @@ async function reencodeWithClearedWholeFrames(argsInput, run, startTime) {
12155
12524
  const error = "[FFmpeg] GIF output could not be parsed to clear its frames";
12156
12525
  return gifEncodeFailure(startTime, outputPath, error);
12157
12526
  }
12158
- writeFileSync5(outputPath, gif);
12527
+ writeFileSync6(outputPath, gif);
12159
12528
  return null;
12160
12529
  }
12161
12530
  async function encodeGifFromDir(framesDir, framePattern, outputPath, input) {
12162
12531
  const startTime = Date.now();
12163
- 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));
12164
12533
  const frameCount = files.length;
12165
12534
  if (frameCount === 0) {
12166
12535
  return gifEncodeFailure(startTime, outputPath, "[FFmpeg] No frame files found in directory");
@@ -12187,7 +12556,7 @@ async function encodeGifFromDir(framesDir, framePattern, outputPath, input) {
12187
12556
  const failure = await reencodeWithClearedWholeFrames(argsInput, run, startTime);
12188
12557
  if (failure) return failure;
12189
12558
  }
12190
- const fileSize = existsSync18(outputPath) ? statSync7(outputPath).size : 0;
12559
+ const fileSize = existsSync19(outputPath) ? statSync8(outputPath).size : 0;
12191
12560
  return {
12192
12561
  success: true,
12193
12562
  outputPath,
@@ -12196,7 +12565,7 @@ async function encodeGifFromDir(framesDir, framePattern, outputPath, input) {
12196
12565
  fileSize
12197
12566
  };
12198
12567
  } finally {
12199
- rmSync9(input.palettePath, { force: true });
12568
+ rmSync11(input.palettePath, { force: true });
12200
12569
  }
12201
12570
  }
12202
12571
  function startEncodeProgress(job, stage, onProgress) {
@@ -12273,7 +12642,7 @@ function encodeWhileCapturing(input, totalFrames) {
12273
12642
  await new Promise((resolve13) => wake = resolve13);
12274
12643
  }
12275
12644
  if (signal.aborted) return;
12276
- const mergedPath = join27(input.framesDir, formatCaptureFrameName(next, "jpg"));
12645
+ const mergedPath = join30(input.framesDir, formatCaptureFrameName(next, "jpg"));
12277
12646
  const frame = await readLandedFrame(landed.get(next), mergedPath);
12278
12647
  landed.delete(next);
12279
12648
  if (!await stream.writeFrame(frame)) {
@@ -12348,19 +12717,19 @@ async function runEncodeStage(input) {
12348
12717
  const stage5Start = Date.now();
12349
12718
  if (isPngSequence) {
12350
12719
  const encoded = startEncodeProgress(job, "Writing PNG sequence", onProgress);
12351
- if (!existsSync18(outputPath)) mkdirSync9(outputPath, { recursive: true });
12352
- 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();
12353
12722
  if (captured.length === 0) {
12354
12723
  throw new Error(
12355
12724
  `[Render] png-sequence output requested but no PNGs were captured to ${framesDir}`
12356
12725
  );
12357
12726
  }
12358
12727
  captured.forEach((name, i) => {
12359
- const dst = join27(outputPath, formatExportFrameName(i, "png"));
12360
- copyFileSync4(join27(framesDir, name), dst);
12728
+ const dst = join30(outputPath, formatExportFrameName(i, "png"));
12729
+ copyFileSync4(join30(framesDir, name), dst);
12361
12730
  });
12362
- if (hasAudio && audioOutputPath && existsSync18(audioOutputPath)) {
12363
- copyFileSync4(audioOutputPath, join27(outputPath, MIXED_AUDIO_FILENAME2));
12731
+ if (hasAudio && audioOutputPath && existsSync19(audioOutputPath)) {
12732
+ copyFileSync4(audioOutputPath, join30(outputPath, MIXED_AUDIO_FILENAME2));
12364
12733
  log.info(
12365
12734
  `[Render] png-sequence: ${MIXED_AUDIO_FILENAME2} sidecar written to ${outputPath}/${MIXED_AUDIO_FILENAME2}`
12366
12735
  );
@@ -12380,7 +12749,7 @@ async function runEncodeStage(input) {
12380
12749
  const encodeResult2 = await encodeGifFromDir(framesDir, framePattern2, outputPath, {
12381
12750
  fps: job.config.fps,
12382
12751
  loop,
12383
- palettePath: join27(dirname11(videoOnlyPath), "gif-palette.png"),
12752
+ palettePath: join30(dirname12(videoOnlyPath), "gif-palette.png"),
12384
12753
  preserveAlpha: needsAlpha,
12385
12754
  signal: abortSignal,
12386
12755
  timeout: engineCfg.ffmpegEncodeTimeout
@@ -12586,14 +12955,14 @@ function sampleDirectoryBytes(dir) {
12586
12955
  if (!current) continue;
12587
12956
  let entries = [];
12588
12957
  try {
12589
- entries = readdirSync5(current);
12958
+ entries = readdirSync6(current);
12590
12959
  } catch {
12591
12960
  continue;
12592
12961
  }
12593
12962
  for (const name of entries) {
12594
- const full = join28(current, name);
12963
+ const full = join31(current, name);
12595
12964
  try {
12596
- const st = statSync8(full);
12965
+ const st = statSync9(full);
12597
12966
  if (st.isDirectory()) {
12598
12967
  stack.push(full);
12599
12968
  } else if (st.isFile()) {
@@ -12741,18 +13110,14 @@ function hasUsableVideoDimensions(metadata) {
12741
13110
  return Number.isFinite(metadata.width) && Number.isFinite(metadata.height) && metadata.width > 0 && metadata.height > 0;
12742
13111
  }
12743
13112
  function collectVideoMetadataHints(extractedVideos) {
12744
- return extractedVideos.filter((video) => hasUsableVideoDimensions(video.metadata)).map((video) => ({
12745
- id: video.videoId,
12746
- width: video.metadata.width,
12747
- height: video.metadata.height
12748
- })).sort((a, b) => a.id.localeCompare(b.id));
13113
+ return extractedVideos.filter((video) => hasUsableVideoDimensions(video.metadata)).map((video) => ({ id: video.videoId, ...displaySize(video.metadata) })).sort((a, b) => a.id.localeCompare(b.id));
12749
13114
  }
12750
13115
  function findMissingFrameRanges(totalFrames, framesDir, frameExt) {
12751
13116
  const ranges = [];
12752
13117
  let rangeStart = null;
12753
13118
  for (let frameIndex = 0; frameIndex < totalFrames; frameIndex++) {
12754
- const framePath = join28(framesDir, formatCaptureFrameName(frameIndex, frameExt));
12755
- const missing = !existsSync19(framePath) || statSync8(framePath).size <= 8;
13119
+ const framePath = join31(framesDir, formatCaptureFrameName(frameIndex, frameExt));
13120
+ const missing = !existsSync20(framePath) || statSync9(framePath).size <= 8;
12756
13121
  if (missing && rangeStart === null) {
12757
13122
  rangeStart = frameIndex;
12758
13123
  } else if (!missing && rangeStart !== null) {
@@ -12768,7 +13133,7 @@ function findMissingFrameRanges(totalFrames, framesDir, frameExt) {
12768
13133
  function buildMissingFrameRetryBatches(ranges, maxWorkers, workDir, attempt, rangeStart = 0, stride = 1) {
12769
13134
  const workersPerBatch = Math.max(1, Math.floor(maxWorkers));
12770
13135
  const batches = [];
12771
- 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}`);
12772
13137
  if (stride > 1) {
12773
13138
  const spans = /* @__PURE__ */ new Map();
12774
13139
  for (const range of ranges) {
@@ -12828,8 +13193,8 @@ function getNextRetryWorkerCount(currentWorkers) {
12828
13193
  return Math.max(1, Math.floor(currentWorkers / 2));
12829
13194
  }
12830
13195
  function resolveRenderWorkDirPrefix(outputPath, jobId, platform2 = process.platform, systemTempDir = tmpdir2()) {
12831
- if (platform2 === "win32" || platform2 === "darwin") return join28(systemTempDir, "hf-render-");
12832
- return join28(dirname12(outputPath), `work-${jobId}-`);
13196
+ if (platform2 === "win32" || platform2 === "darwin") return join31(systemTempDir, "hf-render-");
13197
+ return join31(dirname13(outputPath), `work-${jobId}-`);
12833
13198
  }
12834
13199
  var MAX_TRANSIENT_CAPTURE_RETRIES = 1;
12835
13200
  function isTransientCaptureRetryEligible(failureKind, missing, retriesUsed) {
@@ -12876,8 +13241,8 @@ The composition is too large for the available memory. To reduce memory pressure
12876
13241
  function countCapturedFrames(totalFrames, framesDir, frameExt) {
12877
13242
  let captured = 0;
12878
13243
  for (let frameIndex = 0; frameIndex < totalFrames; frameIndex++) {
12879
- const framePath = join28(framesDir, formatCaptureFrameName(frameIndex, frameExt));
12880
- if (existsSync19(framePath)) captured++;
13244
+ const framePath = join31(framesDir, formatCaptureFrameName(frameIndex, frameExt));
13245
+ if (existsSync20(framePath)) captured++;
12881
13246
  }
12882
13247
  return captured;
12883
13248
  }
@@ -12902,7 +13267,7 @@ async function executeDiskCaptureWithAdaptiveRetry(options) {
12902
13267
  reason: attempt === 0 ? "initial" : pendingTransientRetry ? "transient-retry" : "retry"
12903
13268
  });
12904
13269
  pendingTransientRetry = false;
12905
- const attemptWorkDir = join28(options.workDir, `capture-attempt-${attempt}`);
13270
+ const attemptWorkDir = join31(options.workDir, `capture-attempt-${attempt}`);
12906
13271
  const batches = missingRanges ? buildMissingFrameRetryBatches(
12907
13272
  missingRanges,
12908
13273
  currentWorkers,
@@ -13534,19 +13899,19 @@ function deVerifyFallbackTelemetry(err) {
13534
13899
  };
13535
13900
  }
13536
13901
  function resolveRenderDebugDir() {
13537
- const producerRoot = process.env.PRODUCER_RENDERS_DIR ? resolve8(process.env.PRODUCER_RENDERS_DIR, "..") : resolve8(dirname12(fileURLToPath4(import.meta.url)), "../..");
13538
- 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");
13539
13904
  }
13540
13905
  async function executeRenderJob(job, projectDir, outputPath, progressSink, abortSignal, assertRenderActive) {
13541
13906
  validateHlsRenderConfig(job.config);
13542
13907
  validateDeliveryEncoderConfig(job.config);
13543
13908
  const debugDir = resolveRenderDebugDir();
13544
- const outputDir = dirname12(outputPath);
13545
- if (!existsSync19(outputDir)) mkdirSync10(outputDir, { recursive: true });
13546
- 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);
13547
13912
  const pipelineStart = Date.now();
13548
13913
  const baseLog = job.config.logger ?? defaultLogger;
13549
- const logPath = job.config.debug ? join28(workDir, "render.log") : null;
13914
+ const logPath = job.config.debug ? join31(workDir, "render.log") : null;
13550
13915
  const execution = new RenderExecutionContext({
13551
13916
  request: { renderJobId: job.id, projectDir, outputPath },
13552
13917
  logger: logPath ? createRenderFileLogger(logPath, baseLog) : baseLog,
@@ -13560,7 +13925,7 @@ async function executeRenderJob(job, projectDir, outputPath, progressSink, abort
13560
13925
  log.info("KEEP_TEMP=1, leaving workDir on disk for inspection", { workDir });
13561
13926
  return;
13562
13927
  }
13563
- rmSync10(workDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
13928
+ rmSync12(workDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
13564
13929
  });
13565
13930
  try {
13566
13931
  await executeRenderPipeline({
@@ -13608,7 +13973,7 @@ async function executeRenderPipeline(input) {
13608
13973
  imageDecodeFailures: 0
13609
13974
  };
13610
13975
  let hdrPerf;
13611
- const perfOutputPath = join28(workDir, "perf-summary.json");
13976
+ const perfOutputPath = join31(workDir, "perf-summary.json");
13612
13977
  const cfg = { ...job.config.producerConfig ?? resolveConfig2() };
13613
13978
  const observability = new RenderObservabilityRecorder({
13614
13979
  pipelineStartMs: pipelineStart,
@@ -13684,7 +14049,7 @@ async function executeRenderPipeline(input) {
13684
14049
  job.startedAt = /* @__PURE__ */ new Date();
13685
14050
  assertNotAborted();
13686
14051
  assertConfiguredFfmpegBinariesExist();
13687
- if (!existsSync19(workDir)) mkdirSync10(workDir, { recursive: true });
14052
+ if (!existsSync20(workDir)) mkdirSync12(workDir, { recursive: true });
13688
14053
  if (job.config.debug) {
13689
14054
  log.info("[Render] Debug artifacts enabled", { workDir, logPath });
13690
14055
  }
@@ -13713,36 +14078,11 @@ async function executeRenderPipeline(input) {
13713
14078
  requestedWorkers: job.config.workers ?? "auto"
13714
14079
  });
13715
14080
  const entryFile = job.config.entryFile || "index.html";
13716
- let htmlPath = join28(projectDir, entryFile);
13717
- if (!existsSync19(htmlPath)) {
14081
+ const htmlPath = join31(projectDir, entryFile);
14082
+ if (!existsSync20(htmlPath)) {
13718
14083
  throw new Error(`Entry file not found: ${htmlPath}`);
13719
14084
  }
13720
14085
  assertNotAborted();
13721
- const rawEntry = readTextFile2(htmlPath);
13722
- if (entryFile !== "index.html" && isTemplateEntry(rawEntry)) {
13723
- const wrapperPath = join28(workDir, "standalone-entry.html");
13724
- const projectIndexPath = join28(projectDir, "index.html");
13725
- if (!existsSync19(projectIndexPath)) {
13726
- throw new Error(
13727
- `Template entry file "${entryFile}" requires a project index.html to extract its render shell.`
13728
- );
13729
- }
13730
- const standaloneHtml = extractStandaloneEntryFromIndex(
13731
- readTextFile2(projectIndexPath),
13732
- entryFile,
13733
- rawEntry
13734
- );
13735
- if (!standaloneHtml) {
13736
- throw new Error(
13737
- `Entry file "${entryFile}" is not mounted from index.html via data-composition-src, so it cannot be rendered independently.`
13738
- );
13739
- }
13740
- writeFileSync6(wrapperPath, standaloneHtml, "utf-8");
13741
- htmlPath = wrapperPath;
13742
- log.info("Extracted standalone entry from index.html host context", {
13743
- entryFile
13744
- });
13745
- }
13746
14086
  const stage1Start = Date.now();
13747
14087
  updateJobStatus(job, "preprocessing", "Compiling composition", 5, onProgress, {
13748
14088
  code: "compile"
@@ -13754,7 +14094,6 @@ async function executeRenderPipeline(input) {
13754
14094
  () => runCompileStage({
13755
14095
  projectDir,
13756
14096
  workDir,
13757
- htmlPath,
13758
14097
  entryFile,
13759
14098
  job,
13760
14099
  cfg,
@@ -13916,7 +14255,7 @@ async function executeRenderPipeline(input) {
13916
14255
  canvas: { width, height },
13917
14256
  page: probeSession.page,
13918
14257
  projectDir,
13919
- compiledDir: join28(workDir, "compiled")
14258
+ compiledDir: join31(workDir, "compiled")
13920
14259
  });
13921
14260
  if (videoLayerPlan.videoId === void 0) {
13922
14261
  log.info(`[Render] Video layer passthrough off: ${videoLayerPlan.reason}`);
@@ -13934,7 +14273,22 @@ async function executeRenderPipeline(input) {
13934
14273
  updateJobStatus(job, "preprocessing", "Extracting video frames", 10, onProgress, {
13935
14274
  code: "extract_video"
13936
14275
  });
13937
- 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
+ }
13938
14292
  const extractResult = await observeRenderStage(
13939
14293
  observability,
13940
14294
  "video_extract",
@@ -13951,6 +14305,7 @@ async function executeRenderPipeline(input) {
13951
14305
  assertNotAborted,
13952
14306
  // Local staging can use links; distributed plan() alone requires real copies.
13953
14307
  materializeSymlinks: false,
14308
+ deferRangeExtraction: windowVideoFrames ? true : void 0,
13954
14309
  onProgress
13955
14310
  })
13956
14311
  );
@@ -14047,7 +14402,7 @@ async function executeRenderPipeline(input) {
14047
14402
  try {
14048
14403
  fileServer = await createFileServer({
14049
14404
  projectDir,
14050
- compiledDir: join28(workDir, "compiled"),
14405
+ compiledDir: join31(workDir, "compiled"),
14051
14406
  port: 0,
14052
14407
  preHeadScripts: [VIRTUAL_TIME_SHIM],
14053
14408
  fps: job.config.fps
@@ -14065,8 +14420,8 @@ async function executeRenderPipeline(input) {
14065
14420
  if (!activeFileServer) {
14066
14421
  throw new Error("File server failed to initialize before frame capture");
14067
14422
  }
14068
- const framesDir = join28(workDir, "captured-frames");
14069
- if (!existsSync19(framesDir)) mkdirSync10(framesDir, { recursive: true });
14423
+ const framesDir = join31(workDir, "captured-frames");
14424
+ if (!existsSync20(framesDir)) mkdirSync12(framesDir, { recursive: true });
14070
14425
  updateJobStatus(job, "rendering", "Checking browser GPU", 25, onProgress, {
14071
14426
  code: "start_browsers"
14072
14427
  });
@@ -14144,7 +14499,55 @@ async function executeRenderPipeline(input) {
14144
14499
  });
14145
14500
  const buildPrepCaptureOptions = () => stopPrepSessionOnScriptFailure(buildCaptureOptions());
14146
14501
  const buildWorkerCaptureOptions = () => withScriptFailureStop(buildCaptureOptions(), job, log);
14147
- 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
+ };
14148
14551
  let captureCalibration;
14149
14552
  const htmlInCanvasDetected = compiled.renderModeHints.reasons.some(
14150
14553
  (r) => r.code === "htmlInCanvas"
@@ -14310,32 +14713,41 @@ async function executeRenderPipeline(input) {
14310
14713
  updateJobStatus(job, "rendering", "Measuring capture speed", 25, onProgress, {
14311
14714
  code: "start_browsers"
14312
14715
  });
14313
- const outcome = await observeRenderStage(
14314
- observability,
14315
- "capture_calibration",
14316
- captureStageObservationData({
14317
- forceScreenshot: captureForceScreenshot,
14318
- // Override the default `capturing` — calibration writes probe
14319
- // frames only, not `job.framesRendered`, so heartbeats reporting
14320
- // `framesCompleted: 0` misread as broken. Field signal
14321
- // ts=1784019503.
14322
- stagePhase: "calibrating"
14323
- }),
14324
- () => runCaptureCalibration({
14325
- cfg: calibrationCaptureConfig(cfg),
14326
- fileServer: activeFileServer,
14327
- workDir,
14328
- log,
14329
- job,
14330
- totalFrames,
14331
- forceScreenshot: captureForceScreenshot,
14332
- probeSession,
14333
- buildCaptureOptions: buildPrepCaptureOptions,
14334
- createRenderVideoFrameInjector,
14335
- assertNotAborted
14336
- }),
14337
- { heartbeatMessage: "browser calibrating (frames not started)" }
14338
- );
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
+ }
14339
14751
  captureCalibration = outcome.calibration;
14340
14752
  calibrationEvaluateTimedOut = outcome.calibrationEvaluateTimedOut;
14341
14753
  captureForceScreenshot = outcome.forceScreenshot;
@@ -14567,7 +14979,7 @@ async function executeRenderPipeline(input) {
14567
14979
  hls: ".mp4"
14568
14980
  };
14569
14981
  const videoExt = FORMAT_EXT[outputFormat] ?? ".mp4";
14570
- const videoOnlyPath = join28(workDir, `video-only${videoExt}`);
14982
+ const videoOnlyPath = join31(workDir, `video-only${videoExt}`);
14571
14983
  const usePageSideCompositingForTransitions = (cfg.enablePageSideCompositing || isGif) && compiled.hasShaderTransitions && !hasHdrContent && outputSupportsPageSideShaderCompositing(outputFormat);
14572
14984
  if (usePageSideCompositingForTransitions) {
14573
14985
  activeFileServer.addPreHeadScript(HF_PAGE_SIDE_COMPOSITING_STUB);
@@ -14668,13 +15080,7 @@ async function executeRenderPipeline(input) {
14668
15080
  // excluded routes are the ones whose concat-copy or capture loop the
14669
15081
  // segment contract does not cover (webm VP9 concat is fragile, HDR and
14670
15082
  // shader transitions run their own compositor).
14671
- useSegmentedCapture: shouldSegmentCapture({
14672
- env: process.env,
14673
- durationSeconds: job.duration,
14674
- outputFormat,
14675
- layeredOrEffectRoute: hasHdrContent || compiled.hasShaderTransitions,
14676
- streamingOk: shouldUseStreamingEncode(cfg, outputFormat, 1, job.duration)
14677
- }),
15083
+ useSegmentedCapture: segmentsCapture(hasHdrContent || compiled.hasShaderTransitions),
14678
15084
  useLayeredComposite,
14679
15085
  usePageSideCompositing: usePageSideCompositingForTransitions,
14680
15086
  hasHdrContent,
@@ -14714,10 +15120,14 @@ async function executeRenderPipeline(input) {
14714
15120
  deWorkerInversion,
14715
15121
  deParallelRouter
14716
15122
  });
15123
+ if (videoFrameWindow && capturePlan.kind !== "sdr_segmented")
15124
+ await extractDeferredVideoFrames();
15125
+ updateCaptureObservability({ videoFrameWindow: videoFrameWindow ? "on" : "off" });
14717
15126
  observability.checkpoint("capture_strategy", "resolved", {
14718
15127
  plan: capturePlan.kind,
14719
15128
  workerCount: capturePlan.workerCount,
14720
15129
  forceScreenshot: capturePlan.forceScreenshot,
15130
+ videoFrameWindow: videoFrameWindow ? "on" : "off",
14721
15131
  captureBeyondViewport: resolvedCaptureBeyondViewport ?? null,
14722
15132
  useStreamingEncode: capturePlan.kind === "sdr_streaming",
14723
15133
  useLayeredComposite: capturePlan.kind === "hdr_layered",
@@ -14855,7 +15265,7 @@ async function executeRenderPipeline(input) {
14855
15265
  pageMotionBlur: job.config.pageMotionBlur,
14856
15266
  captureQuality: captureOptions.quality
14857
15267
  });
14858
- const segmentDir = segmentDirFor(join28(projectDir, "renders"), segmentPlanHash);
15268
+ const segmentDir = segmentDirFor(join31(projectDir, "renders"), segmentPlanHash);
14859
15269
  let completedSegments = /* @__PURE__ */ new Set();
14860
15270
  if (job.config.resumeSegments === true) {
14861
15271
  const existing = readSegmentManifest(segmentDir);
@@ -14870,10 +15280,10 @@ async function executeRenderPipeline(input) {
14870
15280
  });
14871
15281
  }
14872
15282
  if (completedSegments.size === 0) {
14873
- rmSync10(segmentDir, { recursive: true, force: true });
15283
+ rmSync12(segmentDir, { recursive: true, force: true });
14874
15284
  }
14875
15285
  } else {
14876
- rmSync10(segmentDir, { recursive: true, force: true });
15286
+ rmSync12(segmentDir, { recursive: true, force: true });
14877
15287
  }
14878
15288
  const emptySegmentManifest = () => ({
14879
15289
  version: 1,
@@ -14923,6 +15333,7 @@ async function executeRenderPipeline(input) {
14923
15333
  },
14924
15334
  buildCaptureOptions: buildWorkerCaptureOptions,
14925
15335
  createRenderVideoFrameInjector,
15336
+ segmentVideoFrames: videoFrameWindow ? buildSegmentVideoFrames() : void 0,
14926
15337
  abortSignal: executionSignal,
14927
15338
  assertNotAborted,
14928
15339
  onProgress,
@@ -14974,7 +15385,7 @@ async function executeRenderPipeline(input) {
14974
15385
  probeSession = null;
14975
15386
  await closeOrphanedProbeForRetry(orphaned, closeCaptureSession8, log, "segmented");
14976
15387
  }
14977
- rmSync10(segmentDir, { recursive: true, force: true });
15388
+ rmSync12(segmentDir, { recursive: true, force: true });
14978
15389
  completedSegments = /* @__PURE__ */ new Set();
14979
15390
  segmentManifest = emptySegmentManifest();
14980
15391
  dedupPerfs.length = 0;
@@ -15013,11 +15424,12 @@ async function executeRenderPipeline(input) {
15013
15424
  );
15014
15425
  updateCaptureObservability({ segmentRetries: segmentedRes.segmentRetries });
15015
15426
  if (job.config.keepSegments !== true) {
15016
- rmSync10(segmentDir, { recursive: true, force: true });
15427
+ rmSync12(segmentDir, { recursive: true, force: true });
15017
15428
  }
15018
15429
  } else {
15019
15430
  capturePlan = replanAfterFailure(capturePlan, { kind: "streaming_unavailable" });
15020
15431
  syncCapturePlan();
15432
+ if (videoFrameWindow) await extractDeferredVideoFrames();
15021
15433
  probeSession = null;
15022
15434
  observability.checkpoint(
15023
15435
  "capture_segmented",
@@ -15288,8 +15700,8 @@ async function executeRenderPipeline(input) {
15288
15700
  "capture_disk",
15289
15701
  "drawElement capture failed; retrying with a fresh screenshot session"
15290
15702
  );
15291
- rmSync10(framesDir, { recursive: true, force: true });
15292
- mkdirSync10(framesDir, { recursive: true });
15703
+ rmSync12(framesDir, { recursive: true, force: true });
15704
+ mkdirSync12(framesDir, { recursive: true });
15293
15705
  resetCaptureAttemptProgress(job);
15294
15706
  dedupPerfs.length = 0;
15295
15707
  cfg.useDrawElement = false;
@@ -15394,7 +15806,7 @@ async function executeRenderPipeline(input) {
15394
15806
  })
15395
15807
  );
15396
15808
  const totalElapsed = Date.now() - pipelineStart;
15397
- const tmpPeakBytes = existsSync19(workDir) ? sampleDirectoryBytes(workDir) : 0;
15809
+ const tmpPeakBytes = existsSync20(workDir) ? sampleDirectoryBytes(workDir) : 0;
15398
15810
  recordTransientRetryObservability();
15399
15811
  observability.checkpoint("pipeline", "artifact validated", { totalElapsedMs: totalElapsed });
15400
15812
  const observabilitySummary = observability.summary({
@@ -15466,7 +15878,7 @@ async function executeRenderPipeline(input) {
15466
15878
  job.perfSummary = perfSummary;
15467
15879
  if (job.config.debug) {
15468
15880
  try {
15469
- writeFileSync6(perfOutputPath, JSON.stringify(perfSummary, null, 2), "utf-8");
15881
+ writeFileSync7(perfOutputPath, JSON.stringify(perfSummary, null, 2), "utf-8");
15470
15882
  } catch (err) {
15471
15883
  log.debug("Failed to write perf summary", {
15472
15884
  perfOutputPath,
@@ -15475,8 +15887,8 @@ async function executeRenderPipeline(input) {
15475
15887
  }
15476
15888
  }
15477
15889
  if (job.config.debug) {
15478
- if (!isPngSequence && !isHls && existsSync19(stagedOutputPath)) {
15479
- const debugOutput = join28(workDir, `output${videoExt}`);
15890
+ if (!isPngSequence && !isHls && existsSync20(stagedOutputPath)) {
15891
+ const debugOutput = join31(workDir, `output${videoExt}`);
15480
15892
  copyFileSync5(stagedOutputPath, debugOutput);
15481
15893
  }
15482
15894
  }
@@ -15590,15 +16002,15 @@ import {
15590
16002
  REFERENCE_BACKGROUND_FIELD as REFERENCE_BACKGROUND_FIELD4,
15591
16003
  referenceBackgroundResult as referenceBackgroundResult6
15592
16004
  } from "@hyperframes/engine";
15593
- import { mkdirSync as mkdirSync18, mkdtempSync as mkdtempSync7, readFileSync as readFileSync14, rmSync as rmSync16 } from "node:fs";
15594
- 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";
15595
16007
  import { tmpdir as tmpdir4 } from "node:os";
15596
16008
 
15597
16009
  // src/services/distributed/mp4Stitch.ts
15598
- 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";
15599
16011
  import { open } from "node:fs/promises";
15600
16012
  import { renameWithRetry as renameWithRetry7 } from "@hyperframes/engine";
15601
- import { dirname as dirname13, join as join29 } from "node:path";
16013
+ import { dirname as dirname14, join as join32 } from "node:path";
15602
16014
  import { finished, pipeline } from "node:stream/promises";
15603
16015
  function boxAt(src, off, at, limit) {
15604
16016
  let size = src.readUInt32BE(off);
@@ -15962,18 +16374,18 @@ async function writeStitchedMp4(chunkPaths, outputPath, audioPath) {
15962
16374
  const sourcePaths = audioPath ? [audioPath, ...chunkPaths] : chunkPaths;
15963
16375
  const layouts = await Promise.all(sourcePaths.map((path) => readMp4Layout(fileRange(path))));
15964
16376
  const header = audioPath ? buildStitchedHeader(layouts.slice(1), layouts[0]) : buildStitchedHeader(layouts);
15965
- mkdirSync11(dirname13(outputPath), { recursive: true });
16377
+ mkdirSync13(dirname14(outputPath), { recursive: true });
15966
16378
  const workDir = mkdtempSync4(`${outputPath}.stitch-`);
15967
- const out = createWriteStream(join29(workDir, "out.mp4"));
16379
+ const out = createWriteStream(join32(workDir, "out.mp4"));
15968
16380
  try {
15969
16381
  out.write(header);
15970
16382
  for (const [i, path] of sourcePaths.entries()) await copyPayload(path, layouts[i], i, out);
15971
16383
  out.end();
15972
16384
  await finished(out);
15973
- renameWithRetry7(join29(workDir, "out.mp4"), outputPath);
16385
+ renameWithRetry7(join32(workDir, "out.mp4"), outputPath);
15974
16386
  } finally {
15975
16387
  out.destroy();
15976
- rmSync11(workDir, { recursive: true, force: true });
16388
+ rmSync13(workDir, { recursive: true, force: true });
15977
16389
  }
15978
16390
  }
15979
16391
 
@@ -15987,13 +16399,13 @@ import { audioFailureDiagnostics as audioFailureDiagnostics3 } from "@hyperframe
15987
16399
  import {
15988
16400
  copyFileSync as copyFileSync6,
15989
16401
  cpSync as cpSync2,
15990
- existsSync as existsSync24,
16402
+ existsSync as existsSync25,
15991
16403
  linkSync,
15992
- mkdirSync as mkdirSync14,
15993
- rmSync as rmSync12,
15994
- writeFileSync as writeFileSync9
16404
+ mkdirSync as mkdirSync16,
16405
+ rmSync as rmSync14,
16406
+ writeFileSync as writeFileSync10
15995
16407
  } from "node:fs";
15996
- 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";
15997
16409
  import {
15998
16410
  fpsToNumber as fpsToNumber9,
15999
16411
  HF_COLOR_GRADING_ATTR as HF_COLOR_GRADING_ATTR2
@@ -16009,28 +16421,26 @@ import {
16009
16421
  } from "@hyperframes/engine";
16010
16422
 
16011
16423
  // src/services/distributed/planFrameCost.ts
16012
- import { join as join32 } from "node:path";
16424
+ import { join as join35 } from "node:path";
16013
16425
  import {
16014
16426
  closeCaptureSession as closeCaptureSession9,
16015
16427
  createCaptureSession as createCaptureSession9,
16016
- createFrameLookupTable as createFrameLookupTable3,
16428
+ createFrameLookupTable as createFrameLookupTable4,
16017
16429
  initializeSession as initializeSession9
16018
16430
  } from "@hyperframes/engine";
16019
16431
 
16020
16432
  // src/services/distributed/chunkFrames.ts
16021
- import { existsSync as existsSync21, readdirSync as readdirSync6 } from "node:fs";
16022
- 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";
16023
16435
  import {
16024
16436
  compositionRequiresWebGpu as compositionRequiresWebGpu3,
16025
- createFrameLookupTable as createFrameLookupTable2,
16026
- createVideoFrameInjector as createVideoFrameInjector2,
16027
- extractVideoFramesRange
16437
+ createVideoFrameInjector as createVideoFrameInjector3
16028
16438
  } from "@hyperframes/engine";
16029
16439
 
16030
16440
  // src/services/distributed/shared.ts
16031
16441
  import { execFile as execFileCallback } from "node:child_process";
16032
- import { dirname as dirname14, join as join30 } from "node:path";
16033
- import { existsSync as existsSync20, readFileSync as readFileSync9 } from "node:fs";
16442
+ import { dirname as dirname15, join as join33 } from "node:path";
16443
+ import { existsSync as existsSync21, readFileSync as readFileSync9 } from "node:fs";
16034
16444
  import { fileURLToPath as fileURLToPath5 } from "node:url";
16035
16445
  import { promisify as promisify2 } from "node:util";
16036
16446
  import {
@@ -16045,8 +16455,8 @@ function isPlanAudioArtifactPath(path) {
16045
16455
  }
16046
16456
  function resolvePlanAudioPath(planDir) {
16047
16457
  for (const name of [PLAN_AUDIO_RELATIVE_PATH, PLAN_AUDIO_LEGACY_RELATIVE_PATH]) {
16048
- const candidate = join30(planDir, name);
16049
- if (existsSync20(candidate)) return candidate;
16458
+ const candidate = join33(planDir, name);
16459
+ if (existsSync21(candidate)) return candidate;
16050
16460
  }
16051
16461
  return null;
16052
16462
  }
@@ -16303,11 +16713,11 @@ function buildSyntheticRenderJob(input) {
16303
16713
  var cachedProducerVersion = null;
16304
16714
  function readProducerVersion() {
16305
16715
  if (cachedProducerVersion !== null) return cachedProducerVersion;
16306
- const startDir = dirname14(fileURLToPath5(import.meta.url));
16716
+ const startDir = dirname15(fileURLToPath5(import.meta.url));
16307
16717
  let current = startDir;
16308
16718
  for (let i = 0; i < 10; i++) {
16309
- const candidate = join30(current, "package.json");
16310
- if (existsSync20(candidate)) {
16719
+ const candidate = join33(current, "package.json");
16720
+ if (existsSync21(candidate)) {
16311
16721
  try {
16312
16722
  const pkg = JSON.parse(readFileSync9(candidate, "utf-8"));
16313
16723
  if (pkg.name === "@hyperframes/producer" && typeof pkg.version === "string") {
@@ -16317,7 +16727,7 @@ function readProducerVersion() {
16317
16727
  } catch {
16318
16728
  }
16319
16729
  }
16320
- const parent = dirname14(current);
16730
+ const parent = dirname15(current);
16321
16731
  if (parent === current) break;
16322
16732
  current = parent;
16323
16733
  }
@@ -16342,11 +16752,11 @@ var RenderChunkValidationError = class extends Error {
16342
16752
  };
16343
16753
  function createChunkVideoFrameInjectorFactory(frameLookup, options) {
16344
16754
  if (!(frameLookup instanceof Promise))
16345
- return () => createVideoFrameInjector2(frameLookup, options);
16755
+ return () => createVideoFrameInjector3(frameLookup, options);
16346
16756
  return () => {
16347
16757
  let hook;
16348
16758
  return async (page, time, heldVideoTime) => {
16349
- hook ??= createVideoFrameInjector2(await frameLookup, options);
16759
+ hook ??= createVideoFrameInjector3(await frameLookup, options);
16350
16760
  await hook?.(page, time, heldVideoTime);
16351
16761
  };
16352
16762
  };
@@ -16360,14 +16770,14 @@ function frameNumberFromFileName(name) {
16360
16770
  function rebuildExtractedFramesFromPlanDir(planDir, videos, indexMode = "dense-v1") {
16361
16771
  const result = [];
16362
16772
  for (const v of videos) {
16363
- const outputDir = join31(planDir, "video-frames", v.videoId);
16364
- if (!existsSync21(outputDir)) {
16773
+ const outputDir = join34(planDir, "video-frames", v.videoId);
16774
+ if (!existsSync22(outputDir)) {
16365
16775
  throw new Error(
16366
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.`
16367
16777
  );
16368
16778
  }
16369
16779
  const ext = (extname4(v.framePattern) || ".jpg").toLowerCase();
16370
- 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) => {
16371
16781
  const leftNumber = frameNumberFromFileName(left);
16372
16782
  const rightNumber = frameNumberFromFileName(right);
16373
16783
  if (leftNumber === null || rightNumber === null) return left.localeCompare(right);
@@ -16379,7 +16789,7 @@ function rebuildExtractedFramesFromPlanDir(planDir, videos, indexMode = "dense-v
16379
16789
  if (!frameName) continue;
16380
16790
  const frameNumber = indexMode === "sparse-v2" ? frameNumberFromFileName(frameName) : null;
16381
16791
  const frameIndex = frameNumber === null ? i : frameNumber - 1;
16382
- framePaths.set(frameIndex, join31(outputDir, frameName));
16792
+ framePaths.set(frameIndex, join34(outputDir, frameName));
16383
16793
  }
16384
16794
  result.push({
16385
16795
  videoId: v.videoId,
@@ -16396,15 +16806,6 @@ function rebuildExtractedFramesFromPlanDir(planDir, videos, indexMode = "dense-v
16396
16806
  }
16397
16807
  return result;
16398
16808
  }
16399
- function contiguousRuns(sortedIndexes) {
16400
- const runs2 = [];
16401
- for (const index of sortedIndexes) {
16402
- const last = runs2[runs2.length - 1];
16403
- if (last && last.firstFrame + last.frames === index) last.frames++;
16404
- else runs2.push({ firstFrame: index, frames: 1 });
16405
- }
16406
- return runs2;
16407
- }
16408
16809
  function buildChunkCaptureOptions(input) {
16409
16810
  const { dimensions, encoder } = input;
16410
16811
  const videoCaptureBeyondViewport = resolveVideoCaptureBeyondViewport(input.videoCount);
@@ -16427,28 +16828,19 @@ function buildChunkCaptureOptions(input) {
16427
16828
  requiresWebGpu: compositionRequiresWebGpu3(input.html)
16428
16829
  };
16429
16830
  }
16430
- function shownDeferredFrameIndexes(planVideos, frames, fps) {
16431
- const shown = /* @__PURE__ */ new Map();
16432
- const deferred = planVideos.extracted.filter((video) => video.deferredRange);
16433
- if (deferred.length === 0) return shown;
16434
- const table = createFrameLookupTable2(
16435
- planVideos.videos,
16436
- deferred.map((video) => ({
16437
- ...video,
16438
- outputDir: "",
16439
- framePaths: new Map(Array.from({ length: video.totalFrames }, (_, i) => [i, String(i)]))
16440
- })),
16441
- 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
16442
16843
  );
16443
- for (const frame of frames) {
16444
- const globalTime = frame * fps.den / fps.num;
16445
- for (const [videoId, payload] of table.getActiveFramePayloads(globalTime)) {
16446
- const indexes = shown.get(videoId) ?? /* @__PURE__ */ new Set();
16447
- indexes.add(payload.frameIndex);
16448
- shown.set(videoId, indexes);
16449
- }
16450
- }
16451
- return shown;
16452
16844
  }
16453
16845
  async function extractDeferredVideoFramesForChunk(input) {
16454
16846
  const deferred = input.planVideos.extracted.filter((video) => video.deferredRange);
@@ -16465,49 +16857,32 @@ async function extractDeferredVideoFramesForChunk(input) {
16465
16857
  url: remoteSources[path].split("?")[0]
16466
16858
  });
16467
16859
  }
16468
- if (deferred.length === 0) return [];
16469
- const shown = shownDeferredFrameIndexes(input.planVideos, input.frames, input.fps);
16470
- return Promise.all(
16471
- deferred.map(async (video) => {
16472
- const outputDir = join31(input.outputRoot, video.videoId);
16473
- const indexes = [...shown.get(video.videoId) ?? []].sort((a, b) => a - b);
16474
- if (indexes.length === 0) {
16475
- return { ...video, outputDir, framePaths: /* @__PURE__ */ new Map(), ownedByLookup: false };
16476
- }
16477
- 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;
16478
16871
  const remoteUrl = remoteSources[range.sourcePath];
16479
- const sourcePath = remoteUrl ?? join31(input.planDir, ...range.sourcePath.split("/"));
16480
- if (remoteUrl === void 0 && !existsSync21(sourcePath)) {
16872
+ const sourcePath = remoteUrl ?? join34(input.planDir, ...range.sourcePath.split("/"));
16873
+ if (remoteUrl === void 0 && !existsSync22(sourcePath)) {
16481
16874
  throw new RenderChunkValidationError(
16482
16875
  MISSING_PLAN_ARTIFACT,
16483
16876
  `[renderChunk] planDir is missing the source for video ${JSON.stringify(video.videoId)}: ${range.sourcePath}`
16484
16877
  );
16485
16878
  }
16486
- const extracted = await extractVideoFramesRange(
16487
- sourcePath,
16488
- video.videoId,
16489
- range.startTime,
16490
- range.durationSeconds,
16491
- {
16492
- fps: input.fps,
16493
- outputDir: input.outputRoot,
16494
- format: range.format,
16495
- frameRanges: contiguousRuns(indexes),
16496
- shownBox: input.shownBoxes?.get(video.videoId)
16497
- },
16498
- void 0,
16499
- input.cfg
16500
- );
16501
- const missing = indexes.find((index) => !extracted.framePaths.has(index));
16502
- if (missing !== void 0) {
16503
- throw new RenderChunkValidationError(
16504
- INVALID_VIDEO_METADATA,
16505
- `[renderChunk] video ${JSON.stringify(video.videoId)} has no frame ${missing}; the plan expected ${video.totalFrames} frames from ${range.sourcePath}.`
16506
- );
16507
- }
16508
- return { ...video, outputDir, framePaths: extracted.framePaths, ownedByLookup: false };
16509
- })
16510
- );
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
+ });
16511
16886
  }
16512
16887
 
16513
16888
  // src/services/distributed/planFrameCost.ts
@@ -16525,12 +16900,12 @@ async function measurePlanSecondsPerFrame(input) {
16525
16900
  planVideos,
16526
16901
  frames: sampled,
16527
16902
  fps: fpsRatio,
16528
- outputRoot: join32(workDir, "frame-cost-video-frames"),
16903
+ outputRoot: join35(workDir, "frame-cost-video-frames"),
16529
16904
  cfg: input.cfg
16530
16905
  })
16531
16906
  ];
16532
- const lookup = extracted.length > 0 ? createFrameLookupTable3(planVideos.videos, extracted, resolveRenderFpsConfig(fpsRatio).value) : null;
16533
- 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");
16534
16909
  const fileServer = await createFileServer({
16535
16910
  projectDir: compiledDir,
16536
16911
  compiledDir,
@@ -16542,7 +16917,7 @@ async function measurePlanSecondsPerFrame(input) {
16542
16917
  try {
16543
16918
  session = await createCaptureSession9(
16544
16919
  fileServer.url,
16545
- join32(workDir, "frame-cost"),
16920
+ join35(workDir, "frame-cost"),
16546
16921
  buildChunkCaptureOptions({
16547
16922
  dimensions: { ...input, fpsNum: fps, fpsDen: 1 },
16548
16923
  encoder: input,
@@ -16639,7 +17014,7 @@ function buildWeightedChunkSlices(weights, chunkCount, minFrames, maxFrames) {
16639
17014
  }
16640
17015
 
16641
17016
  // src/services/render/planReferenceBackground.ts
16642
- import { join as join33 } from "node:path";
17017
+ import { join as join36 } from "node:path";
16643
17018
  import {
16644
17019
  closeCaptureSession as closeCaptureSession10,
16645
17020
  createCaptureSession as createCaptureSession10,
@@ -16652,14 +17027,14 @@ async function readPlanReferenceBackground(input) {
16652
17027
  try {
16653
17028
  server = await createFileServer({
16654
17029
  projectDir: input.projectDir,
16655
- compiledDir: join33(input.workDir, "compiled"),
17030
+ compiledDir: join36(input.workDir, "compiled"),
16656
17031
  port: 0,
16657
17032
  preHeadScripts: [VIRTUAL_TIME_SHIM],
16658
17033
  fps: input.options.fps
16659
17034
  });
16660
17035
  session = await createCaptureSession10(
16661
17036
  server.url,
16662
- join33(input.workDir, "reference"),
17037
+ join36(input.workDir, "reference"),
16663
17038
  {
16664
17039
  ...input.options,
16665
17040
  requiresWebGpu: false
@@ -16695,8 +17070,8 @@ async function readPlanReferenceBackground(input) {
16695
17070
  }
16696
17071
 
16697
17072
  // src/services/render/stages/freezePlan.ts
16698
- import { existsSync as existsSync22, mkdirSync as mkdirSync12, readFileSync as readFileSync10, readdirSync as readdirSync7, writeFileSync as writeFileSync7 } from "node:fs";
16699
- 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";
16700
17075
  import {
16701
17076
  REFERENCE_BACKGROUND_FIELD,
16702
17077
  referenceBackgroundResult as referenceBackgroundResult3
@@ -16889,7 +17264,7 @@ function planFileSha256(absolutePath, digests) {
16889
17264
  return sha256;
16890
17265
  }
16891
17266
  async function prehashPlanFiles(dir, digests) {
16892
- if (!existsSync22(dir)) return;
17267
+ if (!existsSync23(dir)) return;
16893
17268
  for (const { absolutePath } of listPlanFiles(dir)) {
16894
17269
  if (!digests.has(absolutePath)) digests.set(absolutePath, await sha256FileAsync(absolutePath));
16895
17270
  }
@@ -16912,9 +17287,9 @@ function listPlanFiles(planDir) {
16912
17287
  const results = [];
16913
17288
  const rootResolved = resolve9(planDir);
16914
17289
  function walk(dir) {
16915
- const entries = readdirSync7(dir, { withFileTypes: true });
17290
+ const entries = readdirSync8(dir, { withFileTypes: true });
16916
17291
  for (const entry of entries) {
16917
- const full = join34(dir, entry.name);
17292
+ const full = join37(dir, entry.name);
16918
17293
  if (entry.isDirectory()) {
16919
17294
  walk(full);
16920
17295
  } else if (entry.isFile()) {
@@ -16955,19 +17330,19 @@ var PLAN_REMOTE_FILES_RELATIVE_PATH = "meta/remote-files.json";
16955
17330
  function writePlanRemoteFiles(planDir, files) {
16956
17331
  if (files.length === 0) return;
16957
17332
  const sorted = [...files].sort((a, b) => a.path < b.path ? -1 : 1);
16958
- writeFileSync7(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");
16959
17334
  }
16960
17335
  function readPlanRemoteFiles(planDir) {
16961
- const path = join34(planDir, PLAN_REMOTE_FILES_RELATIVE_PATH);
16962
- return existsSync22(path) ? JSON.parse(readFileSync10(path, "utf-8")) : [];
17336
+ const path = join37(planDir, PLAN_REMOTE_FILES_RELATIVE_PATH);
17337
+ return existsSync23(path) ? JSON.parse(readFileSync10(path, "utf-8")) : [];
16963
17338
  }
16964
17339
  function recomputePlanHashFromPlanDir(planDir, digests) {
16965
- const planJsonPath = join34(planDir, "plan.json");
16966
- const encoderJsonPath = join34(planDir, "meta", "encoder.json");
16967
- if (!existsSync22(planJsonPath)) {
17340
+ const planJsonPath = join37(planDir, "plan.json");
17341
+ const encoderJsonPath = join37(planDir, "meta", "encoder.json");
17342
+ if (!existsSync23(planJsonPath)) {
16968
17343
  throw new Error(`[freezePlan] plan.json missing: ${planJsonPath}`);
16969
17344
  }
16970
- if (!existsSync22(encoderJsonPath)) {
17345
+ if (!existsSync23(encoderJsonPath)) {
16971
17346
  throw new Error(`[freezePlan] meta/encoder.json missing: ${encoderJsonPath}`);
16972
17347
  }
16973
17348
  const planJson = JSON.parse(readFileSync10(planJsonPath, "utf-8"));
@@ -16997,21 +17372,21 @@ async function freezePlan(input) {
16997
17372
  hasAudio,
16998
17373
  digests
16999
17374
  } = input;
17000
- if (!existsSync22(planDir)) {
17375
+ if (!existsSync23(planDir)) {
17001
17376
  throw new Error(`[freezePlan] planDir does not exist: ${planDir}`);
17002
17377
  }
17003
- const metaDir = join34(planDir, "meta");
17004
- if (!existsSync22(metaDir)) mkdirSync12(metaDir, { recursive: true });
17005
- writeFileSync7(
17006
- join34(metaDir, "composition.json"),
17378
+ const metaDir = join37(planDir, "meta");
17379
+ if (!existsSync23(metaDir)) mkdirSync14(metaDir, { recursive: true });
17380
+ writeFileSync8(
17381
+ join37(metaDir, "composition.json"),
17007
17382
  `${JSON.stringify(composition, null, 2)}
17008
17383
  `,
17009
17384
  "utf-8"
17010
17385
  );
17011
17386
  const encoderForCanonical = stripUndefined(encoder);
17012
17387
  const encoderConfigCanonicalJson = canonicalJsonStringify(encoderForCanonical);
17013
- writeFileSync7(join34(metaDir, "encoder.json"), encoderConfigCanonicalJson, "utf-8");
17014
- writeFileSync7(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)}
17015
17390
  `, "utf-8");
17016
17391
  const { compositionHtml, assets } = collectPlanAssetShas(planDir, digests);
17017
17392
  const planHash = computePlanHash({
@@ -17036,8 +17411,8 @@ async function freezePlan(input) {
17036
17411
  duration: durationSeconds,
17037
17412
  hasAudio
17038
17413
  };
17039
- const planJsonPath = join34(planDir, "plan.json");
17040
- writeFileSync7(planJsonPath, `${JSON.stringify(planJson, null, 2)}
17414
+ const planJsonPath = join37(planDir, "plan.json");
17415
+ writeFileSync8(planJsonPath, `${JSON.stringify(planJson, null, 2)}
17041
17416
  `, "utf-8");
17042
17417
  return { planJsonPath, planHash };
17043
17418
  }
@@ -17078,9 +17453,9 @@ function applyRuntimeEnvSnapshot(snapshot, env = process.env) {
17078
17453
  }
17079
17454
 
17080
17455
  // src/services/distributed/chunkAudio.ts
17081
- import { closeSync as closeSync6, existsSync as existsSync23, ftruncateSync, mkdirSync as mkdirSync13, openSync as openSync6, readFileSync as readFileSync11 } from "node:fs";
17082
- import { writeFileSync as writeFileSync8, writeSync } from "node:fs";
17083
- import { join as join35 } from "node:path";
17456
+ import { closeSync as closeSync6, existsSync as existsSync24, ftruncateSync, mkdirSync as mkdirSync15, openSync as openSync6, readFileSync as readFileSync11 } from "node:fs";
17457
+ import { writeFileSync as writeFileSync9, writeSync } from "node:fs";
17458
+ import { join as join38 } from "node:path";
17084
17459
  import { appendRenderProvenanceArgs, describeFfmpegFailure as describeFfmpegFailure4, runFfmpeg as runFfmpeg7 } from "@hyperframes/engine";
17085
17460
  function audioCutForChunk(grid, chunks, index, fps) {
17086
17461
  const firstPacketAt = (frame) => Math.min(
@@ -17168,7 +17543,7 @@ async function writeChunkAudioSlices(input) {
17168
17543
  });
17169
17544
  const bytes = readFileSync11(input.audioPath);
17170
17545
  const layout = { sizeBytes: bytes.length, payloadStart, payloadEnd, partStarts: {} };
17171
- mkdirSync13(join35(input.planDir, SLICE_DIR), { recursive: true });
17546
+ mkdirSync15(join38(input.planDir, SLICE_DIR), { recursive: true });
17172
17547
  input.chunks.forEach((_, index) => {
17173
17548
  const cut = audioCutForChunk(grid, input.chunks, index, input.fps);
17174
17549
  if (!cut) return;
@@ -17176,17 +17551,17 @@ async function writeChunkAudioSlices(input) {
17176
17551
  const start = offsets[cut.firstPacket];
17177
17552
  layout.partStarts[index] = start;
17178
17553
  const end = offsets[last] + track.sizes[last];
17179
- writeFileSync8(join35(input.planDir, chunkAudioSlicePath(index)), bytes.subarray(start, end));
17554
+ writeFileSync9(join38(input.planDir, chunkAudioSlicePath(index)), bytes.subarray(start, end));
17180
17555
  });
17181
17556
  const frame = [bytes.subarray(0, payloadStart), bytes.subarray(payloadEnd)];
17182
- writeFileSync8(join35(input.planDir, SLICE_FRAME), Buffer.concat(frame));
17183
- writeFileSync8(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));
17184
17559
  }
17185
17560
  function chunkAudioSource(planDir, chunkIndex, scratchPath) {
17186
- const layoutPath = join35(planDir, SLICE_LAYOUT);
17187
- if (!existsSync23(layoutPath)) return resolvePlanAudioPath(planDir);
17561
+ const layoutPath = join38(planDir, SLICE_LAYOUT);
17562
+ if (!existsSync24(layoutPath)) return resolvePlanAudioPath(planDir);
17188
17563
  const layout = JSON.parse(readFileSync11(layoutPath, "utf-8"));
17189
- const frame = readFileSync11(join35(planDir, SLICE_FRAME));
17564
+ const frame = readFileSync11(join38(planDir, SLICE_FRAME));
17190
17565
  const fd = openSync6(scratchPath, "w");
17191
17566
  try {
17192
17567
  writeSync(fd, frame, 0, layout.payloadStart, 0);
@@ -17199,7 +17574,7 @@ function chunkAudioSource(planDir, chunkIndex, scratchPath) {
17199
17574
  );
17200
17575
  const start = layout.partStarts[chunkIndex];
17201
17576
  if (start !== void 0) {
17202
- const part = readFileSync11(join35(planDir, chunkAudioSlicePath(chunkIndex)));
17577
+ const part = readFileSync11(join38(planDir, chunkAudioSlicePath(chunkIndex)));
17203
17578
  writeSync(fd, part, 0, part.length, start);
17204
17579
  }
17205
17580
  ftruncateSync(fd, layout.sizeBytes);
@@ -17254,8 +17629,8 @@ async function muxChunkWithAudio(input) {
17254
17629
 
17255
17630
  // src/services/distributed/planSize.ts
17256
17631
  import { createHash as createHash9 } from "node:crypto";
17257
- import { lstatSync as lstatSync2, readdirSync as readdirSync8 } from "node:fs";
17258
- 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";
17259
17634
  var MAX_TOP_COMPONENTS = 10;
17260
17635
  var SOURCE_MEDIA_EXTENSIONS = /* @__PURE__ */ new Set([
17261
17636
  ".aac",
@@ -17362,12 +17737,12 @@ function recordFile(result, components, rootDir, rootKind, filePath) {
17362
17737
  function walkRegularFiles(dir, visit) {
17363
17738
  let entries;
17364
17739
  try {
17365
- entries = readdirSync8(dir, { withFileTypes: true });
17740
+ entries = readdirSync9(dir, { withFileTypes: true });
17366
17741
  } catch {
17367
17742
  return;
17368
17743
  }
17369
17744
  for (const entry of entries) {
17370
- const full = join36(dir, entry.name);
17745
+ const full = join39(dir, entry.name);
17371
17746
  if (entry.isDirectory()) {
17372
17747
  walkRegularFiles(full, visit);
17373
17748
  } else if (entry.isFile()) {
@@ -17579,13 +17954,13 @@ function assertPositiveInteger2(name, value) {
17579
17954
  }
17580
17955
  var FREEZE_OWNED_PLAN_FILES = [
17581
17956
  "plan.json",
17582
- join37("meta", "composition.json"),
17583
- join37("meta", "encoder.json"),
17584
- join37("meta", "chunks.json")
17957
+ join40("meta", "composition.json"),
17958
+ join40("meta", "encoder.json"),
17959
+ join40("meta", "chunks.json")
17585
17960
  ];
17586
17961
  function removeFreezeOwnedPlanFiles(planDir) {
17587
17962
  for (const relativePath of FREEZE_OWNED_PLAN_FILES) {
17588
- rmSync12(join37(planDir, relativePath), { force: true });
17963
+ rmSync14(join40(planDir, relativePath), { force: true });
17589
17964
  }
17590
17965
  }
17591
17966
  function buildChunkSlices(totalFrames, chunkCount, effectiveChunkSize) {
@@ -17740,19 +18115,19 @@ function createVideoSourceShipper(input) {
17740
18115
  const known = shipped.get(srcPath);
17741
18116
  if (known) return known;
17742
18117
  const stagedRel = pathInside(input.workVideoFramesDir, srcPath);
17743
- const current = stagedRel === null ? srcPath : join37(input.planDir, "video-frames", stagedRel);
18118
+ const current = stagedRel === null ? srcPath : join40(input.planDir, "video-frames", stagedRel);
17744
18119
  let result = null;
17745
18120
  for (const root of stagedRel === null ? [input.workCompiledDir, input.projectDir] : []) {
17746
18121
  const rel = pathInside(root, srcPath);
17747
- if (rel !== null && (existsSync24(join37(input.planDir, "compiled", rel)) || input.isRemote(srcPath))) {
18122
+ if (rel !== null && (existsSync25(join40(input.planDir, "compiled", rel)) || input.isRemote(srcPath))) {
17748
18123
  result = ["compiled", ...rel.split(sep2)].join("/");
17749
18124
  break;
17750
18125
  }
17751
18126
  }
17752
18127
  if (result === null) {
17753
18128
  result = `video-sources/${shipped.size}${extname6(current)}`;
17754
- const destination = join37(input.planDir, ...result.split("/"));
17755
- 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 });
17756
18131
  try {
17757
18132
  linkSync(current, destination);
17758
18133
  } catch {
@@ -17772,7 +18147,7 @@ async function compilePlanComposition(projectDir, config, planDir, options) {
17772
18147
  useGpu: false,
17773
18148
  browserGpuMode: "software"
17774
18149
  });
17775
- if (!existsSync24(planDir)) mkdirSync14(planDir, { recursive: true });
18150
+ if (!existsSync25(planDir)) mkdirSync16(planDir, { recursive: true });
17776
18151
  const log = config.logger ?? defaultLogger;
17777
18152
  const sizeLimitBytes = options.executionPlanSizeLimitBytes ?? config.planDirSizeLimitBytes ?? PLAN_DIR_SIZE_LIMIT_BYTES;
17778
18153
  const abortSignal = config.abortSignal;
@@ -17801,15 +18176,15 @@ async function compilePlanComposition(projectDir, config, planDir, options) {
17801
18176
  variables: config.variables
17802
18177
  });
17803
18178
  const entryFile = config.entryFile ?? "index.html";
17804
- const htmlPath = join37(projectDir, entryFile);
17805
- if (!existsSync24(htmlPath)) {
18179
+ const htmlPath = join40(projectDir, entryFile);
18180
+ if (!existsSync25(htmlPath)) {
17806
18181
  throw new Error(`[plan] entry file not found: ${htmlPath}`);
17807
18182
  }
17808
- const workDir = join37(planDir, ".plan-work");
17809
- rmSync12(workDir, { recursive: true, force: true });
17810
- mkdirSync14(workDir, { recursive: true });
17811
- const compiledDir = join37(workDir, "compiled");
17812
- 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 });
17813
18188
  let stageStarted = Date.now();
17814
18189
  cpSync2(projectDir, compiledDir, {
17815
18190
  recursive: true,
@@ -17822,10 +18197,10 @@ async function compilePlanComposition(projectDir, config, planDir, options) {
17822
18197
  }
17823
18198
  });
17824
18199
  logPlanStage(log, "copy", stageStarted);
17825
- const finalCompiledDir = join37(planDir, "compiled");
18200
+ const finalCompiledDir = join40(planDir, "compiled");
17826
18201
  const needsAlpha = outputNeedsAlpha(config.format);
17827
18202
  const remoteFiles = Object.entries(options.remoteFiles ?? {});
17828
- const remoteLocalPath = (rel) => join37(projectDir, ...rel.split("/"));
18203
+ const remoteLocalPath = (rel) => join40(projectDir, ...rel.split("/"));
17829
18204
  for (const [, file] of remoteFiles) {
17830
18205
  declareContentAddressedUrl(file.url, file.sha256);
17831
18206
  if (file.probeFactsUrl) declareProbeFactsUrl(file.url, file.probeFactsUrl);
@@ -17846,7 +18221,6 @@ async function compilePlanComposition(projectDir, config, planDir, options) {
17846
18221
  urlByAbsentLocalPath,
17847
18222
  projectDir,
17848
18223
  workDir,
17849
- htmlPath,
17850
18224
  entryFile,
17851
18225
  job,
17852
18226
  cfg,
@@ -18029,17 +18403,17 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
18029
18403
  let stageStarted = Date.now();
18030
18404
  const extractResult = await extractPlanVideos(c, options.deferVideoExtraction);
18031
18405
  logPlanStage(log, "extract", stageStarted);
18032
- const stagedVideoFrames = join37(compiledDir, "__hyperframes_video_frames");
18033
- const videoFramesDst = join37(planDir, "video-frames");
18034
- if (existsSync24(videoFramesDst)) rmSync12(videoFramesDst, { recursive: true, force: true });
18035
- const videoSourcesDir = join37(planDir, "video-sources");
18036
- rmSync12(videoSourcesDir, { recursive: true, force: true });
18037
- if (existsSync24(stagedVideoFrames)) {
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 });
18411
+ if (existsSync25(stagedVideoFrames)) {
18038
18412
  renameWithRetry8(stagedVideoFrames, videoFramesDst);
18039
18413
  } else {
18040
- mkdirSync14(videoFramesDst, { recursive: true });
18414
+ mkdirSync16(videoFramesDst, { recursive: true });
18041
18415
  }
18042
- if (existsSync24(finalCompiledDir)) rmSync12(finalCompiledDir, { recursive: true, force: true });
18416
+ if (existsSync25(finalCompiledDir)) rmSync14(finalCompiledDir, { recursive: true, force: true });
18043
18417
  renameWithRetry8(compiledDir, finalCompiledDir);
18044
18418
  const shipVideoSource = createVideoSourceShipper({
18045
18419
  planDir,
@@ -18069,7 +18443,7 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
18069
18443
  };
18070
18444
  })
18071
18445
  });
18072
- mkdirSync14(join37(planDir, "meta"), { recursive: true });
18446
+ mkdirSync16(join40(planDir, "meta"), { recursive: true });
18073
18447
  writePlanRemoteFiles(
18074
18448
  planDir,
18075
18449
  remoteFiles.map(([rel, { sha256, sizeBytes }]) => ({
@@ -18078,8 +18452,8 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
18078
18452
  sizeBytes
18079
18453
  }))
18080
18454
  );
18081
- writeFileSync9(
18082
- join37(planDir, PLAN_VIDEOS_META_RELATIVE_PATH),
18455
+ writeFileSync10(
18456
+ join40(planDir, PLAN_VIDEOS_META_RELATIVE_PATH),
18083
18457
  JSON.stringify(planVideosJson, null, 2),
18084
18458
  "utf-8"
18085
18459
  );
@@ -18158,11 +18532,11 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
18158
18532
  if (audioResult?.audioError) {
18159
18533
  applyDistributedAudioWarningPolicy(job, audioResult.audioError, audioResult.audioFailures, log);
18160
18534
  }
18161
- const planAudioPath = join37(planDir, PLAN_AUDIO_RELATIVE_PATH);
18162
- if (audioResult?.hasAudio && existsSync24(audioResult.audioOutputPath)) {
18535
+ const planAudioPath = join40(planDir, PLAN_AUDIO_RELATIVE_PATH);
18536
+ if (audioResult?.hasAudio && existsSync25(audioResult.audioOutputPath)) {
18163
18537
  renameWithRetry8(audioResult.audioOutputPath, planAudioPath);
18164
18538
  }
18165
- if (chunksCarryAudio && existsSync24(planAudioPath)) {
18539
+ if (chunksCarryAudio && existsSync25(planAudioPath)) {
18166
18540
  if (options.sliceChunkAudio) {
18167
18541
  stageStarted = Date.now();
18168
18542
  await writeChunkAudioSlices({
@@ -18208,7 +18582,7 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
18208
18582
  format: config.format
18209
18583
  };
18210
18584
  try {
18211
- rmSync12(workDir, { recursive: true, force: true });
18585
+ rmSync14(workDir, { recursive: true, force: true });
18212
18586
  } catch (err) {
18213
18587
  log.warn("[plan] failed to remove temp work dir", {
18214
18588
  workDir,
@@ -18280,9 +18654,9 @@ async function plan(projectDir, config, planDir, options = {}) {
18280
18654
 
18281
18655
  // src/services/distributed/renderChunk.ts
18282
18656
  import { randomBytes } from "node:crypto";
18283
- import { existsSync as existsSync27, mkdirSync as mkdirSync17, readFileSync as readFileSync13, readdirSync as readdirSync10, rmSync as rmSync15, writeFileSync as writeFileSync12 } 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";
18284
18658
  import { cpus as cpus3 } from "node:os";
18285
- import { join as join41 } from "node:path";
18659
+ import { join as join44 } from "node:path";
18286
18660
  import {
18287
18661
  assertSwiftShader,
18288
18662
  BROWSER_GPU_NOT_SOFTWARE as BROWSER_GPU_NOT_SOFTWARE4,
@@ -18290,7 +18664,7 @@ import {
18290
18664
  classifyCaptureFailure as classifyCaptureFailure4,
18291
18665
  closeCaptureSession as closeCaptureSession11,
18292
18666
  createCaptureSession as createCaptureSession11,
18293
- createFrameLookupTable as createFrameLookupTable5,
18667
+ createFrameLookupTable as createFrameLookupTable6,
18294
18668
  deriveBeginFrameProbeTimeTicks as deriveBeginFrameProbeTimeTicks2,
18295
18669
  getEncoderPreset as getEncoderPreset5,
18296
18670
  initializeSession as initializeSession10,
@@ -18307,34 +18681,34 @@ import {
18307
18681
  } from "@hyperframes/engine";
18308
18682
  import {
18309
18683
  copyFileSync as copyFileSync8,
18310
- existsSync as existsSync26,
18684
+ existsSync as existsSync27,
18311
18685
  lstatSync as lstatSync3,
18312
- mkdirSync as mkdirSync16,
18686
+ mkdirSync as mkdirSync18,
18313
18687
  mkdtempSync as mkdtempSync6,
18314
18688
  readFileSync as readFileSync12,
18315
- readdirSync as readdirSync9,
18316
- rmSync as rmSync14,
18317
- statSync as statSync10,
18318
- writeFileSync as writeFileSync11
18689
+ readdirSync as readdirSync10,
18690
+ rmSync as rmSync16,
18691
+ statSync as statSync11,
18692
+ writeFileSync as writeFileSync12
18319
18693
  } from "node:fs";
18320
18694
  import { tmpdir as tmpdir3 } from "node:os";
18321
- 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";
18322
18696
  import { DISTRIBUTED_FPS, isDistributedFps } from "@hyperframes/core";
18323
- import { createFrameLookupTable as createFrameLookupTable4, renameWithRetry as renameWithRetry10 } from "@hyperframes/engine";
18697
+ import { createFrameLookupTable as createFrameLookupTable5, renameWithRetry as renameWithRetry10 } from "@hyperframes/engine";
18324
18698
 
18325
18699
  // src/services/distributed/planV2Publisher.ts
18326
18700
  import {
18327
18701
  copyFileSync as copyFileSync7,
18328
- existsSync as existsSync25,
18702
+ existsSync as existsSync26,
18329
18703
  linkSync as linkSync2,
18330
- mkdirSync as mkdirSync15,
18704
+ mkdirSync as mkdirSync17,
18331
18705
  mkdtempSync as mkdtempSync5,
18332
- rmSync as rmSync13,
18333
- statSync as statSync9,
18334
- writeFileSync as writeFileSync10
18706
+ rmSync as rmSync15,
18707
+ statSync as statSync10,
18708
+ writeFileSync as writeFileSync11
18335
18709
  } from "node:fs";
18336
18710
  import { renameWithRetry as renameWithRetry9 } from "@hyperframes/engine";
18337
- import { dirname as dirname15, join as join39 } from "node:path";
18711
+ import { dirname as dirname16, join as join42 } from "node:path";
18338
18712
 
18339
18713
  // src/services/distributed/planV2Errors.ts
18340
18714
  var PLAN_V2_INTEGRITY_UNRECOVERABLE = "PLAN_V2_INTEGRITY_UNRECOVERABLE";
@@ -18349,7 +18723,7 @@ var PlanV2IntegrityError = class extends Error {
18349
18723
  };
18350
18724
 
18351
18725
  // src/services/distributed/planV2Layout.ts
18352
- import { join as join38 } from "node:path";
18726
+ import { join as join41 } from "node:path";
18353
18727
  var SHA256_PATTERN = /^[0-9a-f]{64}$/;
18354
18728
  function assertPlanV2Sha256(value, field) {
18355
18729
  if (typeof value !== "string" || !SHA256_PATTERN.test(value)) {
@@ -18359,7 +18733,7 @@ function assertPlanV2Sha256(value, field) {
18359
18733
  }
18360
18734
  function planV2BlobPath(root, sha256) {
18361
18735
  const digest = assertPlanV2Sha256(sha256, "artifact sha256");
18362
- return join38(root, "artifacts", "sha256", digest.slice(0, 2), digest);
18736
+ return join41(root, "artifacts", "sha256", digest.slice(0, 2), digest);
18363
18737
  }
18364
18738
 
18365
18739
  // src/services/distributed/planV2Publisher.ts
@@ -18394,27 +18768,27 @@ var LocalPlanV2ArtifactPublisher = class {
18394
18768
  #linkFile;
18395
18769
  #committed = false;
18396
18770
  constructor(destinationDir, options = {}) {
18397
- if (existsSync25(destinationDir)) {
18771
+ if (existsSync26(destinationDir)) {
18398
18772
  throw new PlanV2IntegrityError(`output directory already exists: ${destinationDir}`);
18399
18773
  }
18400
18774
  this.destinationDir = destinationDir;
18401
18775
  this.#linkFile = options.linkFile ?? linkSync2;
18402
- mkdirSync15(dirname15(destinationDir), { recursive: true });
18403
- 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-"));
18404
18778
  }
18405
18779
  async putBlob(blob) {
18406
18780
  const digest = assertPlanV2Sha256(blob.sha256, "published blob sha256");
18407
- const sourceSize = statSync9(blob.sourcePath).size;
18781
+ const sourceSize = statSync10(blob.sourcePath).size;
18408
18782
  if (sourceSize !== blob.sizeBytes) {
18409
18783
  throw new PlanV2IntegrityError(
18410
18784
  `published blob size changed for ${digest}: expected ${blob.sizeBytes}, got ${sourceSize}`
18411
18785
  );
18412
18786
  }
18413
18787
  const destinationPath = planV2BlobPath(this.temporaryDir, digest);
18414
- if (existsSync25(destinationPath)) return;
18415
- mkdirSync15(dirname15(destinationPath), { recursive: true });
18416
- const stagingDir = mkdtempSync5(join39(dirname15(destinationPath), ".plan-v2-blob-"));
18417
- const temporaryPath = join39(stagingDir, "blob");
18788
+ if (existsSync26(destinationPath)) return;
18789
+ mkdirSync17(dirname16(destinationPath), { recursive: true });
18790
+ const stagingDir = mkdtempSync5(join42(dirname16(destinationPath), ".plan-v2-blob-"));
18791
+ const temporaryPath = join42(stagingDir, "blob");
18418
18792
  try {
18419
18793
  try {
18420
18794
  this.#linkFile(blob.sourcePath, temporaryPath);
@@ -18424,24 +18798,24 @@ var LocalPlanV2ArtifactPublisher = class {
18424
18798
  }
18425
18799
  renameWithRetry9(temporaryPath, destinationPath);
18426
18800
  } finally {
18427
- rmSync13(stagingDir, { recursive: true, force: true });
18801
+ rmSync15(stagingDir, { recursive: true, force: true });
18428
18802
  }
18429
18803
  }
18430
18804
  async commitManifest(manifestBytes) {
18431
18805
  for (const digest of new Set(manifestDigests(manifestBytes))) {
18432
- if (!existsSync25(planV2BlobPath(this.temporaryDir, digest))) {
18806
+ if (!existsSync26(planV2BlobPath(this.temporaryDir, digest))) {
18433
18807
  throw new PlanV2IntegrityError(
18434
18808
  `cannot commit manifest before referenced blob is durable: ${digest}`
18435
18809
  );
18436
18810
  }
18437
18811
  }
18438
- writeFileSync10(join39(this.temporaryDir, "plan.json"), manifestBytes, "utf-8");
18812
+ writeFileSync11(join42(this.temporaryDir, "plan.json"), manifestBytes, "utf-8");
18439
18813
  renameWithRetry9(this.temporaryDir, this.destinationDir);
18440
18814
  this.#committed = true;
18441
18815
  }
18442
18816
  async abort() {
18443
18817
  if (!this.#committed) {
18444
- rmSync13(this.temporaryDir, { recursive: true, force: true });
18818
+ rmSync15(this.temporaryDir, { recursive: true, force: true });
18445
18819
  }
18446
18820
  }
18447
18821
  };
@@ -18479,8 +18853,8 @@ function listFiles(root) {
18479
18853
  const files = [];
18480
18854
  const rootResolved = resolve10(root);
18481
18855
  function walk(dir) {
18482
- for (const entry of readdirSync9(dir, { withFileTypes: true })) {
18483
- const absolutePath = join40(dir, entry.name);
18856
+ for (const entry of readdirSync10(dir, { withFileTypes: true })) {
18857
+ const absolutePath = join43(dir, entry.name);
18484
18858
  if (entry.isDirectory()) {
18485
18859
  walk(absolutePath);
18486
18860
  } else if (entry.isFile()) {
@@ -18507,13 +18881,13 @@ function assertValidExtractionCacheCompleteSentinel(path) {
18507
18881
  }
18508
18882
  }
18509
18883
  function validateExtractionCacheCompleteSentinels(executionPlanDir) {
18510
- const videoRoot = join40(executionPlanDir, "video-frames");
18511
- if (!existsSync26(videoRoot)) return;
18512
- for (const videoEntry of readdirSync9(videoRoot, { withFileTypes: true })) {
18884
+ const videoRoot = join43(executionPlanDir, "video-frames");
18885
+ if (!existsSync27(videoRoot)) return;
18886
+ for (const videoEntry of readdirSync10(videoRoot, { withFileTypes: true })) {
18513
18887
  if (!videoEntry.isDirectory()) continue;
18514
- const videoDir = join40(videoRoot, videoEntry.name);
18515
- if (readdirSync9(videoDir).includes(EXTRACTION_CACHE_COMPLETE_SENTINEL)) {
18516
- 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);
18517
18891
  assertValidExtractionCacheCompleteSentinel(sentinelPath);
18518
18892
  }
18519
18893
  }
@@ -18560,15 +18934,15 @@ function listVideoFramePaths(executionPlanDir, videos) {
18560
18934
  if (video.deferredRange) {
18561
18935
  const framePaths2 = /* @__PURE__ */ new Map();
18562
18936
  for (let index = 0; index < video.totalFrames; index++) {
18563
- framePaths2.set(index, join40(outputDir, String(index)));
18937
+ framePaths2.set(index, join43(outputDir, String(index)));
18564
18938
  }
18565
18939
  return { ...video, outputDir, framePaths: framePaths2, ownedByLookup: false };
18566
18940
  }
18567
- const frameNames = readdirSync9(outputDir).sort();
18941
+ const frameNames = readdirSync10(outputDir).sort();
18568
18942
  const framePaths = /* @__PURE__ */ new Map();
18569
18943
  for (const frameName of frameNames) {
18570
18944
  if (frameName === EXTRACTION_CACHE_COMPLETE_SENTINEL) {
18571
- assertValidExtractionCacheCompleteSentinel(join40(outputDir, frameName));
18945
+ assertValidExtractionCacheCompleteSentinel(join43(outputDir, frameName));
18572
18946
  continue;
18573
18947
  }
18574
18948
  const match = /(\d+)(?=\.[^.]+$)/.exec(frameName);
@@ -18587,7 +18961,7 @@ function listVideoFramePaths(executionPlanDir, videos) {
18587
18961
  `duplicate extracted frame index ${frameIndex} in ${outputDir}`
18588
18962
  );
18589
18963
  }
18590
- framePaths.set(frameIndex, join40(outputDir, frameName));
18964
+ framePaths.set(frameIndex, join43(outputDir, frameName));
18591
18965
  }
18592
18966
  return {
18593
18967
  ...video,
@@ -18608,11 +18982,11 @@ function parsePlanVideosJson2(value) {
18608
18982
  }
18609
18983
  }
18610
18984
  function materializeExtractedVideoDirectories(planDir) {
18611
- const videosPath = join40(planDir, PLAN_VIDEOS_META_RELATIVE_PATH);
18612
- if (!existsSync26(videosPath)) return;
18985
+ const videosPath = join43(planDir, PLAN_VIDEOS_META_RELATIVE_PATH);
18986
+ if (!existsSync27(videosPath)) return;
18613
18987
  const videos = parsePlanVideosJson2(readJsonFile(videosPath, PLAN_VIDEOS_META_RELATIVE_PATH));
18614
18988
  for (const video of videos.extracted) {
18615
- mkdirSync16(resolveExtractedVideoOutputDir(planDir, video.videoId), { recursive: true });
18989
+ mkdirSync18(resolveExtractedVideoOutputDir(planDir, video.videoId), { recursive: true });
18616
18990
  }
18617
18991
  }
18618
18992
  function parseChunkSlices(value) {
@@ -18642,13 +19016,13 @@ function parseChunkSlices(value) {
18642
19016
  });
18643
19017
  }
18644
19018
  function buildVideoChunkDependencies(executionPlanDir, dimensions) {
18645
- const videoRoot = join40(executionPlanDir, "video-frames");
18646
- const hasExtractedFrames = existsSync26(videoRoot) && listFiles(videoRoot).length > 0;
18647
- const videosPath = join40(executionPlanDir, PLAN_VIDEOS_META_RELATIVE_PATH);
18648
- if (!existsSync26(videosPath)) {
19019
+ const videoRoot = join43(executionPlanDir, "video-frames");
19020
+ const hasExtractedFrames = existsSync27(videoRoot) && listFiles(videoRoot).length > 0;
19021
+ const videosPath = join43(executionPlanDir, PLAN_VIDEOS_META_RELATIVE_PATH);
19022
+ if (!existsSync27(videosPath)) {
18649
19023
  return hasExtractedFrames ? { mode: "full-source-pack", dependencies: null } : { mode: "exact-rendered-frames", dependencies: /* @__PURE__ */ new Map() };
18650
19024
  }
18651
- const chunksPath = join40(executionPlanDir, "meta", "chunks.json");
19025
+ const chunksPath = join43(executionPlanDir, "meta", "chunks.json");
18652
19026
  const videos = readJsonFile(videosPath, PLAN_VIDEOS_META_RELATIVE_PATH);
18653
19027
  const chunks = readJsonFile(chunksPath, "meta/chunks.json");
18654
19028
  const parsedVideos = parsePlanVideosJson2(videos);
@@ -18656,7 +19030,7 @@ function buildVideoChunkDependencies(executionPlanDir, dimensions) {
18656
19030
  const extracted = listVideoFramePaths(executionPlanDir, parsedVideos);
18657
19031
  const fpsNum = readPositiveInteger2(dimensions.fpsNum, "dimensions.fpsNum");
18658
19032
  const fpsDen = readPositiveInteger2(dimensions.fpsDen, "dimensions.fpsDen");
18659
- const table = createFrameLookupTable4(
19033
+ const table = createFrameLookupTable5(
18660
19034
  parsedVideos.videos,
18661
19035
  extracted,
18662
19036
  resolveRenderFpsConfig({ num: fpsNum, den: fpsDen }).value
@@ -18702,22 +19076,22 @@ function computeManifestHash(manifest) {
18702
19076
  return sha256Hex(`${PLAN_V2_HASH_PREFIX}${canonicalJsonStringify(manifestPayload(manifest))}`);
18703
19077
  }
18704
19078
  function writeBlob(sourcePath, destinationPath) {
18705
- if (existsSync26(destinationPath)) return;
18706
- mkdirSync16(dirname16(destinationPath), { recursive: true });
18707
- const temporaryDir = mkdtempSync6(join40(dirname16(destinationPath), ".plan-v2-blob-"));
18708
- const temporaryPath = join40(temporaryDir, "blob");
19079
+ if (existsSync27(destinationPath)) return;
19080
+ mkdirSync18(dirname17(destinationPath), { recursive: true });
19081
+ const temporaryDir = mkdtempSync6(join43(dirname17(destinationPath), ".plan-v2-blob-"));
19082
+ const temporaryPath = join43(temporaryDir, "blob");
18709
19083
  try {
18710
19084
  copyFileSync8(sourcePath, temporaryPath);
18711
19085
  renameWithRetry10(temporaryPath, destinationPath);
18712
19086
  } finally {
18713
- rmSync14(temporaryDir, { recursive: true, force: true });
19087
+ rmSync16(temporaryDir, { recursive: true, force: true });
18714
19088
  }
18715
19089
  }
18716
19090
  function publishBlobFor(absolutePath, digests) {
18717
19091
  return {
18718
19092
  sourcePath: absolutePath,
18719
19093
  sha256: planFileSha256(absolutePath, digests),
18720
- sizeBytes: statSync10(absolutePath).size
19094
+ sizeBytes: statSync11(absolutePath).size
18721
19095
  };
18722
19096
  }
18723
19097
  async function putBlobs(publisher, blobs) {
@@ -18741,8 +19115,8 @@ async function putTasks(tasks) {
18741
19115
  if (failure) throw failure.reason;
18742
19116
  }
18743
19117
  function buildPlanV2Publication(executionPlanDir, digests) {
18744
- const executionPlanPath = join40(executionPlanDir, "plan.json");
18745
- if (!existsSync26(executionPlanPath)) {
19118
+ const executionPlanPath = join43(executionPlanDir, "plan.json");
19119
+ if (!existsSync27(executionPlanPath)) {
18746
19120
  throw new PlanV2IntegrityError(`execution plan is missing plan.json: ${executionPlanPath}`);
18747
19121
  }
18748
19122
  const executionPlanValue = readJsonFile(executionPlanPath, "execution plan.json");
@@ -18769,7 +19143,7 @@ function buildPlanV2Publication(executionPlanDir, digests) {
18769
19143
  }
18770
19144
  validateExtractionCacheCompleteSentinels(executionPlanDir);
18771
19145
  const videoDependencyPlan = buildVideoChunkDependencies(executionPlanDir, dimensions);
18772
- const encoder = readJsonFile(join40(executionPlanDir, "meta", "encoder.json"), "meta/encoder.json");
19146
+ const encoder = readJsonFile(join43(executionPlanDir, "meta", "encoder.json"), "meta/encoder.json");
18773
19147
  const files = listFiles(executionPlanDir);
18774
19148
  const chunkAudio = !isRecord3(encoder) || encoder.chunksCarryAudio !== true ? "none" : files.some((file) => chunkAudioSliceTarget(file.path) !== null) ? "sliced" : "whole";
18775
19149
  for (const file of files) {
@@ -18822,28 +19196,28 @@ function buildPlanV2Publication(executionPlanDir, digests) {
18822
19196
  };
18823
19197
  }
18824
19198
  function createPlanV2FromExecutionPlan(executionPlanDir, planV2Dir) {
18825
- if (existsSync26(planV2Dir)) {
19199
+ if (existsSync27(planV2Dir)) {
18826
19200
  throw new PlanV2IntegrityError(`output directory already exists: ${planV2Dir}`);
18827
19201
  }
18828
19202
  const publication = buildPlanV2Publication(executionPlanDir);
18829
19203
  if (publication.storedBlobs.length > 0) {
18830
19204
  throw new PlanV2IntegrityError("a local plan v2 directory cannot reference remote files");
18831
19205
  }
18832
- mkdirSync16(dirname16(planV2Dir), { recursive: true });
18833
- 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-"));
18834
19208
  try {
18835
19209
  for (const blob of publication.blobs) {
18836
19210
  writeBlob(blob.sourcePath, planV2BlobPath(tempDir, blob.sha256));
18837
19211
  }
18838
- writeFileSync11(
18839
- join40(tempDir, "plan.json"),
19212
+ writeFileSync12(
19213
+ join43(tempDir, "plan.json"),
18840
19214
  canonicalJsonStringify(publication.manifest),
18841
19215
  "utf-8"
18842
19216
  );
18843
19217
  renameWithRetry10(tempDir, planV2Dir);
18844
19218
  return resultFromManifest(planV2Dir, publication.manifest);
18845
19219
  } catch (error) {
18846
- rmSync14(tempDir, { recursive: true, force: true });
19220
+ rmSync16(tempDir, { recursive: true, force: true });
18847
19221
  throw error;
18848
19222
  }
18849
19223
  }
@@ -18877,7 +19251,7 @@ async function publishExecutionPlan(executionPlanDir, publisher, { digests, log
18877
19251
  }
18878
19252
  }
18879
19253
  async function planV2WithPublisher(projectDir, config, publisher, options = {}) {
18880
- const stagingRoot = mkdtempSync6(join40(options.stagingParentDir ?? tmpdir3(), ".execution-plan-"));
19254
+ const stagingRoot = mkdtempSync6(join43(options.stagingParentDir ?? tmpdir3(), ".execution-plan-"));
18881
19255
  const digests = /* @__PURE__ */ new Map();
18882
19256
  try {
18883
19257
  await buildLocalExecutionPlan(projectDir, config, stagingRoot, {
@@ -18888,7 +19262,7 @@ async function planV2WithPublisher(projectDir, config, publisher, options = {})
18888
19262
  // artifactTargets ships every compiled/ file to all chunks, so each is in the manifest.
18889
19263
  whileAudioMixes: () => putBlobs(
18890
19264
  publisher,
18891
- listFiles(join40(stagingRoot, "compiled")).map(
19265
+ listFiles(join43(stagingRoot, "compiled")).map(
18892
19266
  (file) => publishBlobFor(file.absolutePath, digests)
18893
19267
  )
18894
19268
  ),
@@ -18904,16 +19278,16 @@ async function planV2WithPublisher(projectDir, config, publisher, options = {})
18904
19278
  }
18905
19279
  throw error;
18906
19280
  } finally {
18907
- rmSync14(stagingRoot, { recursive: true, force: true });
19281
+ rmSync16(stagingRoot, { recursive: true, force: true });
18908
19282
  }
18909
19283
  }
18910
19284
  async function planV2(projectDir, config, planV2Dir) {
18911
- if (existsSync26(planV2Dir)) {
19285
+ if (existsSync27(planV2Dir)) {
18912
19286
  throw new PlanV2IntegrityError(`output directory already exists: ${planV2Dir}`);
18913
19287
  }
18914
19288
  const publisher = new LocalPlanV2ArtifactPublisher(planV2Dir);
18915
19289
  const manifest = await planV2WithPublisher(projectDir, config, publisher, {
18916
- stagingParentDir: dirname16(planV2Dir)
19290
+ stagingParentDir: dirname17(planV2Dir)
18917
19291
  });
18918
19292
  return resultFromManifest(planV2Dir, manifest);
18919
19293
  }
@@ -18925,7 +19299,7 @@ function resultFromManifest(planV2Dir, manifest) {
18925
19299
  isRecord3(executionPlan) ? executionPlan[REFERENCE_BACKGROUND_FIELD3] : void 0
18926
19300
  ),
18927
19301
  planDir: planV2Dir,
18928
- manifestPath: join40(planV2Dir, "plan.json"),
19302
+ manifestPath: join43(planV2Dir, "plan.json"),
18929
19303
  planProtocol: PLAN_PROTOCOL_V2,
18930
19304
  planHash: manifest.planHash,
18931
19305
  sourcePlanV1Hash: getPlanV2ExecutionPlanHash(manifest),
@@ -19061,8 +19435,8 @@ function parsePlanV2Manifest(value) {
19061
19435
  return parsed;
19062
19436
  }
19063
19437
  function readPlanV2Manifest(planV2Dir) {
19064
- const manifestPath = join40(planV2Dir, "plan.json");
19065
- if (!existsSync26(manifestPath)) {
19438
+ const manifestPath = join43(planV2Dir, "plan.json");
19439
+ if (!existsSync27(manifestPath)) {
19066
19440
  throw new PlanV2IntegrityError(`missing v2 manifest: ${manifestPath}`);
19067
19441
  }
19068
19442
  const value = readJsonFile(manifestPath, "v2 manifest");
@@ -19081,10 +19455,10 @@ function listPlanV2ArtifactsForTarget(manifest, target) {
19081
19455
  }
19082
19456
  function verifyBlob(planV2Dir, artifact) {
19083
19457
  const sourcePath = planV2BlobPath(planV2Dir, artifact.sha256);
19084
- if (!existsSync26(sourcePath)) {
19458
+ if (!existsSync27(sourcePath)) {
19085
19459
  throw new PlanV2IntegrityError(`missing content-addressed artifact ${artifact.sha256}`);
19086
19460
  }
19087
- const stats = statSync10(sourcePath);
19461
+ const stats = statSync11(sourcePath);
19088
19462
  if (!stats.isFile() || stats.size !== artifact.sizeBytes) {
19089
19463
  throw new PlanV2IntegrityError(`artifact size mismatch for ${artifact.path}`);
19090
19464
  }
@@ -19094,7 +19468,7 @@ function verifyBlob(planV2Dir, artifact) {
19094
19468
  return sourcePath;
19095
19469
  }
19096
19470
  function materializePlanV2Target(planV2Dir, target, destinationDir, options = {}) {
19097
- if (existsSync26(destinationDir)) {
19471
+ if (existsSync27(destinationDir)) {
19098
19472
  throw new PlanV2IntegrityError(`materialization destination already exists: ${destinationDir}`);
19099
19473
  }
19100
19474
  const manifest = readPlanV2Manifest(planV2Dir);
@@ -19106,17 +19480,17 @@ function materializePlanV2Target(planV2Dir, target, destinationDir, options = {}
19106
19480
  artifact,
19107
19481
  sourcePath: verifyBlob(planV2Dir, artifact)
19108
19482
  }));
19109
- mkdirSync16(dirname16(destinationDir), { recursive: true });
19110
- 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-"));
19111
19485
  try {
19112
19486
  for (const { artifact, sourcePath } of verified) {
19113
- const destinationPath = join40(tempDir, ...artifact.path.split("/"));
19114
- mkdirSync16(dirname16(destinationPath), { recursive: true });
19487
+ const destinationPath = join43(tempDir, ...artifact.path.split("/"));
19488
+ mkdirSync18(dirname17(destinationPath), { recursive: true });
19115
19489
  copyFileSync8(sourcePath, destinationPath);
19116
19490
  }
19117
19491
  if (target.role === "chunk") materializeExtractedVideoDirectories(tempDir);
19118
- writeFileSync11(
19119
- join40(tempDir, PLAN_V2_MATERIALIZATION_MARKER),
19492
+ writeFileSync12(
19493
+ join43(tempDir, PLAN_V2_MATERIALIZATION_MARKER),
19120
19494
  canonicalJsonStringify({
19121
19495
  manifest,
19122
19496
  target,
@@ -19126,7 +19500,7 @@ function materializePlanV2Target(planV2Dir, target, destinationDir, options = {}
19126
19500
  );
19127
19501
  renameWithRetry10(tempDir, destinationDir);
19128
19502
  } catch (error) {
19129
- rmSync14(tempDir, { recursive: true, force: true });
19503
+ rmSync16(tempDir, { recursive: true, force: true });
19130
19504
  throw error;
19131
19505
  }
19132
19506
  return {
@@ -19139,7 +19513,7 @@ function materializePlanV2Target(planV2Dir, target, destinationDir, options = {}
19139
19513
  // Join the artifact's OWN name, not the current constant: a plan written
19140
19514
  // before the container change carries the legacy name, and materializing it
19141
19515
  // under the new one would point at a file that was never written.
19142
- audioPath: target.role === "assembler" ? audioArtifact ? join40(destinationDir, audioArtifact.path) : null : null
19516
+ audioPath: target.role === "assembler" ? audioArtifact ? join43(destinationDir, audioArtifact.path) : null : null
19143
19517
  };
19144
19518
  }
19145
19519
  function assertRemoteSources(remoteSources, artifacts) {
@@ -19158,8 +19532,8 @@ function validatePlanV2MaterializedTarget(planDir, expectedTarget) {
19158
19532
  return readValidatedPlanV2Materialization(planDir, expectedTarget)?.manifest ?? null;
19159
19533
  }
19160
19534
  function readValidatedPlanV2Materialization(planDir, expectedTarget) {
19161
- const markerPath = join40(planDir, PLAN_V2_MATERIALIZATION_MARKER);
19162
- if (!existsSync26(markerPath)) return null;
19535
+ const markerPath = join43(planDir, PLAN_V2_MATERIALIZATION_MARKER);
19536
+ if (!existsSync27(markerPath)) return null;
19163
19537
  const marker = readJsonFile(markerPath, "materialization marker");
19164
19538
  if (!isRecord3(marker) || !isRecord3(marker.manifest) || !isRecord3(marker.target)) {
19165
19539
  throw new PlanV2IntegrityError("malformed materialization marker");
@@ -19176,8 +19550,8 @@ function readValidatedPlanV2Materialization(planDir, expectedTarget) {
19176
19550
  assertRemoteSources(remoteSources, required);
19177
19551
  for (const artifact of required) {
19178
19552
  if (Object.hasOwn(remoteSources, artifact.path)) continue;
19179
- const path = join40(planDir, ...artifact.path.split("/"));
19180
- if (!existsSync26(path) || statSync10(path).size !== artifact.sizeBytes) {
19553
+ const path = join43(planDir, ...artifact.path.split("/"));
19554
+ if (!existsSync27(path) || statSync11(path).size !== artifact.sizeBytes) {
19181
19555
  throw new PlanV2IntegrityError(
19182
19556
  `materialized artifact missing or truncated: ${artifact.path}`
19183
19557
  );
@@ -19295,9 +19669,9 @@ async function beginFrameSessionNeedsScreenshotFallback(session, probe = probeBe
19295
19669
  }
19296
19670
  function hashChunkOutput(outputPath, kind) {
19297
19671
  if (kind === "file") return sha256Hex(readFileSync13(outputPath));
19298
- 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();
19299
19673
  const lines = entries.map(
19300
- (name) => `${name}\0${sha256Hex(readFileSync13(join41(outputPath, name)))}`
19674
+ (name) => `${name}\0${sha256Hex(readFileSync13(join44(outputPath, name)))}`
19301
19675
  );
19302
19676
  return sha256Hex(lines.join("\0"));
19303
19677
  }
@@ -19314,10 +19688,10 @@ function resolveLockedVp9CpuUsed(lockedEncoder) {
19314
19688
  async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
19315
19689
  const start = Date.now();
19316
19690
  const log = defaultLogger;
19317
- const planJsonPath = join41(planDir, "plan.json");
19318
- const encoderJsonPath = join41(planDir, "meta", "encoder.json");
19319
- const chunksJsonPath = join41(planDir, "meta", "chunks.json");
19320
- if (!existsSync27(planJsonPath)) {
19691
+ const planJsonPath = join44(planDir, "plan.json");
19692
+ const encoderJsonPath = join44(planDir, "meta", "encoder.json");
19693
+ const chunksJsonPath = join44(planDir, "meta", "chunks.json");
19694
+ if (!existsSync28(planJsonPath)) {
19321
19695
  throw new RenderChunkValidationError(
19322
19696
  MISSING_PLAN_ARTIFACT,
19323
19697
  `[renderChunk] planDir is missing required artifact: ${planJsonPath}`
@@ -19333,7 +19707,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
19333
19707
  const v2Manifest = v2Materialization?.manifest ?? null;
19334
19708
  const remoteSources = v2Materialization?.remoteSources ?? {};
19335
19709
  for (const required of [encoderJsonPath, chunksJsonPath]) {
19336
- if (!existsSync27(required)) {
19710
+ if (!existsSync28(required)) {
19337
19711
  throw new RenderChunkValidationError(
19338
19712
  MISSING_PLAN_ARTIFACT,
19339
19713
  `[renderChunk] planDir is missing required artifact: ${required}`
@@ -19342,9 +19716,9 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
19342
19716
  }
19343
19717
  const encoder = JSON.parse(readFileSync13(encoderJsonPath, "utf-8"));
19344
19718
  const chunks = JSON.parse(readFileSync13(chunksJsonPath, "utf-8"));
19345
- const videosJsonPath = join41(planDir, PLAN_VIDEOS_META_RELATIVE_PATH);
19719
+ const videosJsonPath = join44(planDir, PLAN_VIDEOS_META_RELATIVE_PATH);
19346
19720
  let planVideos = null;
19347
- if (existsSync27(videosJsonPath)) {
19721
+ if (existsSync28(videosJsonPath)) {
19348
19722
  try {
19349
19723
  planVideos = validatePlanVideosForChunk(JSON.parse(readFileSync13(videosJsonPath, "utf-8")));
19350
19724
  } catch (err) {
@@ -19374,8 +19748,8 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
19374
19748
  }
19375
19749
  const range = resolveChunkFrameRange(slice, chunkIndex, options?.frameRange);
19376
19750
  const framesInChunk = range.endFrame - range.startFrame;
19377
- const compiledDir = join41(planDir, "compiled");
19378
- if (!existsSync27(compiledDir)) {
19751
+ const compiledDir = join44(planDir, "compiled");
19752
+ if (!existsSync28(compiledDir)) {
19379
19753
  throw new RenderChunkValidationError(
19380
19754
  MISSING_PLAN_ARTIFACT,
19381
19755
  `[renderChunk] planDir missing compiled/ directory: ${compiledDir}`
@@ -19431,14 +19805,14 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
19431
19805
  forceScreenshotExplicitlyOptedOut: !encoder.forceScreenshot
19432
19806
  };
19433
19807
  const workDir = `${outputChunkPath}.work.${process.pid}.${randomBytes(4).toString("hex")}`;
19434
- mkdirSync17(workDir, { recursive: true });
19435
- const framesDir = join41(workDir, "captured-frames");
19436
- mkdirSync17(framesDir, { recursive: true });
19808
+ mkdirSync19(workDir, { recursive: true });
19809
+ const framesDir = join44(workDir, "captured-frames");
19810
+ mkdirSync19(framesDir, { recursive: true });
19437
19811
  const videoFramesDir = `${workDir}.video-frames`;
19438
19812
  const chunkFrameNumbers = Array.from({ length: framesInChunk }, (_, i) => range.startFrame + i);
19439
19813
  const chunkFps = { num: plan2.dimensions.fpsNum, den: plan2.dimensions.fpsDen };
19440
19814
  const deferredVideos = planVideos?.extracted.filter((video) => video.deferredRange) ?? [];
19441
- const shownFrames = planVideos ? shownDeferredFrameIndexes(planVideos, chunkFrameNumbers, chunkFps) : /* @__PURE__ */ new Map();
19815
+ const shownFrames = planVideos ? shownPlanFrameIndexes(planVideos, chunkFrameNumbers, chunkFps) : /* @__PURE__ */ new Map();
19442
19816
  const remoteVideoIds = new Set(
19443
19817
  planVideos?.extracted.filter(
19444
19818
  (video) => video.deferredRange && Object.hasOwn(remoteSources, video.deferredRange.sourcePath)
@@ -19457,7 +19831,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
19457
19831
  dimensions: plan2.dimensions,
19458
19832
  encoder,
19459
19833
  videoCount: planVideos?.videos.length ?? 0,
19460
- html: readFileSync13(join41(compiledDir, "index.html"), "utf-8"),
19834
+ html: readFileSync13(join44(compiledDir, "index.html"), "utf-8"),
19461
19835
  // The plan ships a deferred source only to chunks that show it; don't wait on the rest.
19462
19836
  skipReadinessVideoIds: deferredVideos.filter(
19463
19837
  (video) => !shownFrames.get(video.videoId)?.size || remoteVideoIds.has(video.videoId)
@@ -19592,7 +19966,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
19592
19966
  deferredVideoFrames.catch(() => {
19593
19967
  });
19594
19968
  const videoFrameLookup = planVideos && planVideos.extracted.length > 0 ? deferredVideoFrames.then(
19595
- (deferred) => createFrameLookupTable5(
19969
+ (deferred) => createFrameLookupTable6(
19596
19970
  planVideos.videos,
19597
19971
  [
19598
19972
  ...rebuildExtractedFramesFromPlanDir(
@@ -19625,10 +19999,10 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
19625
19999
  const effectiveBitrate = encoder.crf != null ? void 0 : encoder.bitrate;
19626
20000
  const videoOnlyPath = encoder.chunksCarryAudio ? `${outputChunkPath}.video.mp4` : outputChunkPath;
19627
20001
  if (isPngSequence) {
19628
- if (!existsSync27(outputChunkPath)) mkdirSync17(outputChunkPath, { recursive: true });
20002
+ if (!existsSync28(outputChunkPath)) mkdirSync19(outputChunkPath, { recursive: true });
19629
20003
  } else {
19630
- const outDir = join41(outputChunkPath, "..");
19631
- if (!existsSync27(outDir)) mkdirSync17(outDir, { recursive: true });
20004
+ const outDir = join44(outputChunkPath, "..");
20005
+ if (!existsSync28(outDir)) mkdirSync19(outDir, { recursive: true });
19632
20006
  }
19633
20007
  const encodeInput = {
19634
20008
  job,
@@ -19744,9 +20118,9 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
19744
20118
  return observedChunkCaptureMode(capturePerfs);
19745
20119
  },
19746
20120
  resetForScreenshotRetry: () => {
19747
- rmSync15(workDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
20121
+ rmSync17(workDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
19748
20122
  boxesMeasured();
19749
- mkdirSync17(framesDir, { recursive: true });
20123
+ mkdirSync19(framesDir, { recursive: true });
19750
20124
  capturePerfs.length = 0;
19751
20125
  captureAttempts.length = 0;
19752
20126
  job.framesRendered = 0;
@@ -19790,8 +20164,8 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
19790
20164
  fps: { num: plan2.dimensions.fpsNum, den: plan2.dimensions.fpsDen },
19791
20165
  timeoutMs: cfg.ffmpegProcessTimeout
19792
20166
  });
19793
- rmSync15(videoOnlyPath, { force: true });
19794
- rmSync15(audioScratchPath, { force: true });
20167
+ rmSync17(videoOnlyPath, { force: true });
20168
+ rmSync17(audioScratchPath, { force: true });
19795
20169
  }
19796
20170
  encodeStageMs = Date.now() - encodeStarted;
19797
20171
  } finally {
@@ -19838,11 +20212,11 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
19838
20212
  producerVersion: plan2.producerVersion,
19839
20213
  ffmpegVersion
19840
20214
  };
19841
- writeFileSync12(perfPath, `${JSON.stringify(perfPayload, null, 2)}
20215
+ writeFileSync13(perfPath, `${JSON.stringify(perfPayload, null, 2)}
19842
20216
  `, "utf-8");
19843
20217
  try {
19844
- rmSync15(workDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
19845
- 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 });
19846
20220
  } catch (err) {
19847
20221
  log.warn("[renderChunk] failed to remove work dir", {
19848
20222
  workDir,
@@ -19901,11 +20275,11 @@ async function renderSpan(projectDir, config, span, outputPath) {
19901
20275
  if (config.cfr === true) {
19902
20276
  throw new RenderSpanUnsupportedError("--start/--end cannot be combined with a cfr re-encode.");
19903
20277
  }
19904
- const workDir = mkdtempSync7(join42(tmpdir4(), "hf-render-span-"));
20278
+ const workDir = mkdtempSync7(join45(tmpdir4(), "hf-render-span-"));
19905
20279
  try {
19906
- const planDir = join42(workDir, "plan");
19907
- const partsDir = join42(workDir, "parts");
19908
- mkdirSync18(partsDir, { recursive: true });
20280
+ const planDir = join45(workDir, "plan");
20281
+ const partsDir = join45(workDir, "parts");
20282
+ mkdirSync20(partsDir, { recursive: true });
19909
20283
  const planned = await plan(
19910
20284
  projectDir,
19911
20285
  { ...config, rejectOnSystemFonts: false, failClosedFontFetch: false },
@@ -19919,7 +20293,7 @@ async function renderSpan(projectDir, config, span, outputPath) {
19919
20293
  planned.totalFrames
19920
20294
  );
19921
20295
  const slices = JSON.parse(
19922
- readFileSync14(join42(planDir, "meta", "chunks.json"), "utf-8")
20296
+ readFileSync14(join45(planDir, "meta", "chunks.json"), "utf-8")
19923
20297
  );
19924
20298
  const parts = [];
19925
20299
  for (const slice of slices) {
@@ -19929,7 +20303,7 @@ async function renderSpan(projectDir, config, span, outputPath) {
19929
20303
  };
19930
20304
  if (range.endFrame <= range.startFrame) continue;
19931
20305
  throwIfAborted(config.abortSignal);
19932
- 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`);
19933
20307
  await renderChunk(planDir, slice.index, part, { frameRange: range });
19934
20308
  parts.push(part);
19935
20309
  }
@@ -19942,7 +20316,7 @@ async function renderSpan(projectDir, config, span, outputPath) {
19942
20316
  framesEncoded: endFrame - startFrame
19943
20317
  };
19944
20318
  } finally {
19945
- rmSync16(workDir, { recursive: true, force: true });
20319
+ rmSync18(workDir, { recursive: true, force: true });
19946
20320
  }
19947
20321
  }
19948
20322
 
@@ -20518,7 +20892,7 @@ import {
20518
20892
  } from "@hyperframes/engine";
20519
20893
 
20520
20894
  // src/index.ts
20521
- import { createVideoFrameInjector as createVideoFrameInjector3 } from "@hyperframes/engine";
20895
+ import { createVideoFrameInjector as createVideoFrameInjector4 } from "@hyperframes/engine";
20522
20896
 
20523
20897
  // src/config.ts
20524
20898
  import {
@@ -20528,15 +20902,15 @@ import {
20528
20902
 
20529
20903
  // src/server.ts
20530
20904
  import {
20531
- existsSync as existsSync30,
20532
- mkdirSync as mkdirSync19,
20533
- statSync as statSync12,
20905
+ existsSync as existsSync31,
20906
+ mkdirSync as mkdirSync21,
20907
+ statSync as statSync13,
20534
20908
  mkdtempSync as mkdtempSync8,
20535
- writeFileSync as writeFileSync13,
20536
- rmSync as rmSync17,
20909
+ writeFileSync as writeFileSync14,
20910
+ rmSync as rmSync19,
20537
20911
  createReadStream as createReadStream5
20538
20912
  } from "node:fs";
20539
- 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";
20540
20914
  import { tmpdir as tmpdir5 } from "node:os";
20541
20915
  import { parseArgs } from "node:util";
20542
20916
  import crypto from "node:crypto";
@@ -20551,15 +20925,15 @@ import {
20551
20925
 
20552
20926
  // src/services/hyperframeLint.ts
20553
20927
  import {
20554
- existsSync as existsSync28,
20928
+ existsSync as existsSync29,
20555
20929
  readFileSync as readFileSync15,
20556
- statSync as statSync11,
20930
+ statSync as statSync12,
20557
20931
  openSync as openSync7,
20558
20932
  fstatSync as fstatSync3,
20559
20933
  closeSync as closeSync7,
20560
20934
  constants as constants3
20561
20935
  } from "node:fs";
20562
- 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";
20563
20937
  import { lintHyperframeHtml } from "@hyperframes/lint";
20564
20938
  function isStringRecord(value) {
20565
20939
  if (!value || typeof value !== "object" || Array.isArray(value)) {
@@ -20593,7 +20967,7 @@ function readEntryHtml(filePath) {
20593
20967
  } catch (error) {
20594
20968
  if (error instanceof Error && "code" in error && (error.code === "ENOENT" || error.code === "ENOTDIR"))
20595
20969
  return null;
20596
- if (!statSync11(filePath, { throwIfNoEntry: false })?.isFile()) return null;
20970
+ if (!statSync12(filePath, { throwIfNoEntry: false })?.isFile()) return null;
20597
20971
  throw error;
20598
20972
  }
20599
20973
  try {
@@ -20605,7 +20979,7 @@ function readEntryHtml(filePath) {
20605
20979
  }
20606
20980
  function readProjectEntryFile(projectDir, preferredEntryFile) {
20607
20981
  const absProjectDir = resolve11(projectDir);
20608
- if (!existsSync28(absProjectDir) || !statSync11(absProjectDir).isDirectory()) {
20982
+ if (!existsSync29(absProjectDir) || !statSync12(absProjectDir).isDirectory()) {
20609
20983
  return { error: `Project directory not found: ${absProjectDir}` };
20610
20984
  }
20611
20985
  const entryCandidates = [preferredEntryFile, "index.html", "src/index.html"].filter(
@@ -20627,7 +21001,7 @@ function readProjectEntryFile(projectDir, preferredEntryFile) {
20627
21001
  }
20628
21002
  }
20629
21003
  return {
20630
- 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")}`
20631
21005
  };
20632
21006
  }
20633
21007
  function prepareHyperframeLintBody(body) {
@@ -20673,8 +21047,8 @@ async function runHyperframeLint(prepared) {
20673
21047
  // src/services/healthWorker.ts
20674
21048
  import { Worker as Worker2 } from "node:worker_threads";
20675
21049
  import { fileURLToPath as fileURLToPath6 } from "node:url";
20676
- import { dirname as dirname17, join as join44 } from "node:path";
20677
- import { existsSync as existsSync29 } from "node:fs";
21050
+ import { dirname as dirname18, join as join47 } from "node:path";
21051
+ import { existsSync as existsSync30 } from "node:fs";
20678
21052
  var DEFAULT_HEALTH_PORT = 9848;
20679
21053
  async function startHealthWorker(options = {}) {
20680
21054
  const log = options.logger ?? defaultLogger2();
@@ -20748,10 +21122,10 @@ function defaultLogger2() {
20748
21122
  };
20749
21123
  }
20750
21124
  function resolveWorkerEntry2() {
20751
- const here = dirname17(fileURLToPath6(import.meta.url));
20752
- 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")];
20753
21127
  for (const candidate of candidates) {
20754
- if (existsSync29(candidate)) return candidate;
21128
+ if (existsSync30(candidate)) return candidate;
20755
21129
  }
20756
21130
  return void 0;
20757
21131
  }
@@ -20896,8 +21270,8 @@ function buildRenderJobConfig(input, outputPath, log) {
20896
21270
  function resolvePreparedRenderOutput(prepared, rendersDir, log) {
20897
21271
  const { input, cleanupProjectDir } = prepared;
20898
21272
  const absoluteOutputPath = resolveOutputPath(input.projectDir, input.outputPath, rendersDir, log);
20899
- const outputDir = dirname18(absoluteOutputPath);
20900
- if (!existsSync30(outputDir)) mkdirSync19(outputDir, { recursive: true });
21273
+ const outputDir = dirname19(absoluteOutputPath);
21274
+ if (!existsSync31(outputDir)) mkdirSync21(outputDir, { recursive: true });
20901
21275
  return { input, cleanupProjectDir, absoluteOutputPath };
20902
21276
  }
20903
21277
  function validateRenderOverrides(body) {
@@ -20938,11 +21312,11 @@ function prepareProjectDirectory(projectDir, options) {
20938
21312
  const candidate = nonEmptyString(projectDir);
20939
21313
  if (!candidate) return null;
20940
21314
  const absProjectDir = resolve12(candidate);
20941
- if (!existsSync30(absProjectDir) || !statSync12(absProjectDir).isDirectory()) {
21315
+ if (!existsSync31(absProjectDir) || !statSync13(absProjectDir).isDirectory()) {
20942
21316
  return { error: `Project directory not found: ${absProjectDir}` };
20943
21317
  }
20944
21318
  const entry = options.entryFile || "index.html";
20945
- if (!existsSync30(resolve12(absProjectDir, entry))) {
21319
+ if (!existsSync31(resolve12(absProjectDir, entry))) {
20946
21320
  return { error: `Entry file "${entry}" not found in project directory: ${absProjectDir}` };
20947
21321
  }
20948
21322
  return { prepared: { input: { projectDir: absProjectDir, ...options } } };
@@ -20963,8 +21337,8 @@ async function resolveInlineRenderHtml(body) {
20963
21337
  }
20964
21338
  function materializeInlineProject(html, options) {
20965
21339
  const tempRoot = process.env.PRODUCER_TMP_PROJECT_DIR || tmpdir5();
20966
- const tempProjectDir = mkdtempSync8(join45(tempRoot, "producer-project-"));
20967
- writeFileSync13(join45(tempProjectDir, "index.html"), html, "utf-8");
21340
+ const tempProjectDir = mkdtempSync8(join48(tempRoot, "producer-project-"));
21341
+ writeFileSync14(join48(tempProjectDir, "index.html"), html, "utf-8");
20968
21342
  return {
20969
21343
  prepared: {
20970
21344
  input: { projectDir: tempProjectDir, ...options },
@@ -21025,7 +21399,7 @@ function createArtifactStore(ttlMs) {
21025
21399
  function cleanupTempDir(dir, log) {
21026
21400
  if (!dir) return;
21027
21401
  try {
21028
- rmSync17(dir, { recursive: true, force: true });
21402
+ rmSync19(dir, { recursive: true, force: true });
21029
21403
  } catch (error) {
21030
21404
  log.warn("Failed to cleanup temp project dir", {
21031
21405
  cleanupProjectDir: dir,
@@ -21034,7 +21408,7 @@ function cleanupTempDir(dir, log) {
21034
21408
  }
21035
21409
  }
21036
21410
  function outputFileSize(path) {
21037
- return existsSync30(path) ? statSync12(path).size : 0;
21411
+ return existsSync31(path) ? statSync13(path).size : 0;
21038
21412
  }
21039
21413
  function createBlockingProgressReporter(log, requestId) {
21040
21414
  let lastLoggedPct = -10;
@@ -21338,11 +21712,11 @@ function createRenderHandlers(options = {}) {
21338
21712
  if (!artifact) {
21339
21713
  return c.json({ success: false, error: "Output artifact not found or expired" }, 404);
21340
21714
  }
21341
- if (!existsSync30(artifact.path)) {
21715
+ if (!existsSync31(artifact.path)) {
21342
21716
  store.delete(token);
21343
21717
  return c.json({ success: false, error: "Output artifact file missing" }, 404);
21344
21718
  }
21345
- const stats = statSync12(artifact.path);
21719
+ const stats = statSync13(artifact.path);
21346
21720
  return new Response(createReadStream5(artifact.path), {
21347
21721
  headers: {
21348
21722
  "content-type": "video/mp4",
@@ -21433,9 +21807,9 @@ import {
21433
21807
  import { quantizeTimeToFrame, MEDIA_VISUAL_STYLE_PROPERTIES } from "@hyperframes/engine";
21434
21808
 
21435
21809
  // src/services/distributed/planV2Execution.ts
21436
- import { mkdtempSync as mkdtempSync10, rmSync as rmSync19 } from "node:fs";
21810
+ import { mkdtempSync as mkdtempSync10, rmSync as rmSync21 } from "node:fs";
21437
21811
  import { tmpdir as tmpdir6 } from "node:os";
21438
- import { join as join47 } from "node:path";
21812
+ import { join as join50 } from "node:path";
21439
21813
 
21440
21814
  // src/services/distributed/assemble.ts
21441
21815
  import {
@@ -21444,16 +21818,16 @@ import {
21444
21818
  } from "@hyperframes/engine";
21445
21819
  import {
21446
21820
  cpSync as cpSync3,
21447
- existsSync as existsSync31,
21448
- mkdirSync as mkdirSync20,
21821
+ existsSync as existsSync32,
21822
+ mkdirSync as mkdirSync22,
21449
21823
  mkdtempSync as mkdtempSync9,
21450
21824
  readFileSync as readFileSync16,
21451
- readdirSync as readdirSync11,
21452
- rmSync as rmSync18,
21453
- statSync as statSync13,
21454
- writeFileSync as writeFileSync14
21825
+ readdirSync as readdirSync12,
21826
+ rmSync as rmSync20,
21827
+ statSync as statSync14,
21828
+ writeFileSync as writeFileSync15
21455
21829
  } from "node:fs";
21456
- import { dirname as dirname19, join as join46 } from "node:path";
21830
+ import { dirname as dirname20, join as join49 } from "node:path";
21457
21831
  import {
21458
21832
  appendRateCapArgs as appendRateCapArgs2,
21459
21833
  appendRenderProvenanceArgs as appendRenderProvenanceArgs2,
@@ -21494,13 +21868,13 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
21494
21868
  outputPath,
21495
21869
  durationMs: Date.now() - start,
21496
21870
  framesEncoded: plan2.totalFrames,
21497
- fileSize: statSync13(outputPath).size
21871
+ fileSize: statSync14(outputPath).size
21498
21872
  };
21499
21873
  }
21500
21874
  const format = plan2.dimensions.format;
21501
21875
  const cfrEncoder = cfr ? readCfrEncoder(planDir, format) : null;
21502
- if (!existsSync31(dirname19(outputPath))) {
21503
- mkdirSync20(dirname19(outputPath), { recursive: true });
21876
+ if (!existsSync32(dirname20(outputPath))) {
21877
+ mkdirSync22(dirname20(outputPath), { recursive: true });
21504
21878
  }
21505
21879
  const workDir = mkdtempSync9(`${outputPath}.assemble-work-`);
21506
21880
  const fps = { num: plan2.dimensions.fpsNum, den: plan2.dimensions.fpsDen };
@@ -21522,8 +21896,8 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
21522
21896
  };
21523
21897
  try {
21524
21898
  const videoSources = cfrEncoder ? [await reencodeCfr(ctx, chunkPaths, cfrEncoder)] : chunkPaths;
21525
- const finalPath = join46(workDir, `output.${format}`);
21526
- const audio = audioPath !== null && existsSync31(audioPath) ? audioPath : null;
21899
+ const finalPath = join49(workDir, `output.${format}`);
21900
+ const audio = audioPath !== null && existsSync32(audioPath) ? audioPath : null;
21527
21901
  await writeJoined(ctx, videoSources, audio, finalPath);
21528
21902
  renameWithRetry11(finalPath, outputPath);
21529
21903
  log.info("[assemble] stage timings", {
@@ -21533,7 +21907,7 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
21533
21907
  });
21534
21908
  } finally {
21535
21909
  try {
21536
- rmSync18(workDir, { recursive: true, force: true });
21910
+ rmSync20(workDir, { recursive: true, force: true });
21537
21911
  } catch (err) {
21538
21912
  log.warn("[assemble] failed to remove work dir", {
21539
21913
  workDir,
@@ -21541,7 +21915,7 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
21541
21915
  });
21542
21916
  }
21543
21917
  }
21544
- const fileSize = existsSync31(outputPath) ? statSync13(outputPath).size : 0;
21918
+ const fileSize = existsSync32(outputPath) ? statSync14(outputPath).size : 0;
21545
21919
  return {
21546
21920
  ...referenceBackgroundResult8(plan2[REFERENCE_BACKGROUND_FIELD6]),
21547
21921
  outputPath,
@@ -21551,15 +21925,15 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
21551
21925
  };
21552
21926
  }
21553
21927
  function readAssemblePlan(planDir, chunkPaths) {
21554
- const planJsonPath = join46(planDir, "plan.json");
21555
- const chunksJsonPath = join46(planDir, "meta", "chunks.json");
21556
- if (!existsSync31(planJsonPath)) {
21928
+ const planJsonPath = join49(planDir, "plan.json");
21929
+ const chunksJsonPath = join49(planDir, "meta", "chunks.json");
21930
+ if (!existsSync32(planJsonPath)) {
21557
21931
  throw new Error(`[assemble] planDir missing plan.json: ${planJsonPath}`);
21558
21932
  }
21559
21933
  const plan2 = JSON.parse(readFileSync16(planJsonPath, "utf-8"));
21560
21934
  readPlanProtocolV1(plan2, DISTRIBUTED_RENDER_CAPABILITIES.roles.assembler);
21561
21935
  validatePlanV2MaterializedTarget(planDir, { role: "assembler" });
21562
- if (!existsSync31(chunksJsonPath)) {
21936
+ if (!existsSync32(chunksJsonPath)) {
21563
21937
  throw new Error(`[assemble] planDir missing meta/chunks.json: ${chunksJsonPath}`);
21564
21938
  }
21565
21939
  const chunks = JSON.parse(readFileSync16(chunksJsonPath, "utf-8"));
@@ -21574,7 +21948,7 @@ function readAssemblePlan(planDir, chunkPaths) {
21574
21948
  }
21575
21949
  }
21576
21950
  for (const path of chunkPaths.flat()) {
21577
- if (!existsSync31(path)) {
21951
+ if (!existsSync32(path)) {
21578
21952
  throw new Error(`[assemble] chunk path does not exist: ${path}`);
21579
21953
  }
21580
21954
  }
@@ -21603,7 +21977,7 @@ function cfrEncodeArgs(inputArgs, encoder, fpsArg) {
21603
21977
  return args;
21604
21978
  }
21605
21979
  async function reencodeCfr(ctx, chunkPaths, encoder) {
21606
- const cfrOutputPath = join46(ctx.workDir, `cfr.${ctx.format}`);
21980
+ const cfrOutputPath = join49(ctx.workDir, `cfr.${ctx.format}`);
21607
21981
  const cfrArgs = cfrEncodeArgs(videoInputArgs(ctx, chunkPaths), encoder, ctx.fpsArg);
21608
21982
  appendRenderProvenanceArgs2(cfrArgs, cfrOutputPath);
21609
21983
  cfrArgs.push("-y", cfrOutputPath);
@@ -21630,7 +22004,7 @@ async function writeJoined(ctx, videoSources, audioPath, finalPath) {
21630
22004
  );
21631
22005
  return;
21632
22006
  }
21633
- const paddedAudioPath = join46(ctx.workDir, "audio-padded.m4a");
22007
+ const paddedAudioPath = join49(ctx.workDir, "audio-padded.m4a");
21634
22008
  const padTrimResult = await ctx.timed(
21635
22009
  "audioMs",
21636
22010
  () => padOrTrimAudioToVideoFrameCount({
@@ -21670,9 +22044,9 @@ async function writeJoined(ctx, videoSources, audioPath, finalPath) {
21670
22044
  }
21671
22045
  }
21672
22046
  function concatDemuxerInput(chunkPaths, workDir, fpsArg) {
21673
- const listPath = join46(workDir, "concat-list.txt");
22047
+ const listPath = join49(workDir, "concat-list.txt");
21674
22048
  const body = chunkPaths.map((path) => `file '${path.replace(/'/g, "'\\''")}'`).join("\n");
21675
- writeFileSync14(listPath, `${body}
22049
+ writeFileSync15(listPath, `${body}
21676
22050
  `, "utf-8");
21677
22051
  return ["-r", fpsArg, "-f", "concat", "-safe", "0", "-i", listPath];
21678
22052
  }
@@ -21708,12 +22082,12 @@ function stitchesSeparateAudio(planDir) {
21708
22082
  return lockedAudio(planDir).separateAudio === true;
21709
22083
  }
21710
22084
  function planFormat(planDir) {
21711
- const plan2 = JSON.parse(readFileSync16(join46(planDir, "plan.json"), "utf-8"));
22085
+ const plan2 = JSON.parse(readFileSync16(join49(planDir, "plan.json"), "utf-8"));
21712
22086
  return plan2.dimensions.format;
21713
22087
  }
21714
22088
  function lockedAudio(planDir) {
21715
- const encoderJsonPath = join46(planDir, "meta", "encoder.json");
21716
- if (!existsSync31(encoderJsonPath)) return {};
22089
+ const encoderJsonPath = join49(planDir, "meta", "encoder.json");
22090
+ if (!existsSync32(encoderJsonPath)) return {};
21717
22091
  return JSON.parse(readFileSync16(encoderJsonPath, "utf-8"));
21718
22092
  }
21719
22093
  function readCfrEncoder(planDir, format) {
@@ -21722,8 +22096,8 @@ function readCfrEncoder(planDir, format) {
21722
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.`
21723
22097
  );
21724
22098
  }
21725
- const encoderJsonPath = join46(planDir, "meta", "encoder.json");
21726
- if (!existsSync31(encoderJsonPath)) {
22099
+ const encoderJsonPath = join49(planDir, "meta", "encoder.json");
22100
+ if (!existsSync32(encoderJsonPath)) {
21727
22101
  throw new Error(`[assemble] planDir missing meta/encoder.json: ${encoderJsonPath}`);
21728
22102
  }
21729
22103
  const encoderJson = JSON.parse(readFileSync16(encoderJsonPath, "utf-8"));
@@ -21735,22 +22109,22 @@ function readCfrEncoder(planDir, format) {
21735
22109
  return encoderJson;
21736
22110
  }
21737
22111
  function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, startTimeMs) {
21738
- if (existsSync31(outputPath)) rmSync18(outputPath, { recursive: true, force: true });
21739
- mkdirSync20(outputPath, { recursive: true });
22112
+ if (existsSync32(outputPath)) rmSync20(outputPath, { recursive: true, force: true });
22113
+ mkdirSync22(outputPath, { recursive: true });
21740
22114
  let globalIdx = 0;
21741
22115
  for (const chunkDir of chunkPaths) {
21742
- if (!statSync13(chunkDir).isDirectory()) {
22116
+ if (!statSync14(chunkDir).isDirectory()) {
21743
22117
  throw new Error(
21744
22118
  `[assemble] png-sequence chunk must be a directory: ${chunkDir} (got a file)`
21745
22119
  );
21746
22120
  }
21747
- const frames = readdirSync11(chunkDir).filter((name) => name.endsWith(".png")).sort();
22121
+ const frames = readdirSync12(chunkDir).filter((name) => name.endsWith(".png")).sort();
21748
22122
  if (frames.length === 0) {
21749
22123
  throw new Error(`[assemble] png-sequence chunk has no frames: ${chunkDir}`);
21750
22124
  }
21751
22125
  for (const frame of frames) {
21752
- const dst = join46(outputPath, formatExportFrameName(globalIdx, "png"));
21753
- cpSync3(join46(chunkDir, frame), dst);
22126
+ const dst = join49(outputPath, formatExportFrameName(globalIdx, "png"));
22127
+ cpSync3(join49(chunkDir, frame), dst);
21754
22128
  globalIdx += 1;
21755
22129
  }
21756
22130
  }
@@ -21759,14 +22133,14 @@ function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, start
21759
22133
  `[assemble] png-sequence frame count mismatch: merged ${globalIdx} frames vs plan.totalFrames=${totalFrames}. Using on-disk count.`
21760
22134
  );
21761
22135
  }
21762
- if (audioPath !== null && existsSync31(audioPath)) {
21763
- const sidecar = join46(outputPath, MIXED_AUDIO_FILENAME4);
22136
+ if (audioPath !== null && existsSync32(audioPath)) {
22137
+ const sidecar = join49(outputPath, MIXED_AUDIO_FILENAME4);
21764
22138
  cpSync3(audioPath, sidecar);
21765
22139
  }
21766
22140
  let fileSize = 0;
21767
- for (const name of readdirSync11(outputPath)) {
22141
+ for (const name of readdirSync12(outputPath)) {
21768
22142
  try {
21769
- fileSize += statSync13(join46(outputPath, name)).size;
22143
+ fileSize += statSync14(join49(outputPath, name)).size;
21770
22144
  } catch {
21771
22145
  }
21772
22146
  }
@@ -21780,18 +22154,18 @@ function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, start
21780
22154
 
21781
22155
  // src/services/distributed/planV2Execution.ts
21782
22156
  async function renderChunkV2(planV2Dir, chunkIndex, outputChunkPath, options) {
21783
- const workRoot = mkdtempSync10(join47(tmpdir6(), "hf-plan-v2-chunk-"));
21784
- const materializedPlanDir = join47(workRoot, "plan");
22157
+ const workRoot = mkdtempSync10(join50(tmpdir6(), "hf-plan-v2-chunk-"));
22158
+ const materializedPlanDir = join50(workRoot, "plan");
21785
22159
  try {
21786
22160
  materializePlanV2Target(planV2Dir, { role: "chunk", chunkIndex }, materializedPlanDir);
21787
22161
  return await renderChunk(materializedPlanDir, chunkIndex, outputChunkPath, options);
21788
22162
  } finally {
21789
- rmSync19(workRoot, { recursive: true, force: true });
22163
+ rmSync21(workRoot, { recursive: true, force: true });
21790
22164
  }
21791
22165
  }
21792
22166
  async function assembleV2(planV2Dir, chunkPaths, outputPath, options) {
21793
- const workRoot = mkdtempSync10(join47(tmpdir6(), "hf-plan-v2-assembler-"));
21794
- const materializedPlanDir = join47(workRoot, "plan");
22167
+ const workRoot = mkdtempSync10(join50(tmpdir6(), "hf-plan-v2-assembler-"));
22168
+ const materializedPlanDir = join50(workRoot, "plan");
21795
22169
  try {
21796
22170
  const materialized = materializePlanV2Target(
21797
22171
  planV2Dir,
@@ -21806,7 +22180,7 @@ async function assembleV2(planV2Dir, chunkPaths, outputPath, options) {
21806
22180
  options
21807
22181
  );
21808
22182
  } finally {
21809
- rmSync19(workRoot, { recursive: true, force: true });
22183
+ rmSync21(workRoot, { recursive: true, force: true });
21810
22184
  }
21811
22185
  }
21812
22186
 
@@ -21857,7 +22231,7 @@ export {
21857
22231
  createRenderHandlers,
21858
22232
  createRenderJob,
21859
22233
  createRenderRequest,
21860
- createVideoFrameInjector3 as createVideoFrameInjector,
22234
+ createVideoFrameInjector4 as createVideoFrameInjector,
21861
22235
  defaultLogger,
21862
22236
  distributedConfigFromRequest,
21863
22237
  executeRenderJob,