hyperframes 0.8.175 → 0.8.177

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.
Files changed (137) hide show
  1. package/bin/hyperframes.mjs +3 -0
  2. package/dist/{add-S4AOKSNH.js → add-YVP5YBNV.js} +5 -5
  3. package/dist/{animatedGifPrep-QRDM2NMV.js → animatedGifPrep-6SPYXSHE.js} +2 -2
  4. package/dist/api.d.ts +2 -0
  5. package/dist/api.js +10 -10
  6. package/dist/{asset-DQ45BUBL.js → asset-CZHAZMDR.js} +2 -2
  7. package/dist/{assetCataloger-NPDCMAYE.js → assetCataloger-5GGNQC3Y.js} +2 -2
  8. package/dist/{audio-OOAXEELO.js → audio-F37HJPGZ.js} +9 -9
  9. package/dist/{auth-33IY64LT.js → auth-CADZEUXD.js} +1 -1
  10. package/dist/{autoUpdate-KGLJUGNZ.js → autoUpdate-46XNDX2V.js} +2 -2
  11. package/dist/{backgroundChecks-DQPHYLVN.js → backgroundChecks-UWDSDJHJ.js} +3 -3
  12. package/dist/backgroundChecksWorker.js +3 -3
  13. package/dist/{beats-WDYSCACS.js → beats-COVC2QGG.js} +4 -4
  14. package/dist/{benchmark-WOAULMYW.js → benchmark-HDPPG2FE.js} +6 -6
  15. package/dist/{browser-2HX46R74.js → browser-JEIXZKJN.js} +3 -3
  16. package/dist/{bundleWithLocalizedFonts-32N7TVM6.js → bundleWithLocalizedFonts-PMRGOQWD.js} +1 -1
  17. package/dist/{capture-5ASPHTMF.js → capture-QVW6JPTF.js} +5 -5
  18. package/dist/{capture-7LR5UVM2.js → capture-YMF7J55W.js} +2 -2
  19. package/dist/captureCompositionFrame-QUYL3DEX.js +88 -0
  20. package/dist/{catalog-LVW3RT4U.js → catalog-DTB6S66P.js} +18 -10
  21. package/dist/{catch-up-W6Z73GXL.js → catch-up-Y4KO7XHK.js} +4 -4
  22. package/dist/{check-C23ZWACZ.js → check-3YLTRK6W.js} +44 -33
  23. package/dist/{checkBrowser-WS6FSLBJ.js → checkBrowser-LEUOWFNZ.js} +67 -47
  24. package/dist/{chunk-W5USVP6Z.js → chunk-2CEIKEFU.js} +1 -1
  25. package/dist/{chunk-XDK2FUAC.js → chunk-2PNLYDGZ.js} +12 -12
  26. package/dist/{chunk-IUOPF3DL.js → chunk-2YT3SCXL.js} +1 -1
  27. package/dist/{chunk-YMU42UU2.js → chunk-457OAAL2.js} +1 -1
  28. package/dist/{chunk-OUMYTLPP.js → chunk-4YBNQDZM.js} +1 -1
  29. package/dist/{chunk-ZGSLXWH2.js → chunk-5ZSLJXQX.js} +1 -1
  30. package/dist/{chunk-CEYY3SOX.js → chunk-6GMZRHYD.js} +52 -16
  31. package/dist/{chunk-J53AJK3Q.js → chunk-6OV3YGZF.js} +1 -1
  32. package/dist/{chunk-G7I3WZK4.js → chunk-7L4JOVV7.js} +1 -1
  33. package/dist/{chunk-QMQZXNGU.js → chunk-AFSBPASX.js} +29 -10
  34. package/dist/{chunk-7GUAZGSW.js → chunk-B7DJQFAC.js} +3 -3
  35. package/dist/{chunk-Z2R7CFWH.js → chunk-BEX7K4WO.js} +1 -1
  36. package/dist/{chunk-YCSCDQDS.js → chunk-BHFIITTD.js} +5 -5
  37. package/dist/{chunk-JT7PV3GL.js → chunk-BQTCQ6VN.js} +7 -13
  38. package/dist/{chunk-S354WIFQ.js → chunk-GVIH4ZQY.js} +1 -1
  39. package/dist/{chunk-WDFLYDKN.js → chunk-GZG3ZZOO.js} +4 -4
  40. package/dist/{chunk-7E4YFL4U.js → chunk-HLBAQBHT.js} +1 -1
  41. package/dist/{chunk-3X3CELOP.js → chunk-JWUKHXM6.js} +1 -1
  42. package/dist/{chunk-O6FWDZYJ.js → chunk-K535ASAM.js} +1 -1
  43. package/dist/{chunk-BHMWVRNS.js → chunk-KRKSY2AE.js} +1 -1
  44. package/dist/{chunk-V7T7WA2K.js → chunk-L3LJVYXS.js} +2 -2
  45. package/dist/{chunk-XRRDVRCY.js → chunk-L6XT6GSV.js} +1 -1
  46. package/dist/{chunk-KZQGUXG5.js → chunk-LEILKM2N.js} +903 -813
  47. package/dist/{chunk-MJZQ44NV.js → chunk-M3MXTYDE.js} +1 -1
  48. package/dist/{chunk-EKDTCSCL.js → chunk-MG66UO4N.js} +1 -1
  49. package/dist/{chunk-3SWMUZ4Y.js → chunk-MNEC244Z.js} +2 -2
  50. package/dist/{chunk-NELZN7SV.js → chunk-MPNVXG3J.js} +2 -2
  51. package/dist/{chunk-4XM6VXLN.js → chunk-MXFCQCKE.js} +2 -2
  52. package/dist/{chunk-NZRAS3FB.js → chunk-P3MMF2JQ.js} +1 -1
  53. package/dist/{chunk-UWUKJDTA.js → chunk-PL4WM5XE.js} +1 -1
  54. package/dist/{chunk-PBKOURSI.js → chunk-S3T3TDAB.js} +1 -1
  55. package/dist/{chunk-NSWIF4VH.js → chunk-SJFA4CZ3.js} +96 -11
  56. package/dist/{chunk-ZD7QKZSS.js → chunk-SSYUONVH.js} +2 -2
  57. package/dist/{chunk-KHR4IJAW.js → chunk-UEGULMAH.js} +84 -42
  58. package/dist/{chunk-KZHYAGC3.js → chunk-WNQUXBDU.js} +3 -3
  59. package/dist/{chunk-RAZVWSU5.js → chunk-X4PK5ZXP.js} +1 -1
  60. package/dist/{chunk-FRNKM4MG.js → chunk-X6HYBA4A.js} +1 -1
  61. package/dist/{chunk-ZDOWJ3NS.js → chunk-XXHHPB6E.js} +11 -11
  62. package/dist/{chunk-ZXHEOTYW.js → chunk-YCRX5APY.js} +1 -1
  63. package/dist/{chunk-RERXOG54.js → chunk-YWSXFFO3.js} +3 -1
  64. package/dist/{clean-KBNXV6DP.js → clean-BDXOJ4PW.js} +12 -12
  65. package/dist/cli.js +51 -51
  66. package/dist/{cloud-BWWKDRR6.js → cloud-HVG2LUBO.js} +4 -4
  67. package/dist/commands/contrast-audit.browser.js +166 -22
  68. package/dist/{compare-UWJRKF5K.js → compare-ZXORZDGQ.js} +20 -20
  69. package/dist/{component-SWOGSIO2.js → component-AZHVBBVP.js} +3 -3
  70. package/dist/{compositions-PIPVEXE3.js → compositions-WFILUFC4.js} +6 -6
  71. package/dist/{delete-OXIDK7N3.js → delete-I34NH2Q4.js} +2 -2
  72. package/dist/{doctor-KLQLFPCF.js → doctor-6YID47TF.js} +6 -6
  73. package/dist/{events-EGCYD45B.js → events-G76VOIBQ.js} +3 -3
  74. package/dist/{events-EVTTGISX.js → events-OKT2KO6S.js} +2 -2
  75. package/dist/{feedback-G4WZLOVD.js → feedback-EGU7SSXF.js} +4 -4
  76. package/dist/{ffmpeg-QIYSAPHO.js → ffmpeg-CSJLJAQ3.js} +2 -2
  77. package/dist/{figma-Q4CKDE7M.js → figma-SUJPNBQQ.js} +3 -3
  78. package/dist/fontLocalizeCli.js +4 -4
  79. package/dist/{get-NPRQH64W.js → get-7MYYFGO7.js} +2 -2
  80. package/dist/{gpuPolicy-L72BQRFR.js → gpuPolicy-C6JWEX7F.js} +2 -2
  81. package/dist/{grade-compare-H4NJEL4P.js → grade-compare-P22VRQLA.js} +21 -21
  82. package/dist/{help-2S3XXDEA.js → help-HL6QBZMC.js} +1 -1
  83. package/dist/{history-X4CGCVEQ.js → history-OG2IXOQA.js} +7 -7
  84. package/dist/hyperframes-player.global.js +1 -1
  85. package/dist/{info-YYUISGL7.js → info-7P77JIWI.js} +5 -5
  86. package/dist/{init-BAAI4ZMR.js → init-JI7QDJV4.js} +18 -18
  87. package/dist/{inspect-LXJ3XJUZ.js → inspect-7GSF776P.js} +19 -19
  88. package/dist/{keyframes-NXXJSQWG.js → keyframes-6NM477BA.js} +6 -6
  89. package/dist/{lambda-PG3JKNWS.js → lambda-4DNBHUPF.js} +2 -2
  90. package/dist/{layout-CBFO36IE.js → layout-IBDEMDYP.js} +19 -19
  91. package/dist/{lint-Y6NWQTKF.js → lint-BGAON4GT.js} +6 -6
  92. package/dist/{list-VY6KIYTX.js → list-PGWNFSLX.js} +2 -2
  93. package/dist/{login-426Q3H3P.js → login-IAGLU3R3.js} +4 -4
  94. package/dist/{manager-3RRUFZ4H.js → manager-VOGZQSK4.js} +7 -5
  95. package/dist/{media-treatment-ZVIXMJZJ.js → media-treatment-Z4N3JEDB.js} +8 -8
  96. package/dist/{models-KC2QBPVK.js → models-QUOIJVQI.js} +6 -6
  97. package/dist/{motionShot-3ZSLQJ4D.js → motionShot-GPNESXTN.js} +7 -7
  98. package/dist/{normalize-audio-DRU5LFYU.js → normalize-audio-C264N2QE.js} +5 -5
  99. package/dist/{open-LPBZQ4DC.js → open-HB5F4HSD.js} +4 -4
  100. package/dist/{parakeetRunner-IFY6NK5G.js → parakeetRunner-2ZQOINZQ.js} +7 -7
  101. package/dist/{pipeline-AGMCDT5A.js → pipeline-JWGTH2BJ.js} +3 -3
  102. package/dist/{play-WVWXNQED.js → play-VQ4HA4CC.js} +10 -10
  103. package/dist/{preflight-I5JGCCKM.js → preflight-4WTT4D5X.js} +3 -3
  104. package/dist/{present-IPTWJUKY.js → present-HU46P43M.js} +9 -9
  105. package/dist/{preview-JVLRQVD7.js → preview-LNTJ62DX.js} +8 -8
  106. package/dist/{producer-HKGHSAON.js → producer-UZZQX6IJ.js} +1 -1
  107. package/dist/{remove-background-WY7KA77M.js → remove-background-OX5WZTXY.js} +4 -4
  108. package/dist/{render-BEDATKWV.js → render-DV76TO7S.js} +5 -5
  109. package/dist/{render-P3PFNCBA.js → render-TPE44TOW.js} +27 -27
  110. package/dist/{sherpa-SQGEFX3S.js → sherpa-IBMU4WV6.js} +6 -6
  111. package/dist/skills/media-use/references/operations.md +3 -1
  112. package/dist/{skills-D2FIQ2JN.js → skills-M2ANTFD6.js} +5 -5
  113. package/dist/{skillsUpdateCheck-2OLTHCBX.js → skillsUpdateCheck-4WNEKKDW.js} +3 -3
  114. package/dist/{snapshot-R7U73M3N.js → snapshot-5AKBT5IQ.js} +36 -32
  115. package/dist/{src-4COYCAFY.js → src-JGPFLYBL.js} +1 -1
  116. package/dist/{src-WOD4AEI3.js → src-SGPA7DM7.js} +3 -3
  117. package/dist/{staticProjectServer-DG4VZO4G.js → staticProjectServer-UVFPQEUZ.js} +4 -4
  118. package/dist/studio/assets/{hyperframes-player-BByRREAU.js → hyperframes-player-BulZP0_1.js} +1 -1
  119. package/dist/studio/assets/{index-Wz-WEqHg.js → index-B1Gypobp.js} +4 -4
  120. package/dist/studio/assets/{index-CULLUqJo.js → index-CGncBUsJ.js} +1 -1
  121. package/dist/studio/index.html +1 -1
  122. package/dist/{studio-api-RGNBVLLL.js → studio-api-RVIU7JP2.js} +2 -2
  123. package/dist/{studioServer-LC5T4OXQ.js → studioServer-K22MWDLW.js} +30 -30
  124. package/dist/{telemetry-BLOE3TWY.js → telemetry-SUAMGBFK.js} +3 -3
  125. package/dist/{timeline-QQPPMADL.js → timeline-SGVBFT36.js} +13 -13
  126. package/dist/{tokens-E3SHBFDU.js → tokens-IFHWY5WC.js} +2 -2
  127. package/dist/{transcribe-D5N7SBDO.js → transcribe-GY4JHB7V.js} +12 -11
  128. package/dist/{transcribe-O2AQ5E2L.js → transcribe-KAOCI5CW.js} +7 -7
  129. package/dist/{updateCheck-ELOZ2CID.js → updateCheck-YUNDJDLW.js} +2 -2
  130. package/dist/{upgrade-JDFZEPZY.js → upgrade-PL2BFWTO.js} +2 -2
  131. package/dist/{urlDownloader-32OGC3EW.js → urlDownloader-FZ5ISJES.js} +1 -1
  132. package/dist/{validate-LQQEABER.js → validate-2NALRIFW.js} +17 -17
  133. package/dist/{video-HXIM27TO.js → video-7CDAIWYC.js} +2 -2
  134. package/package.json +1 -1
  135. package/dist/{chunk-3Q4Y33FT.js → chunk-PIH4P2G6.js} +4 -4
  136. package/dist/{deploy-NKWMPAMH.js → deploy-3TTQZGTF.js} +4 -4
  137. package/dist/{destroy-UKP522AE.js → destroy-BEL232I5.js} +3 -3
