@hyperframes/producer 0.9.3 → 0.9.5

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.
@@ -1,4 +1,9 @@
1
1
  // src/services/distributed/plan.ts
2
+ import {
3
+ REFERENCE_BACKGROUND_FIELD as REFERENCE_BACKGROUND_FIELD2,
4
+ referenceBackgroundResult as referenceBackgroundResult4,
5
+ createReferenceBackgroundRecorder as createReferenceBackgroundRecorder2
6
+ } from "@hyperframes/engine";
2
7
  import { audioFailureDiagnostics as audioFailureDiagnostics3 } from "@hyperframes/engine";
3
8
  import {
4
9
  copyFileSync as copyFileSync6,
@@ -9,7 +14,7 @@ import {
9
14
  rmSync as rmSync7,
10
15
  writeFileSync as writeFileSync7
11
16
  } from "node:fs";
12
- import { extname as extname4, isAbsolute as isAbsolute3, join as join23, relative as relative5, sep as sep2 } from "node:path";
17
+ import { extname as extname4, isAbsolute as isAbsolute3, join as join24, relative as relative5, sep as sep2 } from "node:path";
13
18
  import {
14
19
  fpsToNumber as fpsToNumber9,
15
20
  HF_COLOR_GRADING_ATTR as HF_COLOR_GRADING_ATTR2
@@ -120,6 +125,10 @@ import {
120
125
  augmentProtocolTimeoutError,
121
126
  augmentPageNavigationTimeoutError
122
127
  } from "@hyperframes/engine";
128
+ import {
129
+ referenceBackgroundResult,
130
+ createReferenceBackgroundRecorder
131
+ } from "@hyperframes/engine";
123
132
  import { join as join15, dirname as dirname9, resolve as resolve7 } from "path";
124
133
  import { randomUUID } from "crypto";
125
134
 
@@ -4920,6 +4929,7 @@ async function runProbeStage(input) {
4920
4929
  const motionBlur = job.config.motionBlur;
4921
4930
  const captureFormat = resolveCaptureImageFormat({ needsAlpha, motionBlur });
4922
4931
  const captureOpts = {
4932
+ recordReferenceBackground: input.recordReferenceBackground,
4923
4933
  width,
4924
4934
  height,
4925
4935
  fps: job.config.fps,
@@ -7679,6 +7689,7 @@ async function executeDiskCaptureWithAdaptiveRetry(options) {
7679
7689
  }
7680
7690
  function createRenderJob(config) {
7681
7691
  return {
7692
+ ...referenceBackgroundResult(void 0),
7682
7693
  id: randomUUID(),
7683
7694
  config: {
7684
7695
  ...config,
@@ -8342,9 +8353,69 @@ function buildWeightedChunkSlices(weights, chunkCount, minFrames, maxFrames) {
8342
8353
  return slices;
8343
8354
  }
8344
8355
 
8356
+ // src/services/render/planReferenceBackground.ts
8357
+ import { join as join19 } from "node:path";
8358
+ import {
8359
+ closeCaptureSession as closeCaptureSession10,
8360
+ createCaptureSession as createCaptureSession10,
8361
+ readReferenceBackground,
8362
+ referenceBackgroundResult as referenceBackgroundResult2
8363
+ } from "@hyperframes/engine";
8364
+ async function readPlanReferenceBackground(input) {
8365
+ let server;
8366
+ let session;
8367
+ try {
8368
+ server = await createFileServer({
8369
+ projectDir: input.projectDir,
8370
+ compiledDir: join19(input.workDir, "compiled"),
8371
+ port: 0,
8372
+ preHeadScripts: [VIRTUAL_TIME_SHIM],
8373
+ fps: input.options.fps
8374
+ });
8375
+ session = await createCaptureSession10(
8376
+ server.url,
8377
+ join19(input.workDir, "reference"),
8378
+ {
8379
+ ...input.options,
8380
+ requiresWebGpu: false
8381
+ },
8382
+ null,
8383
+ {
8384
+ ...input.cfg,
8385
+ forceScreenshot: true,
8386
+ useDrawElement: false,
8387
+ enablePageSideCompositing: false
8388
+ }
8389
+ );
8390
+ const page = session.page;
8391
+ await page.goto(`${server.url}/index.html`, {
8392
+ waitUntil: "domcontentloaded",
8393
+ timeout: input.cfg.pageNavigationTimeout
8394
+ });
8395
+ await page.evaluate("window.__hfFlushSync?.()");
8396
+ await page.waitForFunction(
8397
+ '!!(window.__hf && typeof window.__hf.seek === "function" && window.__hf.duration > 0)',
8398
+ { timeout: input.cfg.playerReadyTimeout, polling: 100 }
8399
+ );
8400
+ return referenceBackgroundResult2(await readReferenceBackground(page));
8401
+ } catch (error) {
8402
+ input.log.debug("Reference background unavailable", {
8403
+ error: error instanceof Error ? error.message : String(error)
8404
+ });
8405
+ return referenceBackgroundResult2(void 0);
8406
+ } finally {
8407
+ if (session) await closeCaptureSession10(session).catch(() => void 0);
8408
+ if (server) closeFileServerSafely(server, "reference background", input.log);
8409
+ }
8410
+ }
8411
+
8345
8412
  // src/services/render/stages/freezePlan.ts
8346
8413
  import { existsSync as existsSync14, mkdirSync as mkdirSync7, readFileSync as readFileSync6, readdirSync as readdirSync5, writeFileSync as writeFileSync5 } from "node:fs";
8347
- import { join as join19, relative as relative3, resolve as resolve8 } from "node:path";
8414
+ import { join as join20, relative as relative3, resolve as resolve8 } from "node:path";
8415
+ import {
8416
+ REFERENCE_BACKGROUND_FIELD,
8417
+ referenceBackgroundResult as referenceBackgroundResult3
8418
+ } from "@hyperframes/engine";
8348
8419
 
8349
8420
  // src/services/distributed/planProtocol.ts
8350
8421
  var PLAN_SCHEMA_VERSION = 1;
@@ -8558,7 +8629,7 @@ function listPlanFiles(planDir) {
8558
8629
  function walk(dir) {
8559
8630
  const entries = readdirSync5(dir, { withFileTypes: true });
8560
8631
  for (const entry of entries) {
8561
- const full = join19(dir, entry.name);
8632
+ const full = join20(dir, entry.name);
8562
8633
  if (entry.isDirectory()) {
8563
8634
  walk(full);
8564
8635
  } else if (entry.isFile()) {
@@ -8599,15 +8670,15 @@ var PLAN_REMOTE_FILES_RELATIVE_PATH = "meta/remote-files.json";
8599
8670
  function writePlanRemoteFiles(planDir, files) {
8600
8671
  if (files.length === 0) return;
8601
8672
  const sorted = [...files].sort((a, b) => a.path < b.path ? -1 : 1);
8602
- writeFileSync5(join19(planDir, PLAN_REMOTE_FILES_RELATIVE_PATH), JSON.stringify(sorted), "utf-8");
8673
+ writeFileSync5(join20(planDir, PLAN_REMOTE_FILES_RELATIVE_PATH), JSON.stringify(sorted), "utf-8");
8603
8674
  }
8604
8675
  function readPlanRemoteFiles(planDir) {
8605
- const path = join19(planDir, PLAN_REMOTE_FILES_RELATIVE_PATH);
8676
+ const path = join20(planDir, PLAN_REMOTE_FILES_RELATIVE_PATH);
8606
8677
  return existsSync14(path) ? JSON.parse(readFileSync6(path, "utf-8")) : [];
8607
8678
  }
8608
8679
  function recomputePlanHashFromPlanDir(planDir, digests) {
8609
- const planJsonPath = join19(planDir, "plan.json");
8610
- const encoderJsonPath = join19(planDir, "meta", "encoder.json");
8680
+ const planJsonPath = join20(planDir, "plan.json");
8681
+ const encoderJsonPath = join20(planDir, "meta", "encoder.json");
8611
8682
  if (!existsSync14(planJsonPath)) {
8612
8683
  throw new Error(`[freezePlan] plan.json missing: ${planJsonPath}`);
8613
8684
  }
@@ -8644,18 +8715,18 @@ async function freezePlan(input) {
8644
8715
  if (!existsSync14(planDir)) {
8645
8716
  throw new Error(`[freezePlan] planDir does not exist: ${planDir}`);
8646
8717
  }
8647
- const metaDir = join19(planDir, "meta");
8718
+ const metaDir = join20(planDir, "meta");
8648
8719
  if (!existsSync14(metaDir)) mkdirSync7(metaDir, { recursive: true });
8649
8720
  writeFileSync5(
8650
- join19(metaDir, "composition.json"),
8721
+ join20(metaDir, "composition.json"),
8651
8722
  `${JSON.stringify(composition, null, 2)}
8652
8723
  `,
8653
8724
  "utf-8"
8654
8725
  );
8655
8726
  const encoderForCanonical = stripUndefined(encoder);
8656
8727
  const encoderConfigCanonicalJson = canonicalJsonStringify(encoderForCanonical);
8657
- writeFileSync5(join19(metaDir, "encoder.json"), encoderConfigCanonicalJson, "utf-8");
8658
- writeFileSync5(join19(metaDir, "chunks.json"), `${JSON.stringify(chunks, null, 2)}
8728
+ writeFileSync5(join20(metaDir, "encoder.json"), encoderConfigCanonicalJson, "utf-8");
8729
+ writeFileSync5(join20(metaDir, "chunks.json"), `${JSON.stringify(chunks, null, 2)}
8659
8730
  `, "utf-8");
8660
8731
  const { compositionHtml, assets } = collectPlanAssetShas(planDir, digests);
8661
8732
  const planHash = computePlanHash({
@@ -8668,6 +8739,7 @@ async function freezePlan(input) {
8668
8739
  dimensions
8669
8740
  });
8670
8741
  const planJson = {
8742
+ ...referenceBackgroundResult3(composition[REFERENCE_BACKGROUND_FIELD]),
8671
8743
  protocol: PLAN_PROTOCOL_V1,
8672
8744
  planHash,
8673
8745
  producerVersion,
@@ -8679,7 +8751,7 @@ async function freezePlan(input) {
8679
8751
  duration: durationSeconds,
8680
8752
  hasAudio
8681
8753
  };
8682
- const planJsonPath = join19(planDir, "plan.json");
8754
+ const planJsonPath = join20(planDir, "plan.json");
8683
8755
  writeFileSync5(planJsonPath, `${JSON.stringify(planJson, null, 2)}
8684
8756
  `, "utf-8");
8685
8757
  return { planJsonPath, planHash };
@@ -8723,14 +8795,14 @@ function applyRuntimeEnvSnapshot(snapshot, env = process.env) {
8723
8795
  // src/services/distributed/chunkAudio.ts
8724
8796
  import { closeSync as closeSync4, existsSync as existsSync15, ftruncateSync, mkdirSync as mkdirSync9, openSync as openSync4, readFileSync as readFileSync7 } from "node:fs";
8725
8797
  import { writeFileSync as writeFileSync6, writeSync } from "node:fs";
8726
- import { join as join21 } from "node:path";
8798
+ import { join as join22 } from "node:path";
8727
8799
  import { appendRenderProvenanceArgs, describeFfmpegFailure as describeFfmpegFailure4, runFfmpeg as runFfmpeg7 } from "@hyperframes/engine";
8728
8800
 
8729
8801
  // src/services/distributed/mp4Stitch.ts
8730
8802
  import { createReadStream as createReadStream4, createWriteStream, mkdirSync as mkdirSync8, mkdtempSync as mkdtempSync2, rmSync as rmSync6 } from "node:fs";
8731
8803
  import { open } from "node:fs/promises";
8732
8804
  import { renameWithRetry as renameWithRetry7 } from "@hyperframes/engine";
8733
- import { dirname as dirname11, join as join20 } from "node:path";
8805
+ import { dirname as dirname11, join as join21 } from "node:path";
8734
8806
  import { finished, pipeline } from "node:stream/promises";
8735
8807
  function boxAt(src, off, at, limit) {
8736
8808
  let size = src.readUInt32BE(off);
@@ -9096,13 +9168,13 @@ async function writeStitchedMp4(chunkPaths, outputPath, audioPath) {
9096
9168
  const header = audioPath ? buildStitchedHeader(layouts.slice(1), layouts[0]) : buildStitchedHeader(layouts);
9097
9169
  mkdirSync8(dirname11(outputPath), { recursive: true });
9098
9170
  const workDir = mkdtempSync2(`${outputPath}.stitch-`);
9099
- const out = createWriteStream(join20(workDir, "out.mp4"));
9171
+ const out = createWriteStream(join21(workDir, "out.mp4"));
9100
9172
  try {
9101
9173
  out.write(header);
9102
9174
  for (const [i, path] of sourcePaths.entries()) await copyPayload(path, layouts[i], i, out);
9103
9175
  out.end();
9104
9176
  await finished(out);
9105
- renameWithRetry7(join20(workDir, "out.mp4"), outputPath);
9177
+ renameWithRetry7(join21(workDir, "out.mp4"), outputPath);
9106
9178
  } finally {
9107
9179
  out.destroy();
9108
9180
  rmSync6(workDir, { recursive: true, force: true });
@@ -9196,7 +9268,7 @@ async function writeChunkAudioSlices(input) {
9196
9268
  });
9197
9269
  const bytes = readFileSync7(input.audioPath);
9198
9270
  const layout = { sizeBytes: bytes.length, payloadStart, payloadEnd, partStarts: {} };
9199
- mkdirSync9(join21(input.planDir, SLICE_DIR), { recursive: true });
9271
+ mkdirSync9(join22(input.planDir, SLICE_DIR), { recursive: true });
9200
9272
  input.chunks.forEach((_, index) => {
9201
9273
  const cut = audioCutForChunk(grid, input.chunks, index, input.fps);
9202
9274
  if (!cut) return;
@@ -9204,17 +9276,17 @@ async function writeChunkAudioSlices(input) {
9204
9276
  const start = offsets[cut.firstPacket];
9205
9277
  layout.partStarts[index] = start;
9206
9278
  const end = offsets[last] + track.sizes[last];
9207
- writeFileSync6(join21(input.planDir, chunkAudioSlicePath(index)), bytes.subarray(start, end));
9279
+ writeFileSync6(join22(input.planDir, chunkAudioSlicePath(index)), bytes.subarray(start, end));
9208
9280
  });
9209
9281
  const frame = [bytes.subarray(0, payloadStart), bytes.subarray(payloadEnd)];
9210
- writeFileSync6(join21(input.planDir, SLICE_FRAME), Buffer.concat(frame));
9211
- writeFileSync6(join21(input.planDir, SLICE_LAYOUT), JSON.stringify(layout));
9282
+ writeFileSync6(join22(input.planDir, SLICE_FRAME), Buffer.concat(frame));
9283
+ writeFileSync6(join22(input.planDir, SLICE_LAYOUT), JSON.stringify(layout));
9212
9284
  }
9213
9285
  function chunkAudioSource(planDir, chunkIndex, scratchPath) {
9214
- const layoutPath = join21(planDir, SLICE_LAYOUT);
9286
+ const layoutPath = join22(planDir, SLICE_LAYOUT);
9215
9287
  if (!existsSync15(layoutPath)) return resolvePlanAudioPath(planDir);
9216
9288
  const layout = JSON.parse(readFileSync7(layoutPath, "utf-8"));
9217
- const frame = readFileSync7(join21(planDir, SLICE_FRAME));
9289
+ const frame = readFileSync7(join22(planDir, SLICE_FRAME));
9218
9290
  const fd = openSync4(scratchPath, "w");
9219
9291
  try {
9220
9292
  writeSync(fd, frame, 0, layout.payloadStart, 0);
@@ -9227,7 +9299,7 @@ function chunkAudioSource(planDir, chunkIndex, scratchPath) {
9227
9299
  );
9228
9300
  const start = layout.partStarts[chunkIndex];
9229
9301
  if (start !== void 0) {
9230
- const part = readFileSync7(join21(planDir, chunkAudioSlicePath(chunkIndex)));
9302
+ const part = readFileSync7(join22(planDir, chunkAudioSlicePath(chunkIndex)));
9231
9303
  writeSync(fd, part, 0, part.length, start);
9232
9304
  }
9233
9305
  ftruncateSync(fd, layout.sizeBytes);
@@ -9283,7 +9355,7 @@ async function muxChunkWithAudio(input) {
9283
9355
  // src/services/distributed/planSize.ts
9284
9356
  import { createHash as createHash7 } from "node:crypto";
9285
9357
  import { lstatSync, readdirSync as readdirSync6 } from "node:fs";
9286
- import { extname as extname3, join as join22, relative as relative4 } from "node:path";
9358
+ import { extname as extname3, join as join23, relative as relative4 } from "node:path";
9287
9359
  var MAX_TOP_COMPONENTS = 10;
9288
9360
  var SOURCE_MEDIA_EXTENSIONS = /* @__PURE__ */ new Set([
9289
9361
  ".aac",
@@ -9395,7 +9467,7 @@ function walkRegularFiles(dir, visit) {
9395
9467
  return;
9396
9468
  }
9397
9469
  for (const entry of entries) {
9398
- const full = join22(dir, entry.name);
9470
+ const full = join23(dir, entry.name);
9399
9471
  if (entry.isDirectory()) {
9400
9472
  walkRegularFiles(full, visit);
9401
9473
  } else if (entry.isFile()) {
@@ -9611,13 +9683,13 @@ function assertPositiveInteger(name, value) {
9611
9683
  }
9612
9684
  var FREEZE_OWNED_PLAN_FILES = [
9613
9685
  "plan.json",
9614
- join23("meta", "composition.json"),
9615
- join23("meta", "encoder.json"),
9616
- join23("meta", "chunks.json")
9686
+ join24("meta", "composition.json"),
9687
+ join24("meta", "encoder.json"),
9688
+ join24("meta", "chunks.json")
9617
9689
  ];
9618
9690
  function removeFreezeOwnedPlanFiles(planDir) {
9619
9691
  for (const relativePath of FREEZE_OWNED_PLAN_FILES) {
9620
- rmSync7(join23(planDir, relativePath), { force: true });
9692
+ rmSync7(join24(planDir, relativePath), { force: true });
9621
9693
  }
9622
9694
  }
9623
9695
  function buildChunkSlices(totalFrames, chunkCount, effectiveChunkSize) {
@@ -9772,19 +9844,19 @@ function createVideoSourceShipper(input) {
9772
9844
  const known = shipped.get(srcPath);
9773
9845
  if (known) return known;
9774
9846
  const stagedRel = pathInside(input.workVideoFramesDir, srcPath);
9775
- const current = stagedRel === null ? srcPath : join23(input.planDir, "video-frames", stagedRel);
9847
+ const current = stagedRel === null ? srcPath : join24(input.planDir, "video-frames", stagedRel);
9776
9848
  let result = null;
9777
9849
  for (const root of stagedRel === null ? [input.workCompiledDir, input.projectDir] : []) {
9778
9850
  const rel = pathInside(root, srcPath);
9779
- if (rel !== null && (existsSync16(join23(input.planDir, "compiled", rel)) || input.isRemote(srcPath))) {
9851
+ if (rel !== null && (existsSync16(join24(input.planDir, "compiled", rel)) || input.isRemote(srcPath))) {
9780
9852
  result = ["compiled", ...rel.split(sep2)].join("/");
9781
9853
  break;
9782
9854
  }
9783
9855
  }
9784
9856
  if (result === null) {
9785
9857
  result = `video-sources/${shipped.size}${extname4(current)}`;
9786
- const destination = join23(input.planDir, ...result.split("/"));
9787
- mkdirSync10(join23(input.planDir, "video-sources"), { recursive: true });
9858
+ const destination = join24(input.planDir, ...result.split("/"));
9859
+ mkdirSync10(join24(input.planDir, "video-sources"), { recursive: true });
9788
9860
  try {
9789
9861
  linkSync(current, destination);
9790
9862
  } catch {
@@ -9833,14 +9905,14 @@ async function compilePlanComposition(projectDir, config, planDir, options) {
9833
9905
  variables: config.variables
9834
9906
  });
9835
9907
  const entryFile = config.entryFile ?? "index.html";
9836
- const htmlPath = join23(projectDir, entryFile);
9908
+ const htmlPath = join24(projectDir, entryFile);
9837
9909
  if (!existsSync16(htmlPath)) {
9838
9910
  throw new Error(`[plan] entry file not found: ${htmlPath}`);
9839
9911
  }
9840
- const workDir = join23(planDir, ".plan-work");
9912
+ const workDir = join24(planDir, ".plan-work");
9841
9913
  rmSync7(workDir, { recursive: true, force: true });
9842
9914
  mkdirSync10(workDir, { recursive: true });
9843
- const compiledDir = join23(workDir, "compiled");
9915
+ const compiledDir = join24(workDir, "compiled");
9844
9916
  mkdirSync10(compiledDir, { recursive: true });
9845
9917
  let stageStarted = Date.now();
9846
9918
  cpSync2(projectDir, compiledDir, {
@@ -9854,10 +9926,10 @@ async function compilePlanComposition(projectDir, config, planDir, options) {
9854
9926
  }
9855
9927
  });
9856
9928
  logPlanStage(log, "copy", stageStarted);
9857
- const finalCompiledDir = join23(planDir, "compiled");
9929
+ const finalCompiledDir = join24(planDir, "compiled");
9858
9930
  const needsAlpha = outputNeedsAlpha(config.format);
9859
9931
  const remoteFiles = Object.entries(options.remoteFiles ?? {});
9860
- const remoteLocalPath = (rel) => join23(projectDir, ...rel.split("/"));
9932
+ const remoteLocalPath = (rel) => join24(projectDir, ...rel.split("/"));
9861
9933
  for (const [, file] of remoteFiles) {
9862
9934
  declareContentAddressedUrl(file.url, file.sha256);
9863
9935
  if (file.probeFactsUrl) declareProbeFactsUrl(file.url, file.probeFactsUrl);
@@ -9907,6 +9979,9 @@ async function compilePlanComposition(projectDir, config, planDir, options) {
9907
9979
  }
9908
9980
  stageStarted = Date.now();
9909
9981
  const probeResult = await runProbeStage({
9982
+ recordReferenceBackground: createReferenceBackgroundRecorder2(
9983
+ (result) => Object.assign(job, result)
9984
+ ),
9910
9985
  projectDir,
9911
9986
  workDir,
9912
9987
  job,
@@ -9924,6 +9999,21 @@ async function compilePlanComposition(projectDir, config, planDir, options) {
9924
9999
  subTimelineWaitMemo: {}
9925
10000
  });
9926
10001
  logPlanStage(log, "probe", stageStarted);
10002
+ if (!probeResult.probeSession) {
10003
+ stageStarted = Date.now();
10004
+ Object.assign(
10005
+ job,
10006
+ await readPlanReferenceBackground({
10007
+ projectDir,
10008
+ workDir,
10009
+ cfg,
10010
+ log,
10011
+ options: { width, height, fps: job.config.fps, variables: job.config.variables }
10012
+ })
10013
+ );
10014
+ assertNotAborted();
10015
+ logPlanStage(log, "reference_background", stageStarted);
10016
+ }
9927
10017
  compiled = probeResult.compiled;
9928
10018
  job.duration = probeResult.duration;
9929
10019
  job.totalFrames = probeResult.totalFrames;
@@ -9936,8 +10026,8 @@ async function compilePlanComposition(projectDir, config, planDir, options) {
9936
10026
  if (probeResult.fileServer) closeFileServerSafely(probeResult.fileServer, "plan", log);
9937
10027
  if (probeResult.probeSession) {
9938
10028
  try {
9939
- const { closeCaptureSession: closeCaptureSession11 } = await import("@hyperframes/engine");
9940
- await closeCaptureSession11(probeResult.probeSession);
10029
+ const { closeCaptureSession: closeCaptureSession12 } = await import("@hyperframes/engine");
10030
+ await closeCaptureSession12(probeResult.probeSession);
9941
10031
  } catch (err) {
9942
10032
  log.warn("[plan] probe session close failed", {
9943
10033
  error: err instanceof Error ? err.message : String(err)
@@ -10043,10 +10133,10 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
10043
10133
  let stageStarted = Date.now();
10044
10134
  const extractResult = await extractPlanVideos(c, options.deferVideoExtraction);
10045
10135
  logPlanStage(log, "extract", stageStarted);
10046
- const stagedVideoFrames = join23(compiledDir, "__hyperframes_video_frames");
10047
- const videoFramesDst = join23(planDir, "video-frames");
10136
+ const stagedVideoFrames = join24(compiledDir, "__hyperframes_video_frames");
10137
+ const videoFramesDst = join24(planDir, "video-frames");
10048
10138
  if (existsSync16(videoFramesDst)) rmSync7(videoFramesDst, { recursive: true, force: true });
10049
- const videoSourcesDir = join23(planDir, "video-sources");
10139
+ const videoSourcesDir = join24(planDir, "video-sources");
10050
10140
  rmSync7(videoSourcesDir, { recursive: true, force: true });
10051
10141
  if (existsSync16(stagedVideoFrames)) {
10052
10142
  renameWithRetry8(stagedVideoFrames, videoFramesDst);
@@ -10083,7 +10173,7 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
10083
10173
  };
10084
10174
  })
10085
10175
  });
10086
- mkdirSync10(join23(planDir, "meta"), { recursive: true });
10176
+ mkdirSync10(join24(planDir, "meta"), { recursive: true });
10087
10177
  writePlanRemoteFiles(
10088
10178
  planDir,
10089
10179
  remoteFiles.map(([rel, { sha256, sizeBytes }]) => ({
@@ -10093,7 +10183,7 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
10093
10183
  }))
10094
10184
  );
10095
10185
  writeFileSync7(
10096
- join23(planDir, PLAN_VIDEOS_META_RELATIVE_PATH),
10186
+ join24(planDir, PLAN_VIDEOS_META_RELATIVE_PATH),
10097
10187
  JSON.stringify(planVideosJson, null, 2),
10098
10188
  "utf-8"
10099
10189
  );
@@ -10172,7 +10262,7 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
10172
10262
  if (audioResult?.audioError) {
10173
10263
  applyDistributedAudioWarningPolicy(job, audioResult.audioError, audioResult.audioFailures, log);
10174
10264
  }
10175
- const planAudioPath = join23(planDir, PLAN_AUDIO_RELATIVE_PATH);
10265
+ const planAudioPath = join24(planDir, PLAN_AUDIO_RELATIVE_PATH);
10176
10266
  if (audioResult?.hasAudio && existsSync16(audioResult.audioOutputPath)) {
10177
10267
  renameWithRetry8(audioResult.audioOutputPath, planAudioPath);
10178
10268
  }
@@ -10205,6 +10295,7 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
10205
10295
  separateAudio
10206
10296
  });
10207
10297
  const compositionJson = {
10298
+ ...referenceBackgroundResult4(job[REFERENCE_BACKGROUND_FIELD2]),
10208
10299
  durationSeconds: job.duration ?? 0,
10209
10300
  width,
10210
10301
  height,
@@ -10260,6 +10351,7 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
10260
10351
  log
10261
10352
  });
10262
10353
  return {
10354
+ ...referenceBackgroundResult4(job[REFERENCE_BACKGROUND_FIELD2]),
10263
10355
  executionPlanDir: planDir,
10264
10356
  executionPlanHash: planHash,
10265
10357
  chunkCount,
@@ -10296,6 +10388,10 @@ async function buildPlanAudio(projectDir, config, workRoot, outputPath, options
10296
10388
  }
10297
10389
 
10298
10390
  // src/services/distributed/planV2.ts
10391
+ import {
10392
+ REFERENCE_BACKGROUND_FIELD as REFERENCE_BACKGROUND_FIELD3,
10393
+ referenceBackgroundResult as referenceBackgroundResult5
10394
+ } from "@hyperframes/engine";
10299
10395
  import {
10300
10396
  copyFileSync as copyFileSync8,
10301
10397
  existsSync as existsSync18,
@@ -10309,7 +10405,7 @@ import {
10309
10405
  writeFileSync as writeFileSync9
10310
10406
  } from "node:fs";
10311
10407
  import { tmpdir } from "node:os";
10312
- import { dirname as dirname13, isAbsolute as isAbsolute4, join as join26, relative as relative6, resolve as resolve9, sep as sep3 } from "node:path";
10408
+ import { dirname as dirname13, isAbsolute as isAbsolute4, join as join27, relative as relative6, resolve as resolve9, sep as sep3 } from "node:path";
10313
10409
  import { DISTRIBUTED_FPS, isDistributedFps } from "@hyperframes/core";
10314
10410
  import { createFrameLookupTable as createFrameLookupTable4, renameWithRetry as renameWithRetry10 } from "@hyperframes/engine";
10315
10411
 
@@ -10325,7 +10421,7 @@ import {
10325
10421
  writeFileSync as writeFileSync8
10326
10422
  } from "node:fs";
10327
10423
  import { renameWithRetry as renameWithRetry9 } from "@hyperframes/engine";
10328
- import { dirname as dirname12, join as join25 } from "node:path";
10424
+ import { dirname as dirname12, join as join26 } from "node:path";
10329
10425
 
10330
10426
  // src/services/distributed/planV2Errors.ts
10331
10427
  var PLAN_V2_INTEGRITY_UNRECOVERABLE = "PLAN_V2_INTEGRITY_UNRECOVERABLE";
@@ -10340,7 +10436,7 @@ var PlanV2IntegrityError = class extends Error {
10340
10436
  };
10341
10437
 
10342
10438
  // src/services/distributed/planV2Layout.ts
10343
- import { join as join24 } from "node:path";
10439
+ import { join as join25 } from "node:path";
10344
10440
  var SHA256_PATTERN = /^[0-9a-f]{64}$/;
10345
10441
  function assertPlanV2Sha256(value, field) {
10346
10442
  if (typeof value !== "string" || !SHA256_PATTERN.test(value)) {
@@ -10350,7 +10446,7 @@ function assertPlanV2Sha256(value, field) {
10350
10446
  }
10351
10447
  function planV2BlobPath(root, sha256) {
10352
10448
  const digest = assertPlanV2Sha256(sha256, "artifact sha256");
10353
- return join24(root, "artifacts", "sha256", digest.slice(0, 2), digest);
10449
+ return join25(root, "artifacts", "sha256", digest.slice(0, 2), digest);
10354
10450
  }
10355
10451
 
10356
10452
  // src/services/distributed/planV2Publisher.ts
@@ -10391,7 +10487,7 @@ var LocalPlanV2ArtifactPublisher = class {
10391
10487
  this.destinationDir = destinationDir;
10392
10488
  this.#linkFile = options.linkFile ?? linkSync2;
10393
10489
  mkdirSync11(dirname12(destinationDir), { recursive: true });
10394
- this.temporaryDir = mkdtempSync3(join25(dirname12(destinationDir), ".plan-v2-publish-"));
10490
+ this.temporaryDir = mkdtempSync3(join26(dirname12(destinationDir), ".plan-v2-publish-"));
10395
10491
  }
10396
10492
  async putBlob(blob) {
10397
10493
  const digest = assertPlanV2Sha256(blob.sha256, "published blob sha256");
@@ -10404,8 +10500,8 @@ var LocalPlanV2ArtifactPublisher = class {
10404
10500
  const destinationPath = planV2BlobPath(this.temporaryDir, digest);
10405
10501
  if (existsSync17(destinationPath)) return;
10406
10502
  mkdirSync11(dirname12(destinationPath), { recursive: true });
10407
- const stagingDir = mkdtempSync3(join25(dirname12(destinationPath), ".plan-v2-blob-"));
10408
- const temporaryPath = join25(stagingDir, "blob");
10503
+ const stagingDir = mkdtempSync3(join26(dirname12(destinationPath), ".plan-v2-blob-"));
10504
+ const temporaryPath = join26(stagingDir, "blob");
10409
10505
  try {
10410
10506
  try {
10411
10507
  this.#linkFile(blob.sourcePath, temporaryPath);
@@ -10426,7 +10522,7 @@ var LocalPlanV2ArtifactPublisher = class {
10426
10522
  );
10427
10523
  }
10428
10524
  }
10429
- writeFileSync8(join25(this.temporaryDir, "plan.json"), manifestBytes, "utf-8");
10525
+ writeFileSync8(join26(this.temporaryDir, "plan.json"), manifestBytes, "utf-8");
10430
10526
  renameWithRetry9(this.temporaryDir, this.destinationDir);
10431
10527
  this.#committed = true;
10432
10528
  }
@@ -10471,7 +10567,7 @@ function listFiles(root) {
10471
10567
  const rootResolved = resolve9(root);
10472
10568
  function walk(dir) {
10473
10569
  for (const entry of readdirSync7(dir, { withFileTypes: true })) {
10474
- const absolutePath = join26(dir, entry.name);
10570
+ const absolutePath = join27(dir, entry.name);
10475
10571
  if (entry.isDirectory()) {
10476
10572
  walk(absolutePath);
10477
10573
  } else if (entry.isFile()) {
@@ -10498,13 +10594,13 @@ function assertValidExtractionCacheCompleteSentinel(path) {
10498
10594
  }
10499
10595
  }
10500
10596
  function validateExtractionCacheCompleteSentinels(executionPlanDir) {
10501
- const videoRoot = join26(executionPlanDir, "video-frames");
10597
+ const videoRoot = join27(executionPlanDir, "video-frames");
10502
10598
  if (!existsSync18(videoRoot)) return;
10503
10599
  for (const videoEntry of readdirSync7(videoRoot, { withFileTypes: true })) {
10504
10600
  if (!videoEntry.isDirectory()) continue;
10505
- const videoDir = join26(videoRoot, videoEntry.name);
10601
+ const videoDir = join27(videoRoot, videoEntry.name);
10506
10602
  if (readdirSync7(videoDir).includes(EXTRACTION_CACHE_COMPLETE_SENTINEL)) {
10507
- const sentinelPath = join26(videoDir, EXTRACTION_CACHE_COMPLETE_SENTINEL);
10603
+ const sentinelPath = join27(videoDir, EXTRACTION_CACHE_COMPLETE_SENTINEL);
10508
10604
  assertValidExtractionCacheCompleteSentinel(sentinelPath);
10509
10605
  }
10510
10606
  }
@@ -10551,7 +10647,7 @@ function listVideoFramePaths(executionPlanDir, videos) {
10551
10647
  if (video.deferredRange) {
10552
10648
  const framePaths2 = /* @__PURE__ */ new Map();
10553
10649
  for (let index = 0; index < video.totalFrames; index++) {
10554
- framePaths2.set(index, join26(outputDir, String(index)));
10650
+ framePaths2.set(index, join27(outputDir, String(index)));
10555
10651
  }
10556
10652
  return { ...video, outputDir, framePaths: framePaths2, ownedByLookup: false };
10557
10653
  }
@@ -10559,7 +10655,7 @@ function listVideoFramePaths(executionPlanDir, videos) {
10559
10655
  const framePaths = /* @__PURE__ */ new Map();
10560
10656
  for (const frameName of frameNames) {
10561
10657
  if (frameName === EXTRACTION_CACHE_COMPLETE_SENTINEL) {
10562
- assertValidExtractionCacheCompleteSentinel(join26(outputDir, frameName));
10658
+ assertValidExtractionCacheCompleteSentinel(join27(outputDir, frameName));
10563
10659
  continue;
10564
10660
  }
10565
10661
  const match = /(\d+)(?=\.[^.]+$)/.exec(frameName);
@@ -10578,7 +10674,7 @@ function listVideoFramePaths(executionPlanDir, videos) {
10578
10674
  `duplicate extracted frame index ${frameIndex} in ${outputDir}`
10579
10675
  );
10580
10676
  }
10581
- framePaths.set(frameIndex, join26(outputDir, frameName));
10677
+ framePaths.set(frameIndex, join27(outputDir, frameName));
10582
10678
  }
10583
10679
  return {
10584
10680
  ...video,
@@ -10599,7 +10695,7 @@ function parsePlanVideosJson2(value) {
10599
10695
  }
10600
10696
  }
10601
10697
  function materializeExtractedVideoDirectories(planDir) {
10602
- const videosPath = join26(planDir, PLAN_VIDEOS_META_RELATIVE_PATH);
10698
+ const videosPath = join27(planDir, PLAN_VIDEOS_META_RELATIVE_PATH);
10603
10699
  if (!existsSync18(videosPath)) return;
10604
10700
  const videos = parsePlanVideosJson2(readJsonFile(videosPath, PLAN_VIDEOS_META_RELATIVE_PATH));
10605
10701
  for (const video of videos.extracted) {
@@ -10633,13 +10729,13 @@ function parseChunkSlices(value) {
10633
10729
  });
10634
10730
  }
10635
10731
  function buildVideoChunkDependencies(executionPlanDir, dimensions) {
10636
- const videoRoot = join26(executionPlanDir, "video-frames");
10732
+ const videoRoot = join27(executionPlanDir, "video-frames");
10637
10733
  const hasExtractedFrames = existsSync18(videoRoot) && listFiles(videoRoot).length > 0;
10638
- const videosPath = join26(executionPlanDir, PLAN_VIDEOS_META_RELATIVE_PATH);
10734
+ const videosPath = join27(executionPlanDir, PLAN_VIDEOS_META_RELATIVE_PATH);
10639
10735
  if (!existsSync18(videosPath)) {
10640
10736
  return hasExtractedFrames ? { mode: "full-source-pack", dependencies: null } : { mode: "exact-rendered-frames", dependencies: /* @__PURE__ */ new Map() };
10641
10737
  }
10642
- const chunksPath = join26(executionPlanDir, "meta", "chunks.json");
10738
+ const chunksPath = join27(executionPlanDir, "meta", "chunks.json");
10643
10739
  const videos = readJsonFile(videosPath, PLAN_VIDEOS_META_RELATIVE_PATH);
10644
10740
  const chunks = readJsonFile(chunksPath, "meta/chunks.json");
10645
10741
  const parsedVideos = parsePlanVideosJson2(videos);
@@ -10695,8 +10791,8 @@ function computeManifestHash(manifest) {
10695
10791
  function writeBlob(sourcePath, destinationPath) {
10696
10792
  if (existsSync18(destinationPath)) return;
10697
10793
  mkdirSync12(dirname13(destinationPath), { recursive: true });
10698
- const temporaryDir = mkdtempSync4(join26(dirname13(destinationPath), ".plan-v2-blob-"));
10699
- const temporaryPath = join26(temporaryDir, "blob");
10794
+ const temporaryDir = mkdtempSync4(join27(dirname13(destinationPath), ".plan-v2-blob-"));
10795
+ const temporaryPath = join27(temporaryDir, "blob");
10700
10796
  try {
10701
10797
  copyFileSync8(sourcePath, temporaryPath);
10702
10798
  renameWithRetry10(temporaryPath, destinationPath);
@@ -10732,7 +10828,7 @@ async function putTasks(tasks) {
10732
10828
  if (failure) throw failure.reason;
10733
10829
  }
10734
10830
  function buildPlanV2Publication(executionPlanDir, digests) {
10735
- const executionPlanPath = join26(executionPlanDir, "plan.json");
10831
+ const executionPlanPath = join27(executionPlanDir, "plan.json");
10736
10832
  if (!existsSync18(executionPlanPath)) {
10737
10833
  throw new PlanV2IntegrityError(`execution plan is missing plan.json: ${executionPlanPath}`);
10738
10834
  }
@@ -10760,7 +10856,7 @@ function buildPlanV2Publication(executionPlanDir, digests) {
10760
10856
  }
10761
10857
  validateExtractionCacheCompleteSentinels(executionPlanDir);
10762
10858
  const videoDependencyPlan = buildVideoChunkDependencies(executionPlanDir, dimensions);
10763
- const encoder = readJsonFile(join26(executionPlanDir, "meta", "encoder.json"), "meta/encoder.json");
10859
+ const encoder = readJsonFile(join27(executionPlanDir, "meta", "encoder.json"), "meta/encoder.json");
10764
10860
  const files = listFiles(executionPlanDir);
10765
10861
  const chunkAudio = !isRecord3(encoder) || encoder.chunksCarryAudio !== true ? "none" : files.some((file) => chunkAudioSliceTarget(file.path) !== null) ? "sliced" : "whole";
10766
10862
  for (const file of files) {
@@ -10821,13 +10917,13 @@ function createPlanV2FromExecutionPlan(executionPlanDir, planV2Dir) {
10821
10917
  throw new PlanV2IntegrityError("a local plan v2 directory cannot reference remote files");
10822
10918
  }
10823
10919
  mkdirSync12(dirname13(planV2Dir), { recursive: true });
10824
- const tempDir = mkdtempSync4(join26(dirname13(planV2Dir), ".plan-v2-build-"));
10920
+ const tempDir = mkdtempSync4(join27(dirname13(planV2Dir), ".plan-v2-build-"));
10825
10921
  try {
10826
10922
  for (const blob of publication.blobs) {
10827
10923
  writeBlob(blob.sourcePath, planV2BlobPath(tempDir, blob.sha256));
10828
10924
  }
10829
10925
  writeFileSync9(
10830
- join26(tempDir, "plan.json"),
10926
+ join27(tempDir, "plan.json"),
10831
10927
  canonicalJsonStringify(publication.manifest),
10832
10928
  "utf-8"
10833
10929
  );
@@ -10868,7 +10964,7 @@ async function publishExecutionPlan(executionPlanDir, publisher, { digests, log
10868
10964
  }
10869
10965
  }
10870
10966
  async function planV2WithPublisher(projectDir, config, publisher, options = {}) {
10871
- const stagingRoot = mkdtempSync4(join26(options.stagingParentDir ?? tmpdir(), ".execution-plan-"));
10967
+ const stagingRoot = mkdtempSync4(join27(options.stagingParentDir ?? tmpdir(), ".execution-plan-"));
10872
10968
  const digests = /* @__PURE__ */ new Map();
10873
10969
  try {
10874
10970
  await buildLocalExecutionPlan(projectDir, config, stagingRoot, {
@@ -10879,7 +10975,7 @@ async function planV2WithPublisher(projectDir, config, publisher, options = {})
10879
10975
  // artifactTargets ships every compiled/ file to all chunks, so each is in the manifest.
10880
10976
  whileAudioMixes: () => putBlobs(
10881
10977
  publisher,
10882
- listFiles(join26(stagingRoot, "compiled")).map(
10978
+ listFiles(join27(stagingRoot, "compiled")).map(
10883
10979
  (file) => publishBlobFor(file.absolutePath, digests)
10884
10980
  )
10885
10981
  ),
@@ -10909,9 +11005,14 @@ async function planV2(projectDir, config, planV2Dir) {
10909
11005
  return resultFromManifest(planV2Dir, manifest);
10910
11006
  }
10911
11007
  function resultFromManifest(planV2Dir, manifest) {
11008
+ const artifact = manifest.artifacts.find((entry) => entry.path === "plan.json");
11009
+ const executionPlan = artifact ? readJsonFile(verifyBlob(planV2Dir, artifact), "execution plan.json") : null;
10912
11010
  return {
11011
+ ...referenceBackgroundResult5(
11012
+ isRecord3(executionPlan) ? executionPlan[REFERENCE_BACKGROUND_FIELD3] : void 0
11013
+ ),
10913
11014
  planDir: planV2Dir,
10914
- manifestPath: join26(planV2Dir, "plan.json"),
11015
+ manifestPath: join27(planV2Dir, "plan.json"),
10915
11016
  planProtocol: PLAN_PROTOCOL_V2,
10916
11017
  planHash: manifest.planHash,
10917
11018
  sourcePlanV1Hash: getPlanV2ExecutionPlanHash(manifest),
@@ -11047,7 +11148,7 @@ function parsePlanV2Manifest(value) {
11047
11148
  return parsed;
11048
11149
  }
11049
11150
  function readPlanV2Manifest(planV2Dir) {
11050
- const manifestPath = join26(planV2Dir, "plan.json");
11151
+ const manifestPath = join27(planV2Dir, "plan.json");
11051
11152
  if (!existsSync18(manifestPath)) {
11052
11153
  throw new PlanV2IntegrityError(`missing v2 manifest: ${manifestPath}`);
11053
11154
  }
@@ -11093,16 +11194,16 @@ function materializePlanV2Target(planV2Dir, target, destinationDir, options = {}
11093
11194
  sourcePath: verifyBlob(planV2Dir, artifact)
11094
11195
  }));
11095
11196
  mkdirSync12(dirname13(destinationDir), { recursive: true });
11096
- const tempDir = mkdtempSync4(join26(dirname13(destinationDir), ".plan-v2-materialize-"));
11197
+ const tempDir = mkdtempSync4(join27(dirname13(destinationDir), ".plan-v2-materialize-"));
11097
11198
  try {
11098
11199
  for (const { artifact, sourcePath } of verified) {
11099
- const destinationPath = join26(tempDir, ...artifact.path.split("/"));
11200
+ const destinationPath = join27(tempDir, ...artifact.path.split("/"));
11100
11201
  mkdirSync12(dirname13(destinationPath), { recursive: true });
11101
11202
  copyFileSync8(sourcePath, destinationPath);
11102
11203
  }
11103
11204
  if (target.role === "chunk") materializeExtractedVideoDirectories(tempDir);
11104
11205
  writeFileSync9(
11105
- join26(tempDir, PLAN_V2_MATERIALIZATION_MARKER),
11206
+ join27(tempDir, PLAN_V2_MATERIALIZATION_MARKER),
11106
11207
  canonicalJsonStringify({
11107
11208
  manifest,
11108
11209
  target,
@@ -11125,7 +11226,7 @@ function materializePlanV2Target(planV2Dir, target, destinationDir, options = {}
11125
11226
  // Join the artifact's OWN name, not the current constant: a plan written
11126
11227
  // before the container change carries the legacy name, and materializing it
11127
11228
  // under the new one would point at a file that was never written.
11128
- audioPath: target.role === "assembler" ? audioArtifact ? join26(destinationDir, audioArtifact.path) : null : null
11229
+ audioPath: target.role === "assembler" ? audioArtifact ? join27(destinationDir, audioArtifact.path) : null : null
11129
11230
  };
11130
11231
  }
11131
11232
  function assertRemoteSources(remoteSources, artifacts) {
@@ -11144,7 +11245,7 @@ function validatePlanV2MaterializedTarget(planDir, expectedTarget) {
11144
11245
  return readValidatedPlanV2Materialization(planDir, expectedTarget)?.manifest ?? null;
11145
11246
  }
11146
11247
  function readValidatedPlanV2Materialization(planDir, expectedTarget) {
11147
- const markerPath = join26(planDir, PLAN_V2_MATERIALIZATION_MARKER);
11248
+ const markerPath = join27(planDir, PLAN_V2_MATERIALIZATION_MARKER);
11148
11249
  if (!existsSync18(markerPath)) return null;
11149
11250
  const marker = readJsonFile(markerPath, "materialization marker");
11150
11251
  if (!isRecord3(marker) || !isRecord3(marker.manifest) || !isRecord3(marker.target)) {
@@ -11162,7 +11263,7 @@ function readValidatedPlanV2Materialization(planDir, expectedTarget) {
11162
11263
  assertRemoteSources(remoteSources, required);
11163
11264
  for (const artifact of required) {
11164
11265
  if (Object.hasOwn(remoteSources, artifact.path)) continue;
11165
- const path = join26(planDir, ...artifact.path.split("/"));
11266
+ const path = join27(planDir, ...artifact.path.split("/"));
11166
11267
  if (!existsSync18(path) || statSync7(path).size !== artifact.sizeBytes) {
11167
11268
  throw new PlanV2IntegrityError(
11168
11269
  `materialized artifact missing or truncated: ${artifact.path}`
@@ -11178,9 +11279,13 @@ function readValidatedPlanV2Materialization(planDir, expectedTarget) {
11178
11279
  // src/services/distributed/planV2Execution.ts
11179
11280
  import { mkdtempSync as mkdtempSync6, rmSync as rmSync12 } from "node:fs";
11180
11281
  import { tmpdir as tmpdir2 } from "node:os";
11181
- import { join as join29 } from "node:path";
11282
+ import { join as join30 } from "node:path";
11182
11283
 
11183
11284
  // src/services/distributed/assemble.ts
11285
+ import {
11286
+ REFERENCE_BACKGROUND_FIELD as REFERENCE_BACKGROUND_FIELD4,
11287
+ referenceBackgroundResult as referenceBackgroundResult6
11288
+ } from "@hyperframes/engine";
11184
11289
  import {
11185
11290
  cpSync as cpSync3,
11186
11291
  existsSync as existsSync19,
@@ -11192,7 +11297,7 @@ import {
11192
11297
  statSync as statSync8,
11193
11298
  writeFileSync as writeFileSync10
11194
11299
  } from "node:fs";
11195
- import { dirname as dirname14, join as join27 } from "node:path";
11300
+ import { dirname as dirname14, join as join28 } from "node:path";
11196
11301
  import {
11197
11302
  appendRateCapArgs as appendRateCapArgs2,
11198
11303
  appendRenderProvenanceArgs as appendRenderProvenanceArgs2,
@@ -11220,12 +11325,16 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
11220
11325
  const plan = readAssemblePlan(planDir, chunkOutputs);
11221
11326
  const chunkPaths = chunkOutputs.flat();
11222
11327
  if (plan.dimensions.format === "png-sequence") {
11223
- return mergePngFrameDirs(chunkPaths, outputPath, plan.totalFrames, audioPath, start);
11328
+ return {
11329
+ ...mergePngFrameDirs(chunkPaths, outputPath, plan.totalFrames, audioPath, start),
11330
+ ...referenceBackgroundResult6(plan[REFERENCE_BACKGROUND_FIELD4])
11331
+ };
11224
11332
  }
11225
11333
  if (chunksAreStitchedMp4(planDir, cfr)) {
11226
11334
  const separateAudio = stitchesSeparateAudio(planDir) && audioPath !== null;
11227
11335
  await writeStitchedMp4(chunkPaths, outputPath, separateAudio ? audioPath : void 0);
11228
11336
  return {
11337
+ ...referenceBackgroundResult6(plan[REFERENCE_BACKGROUND_FIELD4]),
11229
11338
  outputPath,
11230
11339
  durationMs: Date.now() - start,
11231
11340
  framesEncoded: plan.totalFrames,
@@ -11257,7 +11366,7 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
11257
11366
  };
11258
11367
  try {
11259
11368
  const videoSources = cfrEncoder ? [await reencodeCfr(ctx, chunkPaths, cfrEncoder)] : chunkPaths;
11260
- const finalPath = join27(workDir, `output.${format}`);
11369
+ const finalPath = join28(workDir, `output.${format}`);
11261
11370
  const audio = audioPath !== null && existsSync19(audioPath) ? audioPath : null;
11262
11371
  await writeJoined(ctx, videoSources, audio, finalPath);
11263
11372
  renameWithRetry11(finalPath, outputPath);
@@ -11278,6 +11387,7 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
11278
11387
  }
11279
11388
  const fileSize = existsSync19(outputPath) ? statSync8(outputPath).size : 0;
11280
11389
  return {
11390
+ ...referenceBackgroundResult6(plan[REFERENCE_BACKGROUND_FIELD4]),
11281
11391
  outputPath,
11282
11392
  durationMs: Date.now() - start,
11283
11393
  framesEncoded: plan.totalFrames,
@@ -11285,8 +11395,8 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
11285
11395
  };
11286
11396
  }
11287
11397
  function readAssemblePlan(planDir, chunkPaths) {
11288
- const planJsonPath = join27(planDir, "plan.json");
11289
- const chunksJsonPath = join27(planDir, "meta", "chunks.json");
11398
+ const planJsonPath = join28(planDir, "plan.json");
11399
+ const chunksJsonPath = join28(planDir, "meta", "chunks.json");
11290
11400
  if (!existsSync19(planJsonPath)) {
11291
11401
  throw new Error(`[assemble] planDir missing plan.json: ${planJsonPath}`);
11292
11402
  }
@@ -11337,7 +11447,7 @@ function cfrEncodeArgs(inputArgs, encoder, fpsArg) {
11337
11447
  return args;
11338
11448
  }
11339
11449
  async function reencodeCfr(ctx, chunkPaths, encoder) {
11340
- const cfrOutputPath = join27(ctx.workDir, `cfr.${ctx.format}`);
11450
+ const cfrOutputPath = join28(ctx.workDir, `cfr.${ctx.format}`);
11341
11451
  const cfrArgs = cfrEncodeArgs(videoInputArgs(ctx, chunkPaths), encoder, ctx.fpsArg);
11342
11452
  appendRenderProvenanceArgs2(cfrArgs, cfrOutputPath);
11343
11453
  cfrArgs.push("-y", cfrOutputPath);
@@ -11364,7 +11474,7 @@ async function writeJoined(ctx, videoSources, audioPath, finalPath) {
11364
11474
  );
11365
11475
  return;
11366
11476
  }
11367
- const paddedAudioPath = join27(ctx.workDir, "audio-padded.m4a");
11477
+ const paddedAudioPath = join28(ctx.workDir, "audio-padded.m4a");
11368
11478
  const padTrimResult = await ctx.timed(
11369
11479
  "audioMs",
11370
11480
  () => padOrTrimAudioToVideoFrameCount({
@@ -11404,7 +11514,7 @@ async function writeJoined(ctx, videoSources, audioPath, finalPath) {
11404
11514
  }
11405
11515
  }
11406
11516
  function concatDemuxerInput(chunkPaths, workDir, fpsArg) {
11407
- const listPath = join27(workDir, "concat-list.txt");
11517
+ const listPath = join28(workDir, "concat-list.txt");
11408
11518
  const body = chunkPaths.map((path) => `file '${path.replace(/'/g, "'\\''")}'`).join("\n");
11409
11519
  writeFileSync10(listPath, `${body}
11410
11520
  `, "utf-8");
@@ -11442,11 +11552,11 @@ function stitchesSeparateAudio(planDir) {
11442
11552
  return lockedAudio(planDir).separateAudio === true;
11443
11553
  }
11444
11554
  function planFormat(planDir) {
11445
- const plan = JSON.parse(readFileSync9(join27(planDir, "plan.json"), "utf-8"));
11555
+ const plan = JSON.parse(readFileSync9(join28(planDir, "plan.json"), "utf-8"));
11446
11556
  return plan.dimensions.format;
11447
11557
  }
11448
11558
  function lockedAudio(planDir) {
11449
- const encoderJsonPath = join27(planDir, "meta", "encoder.json");
11559
+ const encoderJsonPath = join28(planDir, "meta", "encoder.json");
11450
11560
  if (!existsSync19(encoderJsonPath)) return {};
11451
11561
  return JSON.parse(readFileSync9(encoderJsonPath, "utf-8"));
11452
11562
  }
@@ -11456,7 +11566,7 @@ function readCfrEncoder(planDir, format) {
11456
11566
  `[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.`
11457
11567
  );
11458
11568
  }
11459
- const encoderJsonPath = join27(planDir, "meta", "encoder.json");
11569
+ const encoderJsonPath = join28(planDir, "meta", "encoder.json");
11460
11570
  if (!existsSync19(encoderJsonPath)) {
11461
11571
  throw new Error(`[assemble] planDir missing meta/encoder.json: ${encoderJsonPath}`);
11462
11572
  }
@@ -11483,8 +11593,8 @@ function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, start
11483
11593
  throw new Error(`[assemble] png-sequence chunk has no frames: ${chunkDir}`);
11484
11594
  }
11485
11595
  for (const frame of frames) {
11486
- const dst = join27(outputPath, formatExportFrameName(globalIdx, "png"));
11487
- cpSync3(join27(chunkDir, frame), dst);
11596
+ const dst = join28(outputPath, formatExportFrameName(globalIdx, "png"));
11597
+ cpSync3(join28(chunkDir, frame), dst);
11488
11598
  globalIdx += 1;
11489
11599
  }
11490
11600
  }
@@ -11494,13 +11604,13 @@ function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, start
11494
11604
  );
11495
11605
  }
11496
11606
  if (audioPath !== null && existsSync19(audioPath)) {
11497
- const sidecar = join27(outputPath, MIXED_AUDIO_FILENAME4);
11607
+ const sidecar = join28(outputPath, MIXED_AUDIO_FILENAME4);
11498
11608
  cpSync3(audioPath, sidecar);
11499
11609
  }
11500
11610
  let fileSize = 0;
11501
11611
  for (const name of readdirSync8(outputPath)) {
11502
11612
  try {
11503
- fileSize += statSync8(join27(outputPath, name)).size;
11613
+ fileSize += statSync8(join28(outputPath, name)).size;
11504
11614
  } catch {
11505
11615
  }
11506
11616
  }
@@ -11516,14 +11626,14 @@ function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, start
11516
11626
  import { randomBytes } from "node:crypto";
11517
11627
  import { existsSync as existsSync20, mkdirSync as mkdirSync14, readFileSync as readFileSync10, readdirSync as readdirSync9, rmSync as rmSync11, writeFileSync as writeFileSync11 } from "node:fs";
11518
11628
  import { cpus } from "node:os";
11519
- import { join as join28 } from "node:path";
11629
+ import { join as join29 } from "node:path";
11520
11630
  import {
11521
11631
  assertSwiftShader,
11522
11632
  BROWSER_GPU_NOT_SOFTWARE as BROWSER_GPU_NOT_SOFTWARE4,
11523
11633
  calculateOptimalWorkers as calculateOptimalWorkers2,
11524
11634
  classifyCaptureFailure as classifyCaptureFailure4,
11525
- closeCaptureSession as closeCaptureSession10,
11526
- createCaptureSession as createCaptureSession10,
11635
+ closeCaptureSession as closeCaptureSession11,
11636
+ createCaptureSession as createCaptureSession11,
11527
11637
  createFrameLookupTable as createFrameLookupTable5,
11528
11638
  deriveBeginFrameProbeTimeTicks as deriveBeginFrameProbeTimeTicks2,
11529
11639
  getEncoderPreset as getEncoderPreset6,
@@ -11566,10 +11676,10 @@ function resolveChunkFrameRange(slice, chunkIndex, frameRange) {
11566
11676
  return { startFrame, endFrame };
11567
11677
  }
11568
11678
  var distributedCaptureSessionDependencies = {
11569
- createCaptureSession: createCaptureSession10,
11679
+ createCaptureSession: createCaptureSession11,
11570
11680
  assertSwiftShader,
11571
11681
  initializeSession: initializeSession10,
11572
- closeCaptureSession: closeCaptureSession10,
11682
+ closeCaptureSession: closeCaptureSession11,
11573
11683
  readWebGlVendorInfo: readWebGlVendorInfoFromCanvas
11574
11684
  };
11575
11685
  async function createVerifiedDistributedCaptureSession(serverUrl, framesDir, captureOptions, cfg, dependencies = distributedCaptureSessionDependencies) {
@@ -11640,7 +11750,7 @@ function hashChunkOutput(outputPath, kind) {
11640
11750
  if (kind === "file") return sha256Hex(readFileSync10(outputPath));
11641
11751
  const entries = readdirSync9(outputPath).filter((name) => /\.(png|jpg|jpeg)$/i.test(name)).sort();
11642
11752
  const lines = entries.map(
11643
- (name) => `${name}\0${sha256Hex(readFileSync10(join28(outputPath, name)))}`
11753
+ (name) => `${name}\0${sha256Hex(readFileSync10(join29(outputPath, name)))}`
11644
11754
  );
11645
11755
  return sha256Hex(lines.join("\0"));
11646
11756
  }
@@ -11657,9 +11767,9 @@ function resolveLockedVp9CpuUsed(lockedEncoder) {
11657
11767
  async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
11658
11768
  const start = Date.now();
11659
11769
  const log = defaultLogger;
11660
- const planJsonPath = join28(planDir, "plan.json");
11661
- const encoderJsonPath = join28(planDir, "meta", "encoder.json");
11662
- const chunksJsonPath = join28(planDir, "meta", "chunks.json");
11770
+ const planJsonPath = join29(planDir, "plan.json");
11771
+ const encoderJsonPath = join29(planDir, "meta", "encoder.json");
11772
+ const chunksJsonPath = join29(planDir, "meta", "chunks.json");
11663
11773
  if (!existsSync20(planJsonPath)) {
11664
11774
  throw new RenderChunkValidationError(
11665
11775
  MISSING_PLAN_ARTIFACT,
@@ -11685,7 +11795,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
11685
11795
  }
11686
11796
  const encoder = JSON.parse(readFileSync10(encoderJsonPath, "utf-8"));
11687
11797
  const chunks = JSON.parse(readFileSync10(chunksJsonPath, "utf-8"));
11688
- const videosJsonPath = join28(planDir, PLAN_VIDEOS_META_RELATIVE_PATH);
11798
+ const videosJsonPath = join29(planDir, PLAN_VIDEOS_META_RELATIVE_PATH);
11689
11799
  let planVideos = null;
11690
11800
  if (existsSync20(videosJsonPath)) {
11691
11801
  try {
@@ -11717,7 +11827,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
11717
11827
  }
11718
11828
  const range = resolveChunkFrameRange(slice, chunkIndex, options?.frameRange);
11719
11829
  const framesInChunk = range.endFrame - range.startFrame;
11720
- const compiledDir = join28(planDir, "compiled");
11830
+ const compiledDir = join29(planDir, "compiled");
11721
11831
  if (!existsSync20(compiledDir)) {
11722
11832
  throw new RenderChunkValidationError(
11723
11833
  MISSING_PLAN_ARTIFACT,
@@ -11775,7 +11885,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
11775
11885
  };
11776
11886
  const workDir = `${outputChunkPath}.work.${process.pid}.${randomBytes(4).toString("hex")}`;
11777
11887
  mkdirSync14(workDir, { recursive: true });
11778
- const framesDir = join28(workDir, "captured-frames");
11888
+ const framesDir = join29(workDir, "captured-frames");
11779
11889
  mkdirSync14(framesDir, { recursive: true });
11780
11890
  const videoFramesDir = `${workDir}.video-frames`;
11781
11891
  const chunkFrameNumbers = Array.from({ length: framesInChunk }, (_, i) => range.startFrame + i);
@@ -11800,7 +11910,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
11800
11910
  dimensions: plan.dimensions,
11801
11911
  encoder,
11802
11912
  videoCount: planVideos?.videos.length ?? 0,
11803
- html: readFileSync10(join28(compiledDir, "index.html"), "utf-8"),
11913
+ html: readFileSync10(join29(compiledDir, "index.html"), "utf-8"),
11804
11914
  // The plan ships a deferred source only to chunks that show it; don't wait on the rest.
11805
11915
  skipReadinessVideoIds: deferredVideos.filter(
11806
11916
  (video) => !shownFrames.get(video.videoId)?.size || remoteVideoIds.has(video.videoId)
@@ -11871,7 +11981,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
11871
11981
  "[renderChunk] BeginFrame liveness probe failed; using screenshot capture for the entire chunk",
11872
11982
  { chunkIndex }
11873
11983
  );
11874
- await closeCaptureSession10(session).catch(() => {
11984
+ await closeCaptureSession11(session).catch(() => {
11875
11985
  });
11876
11986
  session = null;
11877
11987
  const screenshotBootStarted = Date.now();
@@ -11970,7 +12080,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
11970
12080
  if (isPngSequence) {
11971
12081
  if (!existsSync20(outputChunkPath)) mkdirSync14(outputChunkPath, { recursive: true });
11972
12082
  } else {
11973
- const outDir = join28(outputChunkPath, "..");
12083
+ const outDir = join29(outputChunkPath, "..");
11974
12084
  if (!existsSync20(outDir)) mkdirSync14(outDir, { recursive: true });
11975
12085
  }
11976
12086
  const encodeInput = {
@@ -12144,7 +12254,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
12144
12254
  });
12145
12255
  if (session) {
12146
12256
  try {
12147
- await closeCaptureSession10(session);
12257
+ await closeCaptureSession11(session);
12148
12258
  } catch (err) {
12149
12259
  log.warn("[renderChunk] error closing capture session in finally", {
12150
12260
  error: err instanceof Error ? err.message : String(err)
@@ -12214,8 +12324,8 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
12214
12324
 
12215
12325
  // src/services/distributed/planV2Execution.ts
12216
12326
  async function renderChunkV2(planV2Dir, chunkIndex, outputChunkPath, options) {
12217
- const workRoot = mkdtempSync6(join29(tmpdir2(), "hf-plan-v2-chunk-"));
12218
- const materializedPlanDir = join29(workRoot, "plan");
12327
+ const workRoot = mkdtempSync6(join30(tmpdir2(), "hf-plan-v2-chunk-"));
12328
+ const materializedPlanDir = join30(workRoot, "plan");
12219
12329
  try {
12220
12330
  materializePlanV2Target(planV2Dir, { role: "chunk", chunkIndex }, materializedPlanDir);
12221
12331
  return await renderChunk(materializedPlanDir, chunkIndex, outputChunkPath, options);
@@ -12224,8 +12334,8 @@ async function renderChunkV2(planV2Dir, chunkIndex, outputChunkPath, options) {
12224
12334
  }
12225
12335
  }
12226
12336
  async function assembleV2(planV2Dir, chunkPaths, outputPath, options) {
12227
- const workRoot = mkdtempSync6(join29(tmpdir2(), "hf-plan-v2-assembler-"));
12228
- const materializedPlanDir = join29(workRoot, "plan");
12337
+ const workRoot = mkdtempSync6(join30(tmpdir2(), "hf-plan-v2-assembler-"));
12338
+ const materializedPlanDir = join30(workRoot, "plan");
12229
12339
  try {
12230
12340
  const materialized = materializePlanV2Target(
12231
12341
  planV2Dir,
@@ -12517,7 +12627,7 @@ function validateIntDimension(field, value) {
12517
12627
  // src/services/distributed/projectHash.ts
12518
12628
  import { closeSync as closeSync5, openSync as openSync5, readdirSync as readdirSync10, readSync as readSync4, realpathSync as realpathSync2, statSync as statSync9 } from "node:fs";
12519
12629
  import { createHash as createHash8 } from "node:crypto";
12520
- import { join as join30 } from "node:path";
12630
+ import { join as join31 } from "node:path";
12521
12631
  var SITE_ID_SCHEME = "site-v2\0";
12522
12632
  var READ_CHUNK_BYTES = 256 * 1024;
12523
12633
  function listSiteFiles(projectDir) {
@@ -12526,7 +12636,7 @@ function listSiteFiles(projectDir) {
12526
12636
  function walk(dir, rel) {
12527
12637
  for (const name of readdirSync10(dir).sort()) {
12528
12638
  if (rel === "" && PLAN_PROJECT_DIR_SKIP_SEGMENTS.has(name)) continue;
12529
- const full = join30(dir, name);
12639
+ const full = join31(dir, name);
12530
12640
  const relPath = rel === "" ? name : `${rel}/${name}`;
12531
12641
  let stat;
12532
12642
  try {
@@ -12556,7 +12666,7 @@ function hashProjectDir(projectDir, files = listSiteFiles(projectDir)) {
12556
12666
  for (const rel of files) {
12557
12667
  hash.update(rel);
12558
12668
  hash.update("\0");
12559
- const fd = openSync5(join30(projectDir, rel), "r");
12669
+ const fd = openSync5(join31(projectDir, rel), "r");
12560
12670
  try {
12561
12671
  let read;
12562
12672
  while ((read = readSync4(fd, chunk, 0, chunk.length, null)) > 0) {