@hyperframes/producer 0.8.176 → 0.8.178

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
@@ -1387,9 +1387,9 @@ import {
1387
1387
  } from "node:fs";
1388
1388
  import { hostname, uptime } from "node:os";
1389
1389
  import { dirname as dirname3, join as join4 } from "node:path";
1390
+ import { RENDER_TEMP_DIR } from "@hyperframes/core/project-folders";
1390
1391
  var OWNER_FILE = "owner.json";
1391
1392
  var TRANSACTION_BACKUP = "backup";
1392
- var RENDER_TEMP_DIR = /^(work-[0-9a-f]{8}(-[0-9a-f]{4}){3}-[0-9a-f]{12}|hf-render|\..+\.hf-transaction)-[A-Za-z0-9]{6}$/;
1393
1393
  var RENDER_JOB_DIR = /^[0-9a-f]{8}(-[0-9a-f]{4}){3}-[0-9a-f]{12}$/;
1394
1394
  function pidScope() {
1395
1395
  const attempt = (read) => {
@@ -6067,7 +6067,7 @@ function detectShaderTransitionUsage(html) {
6067
6067
  }
6068
6068
  return false;
6069
6069
  }
6070
- async function resolveMediaDuration(src, mediaStart, playbackRate, baseDir, downloadDir, tagName, elementIdentity, log) {
6070
+ async function resolveMediaDuration(src, mediaStart, playbackRate, baseDir, downloadDir, tagName, elementIdentity, log, urlByAbsentLocalPath) {
6071
6071
  let filePath = src;
6072
6072
  if (isHttpUrl(src)) {
6073
6073
  if (!existsSync10(downloadDir)) mkdirSync6(downloadDir, { recursive: true });
@@ -6081,8 +6081,9 @@ async function resolveMediaDuration(src, mediaStart, playbackRate, baseDir, down
6081
6081
  } else {
6082
6082
  filePath = decodedUrlPath(src);
6083
6083
  if (!filePath.startsWith("/")) filePath = join13(baseDir, filePath);
6084
+ filePath = urlByAbsentLocalPath?.get(filePath) ?? filePath;
6084
6085
  }
6085
- if (!existsSync10(filePath)) {
6086
+ if (!isHttpUrl(filePath) && !existsSync10(filePath)) {
6086
6087
  return { duration: null, resolvedPath: filePath };
6087
6088
  }
6088
6089
  const withSrcContext = (error) => {
@@ -6145,7 +6146,7 @@ function markInferredVariableMediaDurations(html, unresolvedMedia) {
6145
6146
  }
6146
6147
  return changed ? document2.toString() : html;
6147
6148
  }
6148
- async function compileHtmlFile(html, baseDir, downloadDir, log) {
6149
+ async function compileHtmlFile(html, baseDir, downloadDir, log, urlByAbsentLocalPath) {
6149
6150
  const { html: staticCompiled, unresolved } = compileTimingAttrs(html);
6150
6151
  const mediaUnresolved = unresolved.filter(
6151
6152
  (el) => (el.tagName === "video" || el.tagName === "audio") && el.src
@@ -6161,7 +6162,8 @@ async function compileHtmlFile(html, baseDir, downloadDir, log) {
6161
6162
  downloadDir,
6162
6163
  el.tagName,
6163
6164
  el.id,
6164
- log
6165
+ log,
6166
+ urlByAbsentLocalPath
6165
6167
  ).then(({ duration }) => ({ id: el.id, duration }))
6166
6168
  )
6167
6169
  );
@@ -6203,7 +6205,7 @@ async function compileHtmlFile(html, baseDir, downloadDir, log) {
6203
6205
  compiledHtml = compiledHtml.replace(/(<audio\b[^>]*)\s+crossorigin(?:=["'][^"']*["'])?/gi, "$1");
6204
6206
  return { html: compiledHtml, unresolvedCompositions };
6205
6207
  }
6206
- async function parseSubCompositions(html, projectDir, downloadDir, visited = /* @__PURE__ */ new Set()) {
6208
+ async function parseSubCompositions(html, projectDir, downloadDir, visited = /* @__PURE__ */ new Set(), urlByAbsentLocalPath) {
6207
6209
  const subCompositions = /* @__PURE__ */ new Map();
6208
6210
  const { document: document2 } = parseHTML4(html);
6209
6211
  const compEls = document2.querySelectorAll("[data-composition-src]");
@@ -6229,13 +6231,16 @@ async function parseSubCompositions(html, projectDir, downloadDir, visited = /*
6229
6231
  const { html: compiledSub } = await compileHtmlFile(
6230
6232
  item.rawSubHtml,
6231
6233
  dirname6(item.filePath),
6232
- downloadDir
6234
+ downloadDir,
6235
+ void 0,
6236
+ urlByAbsentLocalPath
6233
6237
  );
6234
6238
  const nested = await parseSubCompositions(
6235
6239
  compiledSub,
6236
6240
  projectDir,
6237
6241
  downloadDir,
6238
- item.nestedVisited
6242
+ item.nestedVisited,
6243
+ urlByAbsentLocalPath
6239
6244
  );
6240
6245
  return {
6241
6246
  srcPath: item.srcPath,
@@ -6993,9 +6998,16 @@ async function compileForRender(projectDir, htmlPath, downloadDir, options = {})
6993
6998
  rawHtml,
6994
6999
  projectDir,
6995
7000
  downloadDir,
6996
- options.log
7001
+ options.log,
7002
+ options.urlByAbsentLocalPath
7003
+ );
7004
+ const { subCompositions } = await parseSubCompositions(
7005
+ compiledHtml,
7006
+ projectDir,
7007
+ downloadDir,
7008
+ void 0,
7009
+ options.urlByAbsentLocalPath
6997
7010
  );
6998
- const { subCompositions } = await parseSubCompositions(compiledHtml, projectDir, downloadDir);
6999
7011
  const fullHtml = ensureFullDocument(compiledHtml);
7000
7012
  const inlinedHtml = inlineSubCompositions(
7001
7013
  fullHtml,
@@ -7491,6 +7503,7 @@ async function runCompileStage(input) {
7491
7503
  allowSystemFontCapture,
7492
7504
  abortSignal,
7493
7505
  variables: input.variables,
7506
+ urlByAbsentLocalPath: input.urlByAbsentLocalPath,
7494
7507
  animatedGifCacheDir: cfg.extractCacheDir ? join14(cfg.extractCacheDir, "animated-gif") : void 0,
7495
7508
  ffmpegProcessTimeout: cfg.ffmpegProcessTimeout
7496
7509
  });
@@ -8489,7 +8502,8 @@ async function runExtractVideosStage(input) {
8489
8502
  abortSignal,
8490
8503
  assertNotAborted,
8491
8504
  materializeSymlinks,
8492
- deferRangeExtraction
8505
+ deferRangeExtraction,
8506
+ urlByAbsentLocalPath
8493
8507
  } = input;
8494
8508
  const stage2Start = Date.now();
8495
8509
  const extractionPolicy = resolveVideoExtractionPolicy();
@@ -8583,7 +8597,8 @@ async function runExtractVideosStage(input) {
8583
8597
  maxTransientRetries: extractionPolicy.maxTransientRetries,
8584
8598
  collectProbeFailures: extractionPolicy.failureMode === "enforce",
8585
8599
  deferRangeExtraction,
8586
- contentKeyedDirs: [join16(compiledDir, REMOTE_MEDIA_SUBDIR)]
8600
+ contentKeyedDirs: [join16(compiledDir, REMOTE_MEDIA_SUBDIR)],
8601
+ urlByAbsentLocalPath
8587
8602
  },
8588
8603
  abortSignal,
8589
8604
  cfg,
@@ -8666,6 +8681,7 @@ async function runAudioStage(input) {
8666
8681
  audioGain,
8667
8682
  audios,
8668
8683
  masterFxChain,
8684
+ urlByAbsentLocalPath,
8669
8685
  abortSignal,
8670
8686
  assertNotAborted,
8671
8687
  log
@@ -8685,7 +8701,7 @@ async function runAudioStage(input) {
8685
8701
  audioOutputPath,
8686
8702
  duration,
8687
8703
  abortSignal,
8688
- { ffmpegProcessTimeout, audioGain, masterFxChain },
8704
+ { ffmpegProcessTimeout, audioGain, masterFxChain, urlByAbsentLocalPath },
8689
8705
  compiledDir
8690
8706
  );
8691
8707
  } catch (err) {
@@ -13990,6 +14006,8 @@ function buildChunkCaptureOptions(input) {
13990
14006
  // The controller's snapshot, so the first capture sees the in-process renderer's `__hfVariables`.
13991
14007
  variables: encoder.variables,
13992
14008
  subTimelineWaitMemo: {},
14009
+ skipReadinessVideoIds: input.skipReadinessVideoIds,
14010
+ videoMetadataHints: input.videoMetadataHints,
13993
14011
  ...videoCaptureBeyondViewport !== void 0 ? { captureBeyondViewport: videoCaptureBeyondViewport } : {},
13994
14012
  // A fixed warmup count keeps `beginFrameTimeTicks` host-independent.
13995
14013
  lockWarmupTicks: true,
@@ -13998,6 +14016,19 @@ function buildChunkCaptureOptions(input) {
13998
14016
  }
13999
14017
  async function extractDeferredVideoFramesForChunk(input) {
14000
14018
  const deferred = input.planVideos.extracted.filter((video) => video.deferredRange);
14019
+ const remoteSources = input.remoteSources ?? {};
14020
+ for (const path of Object.keys(remoteSources)) {
14021
+ if (!deferred.some((video) => video.deferredRange.sourcePath === path)) {
14022
+ throw new RenderChunkValidationError(
14023
+ MISSING_PLAN_ARTIFACT,
14024
+ `[renderChunk] ${path} is read by URL but no deferred video uses it`
14025
+ );
14026
+ }
14027
+ defaultLogger.info("[renderChunk] reading video by URL", {
14028
+ path,
14029
+ url: remoteSources[path].split("?")[0]
14030
+ });
14031
+ }
14001
14032
  if (deferred.length === 0) return [];
14002
14033
  const table = createFrameLookupTable2(
14003
14034
  input.planVideos.videos,
@@ -14025,8 +14056,9 @@ async function extractDeferredVideoFramesForChunk(input) {
14025
14056
  return { ...video, outputDir, framePaths: /* @__PURE__ */ new Map(), ownedByLookup: false };
14026
14057
  }
14027
14058
  const range = video.deferredRange;
14028
- const sourcePath = join28(input.planDir, ...range.sourcePath.split("/"));
14029
- if (!existsSync18(sourcePath)) {
14059
+ const remoteUrl = remoteSources[range.sourcePath];
14060
+ const sourcePath = remoteUrl ?? join28(input.planDir, ...range.sourcePath.split("/"));
14061
+ if (remoteUrl === void 0 && !existsSync18(sourcePath)) {
14030
14062
  throw new RenderChunkValidationError(
14031
14063
  MISSING_PLAN_ARTIFACT,
14032
14064
  `[renderChunk] planDir is missing the source for video ${JSON.stringify(video.videoId)}: ${range.sourcePath}`
@@ -14366,6 +14398,16 @@ function collectPlanAssetShas(planDir, digests) {
14366
14398
  }
14367
14399
  return { compositionHtml, assets };
14368
14400
  }
14401
+ var PLAN_REMOTE_FILES_RELATIVE_PATH = "meta/remote-files.json";
14402
+ function writePlanRemoteFiles(planDir, files) {
14403
+ if (files.length === 0) return;
14404
+ const sorted = [...files].sort((a, b) => a.path < b.path ? -1 : 1);
14405
+ writeFileSync6(join30(planDir, PLAN_REMOTE_FILES_RELATIVE_PATH), JSON.stringify(sorted), "utf-8");
14406
+ }
14407
+ function readPlanRemoteFiles(planDir) {
14408
+ const path = join30(planDir, PLAN_REMOTE_FILES_RELATIVE_PATH);
14409
+ return existsSync19(path) ? JSON.parse(readFileSync10(path, "utf-8")) : [];
14410
+ }
14369
14411
  function recomputePlanHashFromPlanDir(planDir, digests) {
14370
14412
  const planJsonPath = join30(planDir, "plan.json");
14371
14413
  const encoderJsonPath = join30(planDir, "meta", "encoder.json");
@@ -14947,7 +14989,7 @@ function createVideoSourceShipper(input) {
14947
14989
  let result = null;
14948
14990
  for (const root of stagedRel === null ? [input.workCompiledDir, input.projectDir] : []) {
14949
14991
  const rel = pathInside(root, srcPath);
14950
- if (rel !== null && existsSync20(join32(input.planDir, "compiled", rel))) {
14992
+ if (rel !== null && (existsSync20(join32(input.planDir, "compiled", rel)) || input.isRemote(srcPath))) {
14951
14993
  result = ["compiled", ...rel.split(sep2)].join("/");
14952
14994
  break;
14953
14995
  }
@@ -15025,8 +15067,17 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
15025
15067
  logPlanStage(log, "copy", stageStarted);
15026
15068
  const finalCompiledDir = join32(planDir, "compiled");
15027
15069
  const needsAlpha = outputNeedsAlpha(config.format);
15070
+ const remoteFiles = Object.entries(options.remoteFiles ?? {});
15071
+ const remoteLocalPath = (rel) => join32(projectDir, ...rel.split("/"));
15072
+ const urlByAbsentLocalPath = new Map(
15073
+ remoteFiles.map(([rel, file]) => [remoteLocalPath(rel), file.url])
15074
+ );
15075
+ const localPathByUrl = new Map(
15076
+ remoteFiles.map(([rel, file]) => [file.url, remoteLocalPath(rel)])
15077
+ );
15028
15078
  stageStarted = Date.now();
15029
15079
  const compileResult = await runCompileStage({
15080
+ urlByAbsentLocalPath,
15030
15081
  projectDir,
15031
15082
  workDir,
15032
15083
  htmlPath,
@@ -15114,7 +15165,8 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
15114
15165
  abortSignal,
15115
15166
  assertNotAborted,
15116
15167
  materializeSymlinks: true,
15117
- deferRangeExtraction: options.deferVideoExtraction
15168
+ deferRangeExtraction: options.deferVideoExtraction,
15169
+ urlByAbsentLocalPath
15118
15170
  });
15119
15171
  logPlanStage(log, "extract", stageStarted);
15120
15172
  if (extractResult.failureToEnforce) throw extractResult.failureToEnforce;
@@ -15137,7 +15189,8 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
15137
15189
  planDir,
15138
15190
  projectDir,
15139
15191
  workCompiledDir: compiledDir,
15140
- workVideoFramesDir: stagedVideoFrames
15192
+ workVideoFramesDir: stagedVideoFrames,
15193
+ isRemote: (srcPath) => urlByAbsentLocalPath.has(srcPath)
15141
15194
  });
15142
15195
  const sourceRoots = [
15143
15196
  [stagedVideoFrames, "video-frames"],
@@ -15147,17 +15200,28 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
15147
15200
  const planVideosJson = buildPlanVideosJson({
15148
15201
  videos: composition.videos,
15149
15202
  compositionEnd: job.duration ?? Number.NaN,
15150
- extracted: (extractResult.extractionResult?.extracted ?? []).map((ext) => ({
15151
- videoId: ext.videoId,
15152
- srcPath: stableSourcePath(sourceRoots, ext.srcPath),
15153
- framePattern: ext.framePattern,
15154
- fps: ext.fps,
15155
- totalFrames: ext.totalFrames,
15156
- metadata: ext.metadata,
15157
- ...ext.deferredRange ? { deferredRange: { ...ext.deferredRange, sourcePath: shipVideoSource(ext.srcPath) } } : {}
15158
- }))
15203
+ extracted: (extractResult.extractionResult?.extracted ?? []).map((ext) => {
15204
+ const sourcePath = localPathByUrl.get(ext.srcPath) ?? ext.srcPath;
15205
+ return {
15206
+ videoId: ext.videoId,
15207
+ srcPath: stableSourcePath(sourceRoots, sourcePath),
15208
+ framePattern: ext.framePattern,
15209
+ fps: ext.fps,
15210
+ totalFrames: ext.totalFrames,
15211
+ metadata: ext.metadata,
15212
+ ...ext.deferredRange ? { deferredRange: { ...ext.deferredRange, sourcePath: shipVideoSource(sourcePath) } } : {}
15213
+ };
15214
+ })
15159
15215
  });
15160
15216
  mkdirSync11(join32(planDir, "meta"), { recursive: true });
15217
+ writePlanRemoteFiles(
15218
+ planDir,
15219
+ remoteFiles.map(([rel, { sha256, sizeBytes }]) => ({
15220
+ path: `compiled/${rel}`,
15221
+ sha256,
15222
+ sizeBytes
15223
+ }))
15224
+ );
15161
15225
  writeFileSync7(
15162
15226
  join32(planDir, PLAN_VIDEOS_META_RELATIVE_PATH),
15163
15227
  JSON.stringify(planVideosJson, null, 2),
@@ -15172,6 +15236,7 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
15172
15236
  projectDir,
15173
15237
  workDir,
15174
15238
  compiledDir: finalCompiledDir,
15239
+ urlByAbsentLocalPath,
15175
15240
  duration: audioSeconds,
15176
15241
  ffmpegProcessTimeout: cfg.ffmpegProcessTimeout,
15177
15242
  audioGain: cfg.audioGain,
@@ -16966,17 +17031,20 @@ function artifactTargets(path, videoDependencies, chunksCarryAudio2) {
16966
17031
  if (path === "plan.json" || path === "meta/chunks.json" || path === "meta/encoder.json") {
16967
17032
  return { chunks: "all", assembler: true };
16968
17033
  }
16969
- if (path === "meta/composition.json" || path === "meta/videos.json" || path.startsWith("compiled/")) {
16970
- return { chunks: "all", assembler: false };
16971
- }
16972
- if (path.startsWith("video-frames/") || path.startsWith("video-sources/")) {
17034
+ if (path.startsWith("video-frames/") || path.startsWith("video-sources/") || path.startsWith("compiled/") && videoDependencies?.has(path)) {
16973
17035
  return {
16974
17036
  chunks: videoDependencies === null ? "all" : videoDependencies.get(path) ?? [],
16975
17037
  assembler: false
16976
17038
  };
16977
17039
  }
17040
+ if (path === "meta/composition.json" || path === "meta/videos.json" || path.startsWith("compiled/")) {
17041
+ return { chunks: "all", assembler: false };
17042
+ }
16978
17043
  return { chunks: "all", assembler: true };
16979
17044
  }
17045
+ function isPlanV2DeferredSourceArtifact(artifact) {
17046
+ return artifact.path.startsWith("compiled/") && artifact.chunks !== "all";
17047
+ }
16980
17048
  function listVideoFramePaths(executionPlanDir, videos) {
16981
17049
  return videos.extracted.map((video) => {
16982
17050
  const outputDir = resolveExtractedVideoOutputDir(executionPlanDir, video.videoId);
@@ -17139,14 +17207,17 @@ function publishBlobFor(absolutePath, digests) {
17139
17207
  };
17140
17208
  }
17141
17209
  async function putBlobs(publisher, blobs) {
17210
+ await putTasks(blobs.map((blob) => () => publisher.putBlob(blob)));
17211
+ }
17212
+ async function putTasks(tasks) {
17142
17213
  const uploadSlots = 32;
17143
17214
  let next = 0;
17144
17215
  let failure;
17145
17216
  const upload = async () => {
17146
- while (!failure && next < blobs.length) {
17147
- const blob = blobs[next++];
17217
+ while (!failure && next < tasks.length) {
17218
+ const task = tasks[next++];
17148
17219
  try {
17149
- await publisher.putBlob(blob);
17220
+ await task();
17150
17221
  } catch (reason) {
17151
17222
  failure ??= { reason };
17152
17223
  }
@@ -17201,6 +17272,15 @@ function buildPlanV2Publication(executionPlanDir, digests) {
17201
17272
  ...targets
17202
17273
  });
17203
17274
  }
17275
+ const storedBlobs = /* @__PURE__ */ new Map();
17276
+ for (const remote of readPlanRemoteFiles(executionPlanDir)) {
17277
+ const { sha256, sizeBytes } = remote;
17278
+ if (!blobs.has(sha256)) storedBlobs.set(sha256, { sha256, sizeBytes });
17279
+ artifacts.push({
17280
+ ...remote,
17281
+ ...artifactTargets(remote.path, videoDependencyPlan.dependencies, chunksCarryAudio2)
17282
+ });
17283
+ }
17204
17284
  const base = {
17205
17285
  protocol: PLAN_PROTOCOL_V2,
17206
17286
  // Retain the established manifest key byte-for-byte. It now acts as the
@@ -17222,7 +17302,8 @@ function buildPlanV2Publication(executionPlanDir, digests) {
17222
17302
  ...base,
17223
17303
  planHash: computeManifestHash(base)
17224
17304
  },
17225
- blobs: [...blobs.values()]
17305
+ blobs: [...blobs.values()],
17306
+ storedBlobs: [...storedBlobs.values()]
17226
17307
  };
17227
17308
  }
17228
17309
  function createPlanV2FromExecutionPlan(executionPlanDir, planV2Dir) {
@@ -17230,6 +17311,9 @@ function createPlanV2FromExecutionPlan(executionPlanDir, planV2Dir) {
17230
17311
  throw new PlanV2IntegrityError(`output directory already exists: ${planV2Dir}`);
17231
17312
  }
17232
17313
  const publication = buildPlanV2Publication(executionPlanDir);
17314
+ if (publication.storedBlobs.length > 0) {
17315
+ throw new PlanV2IntegrityError("a local plan v2 directory cannot reference remote files");
17316
+ }
17233
17317
  mkdirSync14(dirname15(planV2Dir), { recursive: true });
17234
17318
  const tempDir = mkdtempSync6(join38(dirname15(planV2Dir), ".plan-v2-build-"));
17235
17319
  try {
@@ -17259,8 +17343,14 @@ async function publishExecutionPlan(executionPlanDir, publisher, { digests, log
17259
17343
  let stageStarted = Date.now();
17260
17344
  const publication = buildPlanV2Publication(executionPlanDir, digests);
17261
17345
  logPlanStage(log, "publication", stageStarted);
17346
+ if (publication.storedBlobs.length > 0 && !publisher.referenceStoredBlob) {
17347
+ throw new PlanV2IntegrityError("this publisher cannot reference remote files");
17348
+ }
17262
17349
  stageStarted = Date.now();
17263
- await putBlobs(publisher, publication.blobs);
17350
+ await putTasks([
17351
+ ...publication.blobs.map((blob) => () => publisher.putBlob(blob)),
17352
+ ...publication.storedBlobs.map((blob) => () => publisher.referenceStoredBlob(blob))
17353
+ ]);
17264
17354
  logPlanStage(log, "upload", stageStarted);
17265
17355
  stageStarted = Date.now();
17266
17356
  await publisher.commitManifest(canonicalJsonStringify(publication.manifest));
@@ -17284,6 +17374,7 @@ async function planV2WithPublisher(projectDir, config, publisher, options = {})
17284
17374
  await buildLocalExecutionPlan(projectDir, config, stagingRoot, {
17285
17375
  executionPlanSizeLimitBytes: Number.MAX_SAFE_INTEGER,
17286
17376
  deferVideoExtraction: true,
17377
+ remoteFiles: options.remoteFiles,
17287
17378
  digests,
17288
17379
  // artifactTargets ships every compiled/ file to all chunks, so each is in the manifest.
17289
17380
  whileAudioMixes: () => putBlobs(
@@ -17486,14 +17577,16 @@ function verifyBlob(planV2Dir, artifact) {
17486
17577
  }
17487
17578
  return sourcePath;
17488
17579
  }
17489
- function materializePlanV2Target(planV2Dir, target, destinationDir) {
17580
+ function materializePlanV2Target(planV2Dir, target, destinationDir, options = {}) {
17490
17581
  if (existsSync25(destinationDir)) {
17491
17582
  throw new PlanV2IntegrityError(`materialization destination already exists: ${destinationDir}`);
17492
17583
  }
17493
17584
  const manifest = readPlanV2Manifest(planV2Dir);
17494
17585
  const artifacts = listPlanV2ArtifactsForTarget(manifest, target);
17586
+ const remoteSources = options.remoteSources ?? {};
17587
+ assertRemoteSources(remoteSources, artifacts);
17495
17588
  const audioArtifact = artifacts.find((artifact) => isPlanAudioArtifactPath(artifact.path));
17496
- const verified = artifacts.map((artifact) => ({
17589
+ const verified = artifacts.filter((artifact) => !Object.hasOwn(remoteSources, artifact.path)).map((artifact) => ({
17497
17590
  artifact,
17498
17591
  sourcePath: verifyBlob(planV2Dir, artifact)
17499
17592
  }));
@@ -17508,7 +17601,11 @@ function materializePlanV2Target(planV2Dir, target, destinationDir) {
17508
17601
  if (target.role === "chunk") materializeExtractedVideoDirectories(tempDir);
17509
17602
  writeFileSync10(
17510
17603
  join38(tempDir, PLAN_V2_MATERIALIZATION_MARKER),
17511
- canonicalJsonStringify({ manifest, target }),
17604
+ canonicalJsonStringify({
17605
+ manifest,
17606
+ target,
17607
+ ...Object.keys(remoteSources).length > 0 ? { remoteSources } : {}
17608
+ }),
17512
17609
  "utf-8"
17513
17610
  );
17514
17611
  renameSync7(tempDir, destinationDir);
@@ -17529,7 +17626,22 @@ function materializePlanV2Target(planV2Dir, target, destinationDir) {
17529
17626
  audioPath: target.role === "assembler" ? audioArtifact ? join38(destinationDir, audioArtifact.path) : null : null
17530
17627
  };
17531
17628
  }
17629
+ function assertRemoteSources(remoteSources, artifacts) {
17630
+ if (!isRecord3(remoteSources)) throw new PlanV2IntegrityError("remoteSources must be an object");
17631
+ for (const [path, url] of Object.entries(remoteSources)) {
17632
+ const artifact = artifacts.find((candidate) => candidate.path === path);
17633
+ if (!artifact || !isPlanV2DeferredSourceArtifact(artifact)) {
17634
+ throw new PlanV2IntegrityError(`remote source is not a selected deferred video: ${path}`);
17635
+ }
17636
+ if (typeof url !== "string" || !/^https?:\/\//.test(url)) {
17637
+ throw new PlanV2IntegrityError(`remote source for ${path} must be an http(s) URL`);
17638
+ }
17639
+ }
17640
+ }
17532
17641
  function validatePlanV2MaterializedTarget(planDir, expectedTarget) {
17642
+ return readValidatedPlanV2Materialization(planDir, expectedTarget)?.manifest ?? null;
17643
+ }
17644
+ function readValidatedPlanV2Materialization(planDir, expectedTarget) {
17533
17645
  const markerPath = join38(planDir, PLAN_V2_MATERIALIZATION_MARKER);
17534
17646
  if (!existsSync25(markerPath)) return null;
17535
17647
  const marker = readJsonFile(markerPath, "materialization marker");
@@ -17544,7 +17656,10 @@ function validatePlanV2MaterializedTarget(planDir, expectedTarget) {
17544
17656
  }
17545
17657
  const manifest = parsePlanV2Manifest(marker.manifest);
17546
17658
  const required = listPlanV2ArtifactsForTarget(manifest, expectedTarget);
17659
+ const remoteSources = marker.remoteSources ?? {};
17660
+ assertRemoteSources(remoteSources, required);
17547
17661
  for (const artifact of required) {
17662
+ if (Object.hasOwn(remoteSources, artifact.path)) continue;
17548
17663
  const path = join38(planDir, ...artifact.path.split("/"));
17549
17664
  if (!existsSync25(path) || statSync10(path).size !== artifact.sizeBytes) {
17550
17665
  throw new PlanV2IntegrityError(
@@ -17555,7 +17670,7 @@ function validatePlanV2MaterializedTarget(planDir, expectedTarget) {
17555
17670
  throw new PlanV2IntegrityError(`materialized artifact hash mismatch: ${artifact.path}`);
17556
17671
  }
17557
17672
  }
17558
- return manifest;
17673
+ return { manifest, remoteSources };
17559
17674
  }
17560
17675
 
17561
17676
  // src/services/distributed/planV2Execution.ts
@@ -18430,10 +18545,12 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath) {
18430
18545
  const plan2 = JSON.parse(readFileSync14(planJsonPath, "utf-8"));
18431
18546
  readPlanProtocolV1(plan2, DISTRIBUTED_RENDER_CAPABILITIES.roles.chunk);
18432
18547
  const planHashStarted = Date.now();
18433
- const v2Manifest = validatePlanV2MaterializedTarget(planDir, {
18548
+ const v2Materialization = readValidatedPlanV2Materialization(planDir, {
18434
18549
  role: "chunk",
18435
18550
  chunkIndex
18436
18551
  });
18552
+ const v2Manifest = v2Materialization?.manifest ?? null;
18553
+ const remoteSources = v2Materialization?.remoteSources ?? {};
18437
18554
  for (const required of [encoderJsonPath, chunksJsonPath]) {
18438
18555
  if (!existsSync27(required)) {
18439
18556
  throw new RenderChunkValidationError(
@@ -18535,6 +18652,24 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath) {
18535
18652
  const framesDir = join41(workDir, "captured-frames");
18536
18653
  mkdirSync17(framesDir, { recursive: true });
18537
18654
  const videoFramesDir = `${workDir}.video-frames`;
18655
+ const deferredVideoFrames = planVideos ? await extractDeferredVideoFramesForChunk({
18656
+ planDir,
18657
+ planVideos,
18658
+ frames: Array.from(
18659
+ { length: slice.endFrame - slice.startFrame },
18660
+ (_, i) => slice.startFrame + i
18661
+ ),
18662
+ fps: { num: plan2.dimensions.fpsNum, den: plan2.dimensions.fpsDen },
18663
+ outputRoot: videoFramesDir,
18664
+ cfg,
18665
+ remoteSources
18666
+ }) : [];
18667
+ const remoteVideoIds = new Set(
18668
+ planVideos?.extracted.filter(
18669
+ (video) => video.deferredRange && Object.hasOwn(remoteSources, video.deferredRange.sourcePath)
18670
+ ).map((video) => video.videoId)
18671
+ );
18672
+ const remoteVideos = deferredVideoFrames.filter((video) => remoteVideoIds.has(video.videoId));
18538
18673
  const videoFrameLookup = planVideos && planVideos.extracted.length > 0 ? createFrameLookupTable5(
18539
18674
  planVideos.videos,
18540
18675
  [
@@ -18543,17 +18678,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath) {
18543
18678
  planVideos.extracted.filter((video) => !video.deferredRange),
18544
18679
  v2Manifest === null ? "dense-v1" : "sparse-v2"
18545
18680
  ),
18546
- ...await extractDeferredVideoFramesForChunk({
18547
- planDir,
18548
- planVideos,
18549
- frames: Array.from(
18550
- { length: slice.endFrame - slice.startFrame },
18551
- (_, i) => slice.startFrame + i
18552
- ),
18553
- fps: { num: plan2.dimensions.fpsNum, den: plan2.dimensions.fpsDen },
18554
- outputRoot: videoFramesDir,
18555
- cfg
18556
- })
18681
+ ...deferredVideoFrames
18557
18682
  ],
18558
18683
  resolveRenderFpsConfig(job.config.fps).value
18559
18684
  ) : null;
@@ -18571,7 +18696,10 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath) {
18571
18696
  dimensions: plan2.dimensions,
18572
18697
  encoder,
18573
18698
  videoCount: planVideos?.videos.length ?? 0,
18574
- html: readFileSync14(join41(compiledDir, "index.html"), "utf-8")
18699
+ html: readFileSync14(join41(compiledDir, "index.html"), "utf-8"),
18700
+ // The plan ships a deferred source only to chunks that show it; don't wait on the rest.
18701
+ skipReadinessVideoIds: deferredVideoFrames.filter((video) => video.framePaths.size === 0 || remoteVideoIds.has(video.videoId)).map((video) => video.videoId),
18702
+ videoMetadataHints: collectVideoMetadataHints(remoteVideos)
18575
18703
  });
18576
18704
  const chunkWorkerCount = calculateOptimalWorkers2(framesInChunk, void 0, cfg);
18577
18705
  let session = null;
@@ -1407,9 +1407,9 @@ import {
1407
1407
  } from "node:fs";
1408
1408
  import { hostname, uptime } from "node:os";
1409
1409
  import { dirname as dirname3, join as join4 } from "node:path";
1410
+ import { RENDER_TEMP_DIR } from "@hyperframes/core/project-folders";
1410
1411
  var OWNER_FILE = "owner.json";
1411
1412
  var TRANSACTION_BACKUP = "backup";
1412
- var RENDER_TEMP_DIR = /^(work-[0-9a-f]{8}(-[0-9a-f]{4}){3}-[0-9a-f]{12}|hf-render|\..+\.hf-transaction)-[A-Za-z0-9]{6}$/;
1413
1413
  function pidScope() {
1414
1414
  const attempt = (read) => {
1415
1415
  try {
@@ -6086,7 +6086,7 @@ function detectShaderTransitionUsage(html) {
6086
6086
  }
6087
6087
  return false;
6088
6088
  }
6089
- async function resolveMediaDuration(src, mediaStart, playbackRate, baseDir, downloadDir, tagName, elementIdentity, log) {
6089
+ async function resolveMediaDuration(src, mediaStart, playbackRate, baseDir, downloadDir, tagName, elementIdentity, log, urlByAbsentLocalPath) {
6090
6090
  let filePath = src;
6091
6091
  if (isHttpUrl(src)) {
6092
6092
  if (!existsSync10(downloadDir)) mkdirSync6(downloadDir, { recursive: true });
@@ -6100,8 +6100,9 @@ async function resolveMediaDuration(src, mediaStart, playbackRate, baseDir, down
6100
6100
  } else {
6101
6101
  filePath = decodedUrlPath(src);
6102
6102
  if (!filePath.startsWith("/")) filePath = join13(baseDir, filePath);
6103
+ filePath = urlByAbsentLocalPath?.get(filePath) ?? filePath;
6103
6104
  }
6104
- if (!existsSync10(filePath)) {
6105
+ if (!isHttpUrl(filePath) && !existsSync10(filePath)) {
6105
6106
  return { duration: null, resolvedPath: filePath };
6106
6107
  }
6107
6108
  const withSrcContext = (error) => {
@@ -6164,7 +6165,7 @@ function markInferredVariableMediaDurations(html, unresolvedMedia) {
6164
6165
  }
6165
6166
  return changed ? document2.toString() : html;
6166
6167
  }
6167
- async function compileHtmlFile(html, baseDir, downloadDir, log) {
6168
+ async function compileHtmlFile(html, baseDir, downloadDir, log, urlByAbsentLocalPath) {
6168
6169
  const { html: staticCompiled, unresolved } = compileTimingAttrs(html);
6169
6170
  const mediaUnresolved = unresolved.filter(
6170
6171
  (el) => (el.tagName === "video" || el.tagName === "audio") && el.src
@@ -6180,7 +6181,8 @@ async function compileHtmlFile(html, baseDir, downloadDir, log) {
6180
6181
  downloadDir,
6181
6182
  el.tagName,
6182
6183
  el.id,
6183
- log
6184
+ log,
6185
+ urlByAbsentLocalPath
6184
6186
  ).then(({ duration }) => ({ id: el.id, duration }))
6185
6187
  )
6186
6188
  );
@@ -6222,7 +6224,7 @@ async function compileHtmlFile(html, baseDir, downloadDir, log) {
6222
6224
  compiledHtml = compiledHtml.replace(/(<audio\b[^>]*)\s+crossorigin(?:=["'][^"']*["'])?/gi, "$1");
6223
6225
  return { html: compiledHtml, unresolvedCompositions };
6224
6226
  }
6225
- async function parseSubCompositions(html, projectDir, downloadDir, visited = /* @__PURE__ */ new Set()) {
6227
+ async function parseSubCompositions(html, projectDir, downloadDir, visited = /* @__PURE__ */ new Set(), urlByAbsentLocalPath) {
6226
6228
  const subCompositions = /* @__PURE__ */ new Map();
6227
6229
  const { document: document2 } = parseHTML4(html);
6228
6230
  const compEls = document2.querySelectorAll("[data-composition-src]");
@@ -6248,13 +6250,16 @@ async function parseSubCompositions(html, projectDir, downloadDir, visited = /*
6248
6250
  const { html: compiledSub } = await compileHtmlFile(
6249
6251
  item.rawSubHtml,
6250
6252
  dirname6(item.filePath),
6251
- downloadDir
6253
+ downloadDir,
6254
+ void 0,
6255
+ urlByAbsentLocalPath
6252
6256
  );
6253
6257
  const nested = await parseSubCompositions(
6254
6258
  compiledSub,
6255
6259
  projectDir,
6256
6260
  downloadDir,
6257
- item.nestedVisited
6261
+ item.nestedVisited,
6262
+ urlByAbsentLocalPath
6258
6263
  );
6259
6264
  return {
6260
6265
  srcPath: item.srcPath,
@@ -7012,9 +7017,16 @@ async function compileForRender(projectDir, htmlPath, downloadDir, options = {})
7012
7017
  rawHtml,
7013
7018
  projectDir,
7014
7019
  downloadDir,
7015
- options.log
7020
+ options.log,
7021
+ options.urlByAbsentLocalPath
7022
+ );
7023
+ const { subCompositions } = await parseSubCompositions(
7024
+ compiledHtml,
7025
+ projectDir,
7026
+ downloadDir,
7027
+ void 0,
7028
+ options.urlByAbsentLocalPath
7016
7029
  );
7017
- const { subCompositions } = await parseSubCompositions(compiledHtml, projectDir, downloadDir);
7018
7030
  const fullHtml = ensureFullDocument(compiledHtml);
7019
7031
  const inlinedHtml = inlineSubCompositions(
7020
7032
  fullHtml,
@@ -7510,6 +7522,7 @@ async function runCompileStage(input) {
7510
7522
  allowSystemFontCapture,
7511
7523
  abortSignal,
7512
7524
  variables: input.variables,
7525
+ urlByAbsentLocalPath: input.urlByAbsentLocalPath,
7513
7526
  animatedGifCacheDir: cfg.extractCacheDir ? join14(cfg.extractCacheDir, "animated-gif") : void 0,
7514
7527
  ffmpegProcessTimeout: cfg.ffmpegProcessTimeout
7515
7528
  });
@@ -8472,7 +8485,8 @@ async function runExtractVideosStage(input) {
8472
8485
  abortSignal,
8473
8486
  assertNotAborted,
8474
8487
  materializeSymlinks,
8475
- deferRangeExtraction
8488
+ deferRangeExtraction,
8489
+ urlByAbsentLocalPath
8476
8490
  } = input;
8477
8491
  const stage2Start = Date.now();
8478
8492
  const extractionPolicy = resolveVideoExtractionPolicy();
@@ -8566,7 +8580,8 @@ async function runExtractVideosStage(input) {
8566
8580
  maxTransientRetries: extractionPolicy.maxTransientRetries,
8567
8581
  collectProbeFailures: extractionPolicy.failureMode === "enforce",
8568
8582
  deferRangeExtraction,
8569
- contentKeyedDirs: [join16(compiledDir, REMOTE_MEDIA_SUBDIR)]
8583
+ contentKeyedDirs: [join16(compiledDir, REMOTE_MEDIA_SUBDIR)],
8584
+ urlByAbsentLocalPath
8570
8585
  },
8571
8586
  abortSignal,
8572
8587
  cfg,
@@ -8649,6 +8664,7 @@ async function runAudioStage(input) {
8649
8664
  audioGain,
8650
8665
  audios,
8651
8666
  masterFxChain,
8667
+ urlByAbsentLocalPath,
8652
8668
  abortSignal,
8653
8669
  assertNotAborted,
8654
8670
  log
@@ -8668,7 +8684,7 @@ async function runAudioStage(input) {
8668
8684
  audioOutputPath,
8669
8685
  duration,
8670
8686
  abortSignal,
8671
- { ffmpegProcessTimeout, audioGain, masterFxChain },
8687
+ { ffmpegProcessTimeout, audioGain, masterFxChain, urlByAbsentLocalPath },
8672
8688
  compiledDir
8673
8689
  );
8674
8690
  } catch (err) {