@@ -22,7 +22,7 @@ import {
22
22
  EncoderInterruptedError,
23
23
  encoderFailureError,
24
24
  prepareAnimatedGifInputs
25
- } from "./chunk-UWUKJDTA.js";
25
+ } from "./chunk-PL4WM5XE.js";
26
26
  import {
27
27
  BROWSER_GPU_NOT_SOFTWARE,
28
28
  CaptureFailure,
@@ -151,7 +151,7 @@ import {
151
151
  validateEngineConfigSnapshot,
152
152
  verifyDiskDrawElementSamples,
153
153
  writeCapturedFrame
154
- } from "./chunk-RERXOG54.js";
154
+ } from "./chunk-YWSXFFO3.js";
155
155
  import {
156
156
  assignBundledRuntimeCompositionIds,
157
157
  buildVariablesByCompScript,
@@ -13151,477 +13151,113 @@ async function executeRenderPipeline(input) {
13151
13151
  import {
13152
13152
  copyFileSync as copyFileSync4,
13153
13153
  cpSync as cpSync2,
13154
- existsSync as existsSync18,
13154
+ existsSync as existsSync19,
13155
13155
  linkSync,
13156
13156
  mkdirSync as mkdirSync10,
13157
13157
  renameSync as renameSync4,
13158
13158
  rmSync as rmSync9,
13159
13159
  writeFileSync as writeFileSync7
13160
13160
  } from "fs";
13161
- import { extname as extname5, isAbsolute as isAbsolute2, join as join29, relative as relative5, sep as sep2 } from "path";
13161
+ import { extname as extname6, isAbsolute as isAbsolute2, join as join31, relative as relative5, sep as sep2 } from "path";
13162
13162
 
13163
- // ../producer/src/services/render/stages/freezePlan.ts
13164
- import { existsSync as existsSync16, mkdirSync as mkdirSync9, readFileSync as readFileSync8, readdirSync as readdirSync6, writeFileSync as writeFileSync6 } from "fs";
13165
- import { join as join26, relative as relative3, resolve as resolve7 } from "path";
13163
+ // ../producer/src/services/distributed/planFrameCost.ts
13164
+ import { join as join28 } from "path";
13166
13165
 
13167
- // ../producer/src/services/distributed/planProtocol.ts
13168
- var PLAN_SCHEMA_VERSION = 1;
13169
- var PLAN_ARTIFACT_LAYOUT = "plan-dir-v1";
13170
- var PLAN_HASH_SCHEMA = "hyperframes-plan-hash-v1";
13171
- var PLAN_V2_SCHEMA_VERSION = 2;
13172
- var PLAN_V2_ARTIFACT_LAYOUT = "content-addressed-plan-v2";
13173
- var PLAN_V2_HASH_SCHEMA = "hyperframes-plan-manifest-hash-v2";
13174
- var PLAN_PROTOCOL_UNSUPPORTED = "PLAN_PROTOCOL_UNSUPPORTED";
13175
- var PLAN_PROTOCOL_V1 = Object.freeze({
13176
- schemaVersion: PLAN_SCHEMA_VERSION,
13177
- artifactLayout: PLAN_ARTIFACT_LAYOUT,
13178
- hashSchema: PLAN_HASH_SCHEMA
13179
- });
13180
- var CURRENT_PLAN_PROTOCOL = PLAN_PROTOCOL_V1;
13181
- var PLAN_PROTOCOL_V2 = Object.freeze({
13182
- schemaVersion: PLAN_V2_SCHEMA_VERSION,
13183
- artifactLayout: PLAN_V2_ARTIFACT_LAYOUT,
13184
- hashSchema: PLAN_V2_HASH_SCHEMA
13185
- });
13186
- var DISTRIBUTED_RENDER_CAPABILITIES = Object.freeze({
13187
- roles: Object.freeze({
13188
- planner: Object.freeze({
13189
- produces: Object.freeze([PLAN_PROTOCOL_V1, PLAN_PROTOCOL_V2])
13190
- }),
13191
- chunk: Object.freeze({
13192
- accepts: Object.freeze([PLAN_PROTOCOL_V1, PLAN_PROTOCOL_V2]),
13193
- acceptsLegacyV1WithoutDescriptor: true
13194
- }),
13195
- assembler: Object.freeze({
13196
- accepts: Object.freeze([PLAN_PROTOCOL_V1, PLAN_PROTOCOL_V2]),
13197
- acceptsLegacyV1WithoutDescriptor: true
13198
- })
13199
- })
13200
- });
13201
- function getDistributedRenderCapabilities() {
13202
- return DISTRIBUTED_RENDER_CAPABILITIES;
13166
+ // ../producer/src/services/distributed/chunkFrames.ts
13167
+ import { existsSync as existsSync17, readdirSync as readdirSync6 } from "fs";
13168
+ import { extname as extname4, join as join27 } from "path";
13169
+
13170
+ // ../producer/src/services/distributed/shared.ts
13171
+ import { execFile as execFileCallback } from "child_process";
13172
+ import { dirname as dirname10, join as join26 } from "path";
13173
+ import { existsSync as existsSync16, readFileSync as readFileSync8 } from "fs";
13174
+ import { fileURLToPath as fileURLToPath5 } from "url";
13175
+ import { promisify as promisify2 } from "util";
13176
+ var PLAN_VIDEOS_META_RELATIVE_PATH = "meta/videos.json";
13177
+ var PLAN_AUDIO_RELATIVE_PATH = MIXED_AUDIO_FILENAME;
13178
+ var PLAN_AUDIO_LEGACY_RELATIVE_PATH = "audio.aac";
13179
+ function isPlanAudioArtifactPath(path) {
13180
+ return path === PLAN_AUDIO_RELATIVE_PATH || path === PLAN_AUDIO_LEGACY_RELATIVE_PATH;
13203
13181
  }
13204
- var PlanProtocolUnsupportedError = class extends Error {
13205
- // Public adapters inspect this typed code even though OSS producer does not.
13182
+ function resolvePlanAudioPath(planDir) {
13183
+ for (const name of [PLAN_AUDIO_RELATIVE_PATH, PLAN_AUDIO_LEGACY_RELATIVE_PATH]) {
13184
+ const candidate = join26(planDir, name);
13185
+ if (existsSync16(candidate)) return candidate;
13186
+ }
13187
+ return null;
13188
+ }
13189
+ var INVALID_VIDEO_METADATA = "INVALID_VIDEO_METADATA";
13190
+ var PlanVideosMetadataError = class extends Error {
13191
+ // Read by cloud adapters across the package boundary to classify retries.
13206
13192
  // fallow-ignore-next-line unused-class-member
13207
- code = PLAN_PROTOCOL_UNSUPPORTED;
13208
- constructor(reason) {
13209
- super(`[planProtocol] ${reason} (${PLAN_PROTOCOL_UNSUPPORTED})`);
13210
- this.name = "PlanProtocolUnsupportedError";
13193
+ code = INVALID_VIDEO_METADATA;
13194
+ constructor(message) {
13195
+ super(message);
13196
+ this.name = "PlanVideosMetadataError";
13211
13197
  }
13212
13198
  };
13213
- function isRecord(value) {
13214
- return value !== null && typeof value === "object" && !Array.isArray(value);
13215
- }
13216
- function protocolMatches(descriptor, expected) {
13217
- return descriptor.schemaVersion === expected.schemaVersion && descriptor.artifactLayout === expected.artifactLayout && descriptor.hashSchema === expected.hashSchema;
13218
- }
13219
- function capabilitiesAccept(capabilities, protocol) {
13220
- return capabilities.accepts.some(
13221
- (accepted) => accepted.schemaVersion === protocol.schemaVersion && accepted.artifactLayout === protocol.artifactLayout && accepted.hashSchema === protocol.hashSchema
13222
- );
13199
+ function metadataError(field, expectation) {
13200
+ throw new PlanVideosMetadataError(`${field} ${expectation}`);
13223
13201
  }
13224
- function readPlanProtocol(planJson, capabilities = DISTRIBUTED_RENDER_CAPABILITIES.roles.chunk) {
13225
- if (!isRecord(planJson)) {
13226
- throw new PlanProtocolUnsupportedError("plan.json must contain a JSON object");
13227
- }
13228
- if (!Object.prototype.hasOwnProperty.call(planJson, "protocol")) {
13229
- if (!capabilities.acceptsLegacyV1WithoutDescriptor || !capabilitiesAccept(capabilities, PLAN_PROTOCOL_V1)) {
13230
- throw new PlanProtocolUnsupportedError(
13231
- "legacy v1 plan without a protocol descriptor is not accepted by this worker"
13232
- );
13233
- }
13234
- return PLAN_PROTOCOL_V1;
13235
- }
13236
- const descriptor = planJson.protocol;
13237
- if (!isRecord(descriptor)) {
13238
- throw new PlanProtocolUnsupportedError("plan.json protocol descriptor must be an object");
13239
- }
13240
- for (const field of ["schemaVersion", "artifactLayout", "hashSchema"]) {
13241
- if (!Object.prototype.hasOwnProperty.call(descriptor, field)) {
13242
- throw new PlanProtocolUnsupportedError(`plan.json protocol descriptor is missing ${field}`);
13243
- }
13244
- }
13245
- const protocol = protocolMatches(descriptor, PLAN_PROTOCOL_V1) ? PLAN_PROTOCOL_V1 : protocolMatches(descriptor, PLAN_PROTOCOL_V2) ? PLAN_PROTOCOL_V2 : null;
13246
- if (protocol === null || !capabilitiesAccept(capabilities, protocol)) {
13247
- throw new PlanProtocolUnsupportedError("unsupported plan.json protocol descriptor");
13202
+ function readRecord(value, field) {
13203
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
13204
+ metadataError(field, "must be an object");
13248
13205
  }
13249
- return protocol;
13206
+ return value;
13250
13207
  }
13251
- function readPlanProtocolV1(planJson, capabilities = DISTRIBUTED_RENDER_CAPABILITIES.roles.chunk) {
13252
- const protocol = readPlanProtocol(planJson, capabilities);
13253
- if (protocol !== PLAN_PROTOCOL_V1) {
13254
- throw new PlanProtocolUnsupportedError(
13255
- "content-addressed v2 plan must be materialized before v1 layout access"
13256
- );
13208
+ function readNonEmptyString(value, field) {
13209
+ if (typeof value !== "string" || value.length === 0) {
13210
+ metadataError(field, "must be a non-empty string");
13257
13211
  }
13258
- return PLAN_PROTOCOL_V1;
13212
+ return value;
13259
13213
  }
13260
-
13261
- // ../producer/src/services/render/stages/planHash.ts
13262
- import { createHash as createHash5 } from "crypto";
13263
- import { closeSync as closeSync5, createReadStream as createReadStream3, openSync as openSync4, readSync as readSync3 } from "fs";
13264
- var PLAN_HASH_SCHEMA_PREFIX = "hyperframes-plan-hash-v1\0";
13265
- var FIELD_DELIMITER = Buffer.from([0]);
13266
- function computePlanHash(input) {
13267
- const hash = createHash5("sha256");
13268
- hash.update(PLAN_HASH_SCHEMA_PREFIX, "utf8");
13269
- hash.update(input.compositionHtml);
13270
- hash.update(FIELD_DELIMITER);
13271
- const sortedAssets = [...input.assets].sort(
13272
- (a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0
13273
- );
13274
- for (const asset of sortedAssets) {
13275
- hash.update(asset.path, "utf8");
13276
- hash.update(FIELD_DELIMITER);
13277
- hash.update(asset.sha256, "utf8");
13278
- hash.update(FIELD_DELIMITER);
13214
+ function readString(value, field) {
13215
+ if (typeof value !== "string") {
13216
+ metadataError(field, "must be a string");
13279
13217
  }
13280
- hash.update(input.fontSnapshotSha, "utf8");
13281
- hash.update(FIELD_DELIMITER);
13282
- hash.update(input.encoderConfigCanonicalJson, "utf8");
13283
- hash.update(FIELD_DELIMITER);
13284
- hash.update(input.producerVersion, "utf8");
13285
- hash.update(FIELD_DELIMITER);
13286
- hash.update(input.ffmpegVersion, "utf8");
13287
- hash.update(FIELD_DELIMITER);
13288
- const d = input.dimensions;
13289
- hash.update(`${d.fpsNum}/${d.fpsDen}x${d.width}x${d.height}x${d.format}`, "utf8");
13290
- return hash.digest("hex");
13218
+ return value;
13291
13219
  }
13292
- function canonicalJsonStringify(value) {
13293
- if (value === null) return "null";
13294
- if (typeof value === "boolean") return value ? "true" : "false";
13295
- if (typeof value === "number") {
13296
- if (!Number.isFinite(value)) {
13297
- throw new TypeError(`canonicalJsonStringify: non-finite number ${value}`);
13298
- }
13299
- return JSON.stringify(value);
13300
- }
13301
- if (typeof value === "string") return JSON.stringify(value);
13302
- if (Array.isArray(value)) {
13303
- return `[${value.map(canonicalJsonStringify).join(",")}]`;
13304
- }
13305
- if (typeof value === "object") {
13306
- const obj = value;
13307
- const keys = Object.keys(obj).sort();
13308
- const parts = keys.map((k) => `${JSON.stringify(k)}:${canonicalJsonStringify(obj[k])}`);
13309
- return `{${parts.join(",")}}`;
13220
+ function readFiniteNumber(value, field) {
13221
+ if (typeof value !== "number" || !Number.isFinite(value)) {
13222
+ metadataError(field, "must be a finite number");
13310
13223
  }
13311
- throw new TypeError(`canonicalJsonStringify: unsupported value type ${typeof value}`);
13312
- }
13313
- function sha256Hex(bytes) {
13314
- const h = createHash5("sha256");
13315
- h.update(bytes);
13316
- return h.digest("hex");
13224
+ return value;
13317
13225
  }
13318
- function sha256File(path) {
13319
- const hash = createHash5("sha256");
13320
- const buffer = Buffer.allocUnsafe(1024 * 1024);
13321
- const fd = openSync4(path, "r");
13322
- try {
13323
- let bytesRead = 0;
13324
- do {
13325
- bytesRead = readSync3(fd, buffer, 0, buffer.byteLength, null);
13326
- if (bytesRead > 0) hash.update(buffer.subarray(0, bytesRead));
13327
- } while (bytesRead > 0);
13328
- } finally {
13329
- closeSync5(fd);
13226
+ function readBoolean(value, field) {
13227
+ if (typeof value !== "boolean") {
13228
+ metadataError(field, "must be boolean");
13330
13229
  }
13331
- return hash.digest("hex");
13230
+ return value;
13332
13231
  }
13333
- async function sha256FileAsync(path) {
13334
- const hash = createHash5("sha256");
13335
- for await (const chunk of createReadStream3(path, { highWaterMark: 1024 * 1024 })) {
13336
- hash.update(chunk);
13232
+ function readPositiveInteger(value, field) {
13233
+ if (typeof value !== "number" || !Number.isInteger(value) || value <= 0) {
13234
+ metadataError(field, "must be a positive integer");
13337
13235
  }
13338
- return hash.digest("hex");
13236
+ return value;
13339
13237
  }
13340
-
13341
- // ../producer/src/services/render/stages/freezePlan.ts
13342
- var COMPILED_INDEX_RELATIVE_PATH = "compiled/index.html";
13343
- var HASH_EXCLUDED_PLAN_FILES = /* @__PURE__ */ new Set([
13344
- "plan.json",
13345
- "meta/encoder.json",
13346
- COMPILED_INDEX_RELATIVE_PATH
13347
- ]);
13348
- function planFileSha256(absolutePath, digests) {
13349
- let sha256 = digests?.get(absolutePath);
13350
- if (sha256 === void 0) {
13351
- sha256 = sha256File(absolutePath);
13352
- digests?.set(absolutePath, sha256);
13238
+ function readNonNegativeInteger(value, field) {
13239
+ if (typeof value !== "number" || !Number.isInteger(value) || value < 0) {
13240
+ metadataError(field, "must be a non-negative integer");
13353
13241
  }
13354
- return sha256;
13242
+ return value;
13355
13243
  }
13356
- async function prehashPlanFiles(dir, digests) {
13357
- if (!existsSync16(dir)) return;
13358
- for (const { absolutePath } of listPlanFiles(dir)) {
13359
- if (!digests.has(absolutePath)) digests.set(absolutePath, await sha256FileAsync(absolutePath));
13244
+ function readDeferredFrameRange(value, field) {
13245
+ const range = readRecord(value, field);
13246
+ const sourcePath = readNonEmptyString(range.sourcePath, `${field}.sourcePath`);
13247
+ if (sourcePath.startsWith("/") || sourcePath.split(/[\\/]/).some((part) => part === "..")) {
13248
+ metadataError(`${field}.sourcePath`, "must stay inside the plan directory");
13360
13249
  }
13361
- }
13362
- function stripUndefined(value) {
13363
- if (Array.isArray(value)) return value.map(stripUndefined);
13364
- if (value !== null && typeof value === "object") {
13365
- const obj = value;
13366
- const out = {};
13367
- for (const key of Object.keys(obj)) {
13368
- const v = obj[key];
13369
- if (v === void 0) continue;
13370
- out[key] = stripUndefined(v);
13371
- }
13372
- return out;
13250
+ const durationSeconds = readFiniteNumber(range.durationSeconds, `${field}.durationSeconds`);
13251
+ if (durationSeconds <= 0) metadataError(`${field}.durationSeconds`, "must be positive");
13252
+ if (range.format !== "jpg" && range.format !== "png") {
13253
+ metadataError(`${field}.format`, 'must be "jpg" or "png"');
13373
13254
  }
13374
- return value;
13375
- }
13376
- function listPlanFiles(planDir) {
13377
- const results = [];
13378
- const rootResolved = resolve7(planDir);
13379
- function walk(dir) {
13380
- const entries = readdirSync6(dir, { withFileTypes: true });
13381
- for (const entry of entries) {
13382
- const full = join26(dir, entry.name);
13383
- if (entry.isDirectory()) {
13384
- walk(full);
13385
- } else if (entry.isFile()) {
13386
- results.push({
13387
- planRelativePath: relative3(rootResolved, full).split(/[\\/]+/).join("/"),
13388
- absolutePath: full
13389
- });
13390
- }
13391
- }
13392
- }
13393
- walk(rootResolved);
13394
- results.sort((a, b) => a.planRelativePath < b.planRelativePath ? -1 : 1);
13395
- return results;
13396
- }
13397
- function collectPlanAssetShas(planDir, digests) {
13398
- const files = listPlanFiles(planDir);
13399
- let compositionHtml = null;
13400
- const assets = [];
13401
- for (const file of files) {
13402
- if (file.planRelativePath === COMPILED_INDEX_RELATIVE_PATH) {
13403
- compositionHtml = readFileSync8(file.absolutePath);
13404
- continue;
13405
- }
13406
- if (HASH_EXCLUDED_PLAN_FILES.has(file.planRelativePath)) continue;
13407
- assets.push({
13408
- path: file.planRelativePath,
13409
- sha256: planFileSha256(file.absolutePath, digests)
13410
- });
13411
- }
13412
- if (compositionHtml === null) {
13413
- throw new Error(
13414
- `[freezePlan] compiled HTML missing at ${COMPILED_INDEX_RELATIVE_PATH} (upstream compile stage did not materialize the expected file).`
13415
- );
13416
- }
13417
- return { compositionHtml, assets };
13418
- }
13419
- function recomputePlanHashFromPlanDir(planDir, digests) {
13420
- const planJsonPath = join26(planDir, "plan.json");
13421
- const encoderJsonPath = join26(planDir, "meta", "encoder.json");
13422
- if (!existsSync16(planJsonPath)) {
13423
- throw new Error(`[freezePlan] plan.json missing: ${planJsonPath}`);
13424
- }
13425
- if (!existsSync16(encoderJsonPath)) {
13426
- throw new Error(`[freezePlan] meta/encoder.json missing: ${encoderJsonPath}`);
13427
- }
13428
- const planJson = JSON.parse(readFileSync8(planJsonPath, "utf-8"));
13429
- const encoderConfigCanonicalJson = readFileSync8(encoderJsonPath, "utf-8");
13430
- const { compositionHtml, assets } = collectPlanAssetShas(planDir, digests);
13431
- return computePlanHash({
13432
- compositionHtml,
13433
- assets,
13434
- fontSnapshotSha: planJson.fontSnapshotSha,
13435
- encoderConfigCanonicalJson,
13436
- producerVersion: planJson.producerVersion,
13437
- ffmpegVersion: planJson.ffmpegVersion,
13438
- dimensions: planJson.dimensions
13439
- });
13440
- }
13441
- async function freezePlan(input) {
13442
- const {
13443
- planDir,
13444
- composition,
13445
- encoder,
13446
- chunks,
13447
- dimensions,
13448
- producerVersion,
13449
- fontSnapshotSha,
13450
- durationSeconds,
13451
- totalFrames,
13452
- hasAudio,
13453
- digests
13454
- } = input;
13455
- if (!existsSync16(planDir)) {
13456
- throw new Error(`[freezePlan] planDir does not exist: ${planDir}`);
13457
- }
13458
- const metaDir = join26(planDir, "meta");
13459
- if (!existsSync16(metaDir)) mkdirSync9(metaDir, { recursive: true });
13460
- writeFileSync6(
13461
- join26(metaDir, "composition.json"),
13462
- `${JSON.stringify(composition, null, 2)}
13463
- `,
13464
- "utf-8"
13465
- );
13466
- const encoderForCanonical = stripUndefined(encoder);
13467
- const encoderConfigCanonicalJson = canonicalJsonStringify(encoderForCanonical);
13468
- writeFileSync6(join26(metaDir, "encoder.json"), encoderConfigCanonicalJson, "utf-8");
13469
- writeFileSync6(join26(metaDir, "chunks.json"), `${JSON.stringify(chunks, null, 2)}
13470
- `, "utf-8");
13471
- const { compositionHtml, assets } = collectPlanAssetShas(planDir, digests);
13472
- const planHash = computePlanHash({
13473
- compositionHtml,
13474
- assets,
13475
- fontSnapshotSha,
13476
- encoderConfigCanonicalJson,
13477
- producerVersion,
13478
- ffmpegVersion: encoder.ffmpegVersion,
13479
- dimensions
13480
- });
13481
- const planJson = {
13482
- protocol: PLAN_PROTOCOL_V1,
13483
- planHash,
13484
- producerVersion,
13485
- ffmpegVersion: encoder.ffmpegVersion,
13486
- fontSnapshotSha,
13487
- dimensions,
13488
- chunkCount: chunks.length,
13489
- totalFrames,
13490
- duration: durationSeconds,
13491
- hasAudio
13492
- };
13493
- const planJsonPath = join26(planDir, "plan.json");
13494
- writeFileSync6(planJsonPath, `${JSON.stringify(planJson, null, 2)}
13495
- `, "utf-8");
13496
- return { planJsonPath, planHash };
13497
- }
13498
-
13499
- // ../producer/src/services/render/runtimeEnvSnapshot.ts
13500
- var RUNTIME_ENV_PREFIXES = [
13501
- "PRODUCER_RUNTIME_",
13502
- "PRODUCER_RENDER_"
13503
- ];
13504
- function snapshotRuntimeEnv(env = process.env) {
13505
- const snapshot = {};
13506
- for (const key of Object.keys(env)) {
13507
- const matches = RUNTIME_ENV_PREFIXES.some((prefix) => key.startsWith(prefix));
13508
- if (!matches) continue;
13509
- const value = env[key];
13510
- if (typeof value !== "string") continue;
13511
- snapshot[key] = value;
13512
- }
13513
- return snapshot;
13514
- }
13515
- function applyRuntimeEnvSnapshot(snapshot, env = process.env) {
13516
- const previous = {};
13517
- for (const [key, value] of Object.entries(snapshot)) {
13518
- if (typeof value !== "string") continue;
13519
- const matches = RUNTIME_ENV_PREFIXES.some((prefix) => key.startsWith(prefix));
13520
- if (!matches) continue;
13521
- previous[key] = env[key];
13522
- env[key] = value;
13523
- }
13524
- return {
13525
- restore: () => {
13526
- for (const [key, value] of Object.entries(previous)) {
13527
- if (value === void 0) delete env[key];
13528
- else env[key] = value;
13529
- }
13530
- }
13531
- };
13532
- }
13533
-
13534
- // ../producer/src/services/distributed/shared.ts
13535
- import { execFile as execFileCallback } from "child_process";
13536
- import { dirname as dirname10, join as join27 } from "path";
13537
- import { existsSync as existsSync17, readFileSync as readFileSync9 } from "fs";
13538
- import { fileURLToPath as fileURLToPath5 } from "url";
13539
- import { promisify as promisify2 } from "util";
13540
- var PLAN_VIDEOS_META_RELATIVE_PATH = "meta/videos.json";
13541
- var PLAN_AUDIO_RELATIVE_PATH = MIXED_AUDIO_FILENAME;
13542
- var PLAN_AUDIO_LEGACY_RELATIVE_PATH = "audio.aac";
13543
- function isPlanAudioArtifactPath(path) {
13544
- return path === PLAN_AUDIO_RELATIVE_PATH || path === PLAN_AUDIO_LEGACY_RELATIVE_PATH;
13545
- }
13546
- function resolvePlanAudioPath(planDir) {
13547
- for (const name of [PLAN_AUDIO_RELATIVE_PATH, PLAN_AUDIO_LEGACY_RELATIVE_PATH]) {
13548
- const candidate = join27(planDir, name);
13549
- if (existsSync17(candidate)) return candidate;
13550
- }
13551
- return null;
13552
- }
13553
- var INVALID_VIDEO_METADATA = "INVALID_VIDEO_METADATA";
13554
- var PlanVideosMetadataError = class extends Error {
13555
- // Read by cloud adapters across the package boundary to classify retries.
13556
- // fallow-ignore-next-line unused-class-member
13557
- code = INVALID_VIDEO_METADATA;
13558
- constructor(message) {
13559
- super(message);
13560
- this.name = "PlanVideosMetadataError";
13561
- }
13562
- };
13563
- function metadataError(field, expectation) {
13564
- throw new PlanVideosMetadataError(`${field} ${expectation}`);
13565
- }
13566
- function readRecord(value, field) {
13567
- if (value === null || typeof value !== "object" || Array.isArray(value)) {
13568
- metadataError(field, "must be an object");
13569
- }
13570
- return value;
13571
- }
13572
- function readNonEmptyString(value, field) {
13573
- if (typeof value !== "string" || value.length === 0) {
13574
- metadataError(field, "must be a non-empty string");
13575
- }
13576
- return value;
13577
- }
13578
- function readString(value, field) {
13579
- if (typeof value !== "string") {
13580
- metadataError(field, "must be a string");
13581
- }
13582
- return value;
13583
- }
13584
- function readFiniteNumber(value, field) {
13585
- if (typeof value !== "number" || !Number.isFinite(value)) {
13586
- metadataError(field, "must be a finite number");
13587
- }
13588
- return value;
13589
- }
13590
- function readBoolean(value, field) {
13591
- if (typeof value !== "boolean") {
13592
- metadataError(field, "must be boolean");
13593
- }
13594
- return value;
13595
- }
13596
- function readPositiveInteger(value, field) {
13597
- if (typeof value !== "number" || !Number.isInteger(value) || value <= 0) {
13598
- metadataError(field, "must be a positive integer");
13599
- }
13600
- return value;
13601
- }
13602
- function readNonNegativeInteger(value, field) {
13603
- if (typeof value !== "number" || !Number.isInteger(value) || value < 0) {
13604
- metadataError(field, "must be a non-negative integer");
13605
- }
13606
- return value;
13607
- }
13608
- function readDeferredFrameRange(value, field) {
13609
- const range = readRecord(value, field);
13610
- const sourcePath = readNonEmptyString(range.sourcePath, `${field}.sourcePath`);
13611
- if (sourcePath.startsWith("/") || sourcePath.split(/[\\/]/).some((part) => part === "..")) {
13612
- metadataError(`${field}.sourcePath`, "must stay inside the plan directory");
13613
- }
13614
- const durationSeconds = readFiniteNumber(range.durationSeconds, `${field}.durationSeconds`);
13615
- if (durationSeconds <= 0) metadataError(`${field}.durationSeconds`, "must be positive");
13616
- if (range.format !== "jpg" && range.format !== "png") {
13617
- metadataError(`${field}.format`, 'must be "jpg" or "png"');
13618
- }
13619
- return {
13620
- sourcePath,
13621
- startTime: readFiniteNumber(range.startTime, `${field}.startTime`),
13622
- durationSeconds,
13623
- format: range.format
13624
- };
13255
+ return {
13256
+ sourcePath,
13257
+ startTime: readFiniteNumber(range.startTime, `${field}.startTime`),
13258
+ durationSeconds,
13259
+ format: range.format
13260
+ };
13625
13261
  }
13626
13262
  function readRateSpec(value, field) {
13627
13263
  if (value === void 0) return void 0;
@@ -13792,37 +13428,612 @@ function buildSyntheticRenderJob(input) {
13792
13428
  producerConfig: input.producerConfig,
13793
13429
  variables: input.variables
13794
13430
  };
13795
- return createRenderJob(renderConfig);
13431
+ return createRenderJob(renderConfig);
13432
+ }
13433
+ var cachedProducerVersion = null;
13434
+ function readProducerVersion() {
13435
+ if (cachedProducerVersion !== null) return cachedProducerVersion;
13436
+ const startDir = dirname10(fileURLToPath5(import.meta.url));
13437
+ let current = startDir;
13438
+ for (let i = 0; i < 10; i++) {
13439
+ const candidate = join26(current, "package.json");
13440
+ if (existsSync16(candidate)) {
13441
+ try {
13442
+ const pkg = JSON.parse(readFileSync8(candidate, "utf-8"));
13443
+ if (pkg.name === "@hyperframes/producer" && typeof pkg.version === "string") {
13444
+ cachedProducerVersion = pkg.version;
13445
+ return pkg.version;
13446
+ }
13447
+ } catch {
13448
+ }
13449
+ }
13450
+ const parent = dirname10(current);
13451
+ if (parent === current) break;
13452
+ current = parent;
13453
+ }
13454
+ cachedProducerVersion = "0.0.0-unknown";
13455
+ return cachedProducerVersion;
13456
+ }
13457
+
13458
+ // ../producer/src/services/distributed/chunkFrames.ts
13459
+ var FFMPEG_VERSION_MISMATCH = "FFMPEG_VERSION_MISMATCH";
13460
+ var PLAN_HASH_MISMATCH = "PLAN_HASH_MISMATCH";
13461
+ var MISSING_PLAN_ARTIFACT = "MISSING_PLAN_ARTIFACT";
13462
+ var CHUNK_INDEX_OUT_OF_RANGE = "CHUNK_INDEX_OUT_OF_RANGE";
13463
+ var MISSING_RUNTIME_ENV_SNAPSHOT = "MISSING_RUNTIME_ENV_SNAPSHOT";
13464
+ var RenderChunkValidationError = class extends Error {
13465
+ code;
13466
+ constructor(code, message) {
13467
+ super(message);
13468
+ this.name = "RenderChunkValidationError";
13469
+ this.code = code;
13470
+ }
13471
+ };
13472
+ function createChunkVideoFrameInjectorFactory(frameLookup) {
13473
+ return () => createVideoFrameInjector(frameLookup);
13474
+ }
13475
+ function frameNumberFromFileName(name) {
13476
+ const match = /(\d+)(?=\.[^.]+$)/.exec(name);
13477
+ if (!match) return null;
13478
+ const frameNumber = Number(match[1]);
13479
+ return Number.isSafeInteger(frameNumber) ? frameNumber : null;
13480
+ }
13481
+ function rebuildExtractedFramesFromPlanDir(planDir, videos, indexMode = "dense-v1") {
13482
+ const result = [];
13483
+ for (const v of videos) {
13484
+ const outputDir = join27(planDir, "video-frames", v.videoId);
13485
+ if (!existsSync17(outputDir)) {
13486
+ throw new Error(
13487
+ `[renderChunk] planDir missing extracted video frames for ${JSON.stringify(v.videoId)}: ${outputDir} not present. plan() should have written frames here; the planDir is malformed.`
13488
+ );
13489
+ }
13490
+ const ext = (extname4(v.framePattern) || ".jpg").toLowerCase();
13491
+ const frames = readdirSync6(outputDir).filter((name) => name.toLowerCase().endsWith(ext)).sort((left, right) => {
13492
+ const leftNumber = frameNumberFromFileName(left);
13493
+ const rightNumber = frameNumberFromFileName(right);
13494
+ if (leftNumber === null || rightNumber === null) return left.localeCompare(right);
13495
+ return leftNumber - rightNumber || left.localeCompare(right);
13496
+ });
13497
+ const framePaths = /* @__PURE__ */ new Map();
13498
+ for (let i = 0; i < frames.length; i++) {
13499
+ const frameName = frames[i];
13500
+ if (!frameName) continue;
13501
+ const frameNumber = indexMode === "sparse-v2" ? frameNumberFromFileName(frameName) : null;
13502
+ const frameIndex = frameNumber === null ? i : frameNumber - 1;
13503
+ framePaths.set(frameIndex, join27(outputDir, frameName));
13504
+ }
13505
+ result.push({
13506
+ videoId: v.videoId,
13507
+ srcPath: v.srcPath,
13508
+ outputDir,
13509
+ framePattern: v.framePattern,
13510
+ fps: v.fps,
13511
+ totalFrames: v.totalFrames,
13512
+ metadata: v.metadata,
13513
+ framePaths,
13514
+ // The planDir is shared read-only across workers; cleanup must not delete its frames.
13515
+ ownedByLookup: false
13516
+ });
13517
+ }
13518
+ return result;
13519
+ }
13520
+ function contiguousRuns(sortedIndexes) {
13521
+ const runs2 = [];
13522
+ for (const index of sortedIndexes) {
13523
+ const last = runs2[runs2.length - 1];
13524
+ if (last && last.firstFrame + last.frames === index) last.frames++;
13525
+ else runs2.push({ firstFrame: index, frames: 1 });
13526
+ }
13527
+ return runs2;
13528
+ }
13529
+ function buildChunkCaptureOptions(input) {
13530
+ const { dimensions, encoder } = input;
13531
+ const videoCaptureBeyondViewport = resolveVideoCaptureBeyondViewport(input.videoCount);
13532
+ return {
13533
+ width: dimensions.width,
13534
+ height: dimensions.height,
13535
+ fps: { num: dimensions.fpsNum, den: dimensions.fpsDen },
13536
+ format: dimensions.format === "mp4" ? "jpeg" : "png",
13537
+ quality: dimensions.format === "mp4" ? resolveCaptureJpegQuality(encoder.quality) : void 0,
13538
+ deviceScaleFactor: encoder.deviceScaleFactor,
13539
+ // The controller's snapshot, so the first capture sees the in-process renderer's `__hfVariables`.
13540
+ variables: encoder.variables,
13541
+ subTimelineWaitMemo: {},
13542
+ ...videoCaptureBeyondViewport !== void 0 ? { captureBeyondViewport: videoCaptureBeyondViewport } : {},
13543
+ // A fixed warmup count keeps `beginFrameTimeTicks` host-independent.
13544
+ lockWarmupTicks: true,
13545
+ requiresWebGpu: compositionRequiresWebGpu(input.html)
13546
+ };
13547
+ }
13548
+ async function extractDeferredVideoFramesForChunk(input) {
13549
+ const deferred = input.planVideos.extracted.filter((video) => video.deferredRange);
13550
+ if (deferred.length === 0) return [];
13551
+ const table = createFrameLookupTable(
13552
+ input.planVideos.videos,
13553
+ deferred.map((video) => ({
13554
+ ...video,
13555
+ outputDir: input.outputRoot,
13556
+ framePaths: new Map(Array.from({ length: video.totalFrames }, (_, i) => [i, String(i)]))
13557
+ })),
13558
+ resolveRenderFpsConfig(input.fps).value
13559
+ );
13560
+ const shown = /* @__PURE__ */ new Map();
13561
+ for (const frame of input.frames) {
13562
+ const globalTime = frame * input.fps.den / input.fps.num;
13563
+ for (const [videoId, payload] of table.getActiveFramePayloads(globalTime)) {
13564
+ const indexes = shown.get(videoId) ?? /* @__PURE__ */ new Set();
13565
+ indexes.add(payload.frameIndex);
13566
+ shown.set(videoId, indexes);
13567
+ }
13568
+ }
13569
+ return Promise.all(
13570
+ deferred.map(async (video) => {
13571
+ const outputDir = join27(input.outputRoot, video.videoId);
13572
+ const indexes = [...shown.get(video.videoId) ?? []].sort((a, b) => a - b);
13573
+ if (indexes.length === 0) {
13574
+ return { ...video, outputDir, framePaths: /* @__PURE__ */ new Map(), ownedByLookup: false };
13575
+ }
13576
+ const range = video.deferredRange;
13577
+ const sourcePath = join27(input.planDir, ...range.sourcePath.split("/"));
13578
+ if (!existsSync17(sourcePath)) {
13579
+ throw new RenderChunkValidationError(
13580
+ MISSING_PLAN_ARTIFACT,
13581
+ `[renderChunk] planDir is missing the source for video ${JSON.stringify(video.videoId)}: ${range.sourcePath}`
13582
+ );
13583
+ }
13584
+ const extracted = await extractVideoFramesRange(
13585
+ sourcePath,
13586
+ video.videoId,
13587
+ range.startTime,
13588
+ range.durationSeconds,
13589
+ {
13590
+ fps: input.fps,
13591
+ outputDir: input.outputRoot,
13592
+ format: range.format,
13593
+ frameRanges: contiguousRuns(indexes)
13594
+ },
13595
+ void 0,
13596
+ input.cfg
13597
+ );
13598
+ const missing = indexes.find((index) => !extracted.framePaths.has(index));
13599
+ if (missing !== void 0) {
13600
+ throw new RenderChunkValidationError(
13601
+ INVALID_VIDEO_METADATA,
13602
+ `[renderChunk] video ${JSON.stringify(video.videoId)} has no frame ${missing}; the plan expected ${video.totalFrames} frames from ${range.sourcePath}.`
13603
+ );
13604
+ }
13605
+ return { ...video, outputDir, framePaths: extracted.framePaths, ownedByLookup: false };
13606
+ })
13607
+ );
13608
+ }
13609
+
13610
+ // ../producer/src/services/distributed/planFrameCost.ts
13611
+ async function measurePlanSecondsPerFrame(input) {
13612
+ const { planDir, workDir, planVideos, fps, log } = input;
13613
+ const fpsRatio = { num: fps, den: 1 };
13614
+ const sampled = selectCaptureCalibrationFrames(input.totalFrames);
13615
+ const extracted = [
13616
+ ...rebuildExtractedFramesFromPlanDir(
13617
+ planDir,
13618
+ planVideos.extracted.filter((video) => !video.deferredRange)
13619
+ ),
13620
+ ...await extractDeferredVideoFramesForChunk({
13621
+ planDir,
13622
+ planVideos,
13623
+ frames: sampled,
13624
+ fps: fpsRatio,
13625
+ outputRoot: join28(workDir, "frame-cost-video-frames"),
13626
+ cfg: input.cfg
13627
+ })
13628
+ ];
13629
+ const lookup = extracted.length > 0 ? createFrameLookupTable(planVideos.videos, extracted, resolveRenderFpsConfig(fpsRatio).value) : null;
13630
+ const compiledDir = join28(planDir, "compiled");
13631
+ const fileServer = await createFileServer({
13632
+ projectDir: compiledDir,
13633
+ compiledDir,
13634
+ port: 0,
13635
+ preHeadScripts: [buildVirtualTimeShim({ seedRandomFromFrame: true })],
13636
+ fps: fpsRatio
13637
+ });
13638
+ let session = null;
13639
+ try {
13640
+ session = await createCaptureSession(
13641
+ fileServer.url,
13642
+ join28(workDir, "frame-cost"),
13643
+ buildChunkCaptureOptions({
13644
+ dimensions: { ...input, fpsNum: fps, fpsDen: 1 },
13645
+ encoder: input,
13646
+ videoCount: planVideos.videos.length,
13647
+ html: input.html
13648
+ }),
13649
+ createChunkVideoFrameInjectorFactory(lookup)(),
13650
+ createCaptureCalibrationConfig(input.cfg)
13651
+ );
13652
+ await initializeSession(session);
13653
+ const { estimate } = await measureCaptureCostFromSession(session, input.totalFrames, fps, log);
13654
+ return (estimate.p95Ms ?? 0) / 1e3;
13655
+ } finally {
13656
+ if (session) await closeCaptureSession(session).catch(() => {
13657
+ });
13658
+ closeFileServerSafely(fileServer, "plan frame cost", log);
13659
+ }
13660
+ }
13661
+
13662
+ // ../producer/src/services/render/stages/freezePlan.ts
13663
+ import { existsSync as existsSync18, mkdirSync as mkdirSync9, readFileSync as readFileSync9, readdirSync as readdirSync7, writeFileSync as writeFileSync6 } from "fs";
13664
+ import { join as join29, relative as relative3, resolve as resolve7 } from "path";
13665
+
13666
+ // ../producer/src/services/distributed/planProtocol.ts
13667
+ var PLAN_SCHEMA_VERSION = 1;
13668
+ var PLAN_ARTIFACT_LAYOUT = "plan-dir-v1";
13669
+ var PLAN_HASH_SCHEMA = "hyperframes-plan-hash-v1";
13670
+ var PLAN_V2_SCHEMA_VERSION = 2;
13671
+ var PLAN_V2_ARTIFACT_LAYOUT = "content-addressed-plan-v2";
13672
+ var PLAN_V2_HASH_SCHEMA = "hyperframes-plan-manifest-hash-v2";
13673
+ var PLAN_PROTOCOL_UNSUPPORTED = "PLAN_PROTOCOL_UNSUPPORTED";
13674
+ var PLAN_PROTOCOL_V1 = Object.freeze({
13675
+ schemaVersion: PLAN_SCHEMA_VERSION,
13676
+ artifactLayout: PLAN_ARTIFACT_LAYOUT,
13677
+ hashSchema: PLAN_HASH_SCHEMA
13678
+ });
13679
+ var CURRENT_PLAN_PROTOCOL = PLAN_PROTOCOL_V1;
13680
+ var PLAN_PROTOCOL_V2 = Object.freeze({
13681
+ schemaVersion: PLAN_V2_SCHEMA_VERSION,
13682
+ artifactLayout: PLAN_V2_ARTIFACT_LAYOUT,
13683
+ hashSchema: PLAN_V2_HASH_SCHEMA
13684
+ });
13685
+ var DISTRIBUTED_RENDER_CAPABILITIES = Object.freeze({
13686
+ roles: Object.freeze({
13687
+ planner: Object.freeze({
13688
+ produces: Object.freeze([PLAN_PROTOCOL_V1, PLAN_PROTOCOL_V2])
13689
+ }),
13690
+ chunk: Object.freeze({
13691
+ accepts: Object.freeze([PLAN_PROTOCOL_V1, PLAN_PROTOCOL_V2]),
13692
+ acceptsLegacyV1WithoutDescriptor: true
13693
+ }),
13694
+ assembler: Object.freeze({
13695
+ accepts: Object.freeze([PLAN_PROTOCOL_V1, PLAN_PROTOCOL_V2]),
13696
+ acceptsLegacyV1WithoutDescriptor: true
13697
+ })
13698
+ })
13699
+ });
13700
+ function getDistributedRenderCapabilities() {
13701
+ return DISTRIBUTED_RENDER_CAPABILITIES;
13702
+ }
13703
+ var PlanProtocolUnsupportedError = class extends Error {
13704
+ // Public adapters inspect this typed code even though OSS producer does not.
13705
+ // fallow-ignore-next-line unused-class-member
13706
+ code = PLAN_PROTOCOL_UNSUPPORTED;
13707
+ constructor(reason) {
13708
+ super(`[planProtocol] ${reason} (${PLAN_PROTOCOL_UNSUPPORTED})`);
13709
+ this.name = "PlanProtocolUnsupportedError";
13710
+ }
13711
+ };
13712
+ function isRecord(value) {
13713
+ return value !== null && typeof value === "object" && !Array.isArray(value);
13714
+ }
13715
+ function protocolMatches(descriptor, expected) {
13716
+ return descriptor.schemaVersion === expected.schemaVersion && descriptor.artifactLayout === expected.artifactLayout && descriptor.hashSchema === expected.hashSchema;
13717
+ }
13718
+ function capabilitiesAccept(capabilities, protocol) {
13719
+ return capabilities.accepts.some(
13720
+ (accepted) => accepted.schemaVersion === protocol.schemaVersion && accepted.artifactLayout === protocol.artifactLayout && accepted.hashSchema === protocol.hashSchema
13721
+ );
13722
+ }
13723
+ function readPlanProtocol(planJson, capabilities = DISTRIBUTED_RENDER_CAPABILITIES.roles.chunk) {
13724
+ if (!isRecord(planJson)) {
13725
+ throw new PlanProtocolUnsupportedError("plan.json must contain a JSON object");
13726
+ }
13727
+ if (!Object.prototype.hasOwnProperty.call(planJson, "protocol")) {
13728
+ if (!capabilities.acceptsLegacyV1WithoutDescriptor || !capabilitiesAccept(capabilities, PLAN_PROTOCOL_V1)) {
13729
+ throw new PlanProtocolUnsupportedError(
13730
+ "legacy v1 plan without a protocol descriptor is not accepted by this worker"
13731
+ );
13732
+ }
13733
+ return PLAN_PROTOCOL_V1;
13734
+ }
13735
+ const descriptor = planJson.protocol;
13736
+ if (!isRecord(descriptor)) {
13737
+ throw new PlanProtocolUnsupportedError("plan.json protocol descriptor must be an object");
13738
+ }
13739
+ for (const field of ["schemaVersion", "artifactLayout", "hashSchema"]) {
13740
+ if (!Object.prototype.hasOwnProperty.call(descriptor, field)) {
13741
+ throw new PlanProtocolUnsupportedError(`plan.json protocol descriptor is missing ${field}`);
13742
+ }
13743
+ }
13744
+ const protocol = protocolMatches(descriptor, PLAN_PROTOCOL_V1) ? PLAN_PROTOCOL_V1 : protocolMatches(descriptor, PLAN_PROTOCOL_V2) ? PLAN_PROTOCOL_V2 : null;
13745
+ if (protocol === null || !capabilitiesAccept(capabilities, protocol)) {
13746
+ throw new PlanProtocolUnsupportedError("unsupported plan.json protocol descriptor");
13747
+ }
13748
+ return protocol;
13749
+ }
13750
+ function readPlanProtocolV1(planJson, capabilities = DISTRIBUTED_RENDER_CAPABILITIES.roles.chunk) {
13751
+ const protocol = readPlanProtocol(planJson, capabilities);
13752
+ if (protocol !== PLAN_PROTOCOL_V1) {
13753
+ throw new PlanProtocolUnsupportedError(
13754
+ "content-addressed v2 plan must be materialized before v1 layout access"
13755
+ );
13756
+ }
13757
+ return PLAN_PROTOCOL_V1;
13758
+ }
13759
+
13760
+ // ../producer/src/services/render/stages/planHash.ts
13761
+ import { createHash as createHash5 } from "crypto";
13762
+ import { closeSync as closeSync5, createReadStream as createReadStream3, openSync as openSync4, readSync as readSync3 } from "fs";
13763
+ var PLAN_HASH_SCHEMA_PREFIX = "hyperframes-plan-hash-v1\0";
13764
+ var FIELD_DELIMITER = Buffer.from([0]);
13765
+ function computePlanHash(input) {
13766
+ const hash = createHash5("sha256");
13767
+ hash.update(PLAN_HASH_SCHEMA_PREFIX, "utf8");
13768
+ hash.update(input.compositionHtml);
13769
+ hash.update(FIELD_DELIMITER);
13770
+ const sortedAssets = [...input.assets].sort(
13771
+ (a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0
13772
+ );
13773
+ for (const asset of sortedAssets) {
13774
+ hash.update(asset.path, "utf8");
13775
+ hash.update(FIELD_DELIMITER);
13776
+ hash.update(asset.sha256, "utf8");
13777
+ hash.update(FIELD_DELIMITER);
13778
+ }
13779
+ hash.update(input.fontSnapshotSha, "utf8");
13780
+ hash.update(FIELD_DELIMITER);
13781
+ hash.update(input.encoderConfigCanonicalJson, "utf8");
13782
+ hash.update(FIELD_DELIMITER);
13783
+ hash.update(input.producerVersion, "utf8");
13784
+ hash.update(FIELD_DELIMITER);
13785
+ hash.update(input.ffmpegVersion, "utf8");
13786
+ hash.update(FIELD_DELIMITER);
13787
+ const d = input.dimensions;
13788
+ hash.update(`${d.fpsNum}/${d.fpsDen}x${d.width}x${d.height}x${d.format}`, "utf8");
13789
+ return hash.digest("hex");
13790
+ }
13791
+ function canonicalJsonStringify(value) {
13792
+ if (value === null) return "null";
13793
+ if (typeof value === "boolean") return value ? "true" : "false";
13794
+ if (typeof value === "number") {
13795
+ if (!Number.isFinite(value)) {
13796
+ throw new TypeError(`canonicalJsonStringify: non-finite number ${value}`);
13797
+ }
13798
+ return JSON.stringify(value);
13799
+ }
13800
+ if (typeof value === "string") return JSON.stringify(value);
13801
+ if (Array.isArray(value)) {
13802
+ return `[${value.map(canonicalJsonStringify).join(",")}]`;
13803
+ }
13804
+ if (typeof value === "object") {
13805
+ const obj = value;
13806
+ const keys = Object.keys(obj).sort();
13807
+ const parts = keys.map((k) => `${JSON.stringify(k)}:${canonicalJsonStringify(obj[k])}`);
13808
+ return `{${parts.join(",")}}`;
13809
+ }
13810
+ throw new TypeError(`canonicalJsonStringify: unsupported value type ${typeof value}`);
13811
+ }
13812
+ function sha256Hex(bytes) {
13813
+ const h = createHash5("sha256");
13814
+ h.update(bytes);
13815
+ return h.digest("hex");
13816
+ }
13817
+ function sha256File(path) {
13818
+ const hash = createHash5("sha256");
13819
+ const buffer = Buffer.allocUnsafe(1024 * 1024);
13820
+ const fd = openSync4(path, "r");
13821
+ try {
13822
+ let bytesRead = 0;
13823
+ do {
13824
+ bytesRead = readSync3(fd, buffer, 0, buffer.byteLength, null);
13825
+ if (bytesRead > 0) hash.update(buffer.subarray(0, bytesRead));
13826
+ } while (bytesRead > 0);
13827
+ } finally {
13828
+ closeSync5(fd);
13829
+ }
13830
+ return hash.digest("hex");
13831
+ }
13832
+ async function sha256FileAsync(path) {
13833
+ const hash = createHash5("sha256");
13834
+ for await (const chunk of createReadStream3(path, { highWaterMark: 1024 * 1024 })) {
13835
+ hash.update(chunk);
13836
+ }
13837
+ return hash.digest("hex");
13838
+ }
13839
+
13840
+ // ../producer/src/services/render/stages/freezePlan.ts
13841
+ var COMPILED_INDEX_RELATIVE_PATH = "compiled/index.html";
13842
+ var HASH_EXCLUDED_PLAN_FILES = /* @__PURE__ */ new Set([
13843
+ "plan.json",
13844
+ "meta/encoder.json",
13845
+ COMPILED_INDEX_RELATIVE_PATH
13846
+ ]);
13847
+ function planFileSha256(absolutePath, digests) {
13848
+ let sha256 = digests?.get(absolutePath);
13849
+ if (sha256 === void 0) {
13850
+ sha256 = sha256File(absolutePath);
13851
+ digests?.set(absolutePath, sha256);
13852
+ }
13853
+ return sha256;
13854
+ }
13855
+ async function prehashPlanFiles(dir, digests) {
13856
+ if (!existsSync18(dir)) return;
13857
+ for (const { absolutePath } of listPlanFiles(dir)) {
13858
+ if (!digests.has(absolutePath)) digests.set(absolutePath, await sha256FileAsync(absolutePath));
13859
+ }
13860
+ }
13861
+ function stripUndefined(value) {
13862
+ if (Array.isArray(value)) return value.map(stripUndefined);
13863
+ if (value !== null && typeof value === "object") {
13864
+ const obj = value;
13865
+ const out = {};
13866
+ for (const key of Object.keys(obj)) {
13867
+ const v = obj[key];
13868
+ if (v === void 0) continue;
13869
+ out[key] = stripUndefined(v);
13870
+ }
13871
+ return out;
13872
+ }
13873
+ return value;
13874
+ }
13875
+ function listPlanFiles(planDir) {
13876
+ const results = [];
13877
+ const rootResolved = resolve7(planDir);
13878
+ function walk(dir) {
13879
+ const entries = readdirSync7(dir, { withFileTypes: true });
13880
+ for (const entry of entries) {
13881
+ const full = join29(dir, entry.name);
13882
+ if (entry.isDirectory()) {
13883
+ walk(full);
13884
+ } else if (entry.isFile()) {
13885
+ results.push({
13886
+ planRelativePath: relative3(rootResolved, full).split(/[\\/]+/).join("/"),
13887
+ absolutePath: full
13888
+ });
13889
+ }
13890
+ }
13891
+ }
13892
+ walk(rootResolved);
13893
+ results.sort((a, b) => a.planRelativePath < b.planRelativePath ? -1 : 1);
13894
+ return results;
13895
+ }
13896
+ function collectPlanAssetShas(planDir, digests) {
13897
+ const files = listPlanFiles(planDir);
13898
+ let compositionHtml = null;
13899
+ const assets = [];
13900
+ for (const file of files) {
13901
+ if (file.planRelativePath === COMPILED_INDEX_RELATIVE_PATH) {
13902
+ compositionHtml = readFileSync9(file.absolutePath);
13903
+ continue;
13904
+ }
13905
+ if (HASH_EXCLUDED_PLAN_FILES.has(file.planRelativePath)) continue;
13906
+ assets.push({
13907
+ path: file.planRelativePath,
13908
+ sha256: planFileSha256(file.absolutePath, digests)
13909
+ });
13910
+ }
13911
+ if (compositionHtml === null) {
13912
+ throw new Error(
13913
+ `[freezePlan] compiled HTML missing at ${COMPILED_INDEX_RELATIVE_PATH} (upstream compile stage did not materialize the expected file).`
13914
+ );
13915
+ }
13916
+ return { compositionHtml, assets };
13917
+ }
13918
+ function recomputePlanHashFromPlanDir(planDir, digests) {
13919
+ const planJsonPath = join29(planDir, "plan.json");
13920
+ const encoderJsonPath = join29(planDir, "meta", "encoder.json");
13921
+ if (!existsSync18(planJsonPath)) {
13922
+ throw new Error(`[freezePlan] plan.json missing: ${planJsonPath}`);
13923
+ }
13924
+ if (!existsSync18(encoderJsonPath)) {
13925
+ throw new Error(`[freezePlan] meta/encoder.json missing: ${encoderJsonPath}`);
13926
+ }
13927
+ const planJson = JSON.parse(readFileSync9(planJsonPath, "utf-8"));
13928
+ const encoderConfigCanonicalJson = readFileSync9(encoderJsonPath, "utf-8");
13929
+ const { compositionHtml, assets } = collectPlanAssetShas(planDir, digests);
13930
+ return computePlanHash({
13931
+ compositionHtml,
13932
+ assets,
13933
+ fontSnapshotSha: planJson.fontSnapshotSha,
13934
+ encoderConfigCanonicalJson,
13935
+ producerVersion: planJson.producerVersion,
13936
+ ffmpegVersion: planJson.ffmpegVersion,
13937
+ dimensions: planJson.dimensions
13938
+ });
13939
+ }
13940
+ async function freezePlan(input) {
13941
+ const {
13942
+ planDir,
13943
+ composition,
13944
+ encoder,
13945
+ chunks,
13946
+ dimensions,
13947
+ producerVersion,
13948
+ fontSnapshotSha,
13949
+ durationSeconds,
13950
+ totalFrames,
13951
+ hasAudio,
13952
+ digests
13953
+ } = input;
13954
+ if (!existsSync18(planDir)) {
13955
+ throw new Error(`[freezePlan] planDir does not exist: ${planDir}`);
13956
+ }
13957
+ const metaDir = join29(planDir, "meta");
13958
+ if (!existsSync18(metaDir)) mkdirSync9(metaDir, { recursive: true });
13959
+ writeFileSync6(
13960
+ join29(metaDir, "composition.json"),
13961
+ `${JSON.stringify(composition, null, 2)}
13962
+ `,
13963
+ "utf-8"
13964
+ );
13965
+ const encoderForCanonical = stripUndefined(encoder);
13966
+ const encoderConfigCanonicalJson = canonicalJsonStringify(encoderForCanonical);
13967
+ writeFileSync6(join29(metaDir, "encoder.json"), encoderConfigCanonicalJson, "utf-8");
13968
+ writeFileSync6(join29(metaDir, "chunks.json"), `${JSON.stringify(chunks, null, 2)}
13969
+ `, "utf-8");
13970
+ const { compositionHtml, assets } = collectPlanAssetShas(planDir, digests);
13971
+ const planHash = computePlanHash({
13972
+ compositionHtml,
13973
+ assets,
13974
+ fontSnapshotSha,
13975
+ encoderConfigCanonicalJson,
13976
+ producerVersion,
13977
+ ffmpegVersion: encoder.ffmpegVersion,
13978
+ dimensions
13979
+ });
13980
+ const planJson = {
13981
+ protocol: PLAN_PROTOCOL_V1,
13982
+ planHash,
13983
+ producerVersion,
13984
+ ffmpegVersion: encoder.ffmpegVersion,
13985
+ fontSnapshotSha,
13986
+ dimensions,
13987
+ chunkCount: chunks.length,
13988
+ totalFrames,
13989
+ duration: durationSeconds,
13990
+ hasAudio
13991
+ };
13992
+ const planJsonPath = join29(planDir, "plan.json");
13993
+ writeFileSync6(planJsonPath, `${JSON.stringify(planJson, null, 2)}
13994
+ `, "utf-8");
13995
+ return { planJsonPath, planHash };
13796
13996
  }
13797
- var cachedProducerVersion = null;
13798
- function readProducerVersion() {
13799
- if (cachedProducerVersion !== null) return cachedProducerVersion;
13800
- const startDir = dirname10(fileURLToPath5(import.meta.url));
13801
- let current = startDir;
13802
- for (let i = 0; i < 10; i++) {
13803
- const candidate = join27(current, "package.json");
13804
- if (existsSync17(candidate)) {
13805
- try {
13806
- const pkg = JSON.parse(readFileSync9(candidate, "utf-8"));
13807
- if (pkg.name === "@hyperframes/producer" && typeof pkg.version === "string") {
13808
- cachedProducerVersion = pkg.version;
13809
- return pkg.version;
13810
- }
13811
- } catch {
13997
+
13998
+ // ../producer/src/services/render/runtimeEnvSnapshot.ts
13999
+ var RUNTIME_ENV_PREFIXES = [
14000
+ "PRODUCER_RUNTIME_",
14001
+ "PRODUCER_RENDER_"
14002
+ ];
14003
+ function snapshotRuntimeEnv(env = process.env) {
14004
+ const snapshot = {};
14005
+ for (const key of Object.keys(env)) {
14006
+ const matches = RUNTIME_ENV_PREFIXES.some((prefix) => key.startsWith(prefix));
14007
+ if (!matches) continue;
14008
+ const value = env[key];
14009
+ if (typeof value !== "string") continue;
14010
+ snapshot[key] = value;
14011
+ }
14012
+ return snapshot;
14013
+ }
14014
+ function applyRuntimeEnvSnapshot(snapshot, env = process.env) {
14015
+ const previous = {};
14016
+ for (const [key, value] of Object.entries(snapshot)) {
14017
+ if (typeof value !== "string") continue;
14018
+ const matches = RUNTIME_ENV_PREFIXES.some((prefix) => key.startsWith(prefix));
14019
+ if (!matches) continue;
14020
+ previous[key] = env[key];
14021
+ env[key] = value;
14022
+ }
14023
+ return {
14024
+ restore: () => {
14025
+ for (const [key, value] of Object.entries(previous)) {
14026
+ if (value === void 0) delete env[key];
14027
+ else env[key] = value;
13812
14028
  }
13813
14029
  }
13814
- const parent = dirname10(current);
13815
- if (parent === current) break;
13816
- current = parent;
13817
- }
13818
- cachedProducerVersion = "0.0.0-unknown";
13819
- return cachedProducerVersion;
14030
+ };
13820
14031
  }
13821
14032
 
13822
14033
  // ../producer/src/services/distributed/planSize.ts
13823
14034
  import { createHash as createHash6 } from "crypto";
13824
- import { lstatSync as lstatSync2, readdirSync as readdirSync7 } from "fs";
13825
- import { extname as extname4, join as join28, relative as relative4 } from "path";
14035
+ import { lstatSync as lstatSync2, readdirSync as readdirSync8 } from "fs";
14036
+ import { extname as extname5, join as join30, relative as relative4 } from "path";
13826
14037
  var MAX_TOP_COMPONENTS = 10;
13827
14038
  var SOURCE_MEDIA_EXTENSIONS = /* @__PURE__ */ new Set([
13828
14039
  ".aac",
@@ -13882,7 +14093,7 @@ function emptyBreakdown() {
13882
14093
  function addCategoryBytes(result, category, filePath, sizeBytes) {
13883
14094
  if (category === "compiled") {
13884
14095
  result.compiledBytes += sizeBytes;
13885
- if (SOURCE_MEDIA_EXTENSIONS.has(extname4(filePath).toLowerCase())) {
14096
+ if (SOURCE_MEDIA_EXTENSIONS.has(extname5(filePath).toLowerCase())) {
13886
14097
  result.sourceMediaBytes += sizeBytes;
13887
14098
  }
13888
14099
  return;
@@ -13929,12 +14140,12 @@ function recordFile(result, components, rootDir, rootKind, filePath) {
13929
14140
  function walkRegularFiles(dir, visit) {
13930
14141
  let entries;
13931
14142
  try {
13932
- entries = readdirSync7(dir, { withFileTypes: true });
14143
+ entries = readdirSync8(dir, { withFileTypes: true });
13933
14144
  } catch {
13934
14145
  return;
13935
14146
  }
13936
14147
  for (const entry of entries) {
13937
- const full = join28(dir, entry.name);
14148
+ const full = join30(dir, entry.name);
13938
14149
  if (entry.isDirectory()) {
13939
14150
  walkRegularFiles(full, visit);
13940
14151
  } else if (entry.isFile()) {
@@ -13995,8 +14206,17 @@ var MAX_PARALLEL_CHUNKS_CEILING = 256;
13995
14206
  var ESTIMATED_SECONDS_PER_MEGAPIXEL_FRAME = 0.11;
13996
14207
  var ESTIMATED_CHUNK_OVERHEAD_SECONDS = 60;
13997
14208
  var WORKER_TIMEOUT_MARGIN = 0.75;
13998
- function estimateChunkSeconds(frames, megapixels) {
13999
- return ESTIMATED_CHUNK_OVERHEAD_SECONDS + frames * ESTIMATED_SECONDS_PER_MEGAPIXEL_FRAME * megapixels;
14209
+ function estimateFrameSeconds(megapixels, measuredSecondsPerFrame = 0) {
14210
+ return Math.max(ESTIMATED_SECONDS_PER_MEGAPIXEL_FRAME * megapixels, measuredSecondsPerFrame);
14211
+ }
14212
+ function estimateChunkSeconds(frames, megapixels, measuredSecondsPerFrame) {
14213
+ return ESTIMATED_CHUNK_OVERHEAD_SECONDS + frames * estimateFrameSeconds(megapixels, measuredSecondsPerFrame);
14214
+ }
14215
+ function planNeedsFrameCostMeasurement(html) {
14216
+ return new RegExp(`\\b${HF_COLOR_GRADING_ATTR}=`, "i").test(html);
14217
+ }
14218
+ function chunkSizingUsesFrameCost(config) {
14219
+ return config.maxParallelChunks === void 0 && config.chunkSize === void 0 && config.targetChunkFrames === void 0 && (config.workerTimeoutSeconds !== void 0 || config.workerConcurrency !== void 0);
14000
14220
  }
14001
14221
  var CHUNK_EXCEEDS_WORKER_TIMEOUT = "CHUNK_EXCEEDS_WORKER_TIMEOUT";
14002
14222
  var ChunkExceedsWorkerTimeoutError = class extends Error {
@@ -14009,18 +14229,15 @@ var ChunkExceedsWorkerTimeoutError = class extends Error {
14009
14229
  this.name = "ChunkExceedsWorkerTimeoutError";
14010
14230
  }
14011
14231
  };
14012
- function resolveMaxParallelChunks(config, totalFrames, megapixels, log = defaultLogger) {
14232
+ function resolveMaxParallelChunks(config, totalFrames, megapixels, log = defaultLogger, measuredSecondsPerFrame) {
14013
14233
  if (config.maxParallelChunks !== void 0) return config.maxParallelChunks;
14014
14234
  const { workerTimeoutSeconds, workerConcurrency } = config;
14015
14235
  const wave = Math.min(
14016
14236
  workerConcurrency ?? DEFAULT_MAX_PARALLEL_CHUNKS,
14017
14237
  MAX_PARALLEL_CHUNKS_CEILING
14018
14238
  );
14019
- const pinsChunking = config.chunkSize !== void 0 || config.targetChunkFrames !== void 0;
14020
- if (pinsChunking || workerTimeoutSeconds === void 0 && workerConcurrency === void 0) {
14021
- return wave;
14022
- }
14023
- const secondsPerFrame = ESTIMATED_SECONDS_PER_MEGAPIXEL_FRAME * megapixels;
14239
+ if (!chunkSizingUsesFrameCost(config)) return wave;
14240
+ const secondsPerFrame = estimateFrameSeconds(megapixels, measuredSecondsPerFrame);
14024
14241
  const minFramesPerChunk = Math.ceil(ESTIMATED_CHUNK_OVERHEAD_SECONDS / secondsPerFrame);
14025
14242
  const wide = Math.max(1, Math.min(wave, Math.floor(totalFrames / minFramesPerChunk)));
14026
14243
  if (workerTimeoutSeconds === void 0) return wide;
@@ -14031,7 +14248,7 @@ function resolveMaxParallelChunks(config, totalFrames, megapixels, log = default
14031
14248
  if (needed > MAX_PARALLEL_CHUNKS_CEILING) {
14032
14249
  const framesAtCeiling = Math.ceil(totalFrames / MAX_PARALLEL_CHUNKS_CEILING);
14033
14250
  throw new ChunkExceedsWorkerTimeoutError(
14034
- Math.ceil(estimateChunkSeconds(framesAtCeiling, megapixels)),
14251
+ Math.ceil(estimateChunkSeconds(framesAtCeiling, megapixels, measuredSecondsPerFrame)),
14035
14252
  workerTimeoutSeconds
14036
14253
  );
14037
14254
  }
@@ -14138,13 +14355,13 @@ function assertPositiveInteger2(name, value) {
14138
14355
  }
14139
14356
  var FREEZE_OWNED_PLAN_FILES = [
14140
14357
  "plan.json",
14141
- join29("meta", "composition.json"),
14142
- join29("meta", "encoder.json"),
14143
- join29("meta", "chunks.json")
14358
+ join31("meta", "composition.json"),
14359
+ join31("meta", "encoder.json"),
14360
+ join31("meta", "chunks.json")
14144
14361
  ];
14145
14362
  function removeFreezeOwnedPlanFiles(planDir) {
14146
14363
  for (const relativePath of FREEZE_OWNED_PLAN_FILES) {
14147
- rmSync9(join29(planDir, relativePath), { force: true });
14364
+ rmSync9(join31(planDir, relativePath), { force: true });
14148
14365
  }
14149
14366
  }
14150
14367
  function buildChunkSlices(totalFrames, chunkCount, effectiveChunkSize) {
@@ -14275,19 +14492,19 @@ function createVideoSourceShipper(input) {
14275
14492
  const known = shipped.get(srcPath);
14276
14493
  if (known) return known;
14277
14494
  const stagedRel = pathInside(input.workVideoFramesDir, srcPath);
14278
- const current = stagedRel === null ? srcPath : join29(input.planDir, "video-frames", stagedRel);
14495
+ const current = stagedRel === null ? srcPath : join31(input.planDir, "video-frames", stagedRel);
14279
14496
  let result = null;
14280
14497
  for (const root of stagedRel === null ? [input.workCompiledDir, input.projectDir] : []) {
14281
14498
  const rel = pathInside(root, srcPath);
14282
- if (rel !== null && existsSync18(join29(input.planDir, "compiled", rel))) {
14499
+ if (rel !== null && existsSync19(join31(input.planDir, "compiled", rel))) {
14283
14500
  result = ["compiled", ...rel.split(sep2)].join("/");
14284
14501
  break;
14285
14502
  }
14286
14503
  }
14287
14504
  if (result === null) {
14288
- result = `video-sources/${shipped.size}${extname5(current)}`;
14289
- const destination = join29(input.planDir, ...result.split("/"));
14290
- mkdirSync10(join29(input.planDir, "video-sources"), { recursive: true });
14505
+ result = `video-sources/${shipped.size}${extname6(current)}`;
14506
+ const destination = join31(input.planDir, ...result.split("/"));
14507
+ mkdirSync10(join31(input.planDir, "video-sources"), { recursive: true });
14291
14508
  try {
14292
14509
  linkSync(current, destination);
14293
14510
  } catch {
@@ -14305,7 +14522,7 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
14305
14522
  useGpu: false,
14306
14523
  browserGpuMode: "software"
14307
14524
  });
14308
- if (!existsSync18(planDir)) mkdirSync10(planDir, { recursive: true });
14525
+ if (!existsSync19(planDir)) mkdirSync10(planDir, { recursive: true });
14309
14526
  const log = config.logger ?? defaultLogger;
14310
14527
  const sizeLimitBytes = options.executionPlanSizeLimitBytes ?? config.planDirSizeLimitBytes ?? PLAN_DIR_SIZE_LIMIT_BYTES;
14311
14528
  const abortSignal = config.abortSignal;
@@ -14334,14 +14551,14 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
14334
14551
  variables: config.variables
14335
14552
  });
14336
14553
  const entryFile = config.entryFile ?? "index.html";
14337
- const htmlPath = join29(projectDir, entryFile);
14338
- if (!existsSync18(htmlPath)) {
14554
+ const htmlPath = join31(projectDir, entryFile);
14555
+ if (!existsSync19(htmlPath)) {
14339
14556
  throw new Error(`[plan] entry file not found: ${htmlPath}`);
14340
14557
  }
14341
- const workDir = join29(planDir, ".plan-work");
14558
+ const workDir = join31(planDir, ".plan-work");
14342
14559
  rmSync9(workDir, { recursive: true, force: true });
14343
14560
  mkdirSync10(workDir, { recursive: true });
14344
- const compiledDir = join29(workDir, "compiled");
14561
+ const compiledDir = join31(workDir, "compiled");
14345
14562
  mkdirSync10(compiledDir, { recursive: true });
14346
14563
  let stageStarted = Date.now();
14347
14564
  cpSync2(projectDir, compiledDir, {
@@ -14355,7 +14572,7 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
14355
14572
  }
14356
14573
  });
14357
14574
  logPlanStage(log, "copy", stageStarted);
14358
- const finalCompiledDir = join29(planDir, "compiled");
14575
+ const finalCompiledDir = join31(planDir, "compiled");
14359
14576
  const needsAlpha = outputNeedsAlpha(config.format);
14360
14577
  stageStarted = Date.now();
14361
14578
  const compileResult = await runCompileStage({
@@ -14418,7 +14635,7 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
14418
14635
  if (probeResult.fileServer) closeFileServerSafely(probeResult.fileServer, "plan", log);
14419
14636
  if (probeResult.probeSession) {
14420
14637
  try {
14421
- const { closeCaptureSession: closeCaptureSession2 } = await import("./src-4COYCAFY.js");
14638
+ const { closeCaptureSession: closeCaptureSession2 } = await import("./src-JGPFLYBL.js");
14422
14639
  await closeCaptureSession2(probeResult.probeSession);
14423
14640
  } catch (err) {
14424
14641
  log.warn("[plan] probe session close failed", {
@@ -14426,19 +14643,9 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
14426
14643
  });
14427
14644
  }
14428
14645
  }
14429
- const maxParallel = resolveMaxParallelChunks(
14430
- config,
14431
- totalFrames,
14432
- compileResult.outputWidth * compileResult.outputHeight / 1e6,
14433
- log
14434
- );
14435
- const { chunkCount, effectiveChunkSize } = resolveChunkPlan(
14436
- totalFrames,
14437
- config.chunkSize,
14438
- maxParallel,
14439
- config.targetChunkFrames
14440
- );
14441
- const chunks = buildChunkSlices(totalFrames, chunkCount, effectiveChunkSize);
14646
+ const megapixels = compileResult.outputWidth * compileResult.outputHeight / 1e6;
14647
+ let maxParallel = resolveMaxParallelChunks(config, totalFrames, megapixels, log);
14648
+ const measureFrameCost = chunkSizingUsesFrameCost(config) && planNeedsFrameCostMeasurement(compiled.html);
14442
14649
  assertPlanSizeWithinLimit({
14443
14650
  rootDir: compiledDir,
14444
14651
  rootKind: "compiled",
@@ -14463,17 +14670,17 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
14463
14670
  if (extractResult.extractionResult) {
14464
14671
  assertVideoExtractionSucceeded(extractResult.extractionResult);
14465
14672
  }
14466
- const stagedVideoFrames = join29(compiledDir, "__hyperframes_video_frames");
14467
- const videoFramesDst = join29(planDir, "video-frames");
14468
- if (existsSync18(videoFramesDst)) rmSync9(videoFramesDst, { recursive: true, force: true });
14469
- const videoSourcesDir = join29(planDir, "video-sources");
14673
+ const stagedVideoFrames = join31(compiledDir, "__hyperframes_video_frames");
14674
+ const videoFramesDst = join31(planDir, "video-frames");
14675
+ if (existsSync19(videoFramesDst)) rmSync9(videoFramesDst, { recursive: true, force: true });
14676
+ const videoSourcesDir = join31(planDir, "video-sources");
14470
14677
  rmSync9(videoSourcesDir, { recursive: true, force: true });
14471
- if (existsSync18(stagedVideoFrames)) {
14678
+ if (existsSync19(stagedVideoFrames)) {
14472
14679
  renameSync4(stagedVideoFrames, videoFramesDst);
14473
14680
  } else {
14474
14681
  mkdirSync10(videoFramesDst, { recursive: true });
14475
14682
  }
14476
- if (existsSync18(finalCompiledDir)) rmSync9(finalCompiledDir, { recursive: true, force: true });
14683
+ if (existsSync19(finalCompiledDir)) rmSync9(finalCompiledDir, { recursive: true, force: true });
14477
14684
  renameSync4(compiledDir, finalCompiledDir);
14478
14685
  const shipVideoSource = createVideoSourceShipper({
14479
14686
  planDir,
@@ -14499,9 +14706,9 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
14499
14706
  ...ext.deferredRange ? { deferredRange: { ...ext.deferredRange, sourcePath: shipVideoSource(ext.srcPath) } } : {}
14500
14707
  }))
14501
14708
  });
14502
- mkdirSync10(join29(planDir, "meta"), { recursive: true });
14709
+ mkdirSync10(join31(planDir, "meta"), { recursive: true });
14503
14710
  writeFileSync7(
14504
- join29(planDir, PLAN_VIDEOS_META_RELATIVE_PATH),
14711
+ join31(planDir, PLAN_VIDEOS_META_RELATIVE_PATH),
14505
14712
  JSON.stringify(planVideosJson, null, 2),
14506
14713
  "utf-8"
14507
14714
  );
@@ -14509,7 +14716,7 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
14509
14716
  const chunksCarryAudio2 = carriesChunkAudio(config, options);
14510
14717
  const audioSeconds = chunksCarryAudio2 ? totalFrames / config.fps : probeResult.duration;
14511
14718
  stageStarted = Date.now();
14512
- const [audioOutcome, overlapOutcome] = await Promise.allSettled([
14719
+ const [audioOutcome, overlapOutcome, frameCostOutcome] = await Promise.allSettled([
14513
14720
  runAudioStage({
14514
14721
  projectDir,
14515
14722
  workDir,
@@ -14532,19 +14739,53 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
14532
14739
  await options.whileAudioMixes();
14533
14740
  logPlanStage(log, "prepublish", prepublishStarted);
14534
14741
  }
14535
- })()
14742
+ })(),
14743
+ measureFrameCost ? measurePlanSecondsPerFrame({
14744
+ planDir,
14745
+ workDir,
14746
+ html: compiled.html,
14747
+ planVideos: planVideosJson,
14748
+ totalFrames,
14749
+ fps: config.fps,
14750
+ width,
14751
+ height,
14752
+ format: config.format,
14753
+ quality: config.quality ?? "standard",
14754
+ deviceScaleFactor,
14755
+ variables: config.variables,
14756
+ cfg: { ...cfg, forceScreenshot, forceScreenshotExplicitlyOptedOut: !forceScreenshot },
14757
+ log
14758
+ }).finally(() => logPlanStage(log, "frame-cost", stageStarted)) : void 0
14536
14759
  ]);
14537
14760
  if (audioOutcome.status === "rejected") throw audioOutcome.reason;
14538
14761
  if (overlapOutcome.status === "rejected") throw overlapOutcome.reason;
14762
+ if (frameCostOutcome.status === "rejected") throw frameCostOutcome.reason;
14763
+ if (frameCostOutcome.value !== void 0) {
14764
+ log.info("[plan] measured frame cost", { secondsPerFrame: frameCostOutcome.value });
14765
+ maxParallel = resolveMaxParallelChunks(
14766
+ config,
14767
+ totalFrames,
14768
+ megapixels,
14769
+ log,
14770
+ frameCostOutcome.value
14771
+ );
14772
+ }
14773
+ const { chunkCount, effectiveChunkSize } = resolveChunkPlan(
14774
+ totalFrames,
14775
+ config.chunkSize,
14776
+ maxParallel,
14777
+ config.targetChunkFrames
14778
+ );
14779
+ const chunks = buildChunkSlices(totalFrames, chunkCount, effectiveChunkSize);
14539
14780
  const audioResult = audioOutcome.value;
14540
14781
  if (audioResult.audioError) {
14541
14782
  applyDistributedAudioWarningPolicy(job, audioResult.audioError, audioResult.audioFailures, log);
14542
14783
  }
14543
- const planAudioPath = join29(planDir, PLAN_AUDIO_RELATIVE_PATH);
14544
- if (audioResult.hasAudio && existsSync18(audioResult.audioOutputPath)) {
14784
+ const planAudioPath = join31(planDir, PLAN_AUDIO_RELATIVE_PATH);
14785
+ if (audioResult.hasAudio && existsSync19(audioResult.audioOutputPath)) {
14545
14786
  renameSync4(audioResult.audioOutputPath, planAudioPath);
14546
14787
  }
14547
- if (chunksCarryAudio2 && existsSync18(planAudioPath)) {
14788
+ if (chunksCarryAudio2 && existsSync19(planAudioPath)) {
14548
14789
  stageStarted = Date.now();
14549
14790
  const peak = await constrainAacTruePeak({
14550
14791
  audioPath: planAudioPath,
@@ -15129,7 +15370,7 @@ function renderRequestFromDistributedConfig(input) {
15129
15370
 
15130
15371
  // ../producer/src/server.ts
15131
15372
  import {
15132
- existsSync as existsSync21,
15373
+ existsSync as existsSync22,
15133
15374
  mkdirSync as mkdirSync11,
15134
15375
  statSync as statSync7,
15135
15376
  mkdtempSync as mkdtempSync4,
@@ -15137,7 +15378,7 @@ import {
15137
15378
  rmSync as rmSync10,
15138
15379
  createReadStream as createReadStream4
15139
15380
  } from "fs";
15140
- import { resolve as resolve9, dirname as dirname12, join as join32 } from "path";
15381
+ import { resolve as resolve9, dirname as dirname12, join as join34 } from "path";
15141
15382
  import { tmpdir as tmpdir3 } from "os";
15142
15383
  import { parseArgs } from "util";
15143
15384
  import crypto from "crypto";
@@ -15147,7 +15388,7 @@ import { serve as serve2 } from "@hono/node-server";
15147
15388
 
15148
15389
  // ../producer/src/services/hyperframeLint.ts
15149
15390
  import {
15150
- existsSync as existsSync19,
15391
+ existsSync as existsSync20,
15151
15392
  readFileSync as readFileSync10,
15152
15393
  statSync as statSync6,
15153
15394
  openSync as openSync5,
@@ -15155,7 +15396,7 @@ import {
15155
15396
  closeSync as closeSync6,
15156
15397
  constants as constants3
15157
15398
  } from "fs";
15158
- import { resolve as resolve8, join as join30, relative as relative6, isAbsolute as isAbsolute3, sep as sep3 } from "path";
15399
+ import { resolve as resolve8, join as join32, relative as relative6, isAbsolute as isAbsolute3, sep as sep3 } from "path";
15159
15400
  function isStringRecord(value) {
15160
15401
  if (!value || typeof value !== "object" || Array.isArray(value)) {
15161
15402
  return false;
@@ -15200,7 +15441,7 @@ function readEntryHtml(filePath) {
15200
15441
  }
15201
15442
  function readProjectEntryFile(projectDir, preferredEntryFile) {
15202
15443
  const absProjectDir = resolve8(projectDir);
15203
- if (!existsSync19(absProjectDir) || !statSync6(absProjectDir).isDirectory()) {
15444
+ if (!existsSync20(absProjectDir) || !statSync6(absProjectDir).isDirectory()) {
15204
15445
  return { error: `Project directory not found: ${absProjectDir}` };
15205
15446
  }
15206
15447
  const entryCandidates = [preferredEntryFile, "index.html", "src/index.html"].filter(
@@ -15222,7 +15463,7 @@ function readProjectEntryFile(projectDir, preferredEntryFile) {
15222
15463
  }
15223
15464
  }
15224
15465
  return {
15225
- error: `No HTML entry file found in project directory: ${join30(absProjectDir, preferredEntryFile || "index.html")}`
15466
+ error: `No HTML entry file found in project directory: ${join32(absProjectDir, preferredEntryFile || "index.html")}`
15226
15467
  };
15227
15468
  }
15228
15469
  function prepareHyperframeLintBody(body) {
@@ -15268,8 +15509,8 @@ async function runHyperframeLint(prepared) {
15268
15509
  // ../producer/src/services/healthWorker.ts
15269
15510
  import { Worker as Worker2 } from "worker_threads";
15270
15511
  import { fileURLToPath as fileURLToPath6 } from "url";
15271
- import { dirname as dirname11, join as join31 } from "path";
15272
- import { existsSync as existsSync20 } from "fs";
15512
+ import { dirname as dirname11, join as join33 } from "path";
15513
+ import { existsSync as existsSync21 } from "fs";
15273
15514
  var DEFAULT_HEALTH_PORT = 9848;
15274
15515
  async function startHealthWorker(options = {}) {
15275
15516
  const log = options.logger ?? defaultLogger2();
@@ -15344,9 +15585,9 @@ function defaultLogger2() {
15344
15585
  }
15345
15586
  function resolveWorkerEntry2() {
15346
15587
  const here = dirname11(fileURLToPath6(import.meta.url));
15347
- const candidates = [join31(here, "healthWorkerThread.js"), join31(here, "healthWorkerThread.ts")];
15588
+ const candidates = [join33(here, "healthWorkerThread.js"), join33(here, "healthWorkerThread.ts")];
15348
15589
  for (const candidate of candidates) {
15349
- if (existsSync20(candidate)) return candidate;
15590
+ if (existsSync21(candidate)) return candidate;
15350
15591
  }
15351
15592
  return void 0;
15352
15593
  }
@@ -15485,7 +15726,7 @@ function resolvePreparedRenderOutput(prepared, rendersDir, log) {
15485
15726
  const { input, cleanupProjectDir } = prepared;
15486
15727
  const absoluteOutputPath = resolveOutputPath(input.projectDir, input.outputPath, rendersDir, log);
15487
15728
  const outputDir = dirname12(absoluteOutputPath);
15488
- if (!existsSync21(outputDir)) mkdirSync11(outputDir, { recursive: true });
15729
+ if (!existsSync22(outputDir)) mkdirSync11(outputDir, { recursive: true });
15489
15730
  return { input, cleanupProjectDir, absoluteOutputPath };
15490
15731
  }
15491
15732
  function validateRenderOverrides(body) {
@@ -15525,11 +15766,11 @@ function prepareProjectDirectory(projectDir, options) {
15525
15766
  const candidate = nonEmptyString(projectDir);
15526
15767
  if (!candidate) return null;
15527
15768
  const absProjectDir = resolve9(candidate);
15528
- if (!existsSync21(absProjectDir) || !statSync7(absProjectDir).isDirectory()) {
15769
+ if (!existsSync22(absProjectDir) || !statSync7(absProjectDir).isDirectory()) {
15529
15770
  return { error: `Project directory not found: ${absProjectDir}` };
15530
15771
  }
15531
15772
  const entry = options.entryFile || "index.html";
15532
- if (!existsSync21(resolve9(absProjectDir, entry))) {
15773
+ if (!existsSync22(resolve9(absProjectDir, entry))) {
15533
15774
  return { error: `Entry file "${entry}" not found in project directory: ${absProjectDir}` };
15534
15775
  }
15535
15776
  return { prepared: { input: { projectDir: absProjectDir, ...options } } };
@@ -15554,8 +15795,8 @@ async function resolveInlineRenderHtml(body) {
15554
15795
  }
15555
15796
  function materializeInlineProject(html, options) {
15556
15797
  const tempRoot = process.env.PRODUCER_TMP_PROJECT_DIR || tmpdir3();
15557
- const tempProjectDir = mkdtempSync4(join32(tempRoot, "producer-project-"));
15558
- writeFileSync8(join32(tempProjectDir, "index.html"), html, "utf-8");
15798
+ const tempProjectDir = mkdtempSync4(join34(tempRoot, "producer-project-"));
15799
+ writeFileSync8(join34(tempProjectDir, "index.html"), html, "utf-8");
15559
15800
  return {
15560
15801
  prepared: {
15561
15802
  input: { projectDir: tempProjectDir, ...options },
@@ -15621,7 +15862,7 @@ function cleanupTempDir(dir, log) {
15621
15862
  }
15622
15863
  }
15623
15864
  function outputFileSize(path) {
15624
- return existsSync21(path) ? statSync7(path).size : 0;
15865
+ return existsSync22(path) ? statSync7(path).size : 0;
15625
15866
  }
15626
15867
  function createBlockingProgressReporter(log, requestId) {
15627
15868
  let lastLoggedPct = -10;
@@ -15917,7 +16158,7 @@ function createRenderHandlers(options = {}) {
15917
16158
  if (!artifact) {
15918
16159
  return c.json({ success: false, error: "Output artifact not found or expired" }, 404);
15919
16160
  }
15920
- if (!existsSync21(artifact.path)) {
16161
+ if (!existsSync22(artifact.path)) {
15921
16162
  store.delete(token);
15922
16163
  return c.json({ success: false, error: "Output artifact file missing" }, 404);
15923
16164
  }
@@ -15964,7 +16205,7 @@ function startServer(options = {}) {
15964
16205
  });
15965
16206
  async function shutdown(signal) {
15966
16207
  log.info(`Received ${signal}, shutting down`);
15967
- const { drainBrowserPool } = await import("./src-4COYCAFY.js");
16208
+ const { drainBrowserPool } = await import("./src-JGPFLYBL.js");
15968
16209
  await drainBrowserPool().catch(() => {
15969
16210
  });
15970
16211
  const handle = await Promise.race([
@@ -16002,24 +16243,24 @@ if (isPublicServerEntry) {
16002
16243
  // ../producer/src/services/distributed/planV2.ts
16003
16244
  import {
16004
16245
  copyFileSync as copyFileSync6,
16005
- existsSync as existsSync23,
16246
+ existsSync as existsSync24,
16006
16247
  lstatSync as lstatSync3,
16007
16248
  mkdirSync as mkdirSync13,
16008
16249
  mkdtempSync as mkdtempSync6,
16009
16250
  readFileSync as readFileSync11,
16010
- readdirSync as readdirSync8,
16251
+ readdirSync as readdirSync9,
16011
16252
  renameSync as renameSync6,
16012
16253
  rmSync as rmSync12,
16013
16254
  statSync as statSync9,
16014
16255
  writeFileSync as writeFileSync10
16015
16256
  } from "fs";
16016
16257
  import { tmpdir as tmpdir4 } from "os";
16017
- import { dirname as dirname14, isAbsolute as isAbsolute4, join as join35, relative as relative7, resolve as resolve10, sep as sep4 } from "path";
16258
+ import { dirname as dirname14, isAbsolute as isAbsolute4, join as join37, relative as relative7, resolve as resolve10, sep as sep4 } from "path";
16018
16259
 
16019
16260
  // ../producer/src/services/distributed/planV2Publisher.ts
16020
16261
  import {
16021
16262
  copyFileSync as copyFileSync5,
16022
- existsSync as existsSync22,
16263
+ existsSync as existsSync23,
16023
16264
  linkSync as linkSync2,
16024
16265
  mkdirSync as mkdirSync12,
16025
16266
  mkdtempSync as mkdtempSync5,
@@ -16028,7 +16269,7 @@ import {
16028
16269
  statSync as statSync8,
16029
16270
  writeFileSync as writeFileSync9
16030
16271
  } from "fs";
16031
- import { dirname as dirname13, join as join34 } from "path";
16272
+ import { dirname as dirname13, join as join36 } from "path";
16032
16273
 
16033
16274
  // ../producer/src/services/distributed/planV2Errors.ts
16034
16275
  var PLAN_V2_INTEGRITY_UNRECOVERABLE = "PLAN_V2_INTEGRITY_UNRECOVERABLE";
@@ -16043,7 +16284,7 @@ var PlanV2IntegrityError = class extends Error {
16043
16284
  };
16044
16285
 
16045
16286
  // ../producer/src/services/distributed/planV2Layout.ts
16046
- import { join as join33 } from "path";
16287
+ import { join as join35 } from "path";
16047
16288
  var SHA256_PATTERN = /^[0-9a-f]{64}$/;
16048
16289
  function assertPlanV2Sha256(value, field) {
16049
16290
  if (typeof value !== "string" || !SHA256_PATTERN.test(value)) {
@@ -16053,7 +16294,7 @@ function assertPlanV2Sha256(value, field) {
16053
16294
  }
16054
16295
  function planV2BlobPath(root, sha256) {
16055
16296
  const digest = assertPlanV2Sha256(sha256, "artifact sha256");
16056
- return join33(root, "artifacts", "sha256", digest.slice(0, 2), digest);
16297
+ return join35(root, "artifacts", "sha256", digest.slice(0, 2), digest);
16057
16298
  }
16058
16299
 
16059
16300
  // ../producer/src/services/distributed/planV2Publisher.ts
@@ -16088,13 +16329,13 @@ var LocalPlanV2ArtifactPublisher = class {
16088
16329
  #linkFile;
16089
16330
  #committed = false;
16090
16331
  constructor(destinationDir, options = {}) {
16091
- if (existsSync22(destinationDir)) {
16332
+ if (existsSync23(destinationDir)) {
16092
16333
  throw new PlanV2IntegrityError(`output directory already exists: ${destinationDir}`);
16093
16334
  }
16094
16335
  this.destinationDir = destinationDir;
16095
16336
  this.#linkFile = options.linkFile ?? linkSync2;
16096
16337
  mkdirSync12(dirname13(destinationDir), { recursive: true });
16097
- this.temporaryDir = mkdtempSync5(join34(dirname13(destinationDir), ".plan-v2-publish-"));
16338
+ this.temporaryDir = mkdtempSync5(join36(dirname13(destinationDir), ".plan-v2-publish-"));
16098
16339
  }
16099
16340
  async putBlob(blob) {
16100
16341
  const digest = assertPlanV2Sha256(blob.sha256, "published blob sha256");
@@ -16105,10 +16346,10 @@ var LocalPlanV2ArtifactPublisher = class {
16105
16346
  );
16106
16347
  }
16107
16348
  const destinationPath = planV2BlobPath(this.temporaryDir, digest);
16108
- if (existsSync22(destinationPath)) return;
16349
+ if (existsSync23(destinationPath)) return;
16109
16350
  mkdirSync12(dirname13(destinationPath), { recursive: true });
16110
- const stagingDir = mkdtempSync5(join34(dirname13(destinationPath), ".plan-v2-blob-"));
16111
- const temporaryPath = join34(stagingDir, "blob");
16351
+ const stagingDir = mkdtempSync5(join36(dirname13(destinationPath), ".plan-v2-blob-"));
16352
+ const temporaryPath = join36(stagingDir, "blob");
16112
16353
  try {
16113
16354
  try {
16114
16355
  this.#linkFile(blob.sourcePath, temporaryPath);
@@ -16123,13 +16364,13 @@ var LocalPlanV2ArtifactPublisher = class {
16123
16364
  }
16124
16365
  async commitManifest(manifestBytes) {
16125
16366
  for (const digest of new Set(manifestDigests(manifestBytes))) {
16126
- if (!existsSync22(planV2BlobPath(this.temporaryDir, digest))) {
16367
+ if (!existsSync23(planV2BlobPath(this.temporaryDir, digest))) {
16127
16368
  throw new PlanV2IntegrityError(
16128
16369
  `cannot commit manifest before referenced blob is durable: ${digest}`
16129
16370
  );
16130
16371
  }
16131
16372
  }
16132
- writeFileSync9(join34(this.temporaryDir, "plan.json"), manifestBytes, "utf-8");
16373
+ writeFileSync9(join36(this.temporaryDir, "plan.json"), manifestBytes, "utf-8");
16133
16374
  renameSync5(this.temporaryDir, this.destinationDir);
16134
16375
  this.#committed = true;
16135
16376
  }
@@ -16173,8 +16414,8 @@ function listFiles(root) {
16173
16414
  const files = [];
16174
16415
  const rootResolved = resolve10(root);
16175
16416
  function walk(dir) {
16176
- for (const entry of readdirSync8(dir, { withFileTypes: true })) {
16177
- const absolutePath = join35(dir, entry.name);
16417
+ for (const entry of readdirSync9(dir, { withFileTypes: true })) {
16418
+ const absolutePath = join37(dir, entry.name);
16178
16419
  if (entry.isDirectory()) {
16179
16420
  walk(absolutePath);
16180
16421
  } else if (entry.isFile()) {
@@ -16201,13 +16442,13 @@ function assertValidExtractionCacheCompleteSentinel(path) {
16201
16442
  }
16202
16443
  }
16203
16444
  function validateExtractionCacheCompleteSentinels(executionPlanDir) {
16204
- const videoRoot = join35(executionPlanDir, "video-frames");
16205
- if (!existsSync23(videoRoot)) return;
16206
- for (const videoEntry of readdirSync8(videoRoot, { withFileTypes: true })) {
16445
+ const videoRoot = join37(executionPlanDir, "video-frames");
16446
+ if (!existsSync24(videoRoot)) return;
16447
+ for (const videoEntry of readdirSync9(videoRoot, { withFileTypes: true })) {
16207
16448
  if (!videoEntry.isDirectory()) continue;
16208
- const videoDir = join35(videoRoot, videoEntry.name);
16209
- if (readdirSync8(videoDir).includes(EXTRACTION_CACHE_COMPLETE_SENTINEL)) {
16210
- const sentinelPath = join35(videoDir, EXTRACTION_CACHE_COMPLETE_SENTINEL);
16449
+ const videoDir = join37(videoRoot, videoEntry.name);
16450
+ if (readdirSync9(videoDir).includes(EXTRACTION_CACHE_COMPLETE_SENTINEL)) {
16451
+ const sentinelPath = join37(videoDir, EXTRACTION_CACHE_COMPLETE_SENTINEL);
16211
16452
  assertValidExtractionCacheCompleteSentinel(sentinelPath);
16212
16453
  }
16213
16454
  }
@@ -16248,15 +16489,15 @@ function listVideoFramePaths(executionPlanDir, videos) {
16248
16489
  if (video.deferredRange) {
16249
16490
  const framePaths2 = /* @__PURE__ */ new Map();
16250
16491
  for (let index = 0; index < video.totalFrames; index++) {
16251
- framePaths2.set(index, join35(outputDir, String(index)));
16492
+ framePaths2.set(index, join37(outputDir, String(index)));
16252
16493
  }
16253
16494
  return { ...video, outputDir, framePaths: framePaths2, ownedByLookup: false };
16254
16495
  }
16255
- const frameNames = readdirSync8(outputDir).sort();
16496
+ const frameNames = readdirSync9(outputDir).sort();
16256
16497
  const framePaths = /* @__PURE__ */ new Map();
16257
16498
  for (const frameName of frameNames) {
16258
16499
  if (frameName === EXTRACTION_CACHE_COMPLETE_SENTINEL) {
16259
- assertValidExtractionCacheCompleteSentinel(join35(outputDir, frameName));
16500
+ assertValidExtractionCacheCompleteSentinel(join37(outputDir, frameName));
16260
16501
  continue;
16261
16502
  }
16262
16503
  const match = /(\d+)(?=\.[^.]+$)/.exec(frameName);
@@ -16275,7 +16516,7 @@ function listVideoFramePaths(executionPlanDir, videos) {
16275
16516
  `duplicate extracted frame index ${frameIndex} in ${outputDir}`
16276
16517
  );
16277
16518
  }
16278
- framePaths.set(frameIndex, join35(outputDir, frameName));
16519
+ framePaths.set(frameIndex, join37(outputDir, frameName));
16279
16520
  }
16280
16521
  return {
16281
16522
  ...video,
@@ -16296,8 +16537,8 @@ function parsePlanVideosJson2(value) {
16296
16537
  }
16297
16538
  }
16298
16539
  function materializeExtractedVideoDirectories(planDir) {
16299
- const videosPath = join35(planDir, PLAN_VIDEOS_META_RELATIVE_PATH);
16300
- if (!existsSync23(videosPath)) return;
16540
+ const videosPath = join37(planDir, PLAN_VIDEOS_META_RELATIVE_PATH);
16541
+ if (!existsSync24(videosPath)) return;
16301
16542
  const videos = parsePlanVideosJson2(readJsonFile(videosPath, PLAN_VIDEOS_META_RELATIVE_PATH));
16302
16543
  for (const video of videos.extracted) {
16303
16544
  mkdirSync13(resolveExtractedVideoOutputDir(planDir, video.videoId), { recursive: true });
@@ -16325,13 +16566,13 @@ function parseChunkSlices(value) {
16325
16566
  });
16326
16567
  }
16327
16568
  function buildVideoChunkDependencies(executionPlanDir, dimensions) {
16328
- const videoRoot = join35(executionPlanDir, "video-frames");
16329
- const hasExtractedFrames = existsSync23(videoRoot) && listFiles(videoRoot).length > 0;
16330
- const videosPath = join35(executionPlanDir, PLAN_VIDEOS_META_RELATIVE_PATH);
16331
- if (!existsSync23(videosPath)) {
16569
+ const videoRoot = join37(executionPlanDir, "video-frames");
16570
+ const hasExtractedFrames = existsSync24(videoRoot) && listFiles(videoRoot).length > 0;
16571
+ const videosPath = join37(executionPlanDir, PLAN_VIDEOS_META_RELATIVE_PATH);
16572
+ if (!existsSync24(videosPath)) {
16332
16573
  return hasExtractedFrames ? { mode: "full-source-pack", dependencies: null } : { mode: "exact-rendered-frames", dependencies: /* @__PURE__ */ new Map() };
16333
16574
  }
16334
- const chunksPath = join35(executionPlanDir, "meta", "chunks.json");
16575
+ const chunksPath = join37(executionPlanDir, "meta", "chunks.json");
16335
16576
  const videos = readJsonFile(videosPath, PLAN_VIDEOS_META_RELATIVE_PATH);
16336
16577
  const chunks = readJsonFile(chunksPath, "meta/chunks.json");
16337
16578
  const parsedVideos = parsePlanVideosJson2(videos);
@@ -16385,10 +16626,10 @@ function computeManifestHash(manifest) {
16385
16626
  return sha256Hex(`${PLAN_V2_HASH_PREFIX}${canonicalJsonStringify(manifestPayload(manifest))}`);
16386
16627
  }
16387
16628
  function writeBlob(sourcePath, destinationPath) {
16388
- if (existsSync23(destinationPath)) return;
16629
+ if (existsSync24(destinationPath)) return;
16389
16630
  mkdirSync13(dirname14(destinationPath), { recursive: true });
16390
- const temporaryDir = mkdtempSync6(join35(dirname14(destinationPath), ".plan-v2-blob-"));
16391
- const temporaryPath = join35(temporaryDir, "blob");
16631
+ const temporaryDir = mkdtempSync6(join37(dirname14(destinationPath), ".plan-v2-blob-"));
16632
+ const temporaryPath = join37(temporaryDir, "blob");
16392
16633
  try {
16393
16634
  copyFileSync6(sourcePath, temporaryPath);
16394
16635
  renameSync6(temporaryPath, destinationPath);
@@ -16421,8 +16662,8 @@ async function putBlobs(publisher, blobs) {
16421
16662
  if (failure) throw failure.reason;
16422
16663
  }
16423
16664
  function buildPlanV2Publication(executionPlanDir, digests) {
16424
- const executionPlanPath = join35(executionPlanDir, "plan.json");
16425
- if (!existsSync23(executionPlanPath)) {
16665
+ const executionPlanPath = join37(executionPlanDir, "plan.json");
16666
+ if (!existsSync24(executionPlanPath)) {
16426
16667
  throw new PlanV2IntegrityError(`execution plan is missing plan.json: ${executionPlanPath}`);
16427
16668
  }
16428
16669
  const executionPlanValue = readJsonFile(executionPlanPath, "execution plan.json");
@@ -16449,7 +16690,7 @@ function buildPlanV2Publication(executionPlanDir, digests) {
16449
16690
  }
16450
16691
  validateExtractionCacheCompleteSentinels(executionPlanDir);
16451
16692
  const videoDependencyPlan = buildVideoChunkDependencies(executionPlanDir, dimensions);
16452
- const encoder = readJsonFile(join35(executionPlanDir, "meta", "encoder.json"), "meta/encoder.json");
16693
+ const encoder = readJsonFile(join37(executionPlanDir, "meta", "encoder.json"), "meta/encoder.json");
16453
16694
  const chunksCarryAudio2 = isRecord3(encoder) && encoder.chunksCarryAudio === true;
16454
16695
  for (const file of listFiles(executionPlanDir)) {
16455
16696
  if (isExtractionCacheCompleteSentinelPath(file.path)) continue;
@@ -16491,18 +16732,18 @@ function buildPlanV2Publication(executionPlanDir, digests) {
16491
16732
  };
16492
16733
  }
16493
16734
  function createPlanV2FromExecutionPlan(executionPlanDir, planV2Dir) {
16494
- if (existsSync23(planV2Dir)) {
16735
+ if (existsSync24(planV2Dir)) {
16495
16736
  throw new PlanV2IntegrityError(`output directory already exists: ${planV2Dir}`);
16496
16737
  }
16497
16738
  const publication = buildPlanV2Publication(executionPlanDir);
16498
16739
  mkdirSync13(dirname14(planV2Dir), { recursive: true });
16499
- const tempDir = mkdtempSync6(join35(dirname14(planV2Dir), ".plan-v2-build-"));
16740
+ const tempDir = mkdtempSync6(join37(dirname14(planV2Dir), ".plan-v2-build-"));
16500
16741
  try {
16501
16742
  for (const blob of publication.blobs) {
16502
16743
  writeBlob(blob.sourcePath, planV2BlobPath(tempDir, blob.sha256));
16503
16744
  }
16504
16745
  writeFileSync10(
16505
- join35(tempDir, "plan.json"),
16746
+ join37(tempDir, "plan.json"),
16506
16747
  canonicalJsonStringify(publication.manifest),
16507
16748
  "utf-8"
16508
16749
  );
@@ -16543,7 +16784,7 @@ async function publishPlanV2FromV1(executionPlanDir, publisher) {
16543
16784
  return publishPlanV2FromExecutionPlan(executionPlanDir, publisher);
16544
16785
  }
16545
16786
  async function planV2WithPublisher(projectDir, config, publisher, options = {}) {
16546
- const stagingRoot = mkdtempSync6(join35(options.stagingParentDir ?? tmpdir4(), ".execution-plan-"));
16787
+ const stagingRoot = mkdtempSync6(join37(options.stagingParentDir ?? tmpdir4(), ".execution-plan-"));
16547
16788
  const digests = /* @__PURE__ */ new Map();
16548
16789
  try {
16549
16790
  await buildLocalExecutionPlan(projectDir, config, stagingRoot, {
@@ -16553,7 +16794,7 @@ async function planV2WithPublisher(projectDir, config, publisher, options = {})
16553
16794
  // artifactTargets ships every compiled/ file to all chunks, so each is in the manifest.
16554
16795
  whileAudioMixes: () => putBlobs(
16555
16796
  publisher,
16556
- listFiles(join35(stagingRoot, "compiled")).map(
16797
+ listFiles(join37(stagingRoot, "compiled")).map(
16557
16798
  (file) => publishBlobFor(file.absolutePath, digests)
16558
16799
  )
16559
16800
  ),
@@ -16571,7 +16812,7 @@ async function planV2WithPublisher(projectDir, config, publisher, options = {})
16571
16812
  }
16572
16813
  }
16573
16814
  async function planV2(projectDir, config, planV2Dir) {
16574
- if (existsSync23(planV2Dir)) {
16815
+ if (existsSync24(planV2Dir)) {
16575
16816
  throw new PlanV2IntegrityError(`output directory already exists: ${planV2Dir}`);
16576
16817
  }
16577
16818
  const publisher = new LocalPlanV2ArtifactPublisher(planV2Dir);
@@ -16583,7 +16824,7 @@ async function planV2(projectDir, config, planV2Dir) {
16583
16824
  function resultFromManifest(planV2Dir, manifest) {
16584
16825
  return {
16585
16826
  planDir: planV2Dir,
16586
- manifestPath: join35(planV2Dir, "plan.json"),
16827
+ manifestPath: join37(planV2Dir, "plan.json"),
16587
16828
  planProtocol: PLAN_PROTOCOL_V2,
16588
16829
  planHash: manifest.planHash,
16589
16830
  sourcePlanV1Hash: getPlanV2ExecutionPlanHash(manifest),
@@ -16719,8 +16960,8 @@ function parsePlanV2Manifest(value) {
16719
16960
  return parsed;
16720
16961
  }
16721
16962
  function readPlanV2Manifest(planV2Dir) {
16722
- const manifestPath = join35(planV2Dir, "plan.json");
16723
- if (!existsSync23(manifestPath)) {
16963
+ const manifestPath = join37(planV2Dir, "plan.json");
16964
+ if (!existsSync24(manifestPath)) {
16724
16965
  throw new PlanV2IntegrityError(`missing v2 manifest: ${manifestPath}`);
16725
16966
  }
16726
16967
  const value = readJsonFile(manifestPath, "v2 manifest");
@@ -16739,7 +16980,7 @@ function listPlanV2ArtifactsForTarget(manifest, target) {
16739
16980
  }
16740
16981
  function verifyBlob(planV2Dir, artifact) {
16741
16982
  const sourcePath = planV2BlobPath(planV2Dir, artifact.sha256);
16742
- if (!existsSync23(sourcePath)) {
16983
+ if (!existsSync24(sourcePath)) {
16743
16984
  throw new PlanV2IntegrityError(`missing content-addressed artifact ${artifact.sha256}`);
16744
16985
  }
16745
16986
  const stats = statSync9(sourcePath);
@@ -16752,7 +16993,7 @@ function verifyBlob(planV2Dir, artifact) {
16752
16993
  return sourcePath;
16753
16994
  }
16754
16995
  function materializePlanV2Target(planV2Dir, target, destinationDir) {
16755
- if (existsSync23(destinationDir)) {
16996
+ if (existsSync24(destinationDir)) {
16756
16997
  throw new PlanV2IntegrityError(`materialization destination already exists: ${destinationDir}`);
16757
16998
  }
16758
16999
  const manifest = readPlanV2Manifest(planV2Dir);
@@ -16763,16 +17004,16 @@ function materializePlanV2Target(planV2Dir, target, destinationDir) {
16763
17004
  sourcePath: verifyBlob(planV2Dir, artifact)
16764
17005
  }));
16765
17006
  mkdirSync13(dirname14(destinationDir), { recursive: true });
16766
- const tempDir = mkdtempSync6(join35(dirname14(destinationDir), ".plan-v2-materialize-"));
17007
+ const tempDir = mkdtempSync6(join37(dirname14(destinationDir), ".plan-v2-materialize-"));
16767
17008
  try {
16768
17009
  for (const { artifact, sourcePath } of verified) {
16769
- const destinationPath = join35(tempDir, ...artifact.path.split("/"));
17010
+ const destinationPath = join37(tempDir, ...artifact.path.split("/"));
16770
17011
  mkdirSync13(dirname14(destinationPath), { recursive: true });
16771
17012
  copyFileSync6(sourcePath, destinationPath);
16772
17013
  }
16773
17014
  if (target.role === "chunk") materializeExtractedVideoDirectories(tempDir);
16774
17015
  writeFileSync10(
16775
- join35(tempDir, PLAN_V2_MATERIALIZATION_MARKER),
17016
+ join37(tempDir, PLAN_V2_MATERIALIZATION_MARKER),
16776
17017
  canonicalJsonStringify({ manifest, target }),
16777
17018
  "utf-8"
16778
17019
  );
@@ -16791,12 +17032,12 @@ function materializePlanV2Target(planV2Dir, target, destinationDir) {
16791
17032
  // Join the artifact's OWN name, not the current constant: a plan written
16792
17033
  // before the container change carries the legacy name, and materializing it
16793
17034
  // under the new one would point at a file that was never written.
16794
- audioPath: target.role === "assembler" ? audioArtifact ? join35(destinationDir, audioArtifact.path) : null : null
17035
+ audioPath: target.role === "assembler" ? audioArtifact ? join37(destinationDir, audioArtifact.path) : null : null
16795
17036
  };
16796
17037
  }
16797
17038
  function validatePlanV2MaterializedTarget(planDir, expectedTarget) {
16798
- const markerPath = join35(planDir, PLAN_V2_MATERIALIZATION_MARKER);
16799
- if (!existsSync23(markerPath)) return null;
17039
+ const markerPath = join37(planDir, PLAN_V2_MATERIALIZATION_MARKER);
17040
+ if (!existsSync24(markerPath)) return null;
16800
17041
  const marker = readJsonFile(markerPath, "materialization marker");
16801
17042
  if (!isRecord3(marker) || !isRecord3(marker.manifest) || !isRecord3(marker.target)) {
16802
17043
  throw new PlanV2IntegrityError("malformed materialization marker");
@@ -16810,8 +17051,8 @@ function validatePlanV2MaterializedTarget(planDir, expectedTarget) {
16810
17051
  const manifest = parsePlanV2Manifest(marker.manifest);
16811
17052
  const required = listPlanV2ArtifactsForTarget(manifest, expectedTarget);
16812
17053
  for (const artifact of required) {
16813
- const path = join35(planDir, ...artifact.path.split("/"));
16814
- if (!existsSync23(path) || statSync9(path).size !== artifact.sizeBytes) {
17054
+ const path = join37(planDir, ...artifact.path.split("/"));
17055
+ if (!existsSync24(path) || statSync9(path).size !== artifact.sizeBytes) {
16815
17056
  throw new PlanV2IntegrityError(
16816
17057
  `materialized artifact missing or truncated: ${artifact.path}`
16817
17058
  );
@@ -16826,22 +17067,22 @@ function validatePlanV2MaterializedTarget(planDir, expectedTarget) {
16826
17067
  // ../producer/src/services/distributed/planV2Execution.ts
16827
17068
  import { mkdtempSync as mkdtempSync9, rmSync as rmSync16 } from "fs";
16828
17069
  import { tmpdir as tmpdir5 } from "os";
16829
- import { join as join39 } from "path";
17070
+ import { join as join41 } from "path";
16830
17071
 
16831
17072
  // ../producer/src/services/distributed/assemble.ts
16832
17073
  import {
16833
17074
  cpSync as cpSync3,
16834
- existsSync as existsSync24,
17075
+ existsSync as existsSync25,
16835
17076
  mkdirSync as mkdirSync15,
16836
17077
  mkdtempSync as mkdtempSync8,
16837
17078
  readFileSync as readFileSync12,
16838
- readdirSync as readdirSync9,
17079
+ readdirSync as readdirSync10,
16839
17080
  renameSync as renameSync8,
16840
17081
  rmSync as rmSync14,
16841
17082
  statSync as statSync10,
16842
17083
  writeFileSync as writeFileSync11
16843
17084
  } from "fs";
16844
- import { dirname as dirname16, join as join37 } from "path";
17085
+ import { dirname as dirname16, join as join39 } from "path";
16845
17086
 
16846
17087
  // ../producer/src/services/distributed/mp4Stitch.ts
16847
17088
  import {
@@ -16853,7 +17094,7 @@ import {
16853
17094
  rmSync as rmSync13
16854
17095
  } from "fs";
16855
17096
  import { open } from "fs/promises";
16856
- import { dirname as dirname15, join as join36 } from "path";
17097
+ import { dirname as dirname15, join as join38 } from "path";
16857
17098
  import { finished, pipeline } from "stream/promises";
16858
17099
  function boxAt(src, off, at, limit) {
16859
17100
  let size = src.readUInt32BE(off);
@@ -17175,13 +17416,13 @@ async function writeStitchedMp4(chunkPaths, outputPath) {
17175
17416
  const header = buildStitchedHeader(layouts);
17176
17417
  mkdirSync14(dirname15(outputPath), { recursive: true });
17177
17418
  const workDir = mkdtempSync7(`${outputPath}.stitch-`);
17178
- const out = createWriteStream(join36(workDir, "out.mp4"));
17419
+ const out = createWriteStream(join38(workDir, "out.mp4"));
17179
17420
  try {
17180
17421
  out.write(header);
17181
17422
  for (const [i, path] of chunkPaths.entries()) await copyPayload(path, layouts[i], i, out);
17182
17423
  out.end();
17183
17424
  await finished(out);
17184
- renameSync7(join36(workDir, "out.mp4"), outputPath);
17425
+ renameSync7(join38(workDir, "out.mp4"), outputPath);
17185
17426
  } finally {
17186
17427
  out.destroy();
17187
17428
  rmSync13(workDir, { recursive: true, force: true });
@@ -17215,7 +17456,7 @@ async function assemble(planDir, chunkPaths, audioPath, outputPath, options) {
17215
17456
  }
17216
17457
  const format = plan2.dimensions.format;
17217
17458
  const cfrEncoder = cfr ? readCfrEncoder(planDir, format) : null;
17218
- if (!existsSync24(dirname16(outputPath))) {
17459
+ if (!existsSync25(dirname16(outputPath))) {
17219
17460
  mkdirSync15(dirname16(outputPath), { recursive: true });
17220
17461
  }
17221
17462
  const workDir = mkdtempSync8(`${outputPath}.assemble-work-`);
@@ -17238,8 +17479,8 @@ async function assemble(planDir, chunkPaths, audioPath, outputPath, options) {
17238
17479
  };
17239
17480
  try {
17240
17481
  const videoSources = cfrEncoder ? [await reencodeCfr(ctx, chunkPaths, cfrEncoder)] : chunkPaths;
17241
- const finalPath = join37(workDir, `output.${format}`);
17242
- const audio = audioPath !== null && existsSync24(audioPath) ? audioPath : null;
17482
+ const finalPath = join39(workDir, `output.${format}`);
17483
+ const audio = audioPath !== null && existsSync25(audioPath) ? audioPath : null;
17243
17484
  await writeJoined(ctx, videoSources, audio, finalPath);
17244
17485
  renameSync8(finalPath, outputPath);
17245
17486
  log.info("[assemble] stage timings", {
@@ -17257,7 +17498,7 @@ async function assemble(planDir, chunkPaths, audioPath, outputPath, options) {
17257
17498
  });
17258
17499
  }
17259
17500
  }
17260
- const fileSize = existsSync24(outputPath) ? statSync10(outputPath).size : 0;
17501
+ const fileSize = existsSync25(outputPath) ? statSync10(outputPath).size : 0;
17261
17502
  return {
17262
17503
  outputPath,
17263
17504
  durationMs: Date.now() - start,
@@ -17266,15 +17507,15 @@ async function assemble(planDir, chunkPaths, audioPath, outputPath, options) {
17266
17507
  };
17267
17508
  }
17268
17509
  function readAssemblePlan(planDir, chunkPaths) {
17269
- const planJsonPath = join37(planDir, "plan.json");
17270
- const chunksJsonPath = join37(planDir, "meta", "chunks.json");
17271
- if (!existsSync24(planJsonPath)) {
17510
+ const planJsonPath = join39(planDir, "plan.json");
17511
+ const chunksJsonPath = join39(planDir, "meta", "chunks.json");
17512
+ if (!existsSync25(planJsonPath)) {
17272
17513
  throw new Error(`[assemble] planDir missing plan.json: ${planJsonPath}`);
17273
17514
  }
17274
17515
  const plan2 = JSON.parse(readFileSync12(planJsonPath, "utf-8"));
17275
17516
  readPlanProtocolV1(plan2, DISTRIBUTED_RENDER_CAPABILITIES.roles.assembler);
17276
17517
  validatePlanV2MaterializedTarget(planDir, { role: "assembler" });
17277
- if (!existsSync24(chunksJsonPath)) {
17518
+ if (!existsSync25(chunksJsonPath)) {
17278
17519
  throw new Error(`[assemble] planDir missing meta/chunks.json: ${chunksJsonPath}`);
17279
17520
  }
17280
17521
  const chunks = JSON.parse(readFileSync12(chunksJsonPath, "utf-8"));
@@ -17284,7 +17525,7 @@ function readAssemblePlan(planDir, chunkPaths) {
17284
17525
  );
17285
17526
  }
17286
17527
  for (const path of chunkPaths) {
17287
- if (!existsSync24(path)) {
17528
+ if (!existsSync25(path)) {
17288
17529
  throw new Error(`[assemble] chunk path does not exist: ${path}`);
17289
17530
  }
17290
17531
  }
@@ -17294,7 +17535,7 @@ function videoInputArgs(ctx, videoSources) {
17294
17535
  return videoSources.length === 1 ? ["-i", videoSources[0]] : concatDemuxerInput(videoSources, ctx.workDir, ctx.fpsArg);
17295
17536
  }
17296
17537
  async function reencodeCfr(ctx, chunkPaths, encoder) {
17297
- const cfrOutputPath = join37(ctx.workDir, `cfr.${ctx.format}`);
17538
+ const cfrOutputPath = join39(ctx.workDir, `cfr.${ctx.format}`);
17298
17539
  const cfrArgs = [
17299
17540
  ...videoInputArgs(ctx, chunkPaths),
17300
17541
  "-c:v",
@@ -17332,7 +17573,7 @@ async function writeJoined(ctx, videoSources, audioPath, finalPath) {
17332
17573
  );
17333
17574
  return;
17334
17575
  }
17335
- const paddedAudioPath = join37(ctx.workDir, "audio-padded.m4a");
17576
+ const paddedAudioPath = join39(ctx.workDir, "audio-padded.m4a");
17336
17577
  const padTrimResult = await ctx.timed(
17337
17578
  "audioMs",
17338
17579
  () => padOrTrimAudioToVideoFrameCount({
@@ -17372,7 +17613,7 @@ async function writeJoined(ctx, videoSources, audioPath, finalPath) {
17372
17613
  }
17373
17614
  }
17374
17615
  function concatDemuxerInput(chunkPaths, workDir, fpsArg) {
17375
- const listPath = join37(workDir, "concat-list.txt");
17616
+ const listPath = join39(workDir, "concat-list.txt");
17376
17617
  const body = chunkPaths.map((path) => `file '${path.replace(/'/g, "'\\''")}'`).join("\n");
17377
17618
  writeFileSync11(listPath, `${body}
17378
17619
  `, "utf-8");
@@ -17405,8 +17646,8 @@ function chunksAreStitchedMp4(planDir, cfr) {
17405
17646
  return true;
17406
17647
  }
17407
17648
  function chunksCarryAudio(planDir) {
17408
- const encoderJsonPath = join37(planDir, "meta", "encoder.json");
17409
- if (!existsSync24(encoderJsonPath)) return false;
17649
+ const encoderJsonPath = join39(planDir, "meta", "encoder.json");
17650
+ if (!existsSync25(encoderJsonPath)) return false;
17410
17651
  return JSON.parse(readFileSync12(encoderJsonPath, "utf-8")).chunksCarryAudio === true;
17411
17652
  }
17412
17653
  function readCfrEncoder(planDir, format) {
@@ -17415,8 +17656,8 @@ function readCfrEncoder(planDir, format) {
17415
17656
  `[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.`
17416
17657
  );
17417
17658
  }
17418
- const encoderJsonPath = join37(planDir, "meta", "encoder.json");
17419
- if (!existsSync24(encoderJsonPath)) {
17659
+ const encoderJsonPath = join39(planDir, "meta", "encoder.json");
17660
+ if (!existsSync25(encoderJsonPath)) {
17420
17661
  throw new Error(`[assemble] planDir missing meta/encoder.json: ${encoderJsonPath}`);
17421
17662
  }
17422
17663
  const encoderJson = JSON.parse(readFileSync12(encoderJsonPath, "utf-8"));
@@ -17428,7 +17669,7 @@ function readCfrEncoder(planDir, format) {
17428
17669
  return encoderJson;
17429
17670
  }
17430
17671
  function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, startTimeMs) {
17431
- if (existsSync24(outputPath)) rmSync14(outputPath, { recursive: true, force: true });
17672
+ if (existsSync25(outputPath)) rmSync14(outputPath, { recursive: true, force: true });
17432
17673
  mkdirSync15(outputPath, { recursive: true });
17433
17674
  let globalIdx = 0;
17434
17675
  for (const chunkDir of chunkPaths) {
@@ -17437,13 +17678,13 @@ function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, start
17437
17678
  `[assemble] png-sequence chunk must be a directory: ${chunkDir} (got a file)`
17438
17679
  );
17439
17680
  }
17440
- const frames = readdirSync9(chunkDir).filter((name) => name.endsWith(".png")).sort();
17681
+ const frames = readdirSync10(chunkDir).filter((name) => name.endsWith(".png")).sort();
17441
17682
  if (frames.length === 0) {
17442
17683
  throw new Error(`[assemble] png-sequence chunk has no frames: ${chunkDir}`);
17443
17684
  }
17444
17685
  for (const frame of frames) {
17445
- const dst = join37(outputPath, formatExportFrameName(globalIdx, "png"));
17446
- cpSync3(join37(chunkDir, frame), dst);
17686
+ const dst = join39(outputPath, formatExportFrameName(globalIdx, "png"));
17687
+ cpSync3(join39(chunkDir, frame), dst);
17447
17688
  globalIdx += 1;
17448
17689
  }
17449
17690
  }
@@ -17452,14 +17693,14 @@ function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, start
17452
17693
  `[assemble] png-sequence frame count mismatch: merged ${globalIdx} frames vs plan.totalFrames=${totalFrames}. Using on-disk count.`
17453
17694
  );
17454
17695
  }
17455
- if (audioPath !== null && existsSync24(audioPath)) {
17456
- const sidecar = join37(outputPath, MIXED_AUDIO_FILENAME);
17696
+ if (audioPath !== null && existsSync25(audioPath)) {
17697
+ const sidecar = join39(outputPath, MIXED_AUDIO_FILENAME);
17457
17698
  cpSync3(audioPath, sidecar);
17458
17699
  }
17459
17700
  let fileSize = 0;
17460
- for (const name of readdirSync9(outputPath)) {
17701
+ for (const name of readdirSync10(outputPath)) {
17461
17702
  try {
17462
- fileSize += statSync10(join37(outputPath, name)).size;
17703
+ fileSize += statSync10(join39(outputPath, name)).size;
17463
17704
  } catch {
17464
17705
  }
17465
17706
  }
@@ -17473,8 +17714,8 @@ function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, start
17473
17714
 
17474
17715
  // ../producer/src/services/distributed/renderChunk.ts
17475
17716
  import { randomBytes } from "crypto";
17476
- import { existsSync as existsSync25, mkdirSync as mkdirSync16, readFileSync as readFileSync13, readdirSync as readdirSync10, rmSync as rmSync15, writeFileSync as writeFileSync12 } from "fs";
17477
- import { extname as extname6, join as join38 } from "path";
17717
+ import { existsSync as existsSync26, mkdirSync as mkdirSync16, readFileSync as readFileSync13, readdirSync as readdirSync11, rmSync as rmSync15, writeFileSync as writeFileSync12 } from "fs";
17718
+ import { join as join40 } from "path";
17478
17719
 
17479
17720
  // ../producer/src/services/distributed/chunkAudio.ts
17480
17721
  function audioCutForChunk(grid, chunks, index, fps) {
@@ -17572,20 +17813,7 @@ async function muxChunkWithAudio(input) {
17572
17813
  }
17573
17814
 
17574
17815
  // ../producer/src/services/distributed/renderChunk.ts
17575
- var FFMPEG_VERSION_MISMATCH = "FFMPEG_VERSION_MISMATCH";
17576
- var PLAN_HASH_MISMATCH = "PLAN_HASH_MISMATCH";
17577
- var MISSING_PLAN_ARTIFACT = "MISSING_PLAN_ARTIFACT";
17578
- var CHUNK_INDEX_OUT_OF_RANGE = "CHUNK_INDEX_OUT_OF_RANGE";
17579
- var MISSING_RUNTIME_ENV_SNAPSHOT = "MISSING_RUNTIME_ENV_SNAPSHOT";
17580
17816
  var LEGACY_DISTRIBUTED_VP9_CPU_USED = 2;
17581
- var RenderChunkValidationError = class extends Error {
17582
- code;
17583
- constructor(code, message) {
17584
- super(message);
17585
- this.name = "RenderChunkValidationError";
17586
- this.code = code;
17587
- }
17588
- };
17589
17817
  function validatePlanVideosForChunk(value) {
17590
17818
  try {
17591
17819
  return parsePlanVideosJson(value);
@@ -17621,9 +17849,6 @@ async function createVerifiedDistributedCaptureSession(serverUrl, framesDir, cap
17621
17849
  throw error;
17622
17850
  }
17623
17851
  }
17624
- function createChunkVideoFrameInjectorFactory(frameLookup) {
17625
- return () => createVideoFrameInjector(frameLookup);
17626
- }
17627
17852
  function isCaptureMode(value) {
17628
17853
  return value === "beginframe" || value === "screenshot" || value === "drawelement";
17629
17854
  }
@@ -17651,130 +17876,11 @@ async function beginFrameSessionNeedsScreenshotFallback(session, probe = probeBe
17651
17876
  );
17652
17877
  return !await probe(session.page, timeoutMs, probeTick, session.beginFrameIntervalMs);
17653
17878
  }
17654
- function frameNumberFromFileName(name) {
17655
- const match = /(\d+)(?=\.[^.]+$)/.exec(name);
17656
- if (!match) return null;
17657
- const frameNumber = Number(match[1]);
17658
- return Number.isSafeInteger(frameNumber) ? frameNumber : null;
17659
- }
17660
- function rebuildExtractedFramesFromPlanDir(planDir, videos, indexMode = "dense-v1") {
17661
- const result = [];
17662
- for (const v of videos) {
17663
- const outputDir = join38(planDir, "video-frames", v.videoId);
17664
- if (!existsSync25(outputDir)) {
17665
- throw new Error(
17666
- `[renderChunk] planDir missing extracted video frames for ${JSON.stringify(v.videoId)}: ${outputDir} not present. plan() should have written frames here; the planDir is malformed.`
17667
- );
17668
- }
17669
- const ext = (extname6(v.framePattern) || ".jpg").toLowerCase();
17670
- const frames = readdirSync10(outputDir).filter((name) => name.toLowerCase().endsWith(ext)).sort((left, right) => {
17671
- const leftNumber = frameNumberFromFileName(left);
17672
- const rightNumber = frameNumberFromFileName(right);
17673
- if (leftNumber === null || rightNumber === null) return left.localeCompare(right);
17674
- return leftNumber - rightNumber || left.localeCompare(right);
17675
- });
17676
- const framePaths = /* @__PURE__ */ new Map();
17677
- for (let i = 0; i < frames.length; i++) {
17678
- const frameName = frames[i];
17679
- if (!frameName) continue;
17680
- const frameNumber = indexMode === "sparse-v2" ? frameNumberFromFileName(frameName) : null;
17681
- const frameIndex = frameNumber === null ? i : frameNumber - 1;
17682
- framePaths.set(frameIndex, join38(outputDir, frameName));
17683
- }
17684
- result.push({
17685
- videoId: v.videoId,
17686
- srcPath: v.srcPath,
17687
- outputDir,
17688
- framePattern: v.framePattern,
17689
- fps: v.fps,
17690
- totalFrames: v.totalFrames,
17691
- metadata: v.metadata,
17692
- framePaths,
17693
- // The chunk worker doesn't own the planDir's video-frames/ directory
17694
- // (the controller does — adapters that fan out chunks across machines
17695
- // share the planDir as read-only). Mark ownership as false so the
17696
- // injector's eventual cleanup doesn't rm bytes another worker may
17697
- // still be reading.
17698
- ownedByLookup: false
17699
- });
17700
- }
17701
- return result;
17702
- }
17703
- function contiguousRuns(sortedIndexes) {
17704
- const runs2 = [];
17705
- for (const index of sortedIndexes) {
17706
- const last = runs2[runs2.length - 1];
17707
- if (last && last.firstFrame + last.frames === index) last.frames++;
17708
- else runs2.push({ firstFrame: index, frames: 1 });
17709
- }
17710
- return runs2;
17711
- }
17712
- async function extractDeferredVideoFramesForChunk(input) {
17713
- const deferred = input.planVideos.extracted.filter((video) => video.deferredRange);
17714
- if (deferred.length === 0) return [];
17715
- const table = createFrameLookupTable(
17716
- input.planVideos.videos,
17717
- deferred.map((video) => ({
17718
- ...video,
17719
- outputDir: input.outputRoot,
17720
- framePaths: new Map(Array.from({ length: video.totalFrames }, (_, i) => [i, String(i)]))
17721
- })),
17722
- resolveRenderFpsConfig(input.fps).value
17723
- );
17724
- const shown = /* @__PURE__ */ new Map();
17725
- for (let frame = input.slice.startFrame; frame < input.slice.endFrame; frame++) {
17726
- const globalTime = frame * input.fps.den / input.fps.num;
17727
- for (const [videoId, payload] of table.getActiveFramePayloads(globalTime)) {
17728
- const indexes = shown.get(videoId) ?? /* @__PURE__ */ new Set();
17729
- indexes.add(payload.frameIndex);
17730
- shown.set(videoId, indexes);
17731
- }
17732
- }
17733
- return Promise.all(
17734
- deferred.map(async (video) => {
17735
- const outputDir = join38(input.outputRoot, video.videoId);
17736
- const indexes = [...shown.get(video.videoId) ?? []].sort((a, b) => a - b);
17737
- if (indexes.length === 0) {
17738
- return { ...video, outputDir, framePaths: /* @__PURE__ */ new Map(), ownedByLookup: false };
17739
- }
17740
- const range = video.deferredRange;
17741
- const sourcePath = join38(input.planDir, ...range.sourcePath.split("/"));
17742
- if (!existsSync25(sourcePath)) {
17743
- throw new RenderChunkValidationError(
17744
- MISSING_PLAN_ARTIFACT,
17745
- `[renderChunk] planDir is missing the source for video ${JSON.stringify(video.videoId)}: ${range.sourcePath}`
17746
- );
17747
- }
17748
- const extracted = await extractVideoFramesRange(
17749
- sourcePath,
17750
- video.videoId,
17751
- range.startTime,
17752
- range.durationSeconds,
17753
- {
17754
- fps: input.fps,
17755
- outputDir: input.outputRoot,
17756
- format: range.format,
17757
- frameRanges: contiguousRuns(indexes)
17758
- },
17759
- void 0,
17760
- input.cfg
17761
- );
17762
- const missing = indexes.find((index) => !extracted.framePaths.has(index));
17763
- if (missing !== void 0) {
17764
- throw new RenderChunkValidationError(
17765
- INVALID_VIDEO_METADATA,
17766
- `[renderChunk] video ${JSON.stringify(video.videoId)} has no frame ${missing}; the plan expected ${video.totalFrames} frames from ${range.sourcePath}.`
17767
- );
17768
- }
17769
- return { ...video, outputDir, framePaths: extracted.framePaths, ownedByLookup: false };
17770
- })
17771
- );
17772
- }
17773
17879
  function hashChunkOutput(outputPath, kind) {
17774
17880
  if (kind === "file") return sha256Hex(readFileSync13(outputPath));
17775
- const entries = readdirSync10(outputPath).filter((name) => /\.(png|jpg|jpeg)$/i.test(name)).sort();
17881
+ const entries = readdirSync11(outputPath).filter((name) => /\.(png|jpg|jpeg)$/i.test(name)).sort();
17776
17882
  const lines = entries.map(
17777
- (name) => `${name}\0${sha256Hex(readFileSync13(join38(outputPath, name)))}`
17883
+ (name) => `${name}\0${sha256Hex(readFileSync13(join40(outputPath, name)))}`
17778
17884
  );
17779
17885
  return sha256Hex(lines.join("\0"));
17780
17886
  }
@@ -17791,10 +17897,10 @@ function resolveLockedVp9CpuUsed(lockedEncoder) {
17791
17897
  async function renderChunk(planDir, chunkIndex, outputChunkPath) {
17792
17898
  const start = Date.now();
17793
17899
  const log = defaultLogger;
17794
- const planJsonPath = join38(planDir, "plan.json");
17795
- const encoderJsonPath = join38(planDir, "meta", "encoder.json");
17796
- const chunksJsonPath = join38(planDir, "meta", "chunks.json");
17797
- if (!existsSync25(planJsonPath)) {
17900
+ const planJsonPath = join40(planDir, "plan.json");
17901
+ const encoderJsonPath = join40(planDir, "meta", "encoder.json");
17902
+ const chunksJsonPath = join40(planDir, "meta", "chunks.json");
17903
+ if (!existsSync26(planJsonPath)) {
17798
17904
  throw new RenderChunkValidationError(
17799
17905
  MISSING_PLAN_ARTIFACT,
17800
17906
  `[renderChunk] planDir is missing required artifact: ${planJsonPath}`
@@ -17808,7 +17914,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath) {
17808
17914
  chunkIndex
17809
17915
  });
17810
17916
  for (const required of [encoderJsonPath, chunksJsonPath]) {
17811
- if (!existsSync25(required)) {
17917
+ if (!existsSync26(required)) {
17812
17918
  throw new RenderChunkValidationError(
17813
17919
  MISSING_PLAN_ARTIFACT,
17814
17920
  `[renderChunk] planDir is missing required artifact: ${required}`
@@ -17817,9 +17923,9 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath) {
17817
17923
  }
17818
17924
  const encoder = JSON.parse(readFileSync13(encoderJsonPath, "utf-8"));
17819
17925
  const chunks = JSON.parse(readFileSync13(chunksJsonPath, "utf-8"));
17820
- const videosJsonPath = join38(planDir, PLAN_VIDEOS_META_RELATIVE_PATH);
17926
+ const videosJsonPath = join40(planDir, PLAN_VIDEOS_META_RELATIVE_PATH);
17821
17927
  let planVideos = null;
17822
- if (existsSync25(videosJsonPath)) {
17928
+ if (existsSync26(videosJsonPath)) {
17823
17929
  try {
17824
17930
  planVideos = validatePlanVideosForChunk(JSON.parse(readFileSync13(videosJsonPath, "utf-8")));
17825
17931
  } catch (err) {
@@ -17847,8 +17953,8 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath) {
17847
17953
  `[renderChunk] chunk ${chunkIndex} has non-positive frame count: ${framesInChunk}`
17848
17954
  );
17849
17955
  }
17850
- const compiledDir = join38(planDir, "compiled");
17851
- if (!existsSync25(compiledDir)) {
17956
+ const compiledDir = join40(planDir, "compiled");
17957
+ if (!existsSync26(compiledDir)) {
17852
17958
  throw new RenderChunkValidationError(
17853
17959
  MISSING_PLAN_ARTIFACT,
17854
17960
  `[renderChunk] planDir missing compiled/ directory: ${compiledDir}`
@@ -17905,7 +18011,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath) {
17905
18011
  };
17906
18012
  const workDir = `${outputChunkPath}.work.${process.pid}.${randomBytes(4).toString("hex")}`;
17907
18013
  mkdirSync16(workDir, { recursive: true });
17908
- const framesDir = join38(workDir, "captured-frames");
18014
+ const framesDir = join40(workDir, "captured-frames");
17909
18015
  mkdirSync16(framesDir, { recursive: true });
17910
18016
  const videoFramesDir = `${workDir}.video-frames`;
17911
18017
  const videoFrameLookup = planVideos && planVideos.extracted.length > 0 ? createFrameLookupTable(
@@ -17919,7 +18025,10 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath) {
17919
18025
  ...await extractDeferredVideoFramesForChunk({
17920
18026
  planDir,
17921
18027
  planVideos,
17922
- slice,
18028
+ frames: Array.from(
18029
+ { length: slice.endFrame - slice.startFrame },
18030
+ (_, i) => slice.startFrame + i
18031
+ ),
17923
18032
  fps: { num: plan2.dimensions.fpsNum, den: plan2.dimensions.fpsDen },
17924
18033
  outputRoot: videoFramesDir,
17925
18034
  cfg
@@ -17928,9 +18037,6 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath) {
17928
18037
  resolveRenderFpsConfig(job.config.fps).value
17929
18038
  ) : null;
17930
18039
  const createChunkVideoFrameInjector = createChunkVideoFrameInjectorFactory(videoFrameLookup);
17931
- const videoCaptureBeyondViewport = resolveVideoCaptureBeyondViewport(
17932
- planVideos?.videos.length ?? 0
17933
- );
17934
18040
  const fileServer = await createFileServer({
17935
18041
  projectDir: compiledDir,
17936
18042
  compiledDir,
@@ -17940,28 +18046,12 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath) {
17940
18046
  // chunk runtime seek quantization stays on the same fps grid as capture.
17941
18047
  fps: { num: plan2.dimensions.fpsNum, den: plan2.dimensions.fpsDen }
17942
18048
  });
17943
- const captureOptions = {
17944
- width: plan2.dimensions.width,
17945
- height: plan2.dimensions.height,
17946
- fps: { num: plan2.dimensions.fpsNum, den: plan2.dimensions.fpsDen },
17947
- format: plan2.dimensions.format === "mp4" ? "jpeg" : "png",
17948
- quality: plan2.dimensions.format === "mp4" ? resolveCaptureJpegQuality(job.config.quality) : void 0,
17949
- deviceScaleFactor: encoder.deviceScaleFactor,
17950
- // Re-inject the controller's snapshotted variables so the chunk's
17951
- // first capture sees the same `window.__hfVariables` the in-process
17952
- // renderer would have seen. Optional — compositions that don't
17953
- // declare `data-composition-variables` leave this undefined and the
17954
- // engine skips the `evaluateOnNewDocument` injection.
17955
- variables: encoder.variables,
17956
- subTimelineWaitMemo: {},
17957
- ...videoCaptureBeyondViewport !== void 0 ? { captureBeyondViewport: videoCaptureBeyondViewport } : {},
17958
- // lock the BeginFrame warmup loop to a fixed iteration count so
17959
- // `beginFrameTimeTicks` is host-independent. Only chunks ever set this.
17960
- lockWarmupTicks: true,
17961
- requiresWebGpu: compositionRequiresWebGpu(
17962
- readFileSync13(join38(compiledDir, "index.html"), "utf-8")
17963
- )
17964
- };
18049
+ const captureOptions = buildChunkCaptureOptions({
18050
+ dimensions: plan2.dimensions,
18051
+ encoder,
18052
+ videoCount: planVideos?.videos.length ?? 0,
18053
+ html: readFileSync13(join40(compiledDir, "index.html"), "utf-8")
18054
+ });
17965
18055
  const chunkWorkerCount = calculateOptimalWorkers(framesInChunk, void 0, cfg);
17966
18056
  let session = null;
17967
18057
  let outputKind;
@@ -18122,10 +18212,10 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath) {
18122
18212
  const effectiveBitrate = encoder.crf != null ? void 0 : encoder.bitrate;
18123
18213
  const videoOnlyPath = encoder.chunksCarryAudio ? `${outputChunkPath}.video.mp4` : outputChunkPath;
18124
18214
  if (isPngSequence) {
18125
- if (!existsSync25(outputChunkPath)) mkdirSync16(outputChunkPath, { recursive: true });
18215
+ if (!existsSync26(outputChunkPath)) mkdirSync16(outputChunkPath, { recursive: true });
18126
18216
  } else {
18127
- const outDir = join38(outputChunkPath, "..");
18128
- if (!existsSync25(outDir)) mkdirSync16(outDir, { recursive: true });
18217
+ const outDir = join40(outputChunkPath, "..");
18218
+ if (!existsSync26(outDir)) mkdirSync16(outDir, { recursive: true });
18129
18219
  }
18130
18220
  const encodeStarted = Date.now();
18131
18221
  await runEncodeStage({
@@ -18248,8 +18338,8 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath) {
18248
18338
 
18249
18339
  // ../producer/src/services/distributed/planV2Execution.ts
18250
18340
  async function renderChunkV2(planV2Dir, chunkIndex, outputChunkPath) {
18251
- const workRoot = mkdtempSync9(join39(tmpdir5(), "hf-plan-v2-chunk-"));
18252
- const materializedPlanDir = join39(workRoot, "plan");
18341
+ const workRoot = mkdtempSync9(join41(tmpdir5(), "hf-plan-v2-chunk-"));
18342
+ const materializedPlanDir = join41(workRoot, "plan");
18253
18343
  try {
18254
18344
  materializePlanV2Target(planV2Dir, { role: "chunk", chunkIndex }, materializedPlanDir);
18255
18345
  return await renderChunk(materializedPlanDir, chunkIndex, outputChunkPath);
@@ -18258,8 +18348,8 @@ async function renderChunkV2(planV2Dir, chunkIndex, outputChunkPath) {
18258
18348
  }
18259
18349
  }
18260
18350
  async function assembleV2(planV2Dir, chunkPaths, outputPath, options) {
18261
- const workRoot = mkdtempSync9(join39(tmpdir5(), "hf-plan-v2-assembler-"));
18262
- const materializedPlanDir = join39(workRoot, "plan");
18351
+ const workRoot = mkdtempSync9(join41(tmpdir5(), "hf-plan-v2-assembler-"));
18352
+ const materializedPlanDir = join41(workRoot, "plan");
18263
18353
  try {
18264
18354
  const materialized = materializePlanV2Target(
18265
18355
  planV2Dir,
@@ -18279,9 +18369,9 @@ async function assembleV2(planV2Dir, chunkPaths, outputPath, options) {
18279
18369
  }
18280
18370
 
18281
18371
  // ../producer/src/services/distributed/projectHash.ts
18282
- import { readdirSync as readdirSync11, readFileSync as readFileSync14 } from "fs";
18372
+ import { readdirSync as readdirSync12, readFileSync as readFileSync14 } from "fs";
18283
18373
  import { createHash as createHash7 } from "crypto";
18284
- import { join as join40, relative as relative8 } from "path";
18374
+ import { join as join42, relative as relative8 } from "path";
18285
18375
 
18286
18376
  export {
18287
18377
  createConsoleLogger,