@hyperframes/producer 0.9.15 → 0.9.16

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.
@@ -2,11 +2,11 @@
2
2
 
3
3
  // src/server.ts
4
4
  import {
5
- existsSync as existsSync22,
5
+ existsSync as existsSync23,
6
6
  mkdirSync as mkdirSync11,
7
7
  statSync as statSync10,
8
8
  mkdtempSync as mkdtempSync4,
9
- writeFileSync as writeFileSync7,
9
+ writeFileSync as writeFileSync8,
10
10
  rmSync as rmSync11,
11
11
  createReadStream as createReadStream3
12
12
  } from "node:fs";
@@ -26,12 +26,12 @@ import {
26
26
  // src/services/renderOrchestrator.ts
27
27
  import { audioFailureDiagnostics as audioFailureDiagnostics2 } from "@hyperframes/engine";
28
28
  import {
29
- existsSync as existsSync19,
29
+ existsSync as existsSync20,
30
30
  mkdirSync as mkdirSync10,
31
31
  readdirSync as readdirSync5,
32
32
  rmSync as rmSync10,
33
33
  statSync as statSync8,
34
- writeFileSync as writeFileSync6,
34
+ writeFileSync as writeFileSync7,
35
35
  copyFileSync as copyFileSync5,
36
36
  appendFileSync
37
37
  } from "fs";
@@ -46,9 +46,7 @@ import {
46
46
  toFps as toFps2
47
47
  } from "@hyperframes/core";
48
48
  import { HF_AUDIO_GROUP_TAG } from "@hyperframes/core/audio-groups";
49
- import { extractStandaloneEntryFromIndex, isTemplateEntry } from "@hyperframes/core/compiler";
50
49
  import { HTML_BODY_CSS_HEIGHT_FIRST_RE, HTML_BODY_CSS_WIDTH_FIRST_RE } from "@hyperframes/parsers";
51
- import { readTextFile as readTextFile2 } from "@hyperframes/parsers/asset-resolution";
52
50
  import {
53
51
  extractMediaMetadata as extractMediaMetadata4,
54
52
  resolveConfig as resolveConfig2,
@@ -79,7 +77,8 @@ import {
79
77
  isDrawElementCaptureError,
80
78
  getDrawElementVerificationDetails,
81
79
  augmentProtocolTimeoutError,
82
- augmentPageNavigationTimeoutError
80
+ augmentPageNavigationTimeoutError,
81
+ displaySize
83
82
  } from "@hyperframes/engine";
84
83
  import {
85
84
  referenceBackgroundResult,
@@ -5908,8 +5907,11 @@ function countAuthoredTimedClips(html) {
5908
5907
  }
5909
5908
 
5910
5909
  // src/services/render/stages/compileStage.ts
5910
+ import { existsSync as existsSync11, writeFileSync as writeFileSync5 } from "node:fs";
5911
5911
  import { join as join16 } from "node:path";
5912
5912
  import { suggestMatchingPreset } from "@hyperframes/core";
5913
+ import { extractStandaloneEntryFromIndex, isTemplateEntry } from "@hyperframes/core/compiler";
5914
+ import { readTextFile as readTextFile2 } from "@hyperframes/parsers/asset-resolution";
5913
5915
 
5914
5916
  // src/services/htmlCompiler.ts
5915
5917
  import { decodedUrlPath } from "@hyperframes/parsers/asset-paths";
@@ -8614,11 +8616,36 @@ function adaptAspectAgnosticResolution(requested, aspectAgnostic, width, height,
8614
8616
  });
8615
8617
  return flipped;
8616
8618
  }
8619
+ function resolveEntryHtmlPath(input) {
8620
+ const { projectDir, workDir, entryFile, log } = input;
8621
+ const htmlPath = join16(projectDir, entryFile);
8622
+ const rawEntry = readTextFile2(htmlPath);
8623
+ if (entryFile === "index.html" || !isTemplateEntry(rawEntry)) return htmlPath;
8624
+ const projectIndexPath = join16(projectDir, "index.html");
8625
+ if (!existsSync11(projectIndexPath)) {
8626
+ throw new Error(
8627
+ `Template entry file "${entryFile}" requires a project index.html to extract its render shell.`
8628
+ );
8629
+ }
8630
+ const standaloneHtml = extractStandaloneEntryFromIndex(
8631
+ readTextFile2(projectIndexPath),
8632
+ entryFile,
8633
+ rawEntry
8634
+ );
8635
+ if (!standaloneHtml) {
8636
+ throw new Error(
8637
+ `Entry file "${entryFile}" is not mounted from index.html via data-composition-src, so it cannot be rendered independently.`
8638
+ );
8639
+ }
8640
+ const wrapperPath = join16(workDir, "standalone-entry.html");
8641
+ writeFileSync5(wrapperPath, standaloneHtml, "utf-8");
8642
+ log.info("Extracted standalone entry from index.html host context", { entryFile });
8643
+ return wrapperPath;
8644
+ }
8617
8645
  async function runCompileStage(input) {
8618
8646
  const {
8619
8647
  projectDir,
8620
8648
  workDir,
8621
- htmlPath,
8622
8649
  entryFile,
8623
8650
  job,
8624
8651
  cfg,
@@ -8629,6 +8656,7 @@ async function runCompileStage(input) {
8629
8656
  allowSystemFontCapture,
8630
8657
  abortSignal
8631
8658
  } = input;
8659
+ const htmlPath = resolveEntryHtmlPath({ projectDir, workDir, entryFile, log });
8632
8660
  const compileStart = Date.now();
8633
8661
  const compiled = await compileForRender(projectDir, htmlPath, join16(workDir, "downloads"), {
8634
8662
  log,
@@ -9428,7 +9456,7 @@ function validateRenderDuration(input) {
9428
9456
  }
9429
9457
 
9430
9458
  // src/services/render/stages/extractVideosStage.ts
9431
- import { existsSync as existsSync11 } from "node:fs";
9459
+ import { existsSync as existsSync12 } from "node:fs";
9432
9460
  import { join as join18 } from "node:path";
9433
9461
  import {
9434
9462
  classifyVideoExtractionError,
@@ -9698,7 +9726,7 @@ async function runExtractVideosStage(input) {
9698
9726
  const probeFailures = await Promise.all(
9699
9727
  composition.videos.map(async (v) => {
9700
9728
  const videoPath = resolveProjectRelativeSrc3(v.src, projectDir, compiledDir);
9701
- if (!existsSync11(videoPath)) return null;
9729
+ if (!existsSync12(videoPath)) return null;
9702
9730
  try {
9703
9731
  const attempted = extractionPolicy.maxTransientRetries === 0 ? { result: await extractMediaMetadata3(videoPath), retries: 0 } : await runVideoExtractionWithRetry(() => extractMediaMetadata3(videoPath), {
9704
9732
  signal: abortSignal,
@@ -9736,7 +9764,7 @@ async function runExtractVideosStage(input) {
9736
9764
  const probed = await Promise.all(
9737
9765
  composition.images.map(async (img) => {
9738
9766
  const imgPath = resolveProjectRelativeSrc3(img.src, projectDir, compiledDir);
9739
- if (!existsSync11(imgPath)) return null;
9767
+ if (!existsSync12(imgPath)) return null;
9740
9768
  const colorSpace = await probeColorSpaceSafely(imgPath, log);
9741
9769
  if (isHdrColorSpace2(colorSpace)) {
9742
9770
  nativeHdrImageIds.add(img.id);
@@ -9856,7 +9884,7 @@ async function appendVideoAudioFromProbe(input) {
9856
9884
  videos.map(async (video) => {
9857
9885
  const path = isHttpUrl2(video.src) ? video.src : resolveProjectRelativeSrc3(video.src, projectDir, compiledDir);
9858
9886
  const readFrom = urlByAbsentLocalPath?.get(path) ?? path;
9859
- if (!isHttpUrl2(readFrom) && !existsSync11(readFrom)) return null;
9887
+ if (!isHttpUrl2(readFrom) && !existsSync12(readFrom)) return null;
9860
9888
  const metadata = await extractMediaMetadata3(readFrom);
9861
9889
  const window2 = await resolveFinalFrameExtractionWindow2(
9862
9890
  readFrom,
@@ -9876,7 +9904,7 @@ async function appendVideoAudioFromProbe(input) {
9876
9904
  }
9877
9905
 
9878
9906
  // src/services/render/videoLayerPassthroughPlan.ts
9879
- import { existsSync as existsSync12 } from "node:fs";
9907
+ import { existsSync as existsSync13 } from "node:fs";
9880
9908
  import {
9881
9909
  buildVideoLayerInput,
9882
9910
  isHttpUrl as isHttpUrl3,
@@ -9944,7 +9972,7 @@ async function planVideoLayerPassthrough(input) {
9944
9972
  const { video } = q;
9945
9973
  if (isHttpUrl3(video.src)) return { reason: "remote_source" };
9946
9974
  const path = resolveProjectRelativeSrc4(video.src, input.projectDir, input.compiledDir);
9947
- if (!existsSync12(path)) return { reason: "source_missing" };
9975
+ if (!existsSync13(path)) return { reason: "source_missing" };
9948
9976
  const layout = await probeVideoLayout(input.page, video.id, input.canvas, input.duration);
9949
9977
  if ("reason" in layout) return layout;
9950
9978
  const layer = await buildVideoLayerInput(
@@ -9959,7 +9987,7 @@ async function planVideoLayerPassthrough(input) {
9959
9987
  }
9960
9988
 
9961
9989
  // src/services/render/stages/audioStage.ts
9962
- import { existsSync as existsSync14 } from "node:fs";
9990
+ import { existsSync as existsSync15 } from "node:fs";
9963
9991
  import { join as join21 } from "node:path";
9964
9992
  import {
9965
9993
  MIXED_AUDIO_FILENAME,
@@ -9970,7 +9998,7 @@ import {
9970
9998
  } from "@hyperframes/engine";
9971
9999
 
9972
10000
  // src/services/render/audioDeliveryLimit.ts
9973
- import { existsSync as existsSync13 } from "node:fs";
10001
+ import { existsSync as existsSync14 } from "node:fs";
9974
10002
  import { dirname as dirname9, join as join20 } from "node:path";
9975
10003
  import {
9976
10004
  applyAudioFxChain,
@@ -10477,7 +10505,7 @@ async function limitDeliveryPeaks(input, deps = {
10477
10505
  applyChain: applyAudioFxChain,
10478
10506
  encode: encodeAac2,
10479
10507
  rename: renameWithRetry6,
10480
- sourceExists: existsSync13
10508
+ sourceExists: existsSync14
10481
10509
  }) {
10482
10510
  const { audioPath, sourcePath, signal, timeoutMs } = input;
10483
10511
  if (!deps.sourceExists(sourcePath)) return { success: true };
@@ -10547,7 +10575,7 @@ async function runAudioStageScoped(input) {
10547
10575
  );
10548
10576
  audioLoweredDb = guard.loweredDb;
10549
10577
  input.log?.info("[audio] phases", { ...result.audioPhaseMs, guard: Date.now() - guardStart });
10550
- if (input.keepDeliverySource && !guard.sourceStale && existsSync14(pcmSourcePath)) {
10578
+ if (input.keepDeliverySource && !guard.sourceStale && existsSync15(pcmSourcePath)) {
10551
10579
  deliverySourcePath = pcmSourcePath;
10552
10580
  }
10553
10581
  }
@@ -10675,7 +10703,7 @@ async function mixAudioForDelivery(input, duration, pcmSourcePath) {
10675
10703
  }
10676
10704
 
10677
10705
  // src/services/render/stages/captureStage.ts
10678
- import { existsSync as existsSync15, readdirSync as readdirSync3, statfsSync as statfsSync2 } from "node:fs";
10706
+ import { existsSync as existsSync16, readdirSync as readdirSync3, statfsSync as statfsSync2 } from "node:fs";
10679
10707
  import { join as join22 } from "node:path";
10680
10708
  import {
10681
10709
  classifyCaptureFailure as classifyCaptureFailure2,
@@ -10746,7 +10774,7 @@ function diskCaptureShortfallError(needBytes, freeBytes, framesDir, basis, remed
10746
10774
  }
10747
10775
  function measureCaptureFrameBytes(workDir, framesDir) {
10748
10776
  let total = sampleDirectoryBytes(framesDir);
10749
- if (!existsSync15(workDir)) return total;
10777
+ if (!existsSync16(workDir)) return total;
10750
10778
  for (const attempt of readdirSync3(workDir)) {
10751
10779
  if (/^retry-\d+-batch-\d+-worker-\d+$/.test(attempt)) {
10752
10780
  total += sampleDirectoryBytes(join22(workDir, attempt));
@@ -11049,7 +11077,7 @@ async function captureSessionFrames(session, { input, rangeStart, rangeEnd, resu
11049
11077
  }
11050
11078
 
11051
11079
  // src/services/render/stages/captureHdrStage.ts
11052
- import { existsSync as existsSync17, mkdirSync as mkdirSync8 } from "node:fs";
11080
+ import { existsSync as existsSync18, mkdirSync as mkdirSync8 } from "node:fs";
11053
11081
  import { join as join26 } from "node:path";
11054
11082
  import {
11055
11083
  closeCaptureSession as closeCaptureSession7,
@@ -11295,7 +11323,7 @@ import { Worker } from "node:worker_threads";
11295
11323
  import { fileURLToPath as fileURLToPath3, pathToFileURL } from "node:url";
11296
11324
  import { dirname as dirname10, join as join24 } from "node:path";
11297
11325
  import { createRequire } from "node:module";
11298
- import { existsSync as existsSync16 } from "node:fs";
11326
+ import { existsSync as existsSync17 } from "node:fs";
11299
11327
  import { cpus as cpus2 } from "node:os";
11300
11328
  function resolveWorkerEntry(explicit) {
11301
11329
  if (explicit && explicit.length > 0) {
@@ -11308,7 +11336,7 @@ function resolveWorkerEntry(explicit) {
11308
11336
  }
11309
11337
  const moduleDir = dirname10(fileURLToPath3(import.meta.url));
11310
11338
  const jsPath = join24(moduleDir, "shaderTransitionWorker.js");
11311
- if (existsSync16(jsPath)) return { path: jsPath, isTs: false };
11339
+ if (existsSync17(jsPath)) return { path: jsPath, isTs: false };
11312
11340
  const tsPath = join24(moduleDir, "shaderTransitionWorker.ts");
11313
11341
  return { path: tsPath, isTs: true };
11314
11342
  }
@@ -11869,7 +11897,7 @@ async function runCaptureHdrStage(input) {
11869
11897
  }
11870
11898
  const debugDumpEnabled = process.env.KEEP_TEMP === "1";
11871
11899
  const debugDumpDir = debugDumpEnabled ? join26(framesDir, "debug-composite") : null;
11872
- if (debugDumpDir && !existsSync17(debugDumpDir)) {
11900
+ if (debugDumpDir && !existsSync18(debugDumpDir)) {
11873
11901
  mkdirSync8(debugDumpDir, { recursive: true });
11874
11902
  }
11875
11903
  const compositeTransfer = resolveCompositeTransfer(hasHdrContent, effectiveHdr);
@@ -12026,13 +12054,13 @@ async function runCaptureHdrStage(input) {
12026
12054
  // src/services/render/stages/encodeStage.ts
12027
12055
  import {
12028
12056
  copyFileSync as copyFileSync4,
12029
- existsSync as existsSync18,
12057
+ existsSync as existsSync19,
12030
12058
  mkdirSync as mkdirSync9,
12031
12059
  readdirSync as readdirSync4,
12032
12060
  readFileSync as readFileSync8,
12033
12061
  rmSync as rmSync9,
12034
12062
  statSync as statSync7,
12035
- writeFileSync as writeFileSync5
12063
+ writeFileSync as writeFileSync6
12036
12064
  } from "node:fs";
12037
12065
  import { readFile as readFile2 } from "node:fs/promises";
12038
12066
  import { dirname as dirname11, join as join27 } from "node:path";
@@ -12128,7 +12156,7 @@ async function reencodeWithClearedWholeFrames(argsInput, run, startTime) {
12128
12156
  const error = "[FFmpeg] GIF output could not be parsed to clear its frames";
12129
12157
  return gifEncodeFailure(startTime, outputPath, error);
12130
12158
  }
12131
- writeFileSync5(outputPath, gif);
12159
+ writeFileSync6(outputPath, gif);
12132
12160
  return null;
12133
12161
  }
12134
12162
  async function encodeGifFromDir(framesDir, framePattern, outputPath, input) {
@@ -12160,7 +12188,7 @@ async function encodeGifFromDir(framesDir, framePattern, outputPath, input) {
12160
12188
  const failure = await reencodeWithClearedWholeFrames(argsInput, run, startTime);
12161
12189
  if (failure) return failure;
12162
12190
  }
12163
- const fileSize = existsSync18(outputPath) ? statSync7(outputPath).size : 0;
12191
+ const fileSize = existsSync19(outputPath) ? statSync7(outputPath).size : 0;
12164
12192
  return {
12165
12193
  success: true,
12166
12194
  outputPath,
@@ -12321,7 +12349,7 @@ async function runEncodeStage(input) {
12321
12349
  const stage5Start = Date.now();
12322
12350
  if (isPngSequence) {
12323
12351
  const encoded = startEncodeProgress(job, "Writing PNG sequence", onProgress);
12324
- if (!existsSync18(outputPath)) mkdirSync9(outputPath, { recursive: true });
12352
+ if (!existsSync19(outputPath)) mkdirSync9(outputPath, { recursive: true });
12325
12353
  const captured = readdirSync4(framesDir).filter((name) => name.endsWith(".png")).sort();
12326
12354
  if (captured.length === 0) {
12327
12355
  throw new Error(
@@ -12332,7 +12360,7 @@ async function runEncodeStage(input) {
12332
12360
  const dst = join27(outputPath, formatExportFrameName(i, "png"));
12333
12361
  copyFileSync4(join27(framesDir, name), dst);
12334
12362
  });
12335
- if (hasAudio && audioOutputPath && existsSync18(audioOutputPath)) {
12363
+ if (hasAudio && audioOutputPath && existsSync19(audioOutputPath)) {
12336
12364
  copyFileSync4(audioOutputPath, join27(outputPath, MIXED_AUDIO_FILENAME2));
12337
12365
  log.info(
12338
12366
  `[Render] png-sequence: ${MIXED_AUDIO_FILENAME2} sidecar written to ${outputPath}/${MIXED_AUDIO_FILENAME2}`
@@ -12714,18 +12742,14 @@ function hasUsableVideoDimensions(metadata) {
12714
12742
  return Number.isFinite(metadata.width) && Number.isFinite(metadata.height) && metadata.width > 0 && metadata.height > 0;
12715
12743
  }
12716
12744
  function collectVideoMetadataHints(extractedVideos) {
12717
- return extractedVideos.filter((video) => hasUsableVideoDimensions(video.metadata)).map((video) => ({
12718
- id: video.videoId,
12719
- width: video.metadata.width,
12720
- height: video.metadata.height
12721
- })).sort((a, b) => a.id.localeCompare(b.id));
12745
+ return extractedVideos.filter((video) => hasUsableVideoDimensions(video.metadata)).map((video) => ({ id: video.videoId, ...displaySize(video.metadata) })).sort((a, b) => a.id.localeCompare(b.id));
12722
12746
  }
12723
12747
  function findMissingFrameRanges(totalFrames, framesDir, frameExt) {
12724
12748
  const ranges = [];
12725
12749
  let rangeStart = null;
12726
12750
  for (let frameIndex = 0; frameIndex < totalFrames; frameIndex++) {
12727
12751
  const framePath = join28(framesDir, formatCaptureFrameName(frameIndex, frameExt));
12728
- const missing = !existsSync19(framePath) || statSync8(framePath).size <= 8;
12752
+ const missing = !existsSync20(framePath) || statSync8(framePath).size <= 8;
12729
12753
  if (missing && rangeStart === null) {
12730
12754
  rangeStart = frameIndex;
12731
12755
  } else if (!missing && rangeStart !== null) {
@@ -12850,7 +12874,7 @@ function countCapturedFrames(totalFrames, framesDir, frameExt) {
12850
12874
  let captured = 0;
12851
12875
  for (let frameIndex = 0; frameIndex < totalFrames; frameIndex++) {
12852
12876
  const framePath = join28(framesDir, formatCaptureFrameName(frameIndex, frameExt));
12853
- if (existsSync19(framePath)) captured++;
12877
+ if (existsSync20(framePath)) captured++;
12854
12878
  }
12855
12879
  return captured;
12856
12880
  }
@@ -13515,7 +13539,7 @@ async function executeRenderJob(job, projectDir, outputPath, progressSink, abort
13515
13539
  validateDeliveryEncoderConfig(job.config);
13516
13540
  const debugDir = resolveRenderDebugDir();
13517
13541
  const outputDir = dirname12(outputPath);
13518
- if (!existsSync19(outputDir)) mkdirSync10(outputDir, { recursive: true });
13542
+ if (!existsSync20(outputDir)) mkdirSync10(outputDir, { recursive: true });
13519
13543
  const workDir = job.config.debug ? join28(debugDir, job.id) : createRenderWorkDir(resolveRenderWorkDirPrefix(outputPath, job.id), outputDir);
13520
13544
  const pipelineStart = Date.now();
13521
13545
  const baseLog = job.config.logger ?? defaultLogger;
@@ -13657,7 +13681,7 @@ async function executeRenderPipeline(input) {
13657
13681
  job.startedAt = /* @__PURE__ */ new Date();
13658
13682
  assertNotAborted();
13659
13683
  assertConfiguredFfmpegBinariesExist();
13660
- if (!existsSync19(workDir)) mkdirSync10(workDir, { recursive: true });
13684
+ if (!existsSync20(workDir)) mkdirSync10(workDir, { recursive: true });
13661
13685
  if (job.config.debug) {
13662
13686
  log.info("[Render] Debug artifacts enabled", { workDir, logPath });
13663
13687
  }
@@ -13686,36 +13710,11 @@ async function executeRenderPipeline(input) {
13686
13710
  requestedWorkers: job.config.workers ?? "auto"
13687
13711
  });
13688
13712
  const entryFile = job.config.entryFile || "index.html";
13689
- let htmlPath = join28(projectDir, entryFile);
13690
- if (!existsSync19(htmlPath)) {
13713
+ const htmlPath = join28(projectDir, entryFile);
13714
+ if (!existsSync20(htmlPath)) {
13691
13715
  throw new Error(`Entry file not found: ${htmlPath}`);
13692
13716
  }
13693
13717
  assertNotAborted();
13694
- const rawEntry = readTextFile2(htmlPath);
13695
- if (entryFile !== "index.html" && isTemplateEntry(rawEntry)) {
13696
- const wrapperPath = join28(workDir, "standalone-entry.html");
13697
- const projectIndexPath = join28(projectDir, "index.html");
13698
- if (!existsSync19(projectIndexPath)) {
13699
- throw new Error(
13700
- `Template entry file "${entryFile}" requires a project index.html to extract its render shell.`
13701
- );
13702
- }
13703
- const standaloneHtml = extractStandaloneEntryFromIndex(
13704
- readTextFile2(projectIndexPath),
13705
- entryFile,
13706
- rawEntry
13707
- );
13708
- if (!standaloneHtml) {
13709
- throw new Error(
13710
- `Entry file "${entryFile}" is not mounted from index.html via data-composition-src, so it cannot be rendered independently.`
13711
- );
13712
- }
13713
- writeFileSync6(wrapperPath, standaloneHtml, "utf-8");
13714
- htmlPath = wrapperPath;
13715
- log.info("Extracted standalone entry from index.html host context", {
13716
- entryFile
13717
- });
13718
- }
13719
13718
  const stage1Start = Date.now();
13720
13719
  updateJobStatus(job, "preprocessing", "Compiling composition", 5, onProgress, {
13721
13720
  code: "compile"
@@ -13727,7 +13726,6 @@ async function executeRenderPipeline(input) {
13727
13726
  () => runCompileStage({
13728
13727
  projectDir,
13729
13728
  workDir,
13730
- htmlPath,
13731
13729
  entryFile,
13732
13730
  job,
13733
13731
  cfg,
@@ -14039,7 +14037,7 @@ async function executeRenderPipeline(input) {
14039
14037
  throw new Error("File server failed to initialize before frame capture");
14040
14038
  }
14041
14039
  const framesDir = join28(workDir, "captured-frames");
14042
- if (!existsSync19(framesDir)) mkdirSync10(framesDir, { recursive: true });
14040
+ if (!existsSync20(framesDir)) mkdirSync10(framesDir, { recursive: true });
14043
14041
  updateJobStatus(job, "rendering", "Checking browser GPU", 25, onProgress, {
14044
14042
  code: "start_browsers"
14045
14043
  });
@@ -15367,7 +15365,7 @@ async function executeRenderPipeline(input) {
15367
15365
  })
15368
15366
  );
15369
15367
  const totalElapsed = Date.now() - pipelineStart;
15370
- const tmpPeakBytes = existsSync19(workDir) ? sampleDirectoryBytes(workDir) : 0;
15368
+ const tmpPeakBytes = existsSync20(workDir) ? sampleDirectoryBytes(workDir) : 0;
15371
15369
  recordTransientRetryObservability();
15372
15370
  observability.checkpoint("pipeline", "artifact validated", { totalElapsedMs: totalElapsed });
15373
15371
  const observabilitySummary = observability.summary({
@@ -15439,7 +15437,7 @@ async function executeRenderPipeline(input) {
15439
15437
  job.perfSummary = perfSummary;
15440
15438
  if (job.config.debug) {
15441
15439
  try {
15442
- writeFileSync6(perfOutputPath, JSON.stringify(perfSummary, null, 2), "utf-8");
15440
+ writeFileSync7(perfOutputPath, JSON.stringify(perfSummary, null, 2), "utf-8");
15443
15441
  } catch (err) {
15444
15442
  log.debug("Failed to write perf summary", {
15445
15443
  perfOutputPath,
@@ -15448,7 +15446,7 @@ async function executeRenderPipeline(input) {
15448
15446
  }
15449
15447
  }
15450
15448
  if (job.config.debug) {
15451
- if (!isPngSequence && !isHls && existsSync19(stagedOutputPath)) {
15449
+ if (!isPngSequence && !isHls && existsSync20(stagedOutputPath)) {
15452
15450
  const debugOutput = join28(workDir, `output${videoExt}`);
15453
15451
  copyFileSync5(stagedOutputPath, debugOutput);
15454
15452
  }
@@ -15560,7 +15558,7 @@ async function executeRenderPipeline(input) {
15560
15558
 
15561
15559
  // src/services/hyperframeLint.ts
15562
15560
  import {
15563
- existsSync as existsSync20,
15561
+ existsSync as existsSync21,
15564
15562
  readFileSync as readFileSync9,
15565
15563
  statSync as statSync9,
15566
15564
  openSync as openSync5,
@@ -15614,7 +15612,7 @@ function readEntryHtml(filePath) {
15614
15612
  }
15615
15613
  function readProjectEntryFile(projectDir, preferredEntryFile) {
15616
15614
  const absProjectDir = resolve9(projectDir);
15617
- if (!existsSync20(absProjectDir) || !statSync9(absProjectDir).isDirectory()) {
15615
+ if (!existsSync21(absProjectDir) || !statSync9(absProjectDir).isDirectory()) {
15618
15616
  return { error: `Project directory not found: ${absProjectDir}` };
15619
15617
  }
15620
15618
  const entryCandidates = [preferredEntryFile, "index.html", "src/index.html"].filter(
@@ -15683,7 +15681,7 @@ async function runHyperframeLint(prepared) {
15683
15681
  import { Worker as Worker2 } from "node:worker_threads";
15684
15682
  import { fileURLToPath as fileURLToPath5 } from "node:url";
15685
15683
  import { dirname as dirname13, join as join30 } from "node:path";
15686
- import { existsSync as existsSync21 } from "node:fs";
15684
+ import { existsSync as existsSync22 } from "node:fs";
15687
15685
  var DEFAULT_HEALTH_PORT = 9848;
15688
15686
  async function startHealthWorker(options = {}) {
15689
15687
  const log = options.logger ?? defaultLogger2();
@@ -15760,7 +15758,7 @@ function resolveWorkerEntry2() {
15760
15758
  const here = dirname13(fileURLToPath5(import.meta.url));
15761
15759
  const candidates = [join30(here, "healthWorkerThread.js"), join30(here, "healthWorkerThread.ts")];
15762
15760
  for (const candidate of candidates) {
15763
- if (existsSync21(candidate)) return candidate;
15761
+ if (existsSync22(candidate)) return candidate;
15764
15762
  }
15765
15763
  return void 0;
15766
15764
  }
@@ -16310,7 +16308,7 @@ function resolvePreparedRenderOutput(prepared, rendersDir, log) {
16310
16308
  const { input, cleanupProjectDir } = prepared;
16311
16309
  const absoluteOutputPath = resolveOutputPath(input.projectDir, input.outputPath, rendersDir, log);
16312
16310
  const outputDir = dirname14(absoluteOutputPath);
16313
- if (!existsSync22(outputDir)) mkdirSync11(outputDir, { recursive: true });
16311
+ if (!existsSync23(outputDir)) mkdirSync11(outputDir, { recursive: true });
16314
16312
  return { input, cleanupProjectDir, absoluteOutputPath };
16315
16313
  }
16316
16314
  function validateRenderOverrides(body) {
@@ -16351,11 +16349,11 @@ function prepareProjectDirectory(projectDir, options) {
16351
16349
  const candidate = nonEmptyString(projectDir);
16352
16350
  if (!candidate) return null;
16353
16351
  const absProjectDir = resolve10(candidate);
16354
- if (!existsSync22(absProjectDir) || !statSync10(absProjectDir).isDirectory()) {
16352
+ if (!existsSync23(absProjectDir) || !statSync10(absProjectDir).isDirectory()) {
16355
16353
  return { error: `Project directory not found: ${absProjectDir}` };
16356
16354
  }
16357
16355
  const entry = options.entryFile || "index.html";
16358
- if (!existsSync22(resolve10(absProjectDir, entry))) {
16356
+ if (!existsSync23(resolve10(absProjectDir, entry))) {
16359
16357
  return { error: `Entry file "${entry}" not found in project directory: ${absProjectDir}` };
16360
16358
  }
16361
16359
  return { prepared: { input: { projectDir: absProjectDir, ...options } } };
@@ -16377,7 +16375,7 @@ async function resolveInlineRenderHtml(body) {
16377
16375
  function materializeInlineProject(html, options) {
16378
16376
  const tempRoot = process.env.PRODUCER_TMP_PROJECT_DIR || tmpdir3();
16379
16377
  const tempProjectDir = mkdtempSync4(join32(tempRoot, "producer-project-"));
16380
- writeFileSync7(join32(tempProjectDir, "index.html"), html, "utf-8");
16378
+ writeFileSync8(join32(tempProjectDir, "index.html"), html, "utf-8");
16381
16379
  return {
16382
16380
  prepared: {
16383
16381
  input: { projectDir: tempProjectDir, ...options },
@@ -16447,7 +16445,7 @@ function cleanupTempDir(dir, log) {
16447
16445
  }
16448
16446
  }
16449
16447
  function outputFileSize(path) {
16450
- return existsSync22(path) ? statSync10(path).size : 0;
16448
+ return existsSync23(path) ? statSync10(path).size : 0;
16451
16449
  }
16452
16450
  function createBlockingProgressReporter(log, requestId) {
16453
16451
  let lastLoggedPct = -10;
@@ -16751,7 +16749,7 @@ function createRenderHandlers(options = {}) {
16751
16749
  if (!artifact) {
16752
16750
  return c.json({ success: false, error: "Output artifact not found or expired" }, 404);
16753
16751
  }
16754
- if (!existsSync22(artifact.path)) {
16752
+ if (!existsSync23(artifact.path)) {
16755
16753
  store.delete(token);
16756
16754
  return c.json({ success: false, error: "Output artifact file missing" }, 404);
16757
16755
  }
@@ -42,9 +42,7 @@ import type { RenderJob } from "../../renderOrchestrator.js";
42
42
  export interface CompileStageInput {
43
43
  projectDir: string;
44
44
  workDir: string;
45
- /** Absolute path to the entry HTML (already resolved to standalone-entry if needed). */
46
- htmlPath: string;
47
- /** The relative `entryFile` string, used only for log payloads. */
45
+ /** Entry HTML relative to `projectDir`; a `<template>` sub-composition is isolated inside the index shell. */
48
46
  entryFile: string;
49
47
  job: RenderJob;
50
48
  /**
@@ -104,4 +102,15 @@ export interface CompileStageResult {
104
102
  * `3d` | `ancestor_background_image` | `shader_transitions`. Undefined when none fired. */
105
103
  deCompileGate?: string;
106
104
  }
105
+ /**
106
+ * The HTML file to compile for `entryFile`. A `<template>`-wrapped
107
+ * sub-composition has no live root, timeline or duration of its own, so it is
108
+ * rendered through the project's index.html shell with only its mount host kept.
109
+ */
110
+ export declare function resolveEntryHtmlPath(input: {
111
+ projectDir: string;
112
+ workDir: string;
113
+ entryFile: string;
114
+ log: ProducerLogger;
115
+ }): string;
107
116
  export declare function runCompileStage(input: CompileStageInput): Promise<CompileStageResult>;
@@ -1,5 +1,5 @@
1
1
  import { type CanvasResolution, type Fps, type FpsInput, type HfVfxCapture } from "@hyperframes/core";
2
- import { type EngineConfig, type ExtractionPhaseBreakdown, type VideoFrameFormat, type CaptureOptions, type CaptureVideoMetadataHint, type CaptureSession, type BeforeCaptureHook, type ProresProfile, type ParallelProgress, type WorkerTask, type CapturePerfSummary, type CaptureWarning, type SubTimelineWaitOutcome, type WorkerSizing, type StaticVerificationOutcome, explainDrawElementDisabled, type CaptureFailureKind, type MotionBlurOptions } from "@hyperframes/engine";
2
+ import { type EngineConfig, type ExtractionPhaseBreakdown, type VideoFrameFormat, type CaptureOptions, type CaptureVideoMetadataHint, type CaptureSession, type BeforeCaptureHook, type ProresProfile, type ParallelProgress, type WorkerTask, type CapturePerfSummary, type CaptureWarning, type SubTimelineWaitOutcome, type WorkerSizing, type StaticVerificationOutcome, explainDrawElementDisabled, type CaptureFailureKind, displaySize, type MotionBlurOptions } from "@hyperframes/engine";
3
3
  import { type ReferenceBackgroundResult } from "@hyperframes/engine";
4
4
  import { type ProducerLogger } from "../logger.js";
5
5
  import { type RenderOutputFormat } from "./render/renderFormat.js";
@@ -480,10 +480,7 @@ export declare function createRenderFileLogger(logPath: string, base?: ProducerL
480
480
  export declare function collectVideoReadinessSkipIds(nativeHdrVideoIds: ReadonlySet<string>, extractedVideos: readonly ExtractedVideoReadinessInput[]): string[];
481
481
  interface ExtractedVideoReadinessInput {
482
482
  videoId: string;
483
- metadata: {
484
- width: number;
485
- height: number;
486
- };
483
+ metadata: Parameters<typeof displaySize>[0];
487
484
  }
488
485
  export declare function collectVideoMetadataHints(extractedVideos: readonly ExtractedVideoReadinessInput[]): CaptureVideoMetadataHint[];
489
486
  export declare function findMissingFrameRanges(totalFrames: number, framesDir: string, frameExt: "jpg" | "png"): FrameRange[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hyperframes/producer",
3
- "version": "0.9.15",
3
+ "version": "0.9.16",
4
4
  "description": "HTML-to-video rendering engine using Chrome's BeginFrame API",
5
5
  "repository": {
6
6
  "type": "git",
@@ -35,11 +35,11 @@
35
35
  "postcss": "^8.4.0",
36
36
  "puppeteer": "~25.11.0",
37
37
  "puppeteer-core": "^25.11.0",
38
- "@hyperframes/core": "0.9.15",
39
- "@hyperframes/engine": "0.9.15",
40
- "@hyperframes/lint": "0.9.15",
41
- "@hyperframes/parsers": "0.9.15",
42
- "@hyperframes/studio-server": "0.9.15"
38
+ "@hyperframes/lint": "0.9.16",
39
+ "@hyperframes/engine": "0.9.16",
40
+ "@hyperframes/core": "0.9.16",
41
+ "@hyperframes/parsers": "0.9.16",
42
+ "@hyperframes/studio-server": "0.9.16"
43
43
  },
44
44
  "devDependencies": {
45
45
  "@types/node": "^25.0.10",
@@ -48,7 +48,7 @@
48
48
  "tsx": "^4.21.0",
49
49
  "typescript": "^5.7.2",
50
50
  "vitest": "^4.1.11",
51
- "@hyperframes/aws-lambda": "0.9.15"
51
+ "@hyperframes/aws-lambda": "0.9.16"
52
52
  },
53
53
  "engines": {
54
54
  "node": ">=22"