@hyperframes/producer 0.9.4 → 0.9.6

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
@@ -1,3 +1,11 @@
1
+ // src/index.ts
2
+ import {
3
+ REFERENCE_BACKGROUND_FIELD as REFERENCE_BACKGROUND_FIELD7,
4
+ referenceBackgroundResult as referenceBackgroundResult9,
5
+ createReferenceBackgroundRecorder as createReferenceBackgroundRecorder3,
6
+ readReferenceBackground as readReferenceBackground2
7
+ } from "@hyperframes/engine";
8
+
1
9
  // src/services/renderOrchestrator.ts
2
10
  import { audioFailureDiagnostics as audioFailureDiagnostics2 } from "@hyperframes/engine";
3
11
  import {
@@ -56,6 +64,10 @@ import {
56
64
  augmentProtocolTimeoutError,
57
65
  augmentPageNavigationTimeoutError
58
66
  } from "@hyperframes/engine";
67
+ import {
68
+ referenceBackgroundResult,
69
+ createReferenceBackgroundRecorder
70
+ } from "@hyperframes/engine";
59
71
  import { join as join28, dirname as dirname12, resolve as resolve8 } from "path";
60
72
  import { totalmem as totalmem2 } from "node:os";
61
73
  import { randomUUID } from "crypto";
@@ -8801,6 +8813,7 @@ async function runProbeStage(input) {
8801
8813
  const motionBlur = job.config.motionBlur;
8802
8814
  const captureFormat = resolveCaptureImageFormat({ needsAlpha, motionBlur });
8803
8815
  const captureOpts = {
8816
+ recordReferenceBackground: input.recordReferenceBackground,
8804
8817
  width,
8805
8818
  height,
8806
8819
  fps: job.config.fps,
@@ -12479,6 +12492,17 @@ function applyRenderWarningPolicy(job, captureWarnings, log = defaultLogger) {
12479
12492
  throw new RenderQualityError(job.warnings);
12480
12493
  }
12481
12494
  }
12495
+ var stopPrepSessionOnScriptFailure = (options) => ({
12496
+ ...options,
12497
+ onSubTimelineScriptFailure: () => {
12498
+ throw new Error("a composition script failed, so this preparation session stops");
12499
+ }
12500
+ });
12501
+ var surfaceScriptFailure = (error, job) => error instanceof RenderQualityError || error instanceof RenderCancelledError || !job.warnings?.some((warning) => warning.code === "sub_timeline_script_failure") ? error : new RenderQualityError(job.warnings);
12502
+ var withScriptFailureStop = (options, job, log) => ({
12503
+ ...options,
12504
+ onSubTimelineScriptFailure: (warnings) => applyRenderWarningPolicy(job, warnings, log)
12505
+ });
12482
12506
  var RenderCancelledError = class extends Error {
12483
12507
  reason;
12484
12508
  constructor(message = "render_cancelled", reason = "aborted") {
@@ -12846,6 +12870,7 @@ async function executeDiskCaptureWithAdaptiveRetry(options) {
12846
12870
  }
12847
12871
  function createRenderJob(config) {
12848
12872
  return {
12873
+ ...referenceBackgroundResult(void 0),
12849
12874
  id: randomUUID(),
12850
12875
  config: {
12851
12876
  ...config,
@@ -13611,6 +13636,9 @@ async function executeRenderPipeline(input) {
13611
13636
  cfg.protocolTimeout = scaledProtocolTimeout;
13612
13637
  updateCaptureObservability({ protocolTimeoutMs: scaledProtocolTimeout });
13613
13638
  }
13639
+ const recordReferenceBackground = createReferenceBackgroundRecorder(
13640
+ (result) => Object.assign(job, result)
13641
+ );
13614
13642
  const subTimelineWaitMemo = {};
13615
13643
  const probeResult = await observeRenderStage(
13616
13644
  observability,
@@ -13631,7 +13659,8 @@ async function executeRenderPipeline(input) {
13631
13659
  height,
13632
13660
  needsAlpha,
13633
13661
  deviceScaleFactor,
13634
- subTimelineWaitMemo
13662
+ subTimelineWaitMemo,
13663
+ recordReferenceBackground
13635
13664
  }),
13636
13665
  // Browser probe is pre-capture; report `browser calibrating` so a
13637
13666
  // slow probe (~64s SwiftShader warm-up on Windows was the reported
@@ -13887,6 +13916,7 @@ async function executeRenderPipeline(input) {
13887
13916
  let routePageMotionBlur;
13888
13917
  const buildCaptureOptions = () => ({
13889
13918
  ...captureOptions,
13919
+ recordReferenceBackground,
13890
13920
  ...routePageMotionBlur ? { pageMotionBlur: routePageMotionBlur } : {},
13891
13921
  videoMetadataHints,
13892
13922
  skipReadinessVideoIds: videoReadinessSkipIds,
@@ -13919,6 +13949,8 @@ async function executeRenderPipeline(input) {
13919
13949
  });
13920
13950
  }
13921
13951
  });
13952
+ const buildPrepCaptureOptions = () => stopPrepSessionOnScriptFailure(buildCaptureOptions());
13953
+ const buildWorkerCaptureOptions = () => withScriptFailureStop(buildCaptureOptions(), job, log);
13922
13954
  const createRenderVideoFrameInjector = () => createVideoFrameInjector(frameLookup, frameCacheOptions(cfg, workerCount));
13923
13955
  let captureCalibration;
13924
13956
  const htmlInCanvasDetected = compiled.renderModeHints.reasons.some(
@@ -14022,7 +14054,7 @@ async function executeRenderPipeline(input) {
14022
14054
  probeSession = await createCaptureSession8(
14023
14055
  activeFileServer.url,
14024
14056
  framesDir,
14025
- buildCaptureOptions(),
14057
+ buildPrepCaptureOptions(),
14026
14058
  null,
14027
14059
  { ...cfg, forceScreenshot: captureForceScreenshot }
14028
14060
  );
@@ -14105,7 +14137,7 @@ async function executeRenderPipeline(input) {
14105
14137
  totalFrames,
14106
14138
  forceScreenshot: captureForceScreenshot,
14107
14139
  probeSession,
14108
- buildCaptureOptions,
14140
+ buildCaptureOptions: buildPrepCaptureOptions,
14109
14141
  createRenderVideoFrameInjector,
14110
14142
  assertNotAborted
14111
14143
  }),
@@ -14577,7 +14609,7 @@ async function executeRenderPipeline(input) {
14577
14609
  effectiveBitrate,
14578
14610
  deliveryEncoder,
14579
14611
  fileServer: activeFileServer,
14580
- buildCaptureOptions,
14612
+ buildCaptureOptions: buildWorkerCaptureOptions,
14581
14613
  createRenderVideoFrameInjector,
14582
14614
  hdrDiagnostics,
14583
14615
  abortSignal: attempt,
@@ -14695,7 +14727,7 @@ async function executeRenderPipeline(input) {
14695
14727
  // Each segment sets its own GOP (at most the interval); hls never reaches this route.
14696
14728
  ...deliveryEncoder
14697
14729
  },
14698
- buildCaptureOptions,
14730
+ buildCaptureOptions: buildWorkerCaptureOptions,
14699
14731
  createRenderVideoFrameInjector,
14700
14732
  abortSignal: executionSignal,
14701
14733
  assertNotAborted,
@@ -14843,7 +14875,7 @@ async function executeRenderPipeline(input) {
14843
14875
  // encoder's open-GOP output untouched.
14844
14876
  ...deliveryEncoder
14845
14877
  },
14846
- buildCaptureOptions,
14878
+ buildCaptureOptions: buildWorkerCaptureOptions,
14847
14879
  createRenderVideoFrameInjector,
14848
14880
  abortSignal: executionSignal,
14849
14881
  assertNotAborted,
@@ -15028,7 +15060,7 @@ async function executeRenderPipeline(input) {
15028
15060
  probeSession,
15029
15061
  captureAttempts,
15030
15062
  dedupPerfs,
15031
- buildCaptureOptions,
15063
+ buildCaptureOptions: buildWorkerCaptureOptions,
15032
15064
  createRenderVideoFrameInjector,
15033
15065
  abortSignal: executionSignal,
15034
15066
  assertNotAborted,
@@ -15261,7 +15293,7 @@ async function executeRenderPipeline(input) {
15261
15293
  await eventPublisher.flush();
15262
15294
  } catch (caught) {
15263
15295
  await pendingAudio;
15264
- const error = tooSlowError ?? caught;
15296
+ const error = surfaceScriptFailure(tooSlowError ?? caught, job);
15265
15297
  if (!tooSlowError && (error instanceof RenderCancelledError || executionSignal?.aborted)) {
15266
15298
  job.error = error instanceof Error ? error.message : "render_cancelled";
15267
15299
  updateJobStatus(job, "cancelled", "Render cancelled", job.progress, onProgress);
@@ -15353,8 +15385,12 @@ async function executeRenderPipeline(input) {
15353
15385
  }
15354
15386
 
15355
15387
  // src/services/distributed/renderSpan.ts
15388
+ import {
15389
+ REFERENCE_BACKGROUND_FIELD as REFERENCE_BACKGROUND_FIELD4,
15390
+ referenceBackgroundResult as referenceBackgroundResult6
15391
+ } from "@hyperframes/engine";
15356
15392
  import { mkdirSync as mkdirSync18, mkdtempSync as mkdtempSync7, readFileSync as readFileSync13, rmSync as rmSync16 } from "node:fs";
15357
- import { join as join41 } from "node:path";
15393
+ import { join as join42 } from "node:path";
15358
15394
  import { tmpdir as tmpdir4 } from "node:os";
15359
15395
 
15360
15396
  // src/services/distributed/mp4Stitch.ts
@@ -15741,6 +15777,11 @@ async function writeStitchedMp4(chunkPaths, outputPath, audioPath) {
15741
15777
  }
15742
15778
 
15743
15779
  // src/services/distributed/plan.ts
15780
+ import {
15781
+ REFERENCE_BACKGROUND_FIELD as REFERENCE_BACKGROUND_FIELD2,
15782
+ referenceBackgroundResult as referenceBackgroundResult4,
15783
+ createReferenceBackgroundRecorder as createReferenceBackgroundRecorder2
15784
+ } from "@hyperframes/engine";
15744
15785
  import { audioFailureDiagnostics as audioFailureDiagnostics3 } from "@hyperframes/engine";
15745
15786
  import {
15746
15787
  copyFileSync as copyFileSync6,
@@ -15751,7 +15792,7 @@ import {
15751
15792
  rmSync as rmSync12,
15752
15793
  writeFileSync as writeFileSync9
15753
15794
  } from "node:fs";
15754
- import { extname as extname6, isAbsolute as isAbsolute3, join as join36, relative as relative5, sep as sep2 } from "node:path";
15795
+ import { extname as extname6, isAbsolute as isAbsolute3, join as join37, relative as relative5, sep as sep2 } from "node:path";
15755
15796
  import {
15756
15797
  fpsToNumber as fpsToNumber9,
15757
15798
  HF_COLOR_GRADING_ATTR as HF_COLOR_GRADING_ATTR2
@@ -16396,9 +16437,69 @@ function buildWeightedChunkSlices(weights, chunkCount, minFrames, maxFrames) {
16396
16437
  return slices;
16397
16438
  }
16398
16439
 
16440
+ // src/services/render/planReferenceBackground.ts
16441
+ import { join as join33 } from "node:path";
16442
+ import {
16443
+ closeCaptureSession as closeCaptureSession10,
16444
+ createCaptureSession as createCaptureSession10,
16445
+ readReferenceBackground,
16446
+ referenceBackgroundResult as referenceBackgroundResult2
16447
+ } from "@hyperframes/engine";
16448
+ async function readPlanReferenceBackground(input) {
16449
+ let server;
16450
+ let session;
16451
+ try {
16452
+ server = await createFileServer({
16453
+ projectDir: input.projectDir,
16454
+ compiledDir: join33(input.workDir, "compiled"),
16455
+ port: 0,
16456
+ preHeadScripts: [VIRTUAL_TIME_SHIM],
16457
+ fps: input.options.fps
16458
+ });
16459
+ session = await createCaptureSession10(
16460
+ server.url,
16461
+ join33(input.workDir, "reference"),
16462
+ {
16463
+ ...input.options,
16464
+ requiresWebGpu: false
16465
+ },
16466
+ null,
16467
+ {
16468
+ ...input.cfg,
16469
+ forceScreenshot: true,
16470
+ useDrawElement: false,
16471
+ enablePageSideCompositing: false
16472
+ }
16473
+ );
16474
+ const page = session.page;
16475
+ await page.goto(`${server.url}/index.html`, {
16476
+ waitUntil: "domcontentloaded",
16477
+ timeout: input.cfg.pageNavigationTimeout
16478
+ });
16479
+ await page.evaluate("window.__hfFlushSync?.()");
16480
+ await page.waitForFunction(
16481
+ '!!(window.__hf && typeof window.__hf.seek === "function" && window.__hf.duration > 0)',
16482
+ { timeout: input.cfg.playerReadyTimeout, polling: 100 }
16483
+ );
16484
+ return referenceBackgroundResult2(await readReferenceBackground(page));
16485
+ } catch (error) {
16486
+ input.log.debug("Reference background unavailable", {
16487
+ error: error instanceof Error ? error.message : String(error)
16488
+ });
16489
+ return referenceBackgroundResult2(void 0);
16490
+ } finally {
16491
+ if (session) await closeCaptureSession10(session).catch(() => void 0);
16492
+ if (server) closeFileServerSafely(server, "reference background", input.log);
16493
+ }
16494
+ }
16495
+
16399
16496
  // src/services/render/stages/freezePlan.ts
16400
16497
  import { existsSync as existsSync21, mkdirSync as mkdirSync12, readFileSync as readFileSync9, readdirSync as readdirSync7, writeFileSync as writeFileSync7 } from "node:fs";
16401
- import { join as join33, relative as relative3, resolve as resolve9 } from "node:path";
16498
+ import { join as join34, relative as relative3, resolve as resolve9 } from "node:path";
16499
+ import {
16500
+ REFERENCE_BACKGROUND_FIELD,
16501
+ referenceBackgroundResult as referenceBackgroundResult3
16502
+ } from "@hyperframes/engine";
16402
16503
 
16403
16504
  // src/services/distributed/planProtocol.ts
16404
16505
  var PLAN_SCHEMA_VERSION = 1;
@@ -16612,7 +16713,7 @@ function listPlanFiles(planDir) {
16612
16713
  function walk(dir) {
16613
16714
  const entries = readdirSync7(dir, { withFileTypes: true });
16614
16715
  for (const entry of entries) {
16615
- const full = join33(dir, entry.name);
16716
+ const full = join34(dir, entry.name);
16616
16717
  if (entry.isDirectory()) {
16617
16718
  walk(full);
16618
16719
  } else if (entry.isFile()) {
@@ -16653,15 +16754,15 @@ var PLAN_REMOTE_FILES_RELATIVE_PATH = "meta/remote-files.json";
16653
16754
  function writePlanRemoteFiles(planDir, files) {
16654
16755
  if (files.length === 0) return;
16655
16756
  const sorted = [...files].sort((a, b) => a.path < b.path ? -1 : 1);
16656
- writeFileSync7(join33(planDir, PLAN_REMOTE_FILES_RELATIVE_PATH), JSON.stringify(sorted), "utf-8");
16757
+ writeFileSync7(join34(planDir, PLAN_REMOTE_FILES_RELATIVE_PATH), JSON.stringify(sorted), "utf-8");
16657
16758
  }
16658
16759
  function readPlanRemoteFiles(planDir) {
16659
- const path = join33(planDir, PLAN_REMOTE_FILES_RELATIVE_PATH);
16760
+ const path = join34(planDir, PLAN_REMOTE_FILES_RELATIVE_PATH);
16660
16761
  return existsSync21(path) ? JSON.parse(readFileSync9(path, "utf-8")) : [];
16661
16762
  }
16662
16763
  function recomputePlanHashFromPlanDir(planDir, digests) {
16663
- const planJsonPath = join33(planDir, "plan.json");
16664
- const encoderJsonPath = join33(planDir, "meta", "encoder.json");
16764
+ const planJsonPath = join34(planDir, "plan.json");
16765
+ const encoderJsonPath = join34(planDir, "meta", "encoder.json");
16665
16766
  if (!existsSync21(planJsonPath)) {
16666
16767
  throw new Error(`[freezePlan] plan.json missing: ${planJsonPath}`);
16667
16768
  }
@@ -16698,18 +16799,18 @@ async function freezePlan(input) {
16698
16799
  if (!existsSync21(planDir)) {
16699
16800
  throw new Error(`[freezePlan] planDir does not exist: ${planDir}`);
16700
16801
  }
16701
- const metaDir = join33(planDir, "meta");
16802
+ const metaDir = join34(planDir, "meta");
16702
16803
  if (!existsSync21(metaDir)) mkdirSync12(metaDir, { recursive: true });
16703
16804
  writeFileSync7(
16704
- join33(metaDir, "composition.json"),
16805
+ join34(metaDir, "composition.json"),
16705
16806
  `${JSON.stringify(composition, null, 2)}
16706
16807
  `,
16707
16808
  "utf-8"
16708
16809
  );
16709
16810
  const encoderForCanonical = stripUndefined(encoder);
16710
16811
  const encoderConfigCanonicalJson = canonicalJsonStringify(encoderForCanonical);
16711
- writeFileSync7(join33(metaDir, "encoder.json"), encoderConfigCanonicalJson, "utf-8");
16712
- writeFileSync7(join33(metaDir, "chunks.json"), `${JSON.stringify(chunks, null, 2)}
16812
+ writeFileSync7(join34(metaDir, "encoder.json"), encoderConfigCanonicalJson, "utf-8");
16813
+ writeFileSync7(join34(metaDir, "chunks.json"), `${JSON.stringify(chunks, null, 2)}
16713
16814
  `, "utf-8");
16714
16815
  const { compositionHtml, assets } = collectPlanAssetShas(planDir, digests);
16715
16816
  const planHash = computePlanHash({
@@ -16722,6 +16823,7 @@ async function freezePlan(input) {
16722
16823
  dimensions
16723
16824
  });
16724
16825
  const planJson = {
16826
+ ...referenceBackgroundResult3(composition[REFERENCE_BACKGROUND_FIELD]),
16725
16827
  protocol: PLAN_PROTOCOL_V1,
16726
16828
  planHash,
16727
16829
  producerVersion,
@@ -16733,7 +16835,7 @@ async function freezePlan(input) {
16733
16835
  duration: durationSeconds,
16734
16836
  hasAudio
16735
16837
  };
16736
- const planJsonPath = join33(planDir, "plan.json");
16838
+ const planJsonPath = join34(planDir, "plan.json");
16737
16839
  writeFileSync7(planJsonPath, `${JSON.stringify(planJson, null, 2)}
16738
16840
  `, "utf-8");
16739
16841
  return { planJsonPath, planHash };
@@ -16777,7 +16879,7 @@ function applyRuntimeEnvSnapshot(snapshot, env = process.env) {
16777
16879
  // src/services/distributed/chunkAudio.ts
16778
16880
  import { closeSync as closeSync6, existsSync as existsSync22, ftruncateSync, mkdirSync as mkdirSync13, openSync as openSync6, readFileSync as readFileSync10 } from "node:fs";
16779
16881
  import { writeFileSync as writeFileSync8, writeSync } from "node:fs";
16780
- import { join as join34 } from "node:path";
16882
+ import { join as join35 } from "node:path";
16781
16883
  import { appendRenderProvenanceArgs, describeFfmpegFailure as describeFfmpegFailure4, runFfmpeg as runFfmpeg7 } from "@hyperframes/engine";
16782
16884
  function audioCutForChunk(grid, chunks, index, fps) {
16783
16885
  const firstPacketAt = (frame) => Math.min(
@@ -16865,7 +16967,7 @@ async function writeChunkAudioSlices(input) {
16865
16967
  });
16866
16968
  const bytes = readFileSync10(input.audioPath);
16867
16969
  const layout = { sizeBytes: bytes.length, payloadStart, payloadEnd, partStarts: {} };
16868
- mkdirSync13(join34(input.planDir, SLICE_DIR), { recursive: true });
16970
+ mkdirSync13(join35(input.planDir, SLICE_DIR), { recursive: true });
16869
16971
  input.chunks.forEach((_, index) => {
16870
16972
  const cut = audioCutForChunk(grid, input.chunks, index, input.fps);
16871
16973
  if (!cut) return;
@@ -16873,17 +16975,17 @@ async function writeChunkAudioSlices(input) {
16873
16975
  const start = offsets[cut.firstPacket];
16874
16976
  layout.partStarts[index] = start;
16875
16977
  const end = offsets[last] + track.sizes[last];
16876
- writeFileSync8(join34(input.planDir, chunkAudioSlicePath(index)), bytes.subarray(start, end));
16978
+ writeFileSync8(join35(input.planDir, chunkAudioSlicePath(index)), bytes.subarray(start, end));
16877
16979
  });
16878
16980
  const frame = [bytes.subarray(0, payloadStart), bytes.subarray(payloadEnd)];
16879
- writeFileSync8(join34(input.planDir, SLICE_FRAME), Buffer.concat(frame));
16880
- writeFileSync8(join34(input.planDir, SLICE_LAYOUT), JSON.stringify(layout));
16981
+ writeFileSync8(join35(input.planDir, SLICE_FRAME), Buffer.concat(frame));
16982
+ writeFileSync8(join35(input.planDir, SLICE_LAYOUT), JSON.stringify(layout));
16881
16983
  }
16882
16984
  function chunkAudioSource(planDir, chunkIndex, scratchPath) {
16883
- const layoutPath = join34(planDir, SLICE_LAYOUT);
16985
+ const layoutPath = join35(planDir, SLICE_LAYOUT);
16884
16986
  if (!existsSync22(layoutPath)) return resolvePlanAudioPath(planDir);
16885
16987
  const layout = JSON.parse(readFileSync10(layoutPath, "utf-8"));
16886
- const frame = readFileSync10(join34(planDir, SLICE_FRAME));
16988
+ const frame = readFileSync10(join35(planDir, SLICE_FRAME));
16887
16989
  const fd = openSync6(scratchPath, "w");
16888
16990
  try {
16889
16991
  writeSync(fd, frame, 0, layout.payloadStart, 0);
@@ -16896,7 +16998,7 @@ function chunkAudioSource(planDir, chunkIndex, scratchPath) {
16896
16998
  );
16897
16999
  const start = layout.partStarts[chunkIndex];
16898
17000
  if (start !== void 0) {
16899
- const part = readFileSync10(join34(planDir, chunkAudioSlicePath(chunkIndex)));
17001
+ const part = readFileSync10(join35(planDir, chunkAudioSlicePath(chunkIndex)));
16900
17002
  writeSync(fd, part, 0, part.length, start);
16901
17003
  }
16902
17004
  ftruncateSync(fd, layout.sizeBytes);
@@ -16952,7 +17054,7 @@ async function muxChunkWithAudio(input) {
16952
17054
  // src/services/distributed/planSize.ts
16953
17055
  import { createHash as createHash9 } from "node:crypto";
16954
17056
  import { lstatSync as lstatSync2, readdirSync as readdirSync8 } from "node:fs";
16955
- import { extname as extname5, join as join35, relative as relative4 } from "node:path";
17057
+ import { extname as extname5, join as join36, relative as relative4 } from "node:path";
16956
17058
  var MAX_TOP_COMPONENTS = 10;
16957
17059
  var SOURCE_MEDIA_EXTENSIONS = /* @__PURE__ */ new Set([
16958
17060
  ".aac",
@@ -17064,7 +17166,7 @@ function walkRegularFiles(dir, visit) {
17064
17166
  return;
17065
17167
  }
17066
17168
  for (const entry of entries) {
17067
- const full = join35(dir, entry.name);
17169
+ const full = join36(dir, entry.name);
17068
17170
  if (entry.isDirectory()) {
17069
17171
  walkRegularFiles(full, visit);
17070
17172
  } else if (entry.isFile()) {
@@ -17276,13 +17378,13 @@ function assertPositiveInteger2(name, value) {
17276
17378
  }
17277
17379
  var FREEZE_OWNED_PLAN_FILES = [
17278
17380
  "plan.json",
17279
- join36("meta", "composition.json"),
17280
- join36("meta", "encoder.json"),
17281
- join36("meta", "chunks.json")
17381
+ join37("meta", "composition.json"),
17382
+ join37("meta", "encoder.json"),
17383
+ join37("meta", "chunks.json")
17282
17384
  ];
17283
17385
  function removeFreezeOwnedPlanFiles(planDir) {
17284
17386
  for (const relativePath of FREEZE_OWNED_PLAN_FILES) {
17285
- rmSync12(join36(planDir, relativePath), { force: true });
17387
+ rmSync12(join37(planDir, relativePath), { force: true });
17286
17388
  }
17287
17389
  }
17288
17390
  function buildChunkSlices(totalFrames, chunkCount, effectiveChunkSize) {
@@ -17437,19 +17539,19 @@ function createVideoSourceShipper(input) {
17437
17539
  const known = shipped.get(srcPath);
17438
17540
  if (known) return known;
17439
17541
  const stagedRel = pathInside(input.workVideoFramesDir, srcPath);
17440
- const current = stagedRel === null ? srcPath : join36(input.planDir, "video-frames", stagedRel);
17542
+ const current = stagedRel === null ? srcPath : join37(input.planDir, "video-frames", stagedRel);
17441
17543
  let result = null;
17442
17544
  for (const root of stagedRel === null ? [input.workCompiledDir, input.projectDir] : []) {
17443
17545
  const rel = pathInside(root, srcPath);
17444
- if (rel !== null && (existsSync23(join36(input.planDir, "compiled", rel)) || input.isRemote(srcPath))) {
17546
+ if (rel !== null && (existsSync23(join37(input.planDir, "compiled", rel)) || input.isRemote(srcPath))) {
17445
17547
  result = ["compiled", ...rel.split(sep2)].join("/");
17446
17548
  break;
17447
17549
  }
17448
17550
  }
17449
17551
  if (result === null) {
17450
17552
  result = `video-sources/${shipped.size}${extname6(current)}`;
17451
- const destination = join36(input.planDir, ...result.split("/"));
17452
- mkdirSync14(join36(input.planDir, "video-sources"), { recursive: true });
17553
+ const destination = join37(input.planDir, ...result.split("/"));
17554
+ mkdirSync14(join37(input.planDir, "video-sources"), { recursive: true });
17453
17555
  try {
17454
17556
  linkSync(current, destination);
17455
17557
  } catch {
@@ -17498,14 +17600,14 @@ async function compilePlanComposition(projectDir, config, planDir, options) {
17498
17600
  variables: config.variables
17499
17601
  });
17500
17602
  const entryFile = config.entryFile ?? "index.html";
17501
- const htmlPath = join36(projectDir, entryFile);
17603
+ const htmlPath = join37(projectDir, entryFile);
17502
17604
  if (!existsSync23(htmlPath)) {
17503
17605
  throw new Error(`[plan] entry file not found: ${htmlPath}`);
17504
17606
  }
17505
- const workDir = join36(planDir, ".plan-work");
17607
+ const workDir = join37(planDir, ".plan-work");
17506
17608
  rmSync12(workDir, { recursive: true, force: true });
17507
17609
  mkdirSync14(workDir, { recursive: true });
17508
- const compiledDir = join36(workDir, "compiled");
17610
+ const compiledDir = join37(workDir, "compiled");
17509
17611
  mkdirSync14(compiledDir, { recursive: true });
17510
17612
  let stageStarted = Date.now();
17511
17613
  cpSync2(projectDir, compiledDir, {
@@ -17519,10 +17621,10 @@ async function compilePlanComposition(projectDir, config, planDir, options) {
17519
17621
  }
17520
17622
  });
17521
17623
  logPlanStage(log, "copy", stageStarted);
17522
- const finalCompiledDir = join36(planDir, "compiled");
17624
+ const finalCompiledDir = join37(planDir, "compiled");
17523
17625
  const needsAlpha = outputNeedsAlpha(config.format);
17524
17626
  const remoteFiles = Object.entries(options.remoteFiles ?? {});
17525
- const remoteLocalPath = (rel) => join36(projectDir, ...rel.split("/"));
17627
+ const remoteLocalPath = (rel) => join37(projectDir, ...rel.split("/"));
17526
17628
  for (const [, file] of remoteFiles) {
17527
17629
  declareContentAddressedUrl(file.url, file.sha256);
17528
17630
  if (file.probeFactsUrl) declareProbeFactsUrl(file.url, file.probeFactsUrl);
@@ -17572,6 +17674,9 @@ async function compilePlanComposition(projectDir, config, planDir, options) {
17572
17674
  }
17573
17675
  stageStarted = Date.now();
17574
17676
  const probeResult = await runProbeStage({
17677
+ recordReferenceBackground: createReferenceBackgroundRecorder2(
17678
+ (result) => Object.assign(job, result)
17679
+ ),
17575
17680
  projectDir,
17576
17681
  workDir,
17577
17682
  job,
@@ -17589,6 +17694,21 @@ async function compilePlanComposition(projectDir, config, planDir, options) {
17589
17694
  subTimelineWaitMemo: {}
17590
17695
  });
17591
17696
  logPlanStage(log, "probe", stageStarted);
17697
+ if (!probeResult.probeSession) {
17698
+ stageStarted = Date.now();
17699
+ Object.assign(
17700
+ job,
17701
+ await readPlanReferenceBackground({
17702
+ projectDir,
17703
+ workDir,
17704
+ cfg,
17705
+ log,
17706
+ options: { width, height, fps: job.config.fps, variables: job.config.variables }
17707
+ })
17708
+ );
17709
+ assertNotAborted();
17710
+ logPlanStage(log, "reference_background", stageStarted);
17711
+ }
17592
17712
  compiled = probeResult.compiled;
17593
17713
  job.duration = probeResult.duration;
17594
17714
  job.totalFrames = probeResult.totalFrames;
@@ -17601,8 +17721,8 @@ async function compilePlanComposition(projectDir, config, planDir, options) {
17601
17721
  if (probeResult.fileServer) closeFileServerSafely(probeResult.fileServer, "plan", log);
17602
17722
  if (probeResult.probeSession) {
17603
17723
  try {
17604
- const { closeCaptureSession: closeCaptureSession12 } = await import("@hyperframes/engine");
17605
- await closeCaptureSession12(probeResult.probeSession);
17724
+ const { closeCaptureSession: closeCaptureSession13 } = await import("@hyperframes/engine");
17725
+ await closeCaptureSession13(probeResult.probeSession);
17606
17726
  } catch (err) {
17607
17727
  log.warn("[plan] probe session close failed", {
17608
17728
  error: err instanceof Error ? err.message : String(err)
@@ -17708,10 +17828,10 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
17708
17828
  let stageStarted = Date.now();
17709
17829
  const extractResult = await extractPlanVideos(c, options.deferVideoExtraction);
17710
17830
  logPlanStage(log, "extract", stageStarted);
17711
- const stagedVideoFrames = join36(compiledDir, "__hyperframes_video_frames");
17712
- const videoFramesDst = join36(planDir, "video-frames");
17831
+ const stagedVideoFrames = join37(compiledDir, "__hyperframes_video_frames");
17832
+ const videoFramesDst = join37(planDir, "video-frames");
17713
17833
  if (existsSync23(videoFramesDst)) rmSync12(videoFramesDst, { recursive: true, force: true });
17714
- const videoSourcesDir = join36(planDir, "video-sources");
17834
+ const videoSourcesDir = join37(planDir, "video-sources");
17715
17835
  rmSync12(videoSourcesDir, { recursive: true, force: true });
17716
17836
  if (existsSync23(stagedVideoFrames)) {
17717
17837
  renameWithRetry8(stagedVideoFrames, videoFramesDst);
@@ -17748,7 +17868,7 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
17748
17868
  };
17749
17869
  })
17750
17870
  });
17751
- mkdirSync14(join36(planDir, "meta"), { recursive: true });
17871
+ mkdirSync14(join37(planDir, "meta"), { recursive: true });
17752
17872
  writePlanRemoteFiles(
17753
17873
  planDir,
17754
17874
  remoteFiles.map(([rel, { sha256, sizeBytes }]) => ({
@@ -17758,7 +17878,7 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
17758
17878
  }))
17759
17879
  );
17760
17880
  writeFileSync9(
17761
- join36(planDir, PLAN_VIDEOS_META_RELATIVE_PATH),
17881
+ join37(planDir, PLAN_VIDEOS_META_RELATIVE_PATH),
17762
17882
  JSON.stringify(planVideosJson, null, 2),
17763
17883
  "utf-8"
17764
17884
  );
@@ -17837,7 +17957,7 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
17837
17957
  if (audioResult?.audioError) {
17838
17958
  applyDistributedAudioWarningPolicy(job, audioResult.audioError, audioResult.audioFailures, log);
17839
17959
  }
17840
- const planAudioPath = join36(planDir, PLAN_AUDIO_RELATIVE_PATH);
17960
+ const planAudioPath = join37(planDir, PLAN_AUDIO_RELATIVE_PATH);
17841
17961
  if (audioResult?.hasAudio && existsSync23(audioResult.audioOutputPath)) {
17842
17962
  renameWithRetry8(audioResult.audioOutputPath, planAudioPath);
17843
17963
  }
@@ -17870,6 +17990,7 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
17870
17990
  separateAudio
17871
17991
  });
17872
17992
  const compositionJson = {
17993
+ ...referenceBackgroundResult4(job[REFERENCE_BACKGROUND_FIELD2]),
17873
17994
  durationSeconds: job.duration ?? 0,
17874
17995
  width,
17875
17996
  height,
@@ -17925,6 +18046,7 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
17925
18046
  log
17926
18047
  });
17927
18048
  return {
18049
+ ...referenceBackgroundResult4(job[REFERENCE_BACKGROUND_FIELD2]),
17928
18050
  executionPlanDir: planDir,
17929
18051
  executionPlanHash: planHash,
17930
18052
  chunkCount,
@@ -17940,6 +18062,7 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
17940
18062
  async function plan(projectDir, config, planDir, options = {}) {
17941
18063
  const executionPlan = await buildLocalExecutionPlan(projectDir, config, planDir, options);
17942
18064
  return {
18065
+ ...referenceBackgroundResult4(executionPlan[REFERENCE_BACKGROUND_FIELD2]),
17943
18066
  planDir: executionPlan.executionPlanDir,
17944
18067
  planProtocol: PLAN_PROTOCOL_V1,
17945
18068
  planHash: executionPlan.executionPlanHash,
@@ -17958,14 +18081,14 @@ async function plan(projectDir, config, planDir, options = {}) {
17958
18081
  import { randomBytes } from "node:crypto";
17959
18082
  import { existsSync as existsSync26, mkdirSync as mkdirSync17, readFileSync as readFileSync12, readdirSync as readdirSync10, rmSync as rmSync15, writeFileSync as writeFileSync12 } from "node:fs";
17960
18083
  import { cpus as cpus3 } from "node:os";
17961
- import { join as join40 } from "node:path";
18084
+ import { join as join41 } from "node:path";
17962
18085
  import {
17963
18086
  assertSwiftShader,
17964
18087
  BROWSER_GPU_NOT_SOFTWARE as BROWSER_GPU_NOT_SOFTWARE4,
17965
18088
  calculateOptimalWorkers as calculateOptimalWorkers2,
17966
18089
  classifyCaptureFailure as classifyCaptureFailure4,
17967
- closeCaptureSession as closeCaptureSession10,
17968
- createCaptureSession as createCaptureSession10,
18090
+ closeCaptureSession as closeCaptureSession11,
18091
+ createCaptureSession as createCaptureSession11,
17969
18092
  createFrameLookupTable as createFrameLookupTable5,
17970
18093
  deriveBeginFrameProbeTimeTicks as deriveBeginFrameProbeTimeTicks2,
17971
18094
  getEncoderPreset as getEncoderPreset5,
@@ -17977,6 +18100,10 @@ import {
17977
18100
  } from "@hyperframes/engine";
17978
18101
 
17979
18102
  // src/services/distributed/planV2.ts
18103
+ import {
18104
+ REFERENCE_BACKGROUND_FIELD as REFERENCE_BACKGROUND_FIELD3,
18105
+ referenceBackgroundResult as referenceBackgroundResult5
18106
+ } from "@hyperframes/engine";
17980
18107
  import {
17981
18108
  copyFileSync as copyFileSync8,
17982
18109
  existsSync as existsSync25,
@@ -17990,7 +18117,7 @@ import {
17990
18117
  writeFileSync as writeFileSync11
17991
18118
  } from "node:fs";
17992
18119
  import { tmpdir as tmpdir3 } from "node:os";
17993
- import { dirname as dirname16, isAbsolute as isAbsolute4, join as join39, relative as relative6, resolve as resolve10, sep as sep3 } from "node:path";
18120
+ import { dirname as dirname16, isAbsolute as isAbsolute4, join as join40, relative as relative6, resolve as resolve10, sep as sep3 } from "node:path";
17994
18121
  import { DISTRIBUTED_FPS, isDistributedFps } from "@hyperframes/core";
17995
18122
  import { createFrameLookupTable as createFrameLookupTable4, renameWithRetry as renameWithRetry10 } from "@hyperframes/engine";
17996
18123
 
@@ -18006,7 +18133,7 @@ import {
18006
18133
  writeFileSync as writeFileSync10
18007
18134
  } from "node:fs";
18008
18135
  import { renameWithRetry as renameWithRetry9 } from "@hyperframes/engine";
18009
- import { dirname as dirname15, join as join38 } from "node:path";
18136
+ import { dirname as dirname15, join as join39 } from "node:path";
18010
18137
 
18011
18138
  // src/services/distributed/planV2Errors.ts
18012
18139
  var PLAN_V2_INTEGRITY_UNRECOVERABLE = "PLAN_V2_INTEGRITY_UNRECOVERABLE";
@@ -18021,7 +18148,7 @@ var PlanV2IntegrityError = class extends Error {
18021
18148
  };
18022
18149
 
18023
18150
  // src/services/distributed/planV2Layout.ts
18024
- import { join as join37 } from "node:path";
18151
+ import { join as join38 } from "node:path";
18025
18152
  var SHA256_PATTERN = /^[0-9a-f]{64}$/;
18026
18153
  function assertPlanV2Sha256(value, field) {
18027
18154
  if (typeof value !== "string" || !SHA256_PATTERN.test(value)) {
@@ -18031,7 +18158,7 @@ function assertPlanV2Sha256(value, field) {
18031
18158
  }
18032
18159
  function planV2BlobPath(root, sha256) {
18033
18160
  const digest = assertPlanV2Sha256(sha256, "artifact sha256");
18034
- return join37(root, "artifacts", "sha256", digest.slice(0, 2), digest);
18161
+ return join38(root, "artifacts", "sha256", digest.slice(0, 2), digest);
18035
18162
  }
18036
18163
 
18037
18164
  // src/services/distributed/planV2Publisher.ts
@@ -18072,7 +18199,7 @@ var LocalPlanV2ArtifactPublisher = class {
18072
18199
  this.destinationDir = destinationDir;
18073
18200
  this.#linkFile = options.linkFile ?? linkSync2;
18074
18201
  mkdirSync15(dirname15(destinationDir), { recursive: true });
18075
- this.temporaryDir = mkdtempSync5(join38(dirname15(destinationDir), ".plan-v2-publish-"));
18202
+ this.temporaryDir = mkdtempSync5(join39(dirname15(destinationDir), ".plan-v2-publish-"));
18076
18203
  }
18077
18204
  async putBlob(blob) {
18078
18205
  const digest = assertPlanV2Sha256(blob.sha256, "published blob sha256");
@@ -18085,8 +18212,8 @@ var LocalPlanV2ArtifactPublisher = class {
18085
18212
  const destinationPath = planV2BlobPath(this.temporaryDir, digest);
18086
18213
  if (existsSync24(destinationPath)) return;
18087
18214
  mkdirSync15(dirname15(destinationPath), { recursive: true });
18088
- const stagingDir = mkdtempSync5(join38(dirname15(destinationPath), ".plan-v2-blob-"));
18089
- const temporaryPath = join38(stagingDir, "blob");
18215
+ const stagingDir = mkdtempSync5(join39(dirname15(destinationPath), ".plan-v2-blob-"));
18216
+ const temporaryPath = join39(stagingDir, "blob");
18090
18217
  try {
18091
18218
  try {
18092
18219
  this.#linkFile(blob.sourcePath, temporaryPath);
@@ -18107,7 +18234,7 @@ var LocalPlanV2ArtifactPublisher = class {
18107
18234
  );
18108
18235
  }
18109
18236
  }
18110
- writeFileSync10(join38(this.temporaryDir, "plan.json"), manifestBytes, "utf-8");
18237
+ writeFileSync10(join39(this.temporaryDir, "plan.json"), manifestBytes, "utf-8");
18111
18238
  renameWithRetry9(this.temporaryDir, this.destinationDir);
18112
18239
  this.#committed = true;
18113
18240
  }
@@ -18152,7 +18279,7 @@ function listFiles(root) {
18152
18279
  const rootResolved = resolve10(root);
18153
18280
  function walk(dir) {
18154
18281
  for (const entry of readdirSync9(dir, { withFileTypes: true })) {
18155
- const absolutePath = join39(dir, entry.name);
18282
+ const absolutePath = join40(dir, entry.name);
18156
18283
  if (entry.isDirectory()) {
18157
18284
  walk(absolutePath);
18158
18285
  } else if (entry.isFile()) {
@@ -18179,13 +18306,13 @@ function assertValidExtractionCacheCompleteSentinel(path) {
18179
18306
  }
18180
18307
  }
18181
18308
  function validateExtractionCacheCompleteSentinels(executionPlanDir) {
18182
- const videoRoot = join39(executionPlanDir, "video-frames");
18309
+ const videoRoot = join40(executionPlanDir, "video-frames");
18183
18310
  if (!existsSync25(videoRoot)) return;
18184
18311
  for (const videoEntry of readdirSync9(videoRoot, { withFileTypes: true })) {
18185
18312
  if (!videoEntry.isDirectory()) continue;
18186
- const videoDir = join39(videoRoot, videoEntry.name);
18313
+ const videoDir = join40(videoRoot, videoEntry.name);
18187
18314
  if (readdirSync9(videoDir).includes(EXTRACTION_CACHE_COMPLETE_SENTINEL)) {
18188
- const sentinelPath = join39(videoDir, EXTRACTION_CACHE_COMPLETE_SENTINEL);
18315
+ const sentinelPath = join40(videoDir, EXTRACTION_CACHE_COMPLETE_SENTINEL);
18189
18316
  assertValidExtractionCacheCompleteSentinel(sentinelPath);
18190
18317
  }
18191
18318
  }
@@ -18232,7 +18359,7 @@ function listVideoFramePaths(executionPlanDir, videos) {
18232
18359
  if (video.deferredRange) {
18233
18360
  const framePaths2 = /* @__PURE__ */ new Map();
18234
18361
  for (let index = 0; index < video.totalFrames; index++) {
18235
- framePaths2.set(index, join39(outputDir, String(index)));
18362
+ framePaths2.set(index, join40(outputDir, String(index)));
18236
18363
  }
18237
18364
  return { ...video, outputDir, framePaths: framePaths2, ownedByLookup: false };
18238
18365
  }
@@ -18240,7 +18367,7 @@ function listVideoFramePaths(executionPlanDir, videos) {
18240
18367
  const framePaths = /* @__PURE__ */ new Map();
18241
18368
  for (const frameName of frameNames) {
18242
18369
  if (frameName === EXTRACTION_CACHE_COMPLETE_SENTINEL) {
18243
- assertValidExtractionCacheCompleteSentinel(join39(outputDir, frameName));
18370
+ assertValidExtractionCacheCompleteSentinel(join40(outputDir, frameName));
18244
18371
  continue;
18245
18372
  }
18246
18373
  const match = /(\d+)(?=\.[^.]+$)/.exec(frameName);
@@ -18259,7 +18386,7 @@ function listVideoFramePaths(executionPlanDir, videos) {
18259
18386
  `duplicate extracted frame index ${frameIndex} in ${outputDir}`
18260
18387
  );
18261
18388
  }
18262
- framePaths.set(frameIndex, join39(outputDir, frameName));
18389
+ framePaths.set(frameIndex, join40(outputDir, frameName));
18263
18390
  }
18264
18391
  return {
18265
18392
  ...video,
@@ -18280,7 +18407,7 @@ function parsePlanVideosJson2(value) {
18280
18407
  }
18281
18408
  }
18282
18409
  function materializeExtractedVideoDirectories(planDir) {
18283
- const videosPath = join39(planDir, PLAN_VIDEOS_META_RELATIVE_PATH);
18410
+ const videosPath = join40(planDir, PLAN_VIDEOS_META_RELATIVE_PATH);
18284
18411
  if (!existsSync25(videosPath)) return;
18285
18412
  const videos = parsePlanVideosJson2(readJsonFile(videosPath, PLAN_VIDEOS_META_RELATIVE_PATH));
18286
18413
  for (const video of videos.extracted) {
@@ -18314,13 +18441,13 @@ function parseChunkSlices(value) {
18314
18441
  });
18315
18442
  }
18316
18443
  function buildVideoChunkDependencies(executionPlanDir, dimensions) {
18317
- const videoRoot = join39(executionPlanDir, "video-frames");
18444
+ const videoRoot = join40(executionPlanDir, "video-frames");
18318
18445
  const hasExtractedFrames = existsSync25(videoRoot) && listFiles(videoRoot).length > 0;
18319
- const videosPath = join39(executionPlanDir, PLAN_VIDEOS_META_RELATIVE_PATH);
18446
+ const videosPath = join40(executionPlanDir, PLAN_VIDEOS_META_RELATIVE_PATH);
18320
18447
  if (!existsSync25(videosPath)) {
18321
18448
  return hasExtractedFrames ? { mode: "full-source-pack", dependencies: null } : { mode: "exact-rendered-frames", dependencies: /* @__PURE__ */ new Map() };
18322
18449
  }
18323
- const chunksPath = join39(executionPlanDir, "meta", "chunks.json");
18450
+ const chunksPath = join40(executionPlanDir, "meta", "chunks.json");
18324
18451
  const videos = readJsonFile(videosPath, PLAN_VIDEOS_META_RELATIVE_PATH);
18325
18452
  const chunks = readJsonFile(chunksPath, "meta/chunks.json");
18326
18453
  const parsedVideos = parsePlanVideosJson2(videos);
@@ -18376,8 +18503,8 @@ function computeManifestHash(manifest) {
18376
18503
  function writeBlob(sourcePath, destinationPath) {
18377
18504
  if (existsSync25(destinationPath)) return;
18378
18505
  mkdirSync16(dirname16(destinationPath), { recursive: true });
18379
- const temporaryDir = mkdtempSync6(join39(dirname16(destinationPath), ".plan-v2-blob-"));
18380
- const temporaryPath = join39(temporaryDir, "blob");
18506
+ const temporaryDir = mkdtempSync6(join40(dirname16(destinationPath), ".plan-v2-blob-"));
18507
+ const temporaryPath = join40(temporaryDir, "blob");
18381
18508
  try {
18382
18509
  copyFileSync8(sourcePath, temporaryPath);
18383
18510
  renameWithRetry10(temporaryPath, destinationPath);
@@ -18413,7 +18540,7 @@ async function putTasks(tasks) {
18413
18540
  if (failure) throw failure.reason;
18414
18541
  }
18415
18542
  function buildPlanV2Publication(executionPlanDir, digests) {
18416
- const executionPlanPath = join39(executionPlanDir, "plan.json");
18543
+ const executionPlanPath = join40(executionPlanDir, "plan.json");
18417
18544
  if (!existsSync25(executionPlanPath)) {
18418
18545
  throw new PlanV2IntegrityError(`execution plan is missing plan.json: ${executionPlanPath}`);
18419
18546
  }
@@ -18441,7 +18568,7 @@ function buildPlanV2Publication(executionPlanDir, digests) {
18441
18568
  }
18442
18569
  validateExtractionCacheCompleteSentinels(executionPlanDir);
18443
18570
  const videoDependencyPlan = buildVideoChunkDependencies(executionPlanDir, dimensions);
18444
- const encoder = readJsonFile(join39(executionPlanDir, "meta", "encoder.json"), "meta/encoder.json");
18571
+ const encoder = readJsonFile(join40(executionPlanDir, "meta", "encoder.json"), "meta/encoder.json");
18445
18572
  const files = listFiles(executionPlanDir);
18446
18573
  const chunkAudio = !isRecord3(encoder) || encoder.chunksCarryAudio !== true ? "none" : files.some((file) => chunkAudioSliceTarget(file.path) !== null) ? "sliced" : "whole";
18447
18574
  for (const file of files) {
@@ -18502,13 +18629,13 @@ function createPlanV2FromExecutionPlan(executionPlanDir, planV2Dir) {
18502
18629
  throw new PlanV2IntegrityError("a local plan v2 directory cannot reference remote files");
18503
18630
  }
18504
18631
  mkdirSync16(dirname16(planV2Dir), { recursive: true });
18505
- const tempDir = mkdtempSync6(join39(dirname16(planV2Dir), ".plan-v2-build-"));
18632
+ const tempDir = mkdtempSync6(join40(dirname16(planV2Dir), ".plan-v2-build-"));
18506
18633
  try {
18507
18634
  for (const blob of publication.blobs) {
18508
18635
  writeBlob(blob.sourcePath, planV2BlobPath(tempDir, blob.sha256));
18509
18636
  }
18510
18637
  writeFileSync11(
18511
- join39(tempDir, "plan.json"),
18638
+ join40(tempDir, "plan.json"),
18512
18639
  canonicalJsonStringify(publication.manifest),
18513
18640
  "utf-8"
18514
18641
  );
@@ -18549,7 +18676,7 @@ async function publishExecutionPlan(executionPlanDir, publisher, { digests, log
18549
18676
  }
18550
18677
  }
18551
18678
  async function planV2WithPublisher(projectDir, config, publisher, options = {}) {
18552
- const stagingRoot = mkdtempSync6(join39(options.stagingParentDir ?? tmpdir3(), ".execution-plan-"));
18679
+ const stagingRoot = mkdtempSync6(join40(options.stagingParentDir ?? tmpdir3(), ".execution-plan-"));
18553
18680
  const digests = /* @__PURE__ */ new Map();
18554
18681
  try {
18555
18682
  await buildLocalExecutionPlan(projectDir, config, stagingRoot, {
@@ -18560,7 +18687,7 @@ async function planV2WithPublisher(projectDir, config, publisher, options = {})
18560
18687
  // artifactTargets ships every compiled/ file to all chunks, so each is in the manifest.
18561
18688
  whileAudioMixes: () => putBlobs(
18562
18689
  publisher,
18563
- listFiles(join39(stagingRoot, "compiled")).map(
18690
+ listFiles(join40(stagingRoot, "compiled")).map(
18564
18691
  (file) => publishBlobFor(file.absolutePath, digests)
18565
18692
  )
18566
18693
  ),
@@ -18590,9 +18717,14 @@ async function planV2(projectDir, config, planV2Dir) {
18590
18717
  return resultFromManifest(planV2Dir, manifest);
18591
18718
  }
18592
18719
  function resultFromManifest(planV2Dir, manifest) {
18720
+ const artifact = manifest.artifacts.find((entry) => entry.path === "plan.json");
18721
+ const executionPlan = artifact ? readJsonFile(verifyBlob(planV2Dir, artifact), "execution plan.json") : null;
18593
18722
  return {
18723
+ ...referenceBackgroundResult5(
18724
+ isRecord3(executionPlan) ? executionPlan[REFERENCE_BACKGROUND_FIELD3] : void 0
18725
+ ),
18594
18726
  planDir: planV2Dir,
18595
- manifestPath: join39(planV2Dir, "plan.json"),
18727
+ manifestPath: join40(planV2Dir, "plan.json"),
18596
18728
  planProtocol: PLAN_PROTOCOL_V2,
18597
18729
  planHash: manifest.planHash,
18598
18730
  sourcePlanV1Hash: getPlanV2ExecutionPlanHash(manifest),
@@ -18728,7 +18860,7 @@ function parsePlanV2Manifest(value) {
18728
18860
  return parsed;
18729
18861
  }
18730
18862
  function readPlanV2Manifest(planV2Dir) {
18731
- const manifestPath = join39(planV2Dir, "plan.json");
18863
+ const manifestPath = join40(planV2Dir, "plan.json");
18732
18864
  if (!existsSync25(manifestPath)) {
18733
18865
  throw new PlanV2IntegrityError(`missing v2 manifest: ${manifestPath}`);
18734
18866
  }
@@ -18774,16 +18906,16 @@ function materializePlanV2Target(planV2Dir, target, destinationDir, options = {}
18774
18906
  sourcePath: verifyBlob(planV2Dir, artifact)
18775
18907
  }));
18776
18908
  mkdirSync16(dirname16(destinationDir), { recursive: true });
18777
- const tempDir = mkdtempSync6(join39(dirname16(destinationDir), ".plan-v2-materialize-"));
18909
+ const tempDir = mkdtempSync6(join40(dirname16(destinationDir), ".plan-v2-materialize-"));
18778
18910
  try {
18779
18911
  for (const { artifact, sourcePath } of verified) {
18780
- const destinationPath = join39(tempDir, ...artifact.path.split("/"));
18912
+ const destinationPath = join40(tempDir, ...artifact.path.split("/"));
18781
18913
  mkdirSync16(dirname16(destinationPath), { recursive: true });
18782
18914
  copyFileSync8(sourcePath, destinationPath);
18783
18915
  }
18784
18916
  if (target.role === "chunk") materializeExtractedVideoDirectories(tempDir);
18785
18917
  writeFileSync11(
18786
- join39(tempDir, PLAN_V2_MATERIALIZATION_MARKER),
18918
+ join40(tempDir, PLAN_V2_MATERIALIZATION_MARKER),
18787
18919
  canonicalJsonStringify({
18788
18920
  manifest,
18789
18921
  target,
@@ -18806,7 +18938,7 @@ function materializePlanV2Target(planV2Dir, target, destinationDir, options = {}
18806
18938
  // Join the artifact's OWN name, not the current constant: a plan written
18807
18939
  // before the container change carries the legacy name, and materializing it
18808
18940
  // under the new one would point at a file that was never written.
18809
- audioPath: target.role === "assembler" ? audioArtifact ? join39(destinationDir, audioArtifact.path) : null : null
18941
+ audioPath: target.role === "assembler" ? audioArtifact ? join40(destinationDir, audioArtifact.path) : null : null
18810
18942
  };
18811
18943
  }
18812
18944
  function assertRemoteSources(remoteSources, artifacts) {
@@ -18825,7 +18957,7 @@ function validatePlanV2MaterializedTarget(planDir, expectedTarget) {
18825
18957
  return readValidatedPlanV2Materialization(planDir, expectedTarget)?.manifest ?? null;
18826
18958
  }
18827
18959
  function readValidatedPlanV2Materialization(planDir, expectedTarget) {
18828
- const markerPath = join39(planDir, PLAN_V2_MATERIALIZATION_MARKER);
18960
+ const markerPath = join40(planDir, PLAN_V2_MATERIALIZATION_MARKER);
18829
18961
  if (!existsSync25(markerPath)) return null;
18830
18962
  const marker = readJsonFile(markerPath, "materialization marker");
18831
18963
  if (!isRecord3(marker) || !isRecord3(marker.manifest) || !isRecord3(marker.target)) {
@@ -18843,7 +18975,7 @@ function readValidatedPlanV2Materialization(planDir, expectedTarget) {
18843
18975
  assertRemoteSources(remoteSources, required);
18844
18976
  for (const artifact of required) {
18845
18977
  if (Object.hasOwn(remoteSources, artifact.path)) continue;
18846
- const path = join39(planDir, ...artifact.path.split("/"));
18978
+ const path = join40(planDir, ...artifact.path.split("/"));
18847
18979
  if (!existsSync25(path) || statSync10(path).size !== artifact.sizeBytes) {
18848
18980
  throw new PlanV2IntegrityError(
18849
18981
  `materialized artifact missing or truncated: ${artifact.path}`
@@ -18890,10 +19022,10 @@ function resolveChunkFrameRange(slice, chunkIndex, frameRange) {
18890
19022
  return { startFrame, endFrame };
18891
19023
  }
18892
19024
  var distributedCaptureSessionDependencies = {
18893
- createCaptureSession: createCaptureSession10,
19025
+ createCaptureSession: createCaptureSession11,
18894
19026
  assertSwiftShader,
18895
19027
  initializeSession: initializeSession10,
18896
- closeCaptureSession: closeCaptureSession10,
19028
+ closeCaptureSession: closeCaptureSession11,
18897
19029
  readWebGlVendorInfo: readWebGlVendorInfoFromCanvas
18898
19030
  };
18899
19031
  async function createVerifiedDistributedCaptureSession(serverUrl, framesDir, captureOptions, cfg, dependencies = distributedCaptureSessionDependencies) {
@@ -18964,7 +19096,7 @@ function hashChunkOutput(outputPath, kind) {
18964
19096
  if (kind === "file") return sha256Hex(readFileSync12(outputPath));
18965
19097
  const entries = readdirSync10(outputPath).filter((name) => /\.(png|jpg|jpeg)$/i.test(name)).sort();
18966
19098
  const lines = entries.map(
18967
- (name) => `${name}\0${sha256Hex(readFileSync12(join40(outputPath, name)))}`
19099
+ (name) => `${name}\0${sha256Hex(readFileSync12(join41(outputPath, name)))}`
18968
19100
  );
18969
19101
  return sha256Hex(lines.join("\0"));
18970
19102
  }
@@ -18981,9 +19113,9 @@ function resolveLockedVp9CpuUsed(lockedEncoder) {
18981
19113
  async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
18982
19114
  const start = Date.now();
18983
19115
  const log = defaultLogger;
18984
- const planJsonPath = join40(planDir, "plan.json");
18985
- const encoderJsonPath = join40(planDir, "meta", "encoder.json");
18986
- const chunksJsonPath = join40(planDir, "meta", "chunks.json");
19116
+ const planJsonPath = join41(planDir, "plan.json");
19117
+ const encoderJsonPath = join41(planDir, "meta", "encoder.json");
19118
+ const chunksJsonPath = join41(planDir, "meta", "chunks.json");
18987
19119
  if (!existsSync26(planJsonPath)) {
18988
19120
  throw new RenderChunkValidationError(
18989
19121
  MISSING_PLAN_ARTIFACT,
@@ -19009,7 +19141,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
19009
19141
  }
19010
19142
  const encoder = JSON.parse(readFileSync12(encoderJsonPath, "utf-8"));
19011
19143
  const chunks = JSON.parse(readFileSync12(chunksJsonPath, "utf-8"));
19012
- const videosJsonPath = join40(planDir, PLAN_VIDEOS_META_RELATIVE_PATH);
19144
+ const videosJsonPath = join41(planDir, PLAN_VIDEOS_META_RELATIVE_PATH);
19013
19145
  let planVideos = null;
19014
19146
  if (existsSync26(videosJsonPath)) {
19015
19147
  try {
@@ -19041,7 +19173,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
19041
19173
  }
19042
19174
  const range = resolveChunkFrameRange(slice, chunkIndex, options?.frameRange);
19043
19175
  const framesInChunk = range.endFrame - range.startFrame;
19044
- const compiledDir = join40(planDir, "compiled");
19176
+ const compiledDir = join41(planDir, "compiled");
19045
19177
  if (!existsSync26(compiledDir)) {
19046
19178
  throw new RenderChunkValidationError(
19047
19179
  MISSING_PLAN_ARTIFACT,
@@ -19099,7 +19231,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
19099
19231
  };
19100
19232
  const workDir = `${outputChunkPath}.work.${process.pid}.${randomBytes(4).toString("hex")}`;
19101
19233
  mkdirSync17(workDir, { recursive: true });
19102
- const framesDir = join40(workDir, "captured-frames");
19234
+ const framesDir = join41(workDir, "captured-frames");
19103
19235
  mkdirSync17(framesDir, { recursive: true });
19104
19236
  const videoFramesDir = `${workDir}.video-frames`;
19105
19237
  const chunkFrameNumbers = Array.from({ length: framesInChunk }, (_, i) => range.startFrame + i);
@@ -19124,7 +19256,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
19124
19256
  dimensions: plan2.dimensions,
19125
19257
  encoder,
19126
19258
  videoCount: planVideos?.videos.length ?? 0,
19127
- html: readFileSync12(join40(compiledDir, "index.html"), "utf-8"),
19259
+ html: readFileSync12(join41(compiledDir, "index.html"), "utf-8"),
19128
19260
  // The plan ships a deferred source only to chunks that show it; don't wait on the rest.
19129
19261
  skipReadinessVideoIds: deferredVideos.filter(
19130
19262
  (video) => !shownFrames.get(video.videoId)?.size || remoteVideoIds.has(video.videoId)
@@ -19195,7 +19327,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
19195
19327
  "[renderChunk] BeginFrame liveness probe failed; using screenshot capture for the entire chunk",
19196
19328
  { chunkIndex }
19197
19329
  );
19198
- await closeCaptureSession10(session).catch(() => {
19330
+ await closeCaptureSession11(session).catch(() => {
19199
19331
  });
19200
19332
  session = null;
19201
19333
  const screenshotBootStarted = Date.now();
@@ -19294,7 +19426,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
19294
19426
  if (isPngSequence) {
19295
19427
  if (!existsSync26(outputChunkPath)) mkdirSync17(outputChunkPath, { recursive: true });
19296
19428
  } else {
19297
- const outDir = join40(outputChunkPath, "..");
19429
+ const outDir = join41(outputChunkPath, "..");
19298
19430
  if (!existsSync26(outDir)) mkdirSync17(outDir, { recursive: true });
19299
19431
  }
19300
19432
  const encodeInput = {
@@ -19468,7 +19600,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
19468
19600
  });
19469
19601
  if (session) {
19470
19602
  try {
19471
- await closeCaptureSession10(session);
19603
+ await closeCaptureSession11(session);
19472
19604
  } catch (err) {
19473
19605
  log.warn("[renderChunk] error closing capture session in finally", {
19474
19606
  error: err instanceof Error ? err.message : String(err)
@@ -19568,10 +19700,10 @@ async function renderSpan(projectDir, config, span, outputPath) {
19568
19700
  if (config.cfr === true) {
19569
19701
  throw new RenderSpanUnsupportedError("--start/--end cannot be combined with a cfr re-encode.");
19570
19702
  }
19571
- const workDir = mkdtempSync7(join41(tmpdir4(), "hf-render-span-"));
19703
+ const workDir = mkdtempSync7(join42(tmpdir4(), "hf-render-span-"));
19572
19704
  try {
19573
- const planDir = join41(workDir, "plan");
19574
- const partsDir = join41(workDir, "parts");
19705
+ const planDir = join42(workDir, "plan");
19706
+ const partsDir = join42(workDir, "parts");
19575
19707
  mkdirSync18(partsDir, { recursive: true });
19576
19708
  const planned = await plan(
19577
19709
  projectDir,
@@ -19586,7 +19718,7 @@ async function renderSpan(projectDir, config, span, outputPath) {
19586
19718
  planned.totalFrames
19587
19719
  );
19588
19720
  const slices = JSON.parse(
19589
- readFileSync13(join41(planDir, "meta", "chunks.json"), "utf-8")
19721
+ readFileSync13(join42(planDir, "meta", "chunks.json"), "utf-8")
19590
19722
  );
19591
19723
  const parts = [];
19592
19724
  for (const slice of slices) {
@@ -19596,13 +19728,18 @@ async function renderSpan(projectDir, config, span, outputPath) {
19596
19728
  };
19597
19729
  if (range.endFrame <= range.startFrame) continue;
19598
19730
  throwIfAborted(config.abortSignal);
19599
- const part = join41(partsDir, `part-${String(parts.length).padStart(4, "0")}.mp4`);
19731
+ const part = join42(partsDir, `part-${String(parts.length).padStart(4, "0")}.mp4`);
19600
19732
  await renderChunk(planDir, slice.index, part, { frameRange: range });
19601
19733
  parts.push(part);
19602
19734
  }
19603
19735
  throwIfAborted(config.abortSignal);
19604
19736
  await writeStitchedMp4(parts, outputPath);
19605
- return { startFrame, endFrame, framesEncoded: endFrame - startFrame };
19737
+ return {
19738
+ ...referenceBackgroundResult6(planned[REFERENCE_BACKGROUND_FIELD4]),
19739
+ startFrame,
19740
+ endFrame,
19741
+ framesEncoded: endFrame - startFrame
19742
+ };
19606
19743
  } finally {
19607
19744
  rmSync16(workDir, { recursive: true, force: true });
19608
19745
  }
@@ -20167,9 +20304,9 @@ function renderRequestFromDistributedConfig(input) {
20167
20304
 
20168
20305
  // src/services/frameCapture.ts
20169
20306
  import {
20170
- createCaptureSession as createCaptureSession11,
20307
+ createCaptureSession as createCaptureSession12,
20171
20308
  initializeSession as initializeSession11,
20172
- closeCaptureSession as closeCaptureSession11,
20309
+ closeCaptureSession as closeCaptureSession12,
20173
20310
  captureFrame as captureFrame2,
20174
20311
  captureFrameToBuffer as captureFrameToBuffer4,
20175
20312
  getCompositionDuration as getCompositionDuration2,
@@ -20197,14 +20334,18 @@ import {
20197
20334
  rmSync as rmSync17,
20198
20335
  createReadStream as createReadStream5
20199
20336
  } from "node:fs";
20200
- import { resolve as resolve12, dirname as dirname18, join as join44 } from "node:path";
20337
+ import { resolve as resolve12, dirname as dirname18, join as join45 } from "node:path";
20201
20338
  import { tmpdir as tmpdir5 } from "node:os";
20202
20339
  import { parseArgs } from "node:util";
20203
20340
  import crypto from "node:crypto";
20204
20341
  import { Hono as Hono2 } from "hono";
20205
20342
  import { streamSSE } from "hono/streaming";
20206
20343
  import { serve as serve2 } from "@hono/node-server";
20207
- import { fetchPublicHttpsText as fetchPublicHttpsText2 } from "@hyperframes/engine";
20344
+ import {
20345
+ fetchPublicHttpsText as fetchPublicHttpsText2,
20346
+ REFERENCE_BACKGROUND_FIELD as REFERENCE_BACKGROUND_FIELD5,
20347
+ referenceBackgroundResult as referenceBackgroundResult7
20348
+ } from "@hyperframes/engine";
20208
20349
 
20209
20350
  // src/services/hyperframeLint.ts
20210
20351
  import {
@@ -20216,7 +20357,7 @@ import {
20216
20357
  closeSync as closeSync7,
20217
20358
  constants as constants3
20218
20359
  } from "node:fs";
20219
- import { resolve as resolve11, join as join42, relative as relative7, isAbsolute as isAbsolute5, sep as sep4 } from "node:path";
20360
+ import { resolve as resolve11, join as join43, relative as relative7, isAbsolute as isAbsolute5, sep as sep4 } from "node:path";
20220
20361
  import { lintHyperframeHtml } from "@hyperframes/lint";
20221
20362
  function isStringRecord(value) {
20222
20363
  if (!value || typeof value !== "object" || Array.isArray(value)) {
@@ -20284,7 +20425,7 @@ function readProjectEntryFile(projectDir, preferredEntryFile) {
20284
20425
  }
20285
20426
  }
20286
20427
  return {
20287
- error: `No HTML entry file found in project directory: ${join42(absProjectDir, preferredEntryFile || "index.html")}`
20428
+ error: `No HTML entry file found in project directory: ${join43(absProjectDir, preferredEntryFile || "index.html")}`
20288
20429
  };
20289
20430
  }
20290
20431
  function prepareHyperframeLintBody(body) {
@@ -20330,7 +20471,7 @@ async function runHyperframeLint(prepared) {
20330
20471
  // src/services/healthWorker.ts
20331
20472
  import { Worker as Worker2 } from "node:worker_threads";
20332
20473
  import { fileURLToPath as fileURLToPath6 } from "node:url";
20333
- import { dirname as dirname17, join as join43 } from "node:path";
20474
+ import { dirname as dirname17, join as join44 } from "node:path";
20334
20475
  import { existsSync as existsSync28 } from "node:fs";
20335
20476
  var DEFAULT_HEALTH_PORT = 9848;
20336
20477
  async function startHealthWorker(options = {}) {
@@ -20406,7 +20547,7 @@ function defaultLogger2() {
20406
20547
  }
20407
20548
  function resolveWorkerEntry2() {
20408
20549
  const here = dirname17(fileURLToPath6(import.meta.url));
20409
- const candidates = [join43(here, "healthWorkerThread.js"), join43(here, "healthWorkerThread.ts")];
20550
+ const candidates = [join44(here, "healthWorkerThread.js"), join44(here, "healthWorkerThread.ts")];
20410
20551
  for (const candidate of candidates) {
20411
20552
  if (existsSync28(candidate)) return candidate;
20412
20553
  }
@@ -20617,8 +20758,8 @@ async function resolveInlineRenderHtml(body) {
20617
20758
  }
20618
20759
  function materializeInlineProject(html, options) {
20619
20760
  const tempRoot = process.env.PRODUCER_TMP_PROJECT_DIR || tmpdir5();
20620
- const tempProjectDir = mkdtempSync8(join44(tempRoot, "producer-project-"));
20621
- writeFileSync13(join44(tempProjectDir, "index.html"), html, "utf-8");
20761
+ const tempProjectDir = mkdtempSync8(join45(tempRoot, "producer-project-"));
20762
+ writeFileSync13(join45(tempProjectDir, "index.html"), html, "utf-8");
20622
20763
  return {
20623
20764
  prepared: {
20624
20765
  input: { projectDir: tempProjectDir, ...options },
@@ -20875,6 +21016,7 @@ function createRenderHandlers(options = {}) {
20875
21016
  outputUrl,
20876
21017
  fileSize,
20877
21018
  durationMs,
21019
+ ...referenceBackgroundResult7(job[REFERENCE_BACKGROUND_FIELD5]),
20878
21020
  videoDurationSeconds: job.duration ?? null,
20879
21021
  outcome: job.outcome ?? "completed",
20880
21022
  warnings: job.warnings,
@@ -20962,6 +21104,7 @@ function createRenderHandlers(options = {}) {
20962
21104
  outputToken,
20963
21105
  outputUrl,
20964
21106
  fileSize,
21107
+ ...referenceBackgroundResult7(job[REFERENCE_BACKGROUND_FIELD5]),
20965
21108
  videoDurationSeconds: job.duration ?? null,
20966
21109
  outcome: job.outcome ?? "completed",
20967
21110
  warnings: job.warnings,
@@ -21087,9 +21230,13 @@ import { quantizeTimeToFrame, MEDIA_VISUAL_STYLE_PROPERTIES } from "@hyperframes
21087
21230
  // src/services/distributed/planV2Execution.ts
21088
21231
  import { mkdtempSync as mkdtempSync10, rmSync as rmSync19 } from "node:fs";
21089
21232
  import { tmpdir as tmpdir6 } from "node:os";
21090
- import { join as join46 } from "node:path";
21233
+ import { join as join47 } from "node:path";
21091
21234
 
21092
21235
  // src/services/distributed/assemble.ts
21236
+ import {
21237
+ REFERENCE_BACKGROUND_FIELD as REFERENCE_BACKGROUND_FIELD6,
21238
+ referenceBackgroundResult as referenceBackgroundResult8
21239
+ } from "@hyperframes/engine";
21093
21240
  import {
21094
21241
  cpSync as cpSync3,
21095
21242
  existsSync as existsSync30,
@@ -21101,7 +21248,7 @@ import {
21101
21248
  statSync as statSync13,
21102
21249
  writeFileSync as writeFileSync14
21103
21250
  } from "node:fs";
21104
- import { dirname as dirname19, join as join45 } from "node:path";
21251
+ import { dirname as dirname19, join as join46 } from "node:path";
21105
21252
  import {
21106
21253
  appendRateCapArgs as appendRateCapArgs2,
21107
21254
  appendRenderProvenanceArgs as appendRenderProvenanceArgs2,
@@ -21129,12 +21276,16 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
21129
21276
  const plan2 = readAssemblePlan(planDir, chunkOutputs);
21130
21277
  const chunkPaths = chunkOutputs.flat();
21131
21278
  if (plan2.dimensions.format === "png-sequence") {
21132
- return mergePngFrameDirs(chunkPaths, outputPath, plan2.totalFrames, audioPath, start);
21279
+ return {
21280
+ ...mergePngFrameDirs(chunkPaths, outputPath, plan2.totalFrames, audioPath, start),
21281
+ ...referenceBackgroundResult8(plan2[REFERENCE_BACKGROUND_FIELD6])
21282
+ };
21133
21283
  }
21134
21284
  if (chunksAreStitchedMp4(planDir, cfr)) {
21135
21285
  const separateAudio = stitchesSeparateAudio(planDir) && audioPath !== null;
21136
21286
  await writeStitchedMp4(chunkPaths, outputPath, separateAudio ? audioPath : void 0);
21137
21287
  return {
21288
+ ...referenceBackgroundResult8(plan2[REFERENCE_BACKGROUND_FIELD6]),
21138
21289
  outputPath,
21139
21290
  durationMs: Date.now() - start,
21140
21291
  framesEncoded: plan2.totalFrames,
@@ -21166,7 +21317,7 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
21166
21317
  };
21167
21318
  try {
21168
21319
  const videoSources = cfrEncoder ? [await reencodeCfr(ctx, chunkPaths, cfrEncoder)] : chunkPaths;
21169
- const finalPath = join45(workDir, `output.${format}`);
21320
+ const finalPath = join46(workDir, `output.${format}`);
21170
21321
  const audio = audioPath !== null && existsSync30(audioPath) ? audioPath : null;
21171
21322
  await writeJoined(ctx, videoSources, audio, finalPath);
21172
21323
  renameWithRetry11(finalPath, outputPath);
@@ -21187,6 +21338,7 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
21187
21338
  }
21188
21339
  const fileSize = existsSync30(outputPath) ? statSync13(outputPath).size : 0;
21189
21340
  return {
21341
+ ...referenceBackgroundResult8(plan2[REFERENCE_BACKGROUND_FIELD6]),
21190
21342
  outputPath,
21191
21343
  durationMs: Date.now() - start,
21192
21344
  framesEncoded: plan2.totalFrames,
@@ -21194,8 +21346,8 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
21194
21346
  };
21195
21347
  }
21196
21348
  function readAssemblePlan(planDir, chunkPaths) {
21197
- const planJsonPath = join45(planDir, "plan.json");
21198
- const chunksJsonPath = join45(planDir, "meta", "chunks.json");
21349
+ const planJsonPath = join46(planDir, "plan.json");
21350
+ const chunksJsonPath = join46(planDir, "meta", "chunks.json");
21199
21351
  if (!existsSync30(planJsonPath)) {
21200
21352
  throw new Error(`[assemble] planDir missing plan.json: ${planJsonPath}`);
21201
21353
  }
@@ -21246,7 +21398,7 @@ function cfrEncodeArgs(inputArgs, encoder, fpsArg) {
21246
21398
  return args;
21247
21399
  }
21248
21400
  async function reencodeCfr(ctx, chunkPaths, encoder) {
21249
- const cfrOutputPath = join45(ctx.workDir, `cfr.${ctx.format}`);
21401
+ const cfrOutputPath = join46(ctx.workDir, `cfr.${ctx.format}`);
21250
21402
  const cfrArgs = cfrEncodeArgs(videoInputArgs(ctx, chunkPaths), encoder, ctx.fpsArg);
21251
21403
  appendRenderProvenanceArgs2(cfrArgs, cfrOutputPath);
21252
21404
  cfrArgs.push("-y", cfrOutputPath);
@@ -21273,7 +21425,7 @@ async function writeJoined(ctx, videoSources, audioPath, finalPath) {
21273
21425
  );
21274
21426
  return;
21275
21427
  }
21276
- const paddedAudioPath = join45(ctx.workDir, "audio-padded.m4a");
21428
+ const paddedAudioPath = join46(ctx.workDir, "audio-padded.m4a");
21277
21429
  const padTrimResult = await ctx.timed(
21278
21430
  "audioMs",
21279
21431
  () => padOrTrimAudioToVideoFrameCount({
@@ -21313,7 +21465,7 @@ async function writeJoined(ctx, videoSources, audioPath, finalPath) {
21313
21465
  }
21314
21466
  }
21315
21467
  function concatDemuxerInput(chunkPaths, workDir, fpsArg) {
21316
- const listPath = join45(workDir, "concat-list.txt");
21468
+ const listPath = join46(workDir, "concat-list.txt");
21317
21469
  const body = chunkPaths.map((path) => `file '${path.replace(/'/g, "'\\''")}'`).join("\n");
21318
21470
  writeFileSync14(listPath, `${body}
21319
21471
  `, "utf-8");
@@ -21351,11 +21503,11 @@ function stitchesSeparateAudio(planDir) {
21351
21503
  return lockedAudio(planDir).separateAudio === true;
21352
21504
  }
21353
21505
  function planFormat(planDir) {
21354
- const plan2 = JSON.parse(readFileSync15(join45(planDir, "plan.json"), "utf-8"));
21506
+ const plan2 = JSON.parse(readFileSync15(join46(planDir, "plan.json"), "utf-8"));
21355
21507
  return plan2.dimensions.format;
21356
21508
  }
21357
21509
  function lockedAudio(planDir) {
21358
- const encoderJsonPath = join45(planDir, "meta", "encoder.json");
21510
+ const encoderJsonPath = join46(planDir, "meta", "encoder.json");
21359
21511
  if (!existsSync30(encoderJsonPath)) return {};
21360
21512
  return JSON.parse(readFileSync15(encoderJsonPath, "utf-8"));
21361
21513
  }
@@ -21365,7 +21517,7 @@ function readCfrEncoder(planDir, format) {
21365
21517
  `[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.`
21366
21518
  );
21367
21519
  }
21368
- const encoderJsonPath = join45(planDir, "meta", "encoder.json");
21520
+ const encoderJsonPath = join46(planDir, "meta", "encoder.json");
21369
21521
  if (!existsSync30(encoderJsonPath)) {
21370
21522
  throw new Error(`[assemble] planDir missing meta/encoder.json: ${encoderJsonPath}`);
21371
21523
  }
@@ -21392,8 +21544,8 @@ function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, start
21392
21544
  throw new Error(`[assemble] png-sequence chunk has no frames: ${chunkDir}`);
21393
21545
  }
21394
21546
  for (const frame of frames) {
21395
- const dst = join45(outputPath, formatExportFrameName(globalIdx, "png"));
21396
- cpSync3(join45(chunkDir, frame), dst);
21547
+ const dst = join46(outputPath, formatExportFrameName(globalIdx, "png"));
21548
+ cpSync3(join46(chunkDir, frame), dst);
21397
21549
  globalIdx += 1;
21398
21550
  }
21399
21551
  }
@@ -21403,13 +21555,13 @@ function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, start
21403
21555
  );
21404
21556
  }
21405
21557
  if (audioPath !== null && existsSync30(audioPath)) {
21406
- const sidecar = join45(outputPath, MIXED_AUDIO_FILENAME4);
21558
+ const sidecar = join46(outputPath, MIXED_AUDIO_FILENAME4);
21407
21559
  cpSync3(audioPath, sidecar);
21408
21560
  }
21409
21561
  let fileSize = 0;
21410
21562
  for (const name of readdirSync11(outputPath)) {
21411
21563
  try {
21412
- fileSize += statSync13(join45(outputPath, name)).size;
21564
+ fileSize += statSync13(join46(outputPath, name)).size;
21413
21565
  } catch {
21414
21566
  }
21415
21567
  }
@@ -21423,8 +21575,8 @@ function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, start
21423
21575
 
21424
21576
  // src/services/distributed/planV2Execution.ts
21425
21577
  async function renderChunkV2(planV2Dir, chunkIndex, outputChunkPath, options) {
21426
- const workRoot = mkdtempSync10(join46(tmpdir6(), "hf-plan-v2-chunk-"));
21427
- const materializedPlanDir = join46(workRoot, "plan");
21578
+ const workRoot = mkdtempSync10(join47(tmpdir6(), "hf-plan-v2-chunk-"));
21579
+ const materializedPlanDir = join47(workRoot, "plan");
21428
21580
  try {
21429
21581
  materializePlanV2Target(planV2Dir, { role: "chunk", chunkIndex }, materializedPlanDir);
21430
21582
  return await renderChunk(materializedPlanDir, chunkIndex, outputChunkPath, options);
@@ -21433,8 +21585,8 @@ async function renderChunkV2(planV2Dir, chunkIndex, outputChunkPath, options) {
21433
21585
  }
21434
21586
  }
21435
21587
  async function assembleV2(planV2Dir, chunkPaths, outputPath, options) {
21436
- const workRoot = mkdtempSync10(join46(tmpdir6(), "hf-plan-v2-assembler-"));
21437
- const materializedPlanDir = join46(workRoot, "plan");
21588
+ const workRoot = mkdtempSync10(join47(tmpdir6(), "hf-plan-v2-assembler-"));
21589
+ const materializedPlanDir = join47(workRoot, "plan");
21438
21590
  try {
21439
21591
  const materialized = materializePlanV2Target(
21440
21592
  planV2Dir,
@@ -21477,6 +21629,7 @@ export {
21477
21629
  PLAN_V2_SCHEMA_VERSION,
21478
21630
  PlanProtocolUnsupportedError,
21479
21631
  PlanV2IntegrityError,
21632
+ REFERENCE_BACKGROUND_FIELD7 as REFERENCE_BACKGROUND_FIELD,
21480
21633
  RENDER_JOB_DIR,
21481
21634
  RENDER_REQUEST_VERSION,
21482
21635
  RenderCancelledError,
@@ -21487,14 +21640,15 @@ export {
21487
21640
  assembleV2,
21488
21641
  captureFrame2 as captureFrame,
21489
21642
  captureFrameToBuffer4 as captureFrameToBuffer,
21490
- closeCaptureSession11 as closeCaptureSession,
21643
+ closeCaptureSession12 as closeCaptureSession,
21491
21644
  collectRenderMedia,
21492
- createCaptureSession11 as createCaptureSession,
21645
+ createCaptureSession12 as createCaptureSession,
21493
21646
  createConsoleLogger,
21494
21647
  createFileServer,
21495
21648
  createOwnedRenderDir,
21496
21649
  createPlanV2FromExecutionPlan,
21497
21650
  createProducerApp,
21651
+ createReferenceBackgroundRecorder3 as createReferenceBackgroundRecorder,
21498
21652
  createRenderHandlers,
21499
21653
  createRenderJob,
21500
21654
  createRenderRequest,
@@ -21527,6 +21681,8 @@ export {
21527
21681
  publishPlanV2FromExecutionPlan,
21528
21682
  quantizeTimeToFrame,
21529
21683
  readPlanV2Manifest,
21684
+ readReferenceBackground2 as readReferenceBackground,
21685
+ referenceBackgroundResult9 as referenceBackgroundResult,
21530
21686
  renderChunk,
21531
21687
  renderChunkV2,
21532
21688
  renderConfigFromRequest,