hyperframes 0.8.177 → 0.8.179

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 (182) hide show
  1. package/dist/{add-YVP5YBNV.js → add-2CCBND75.js} +12 -12
  2. package/dist/{animatedGifPrep-6SPYXSHE.js → animatedGifPrep-S6XXOOXK.js} +7 -7
  3. package/dist/api.js +16 -16
  4. package/dist/{asset-CZHAZMDR.js → asset-7HOLVSD5.js} +2 -2
  5. package/dist/{assetCataloger-5GGNQC3Y.js → assetCataloger-MSAMR77T.js} +7 -7
  6. package/dist/{audio-F37HJPGZ.js → audio-356GVOLJ.js} +13 -13
  7. package/dist/{auth-CADZEUXD.js → auth-YHI5VR4X.js} +1 -1
  8. package/dist/{autoUpdate-46XNDX2V.js → autoUpdate-OP345EBV.js} +2 -2
  9. package/dist/{backgroundChecks-UWDSDJHJ.js → backgroundChecks-6NVELE25.js} +3 -3
  10. package/dist/backgroundChecksWorker.js +3 -3
  11. package/dist/{batchRender-KXXTVNPB.js → batchRender-OOQYGH5A.js} +4 -4
  12. package/dist/{beats-COVC2QGG.js → beats-DQH5SOHM.js} +5 -5
  13. package/dist/{benchmark-HDPPG2FE.js → benchmark-2W2NPNLJ.js} +9 -9
  14. package/dist/{browser-JEIXZKJN.js → browser-SCM75524.js} +9 -8
  15. package/dist/{bundleWithLocalizedFonts-PMRGOQWD.js → bundleWithLocalizedFonts-4F6HKLMZ.js} +2 -2
  16. package/dist/{capture-QVW6JPTF.js → capture-YRS6SZ6B.js} +11 -11
  17. package/dist/{capture-YMF7J55W.js → capture-ZBGAJYVH.js} +2 -2
  18. package/dist/{captureCompositionFrame-QUYL3DEX.js → captureCompositionFrame-Z33NWUED.js} +11 -11
  19. package/dist/{catalog-DTB6S66P.js → catalog-MOSGL65T.js} +13 -13
  20. package/dist/{catch-up-Y4KO7XHK.js → catch-up-TCAZFTMO.js} +5 -5
  21. package/dist/{check-3YLTRK6W.js → check-QVXSTHZB.js} +39 -39
  22. package/dist/{checkBrowser-LEUOWFNZ.js → checkBrowser-NF2CMWYR.js} +35 -35
  23. package/dist/chunk-2R5ZKWSV.js +26 -0
  24. package/dist/{chunk-HIP62XAW.js → chunk-2X4PQEYM.js} +1 -3
  25. package/dist/{chunk-42T7OP4O.js → chunk-32GKQ73S.js} +1 -1
  26. package/dist/{chunk-PIH4P2G6.js → chunk-3EJYUZHW.js} +16 -14
  27. package/dist/{chunk-L6XT6GSV.js → chunk-52L2L46Q.js} +1 -1
  28. package/dist/chunk-537IYAEE.js +80 -0
  29. package/dist/{chunk-7L4JOVV7.js → chunk-5HZAIPHY.js} +1 -1
  30. package/dist/{chunk-GVIH4ZQY.js → chunk-5RS2FIZU.js} +1 -1
  31. package/dist/{chunk-T76PLPD6.js → chunk-634HSJ3J.js} +2 -1
  32. package/dist/{chunk-L3LJVYXS.js → chunk-A2BB2XQU.js} +2 -2
  33. package/dist/{chunk-DQY6WTF7.js → chunk-A3RUVTGB.js} +1 -1
  34. package/dist/{chunk-P3MMF2JQ.js → chunk-AEWZDKFS.js} +1 -1
  35. package/dist/{chunk-OYEDHVSI.js → chunk-AFKVI7FJ.js} +1 -1
  36. package/dist/{chunk-DNZMBQTH.js → chunk-AHPGAESX.js} +1 -1
  37. package/dist/{chunk-VEQ7DKPG.js → chunk-AHS5QYKF.js} +1 -1
  38. package/dist/{chunk-FFDC5OWV.js → chunk-ALVXFXUG.js} +5 -3
  39. package/dist/{chunk-TNDNFZOK.js → chunk-BGKZ37BC.js} +2 -2
  40. package/dist/{chunk-LY2GLSOS.js → chunk-BJJ3QF7T.js} +1 -1
  41. package/dist/{chunk-GZG3ZZOO.js → chunk-BNGRVR3Y.js} +4 -4
  42. package/dist/{chunk-AFSBPASX.js → chunk-BW2UQR4A.js} +5 -5
  43. package/dist/{chunk-2PNLYDGZ.js → chunk-C5XU53VP.js} +10 -10
  44. package/dist/{chunk-NL5F3AD4.js → chunk-CML25PAF.js} +2 -4
  45. package/dist/{chunk-S3T3TDAB.js → chunk-COK5OUBX.js} +2 -2
  46. package/dist/{chunk-MG66UO4N.js → chunk-CW3VIQMZ.js} +1 -1
  47. package/dist/{chunk-6GMZRHYD.js → chunk-DHKYJYOY.js} +3 -3
  48. package/dist/{chunk-MPNVXG3J.js → chunk-DLF5KIBL.js} +2 -2
  49. package/dist/{chunk-XRKMITSS.js → chunk-E24HSMZV.js} +12 -51
  50. package/dist/{chunk-FE2XTCTM.js → chunk-EEP5Y5IE.js} +1 -18
  51. package/dist/{chunk-EOOEC32M.js → chunk-EVMY7IPL.js} +22 -0
  52. package/dist/{chunk-KRKSY2AE.js → chunk-F3GML67X.js} +1 -1
  53. package/dist/{chunk-PL4WM5XE.js → chunk-FIAQ4J45.js} +2 -2
  54. package/dist/{chunk-6OV3YGZF.js → chunk-FTWOVWHR.js} +2 -2
  55. package/dist/{chunk-457OAAL2.js → chunk-HU4PBQGJ.js} +1 -1
  56. package/dist/{chunk-GMJ3ILLW.js → chunk-ICMXC3MI.js} +7 -24
  57. package/dist/{chunk-LZT3NUTE.js → chunk-IE6O2VXV.js} +1 -1
  58. package/dist/{chunk-B7DJQFAC.js → chunk-IHWI3KZY.js} +4 -4
  59. package/dist/{chunk-WNQUXBDU.js → chunk-ILLFG4RN.js} +3 -3
  60. package/dist/{chunk-LEILKM2N.js → chunk-JRIXYXG3.js} +1361 -1151
  61. package/dist/{chunk-MNEC244Z.js → chunk-LBRJKTVY.js} +2 -2
  62. package/dist/{chunk-X4PK5ZXP.js → chunk-LO4DHZLH.js} +3 -3
  63. package/dist/{chunk-SJFA4CZ3.js → chunk-M2DZX2DP.js} +2 -2
  64. package/dist/{chunk-2CEIKEFU.js → chunk-M5CJQ6EH.js} +1 -1
  65. package/dist/{chunk-JWUKHXM6.js → chunk-MK6D6HVT.js} +1 -1
  66. package/dist/{chunk-BQTCQ6VN.js → chunk-MXNKK7HY.js} +3 -3
  67. package/dist/{chunk-BHFIITTD.js → chunk-N3U7WHQK.js} +5 -5
  68. package/dist/{chunk-SSYUONVH.js → chunk-NTMCSIQB.js} +2 -2
  69. package/dist/{chunk-BEX7K4WO.js → chunk-OINJILMB.js} +2 -2
  70. package/dist/{chunk-UEGULMAH.js → chunk-ORM7SJ7K.js} +7 -7
  71. package/dist/{chunk-2YT3SCXL.js → chunk-OXP77SHD.js} +2 -2
  72. package/dist/chunk-PLNQW2C6.js +3805 -0
  73. package/dist/{chunk-JNL3RMDG.js → chunk-PU33GPG4.js} +1 -1
  74. package/dist/{chunk-XXHHPB6E.js → chunk-Q73AGCTV.js} +11 -11
  75. package/dist/{chunk-W47D3KOB.js → chunk-RHWJKSGH.js} +1 -1
  76. package/dist/{chunk-M3MXTYDE.js → chunk-RJIJWETG.js} +1 -1
  77. package/dist/{chunk-K535ASAM.js → chunk-TINAVSGR.js} +1 -1
  78. package/dist/{chunk-C6TPGUWD.js → chunk-TTVHYD5R.js} +2 -2
  79. package/dist/{chunk-QWCXGTOH.js → chunk-UOHQV6AV.js} +24 -12
  80. package/dist/{chunk-5ZSLJXQX.js → chunk-UWPUEI5U.js} +2 -2
  81. package/dist/{chunk-4YBNQDZM.js → chunk-VCDWVVDV.js} +11 -7
  82. package/dist/{chunk-MXFCQCKE.js → chunk-VI7IQ2QF.js} +7 -7
  83. package/dist/{chunk-YWSXFFO3.js → chunk-VU4JLRNH.js} +156 -76
  84. package/dist/{chunk-YCRX5APY.js → chunk-WOXRCPJU.js} +1 -1
  85. package/dist/{chunk-PO2SDVQA.js → chunk-WQ6ZXHFQ.js} +3 -1
  86. package/dist/{chunk-X6HYBA4A.js → chunk-XSQWXUK2.js} +1 -1
  87. package/dist/{chunk-HLBAQBHT.js → chunk-XX4ZYQR6.js} +1 -1
  88. package/dist/{chunk-NWEER3QY.js → chunk-YH7VQAKX.js} +2 -2
  89. package/dist/{chunk-DZMWNCHM.js → chunk-ZAJO3YCQ.js} +2 -2
  90. package/dist/{clean-BDXOJ4PW.js → clean-SJ2UIDKG.js} +20 -20
  91. package/dist/cli.js +54 -54
  92. package/dist/{cloud-HVG2LUBO.js → cloud-DH7QGNPZ.js} +4 -4
  93. package/dist/{cloudrun-P35BPQCO.js → cloudrun-ZA2ZH2VZ.js} +10 -8
  94. package/dist/{compare-ZXORZDGQ.js → compare-5JNN5BHX.js} +29 -29
  95. package/dist/{compiler-5TFPJPQQ.js → compiler-CQ6OKYT5.js} +2 -2
  96. package/dist/{component-AZHVBBVP.js → component-LJQWLOXZ.js} +3 -3
  97. package/dist/{compositions-WFILUFC4.js → compositions-UILNO2RF.js} +11 -11
  98. package/dist/{delete-I34NH2Q4.js → delete-WIO2MDPB.js} +2 -2
  99. package/dist/deploy-BUEYC7MP.js +196 -0
  100. package/dist/{destroy-BEL232I5.js → destroy-QBLMYDBF.js} +6 -3
  101. package/dist/{dist-U2HCECNB.js → dist-MHJYD5IZ.js} +6 -4
  102. package/dist/{doctor-6YID47TF.js → doctor-5G2RXBQX.js} +12 -12
  103. package/dist/{events-G76VOIBQ.js → events-2LHNVY2F.js} +4 -4
  104. package/dist/{events-OKT2KO6S.js → events-BXODXMJ6.js} +2 -2
  105. package/dist/{feedback-EGU7SSXF.js → feedback-57END2CW.js} +7 -7
  106. package/dist/{ffmpeg-CSJLJAQ3.js → ffmpeg-64E4VH6W.js} +7 -7
  107. package/dist/{figma-SUJPNBQQ.js → figma-O5MO4GF5.js} +3 -3
  108. package/dist/fontLocalizeCli.js +10 -10
  109. package/dist/{get-7MYYFGO7.js → get-OEWPJ4HH.js} +2 -2
  110. package/dist/{gpuPolicy-C6JWEX7F.js → gpuPolicy-IGZOHHMX.js} +7 -7
  111. package/dist/{grade-compare-P22VRQLA.js → grade-compare-PG2UOMHO.js} +30 -30
  112. package/dist/{help-HL6QBZMC.js → help-44J4SQTE.js} +1 -1
  113. package/dist/{history-OG2IXOQA.js → history-CUA53AOE.js} +10 -10
  114. package/dist/hyperframe-runtime.js +57 -55
  115. package/dist/hyperframe.manifest.json +1 -1
  116. package/dist/hyperframe.runtime.iife.js +57 -55
  117. package/dist/hyperframes-player.global.js +1 -1
  118. package/dist/{info-7P77JIWI.js → info-UDXYDQVK.js} +8 -8
  119. package/dist/{init-JI7QDJV4.js → init-CVT6HLW2.js} +28 -28
  120. package/dist/{inspect-7GSF776P.js → inspect-6F72RJQG.js} +30 -30
  121. package/dist/{installer-NY5E5HCO.js → installer-ADKWK4I4.js} +5 -5
  122. package/dist/{keyframes-6NM477BA.js → keyframes-GJ7EEWW2.js} +7 -7
  123. package/dist/{lambda-4DNBHUPF.js → lambda-WFSBYU6U.js} +16 -21
  124. package/dist/{layout-IBDEMDYP.js → layout-SASENZOG.js} +30 -30
  125. package/dist/{lint-BGAON4GT.js → lint-JX57B52X.js} +7 -7
  126. package/dist/{list-PGWNFSLX.js → list-WG7NTE63.js} +2 -2
  127. package/dist/{login-IAGLU3R3.js → login-3NFLD65G.js} +4 -4
  128. package/dist/{manager-VOGZQSK4.js → manager-BKHRATMY.js} +9 -9
  129. package/dist/{manager-HEUPD2YB.js → manager-V2QCCPOJ.js} +4 -3
  130. package/dist/{media-treatment-Z4N3JEDB.js → media-treatment-QI5F2SMU.js} +13 -13
  131. package/dist/{models-QUOIJVQI.js → models-TXURF3J6.js} +11 -11
  132. package/dist/{motionShot-GPNESXTN.js → motionShot-HRM6NMT4.js} +15 -15
  133. package/dist/{normalize-audio-C264N2QE.js → normalize-audio-IEKL43N4.js} +8 -8
  134. package/dist/{open-HB5F4HSD.js → open-GU3I75TB.js} +12 -12
  135. package/dist/{parakeetRunner-2ZQOINZQ.js → parakeetRunner-S2QI3PCA.js} +13 -13
  136. package/dist/{pipeline-JWGTH2BJ.js → pipeline-OO2MYYIV.js} +9 -9
  137. package/dist/{play-VQ4HA4CC.js → play-VL2WQCT3.js} +21 -21
  138. package/dist/{preflight-4WTT4D5X.js → preflight-GBPRLIM6.js} +9 -9
  139. package/dist/{present-HU46P43M.js → present-YWSFX5CH.js} +17 -17
  140. package/dist/{preview-LNTJ62DX.js → preview-SMG6GXLY.js} +18 -18
  141. package/dist/{producer-UZZQX6IJ.js → producer-67RIQCWZ.js} +1 -1
  142. package/dist/{projectConfig-HDALEM6S.js → projectConfig-NQQDEDAK.js} +7 -7
  143. package/dist/{publication-NX4VC7UU.js → publication-7DW4IJPC.js} +3 -3
  144. package/dist/{publish-AZRRQUXM.js → publish-FJKJ74IF.js} +10 -10
  145. package/dist/{remove-background-OX5WZTXY.js → remove-background-HO2NBZCJ.js} +10 -10
  146. package/dist/{render-HGT3EGIK.js → render-5TTZDI5N.js} +7 -14
  147. package/dist/{render-DV76TO7S.js → render-DH4CGGLO.js} +9 -9
  148. package/dist/{render-TPE44TOW.js → render-MB7PW2YQ.js} +32 -32
  149. package/dist/{render-batch-MUCDZ2WF.js → render-batch-4RPH66OX.js} +11 -17
  150. package/dist/renderSetupWorker.js +2 -2
  151. package/dist/{resolver-FLQ42X2Y.js → resolver-UW6NYBJA.js} +5 -5
  152. package/dist/{sam-IKPEJRRO.js → sam-MC24SZHC.js} +1 -1
  153. package/dist/{sherpa-IBMU4WV6.js → sherpa-LVXRZGE3.js} +12 -12
  154. package/dist/{sites-HZMADOA3.js → sites-3ZBYB5X4.js} +2 -1
  155. package/dist/{skills-M2ANTFD6.js → skills-NDWQPEUD.js} +6 -6
  156. package/dist/{skillsUpdateCheck-4WNEKKDW.js → skillsUpdateCheck-HZVZ3IUU.js} +3 -3
  157. package/dist/{snapshot-5AKBT5IQ.js → snapshot-MCHWMAPH.js} +33 -33
  158. package/dist/{src-SGPA7DM7.js → src-E34ZBMAS.js} +9 -9
  159. package/dist/{src-JGPFLYBL.js → src-XRFOS5LP.js} +6 -6
  160. package/dist/{staticProjectServer-UVFPQEUZ.js → staticProjectServer-COEAKTGH.js} +17 -17
  161. package/dist/studio/assets/{hyperframes-player-BulZP0_1.js → hyperframes-player-C0SyMC21.js} +1 -1
  162. package/dist/studio/assets/{index-CGncBUsJ.js → index-DHUassFP.js} +1 -1
  163. package/dist/studio/assets/{index-B1Gypobp.js → index-ueMRyAJ2.js} +173 -172
  164. package/dist/studio/index.html +1 -1
  165. package/dist/{studio-api-RVIU7JP2.js → studio-api-455XWQPD.js} +11 -11
  166. package/dist/{studioServer-K22MWDLW.js → studioServer-XK5N6PIB.js} +52 -67
  167. package/dist/{synthesize-W2M7QGDV.js → synthesize-I2C24XJ5.js} +2 -2
  168. package/dist/{telemetry-SUAMGBFK.js → telemetry-DD5ZIBOK.js} +4 -4
  169. package/dist/{timeline-SGVBFT36.js → timeline-MJJEX5CU.js} +20 -20
  170. package/dist/{tokens-IFHWY5WC.js → tokens-AKUZMYRE.js} +2 -2
  171. package/dist/{transcribe-KAOCI5CW.js → transcribe-ER7GGF4L.js} +11 -11
  172. package/dist/{transcribe-GY4JHB7V.js → transcribe-ZQI5P2LH.js} +17 -17
  173. package/dist/{tts-RE227GFP.js → tts-EDK3QS7M.js} +3 -3
  174. package/dist/{updateCheck-YUNDJDLW.js → updateCheck-GYFCXFWR.js} +2 -2
  175. package/dist/{upgrade-PL2BFWTO.js → upgrade-UNWHMZP5.js} +2 -2
  176. package/dist/{urlDownloader-FZ5ISJES.js → urlDownloader-QF7EBQC2.js} +6 -6
  177. package/dist/{validate-2NALRIFW.js → validate-32CQZFNZ.js} +24 -24
  178. package/dist/{video-7CDAIWYC.js → video-2KGC3RIQ.js} +7 -7
  179. package/package.json +1 -2
  180. package/dist/chunk-EEXE43II.js +0 -3805
  181. package/dist/chunk-TWWSYAKJ.js +0 -24
  182. package/dist/deploy-3TTQZGTF.js +0 -116
@@ -8,8 +8,9 @@ import {
8
8
  parseEncoderOptions
9
9
  } from "./chunk-KLIKCX6Q.js";
10
10
  import {
11
+ RENDER_TEMP_DIR,
11
12
  createStudioPositionSeekReapplyScript
12
- } from "./chunk-EOOEC32M.js";
13
+ } from "./chunk-EVMY7IPL.js";
13
14
  import {
14
15
  GENERIC_FAMILIES,
15
16
  collectFontFamilyCustomProperties,
@@ -22,7 +23,7 @@ import {
22
23
  EncoderInterruptedError,
23
24
  encoderFailureError,
24
25
  prepareAnimatedGifInputs
25
- } from "./chunk-PL4WM5XE.js";
26
+ } from "./chunk-FIAQ4J45.js";
26
27
  import {
27
28
  BROWSER_GPU_NOT_SOFTWARE,
28
29
  CaptureFailure,
@@ -151,7 +152,7 @@ import {
151
152
  validateEngineConfigSnapshot,
152
153
  verifyDiskDrawElementSamples,
153
154
  writeCapturedFrame
154
- } from "./chunk-YWSXFFO3.js";
155
+ } from "./chunk-VU4JLRNH.js";
155
156
  import {
156
157
  assignBundledRuntimeCompositionIds,
157
158
  buildVariablesByCompScript,
@@ -164,7 +165,7 @@ import {
164
165
  isTemplateEntry,
165
166
  parsesAsScript,
166
167
  prepareFlattenedInnerRoot
167
- } from "./chunk-JNL3RMDG.js";
168
+ } from "./chunk-PU33GPG4.js";
168
169
  import {
169
170
  LOW_MEMORY_TOTAL_MB_THRESHOLD,
170
171
  getCgroupMemoryLimitMb,
@@ -190,7 +191,7 @@ import {
190
191
  resolveAbsoluteMediaStartSeconds,
191
192
  resolveAuthoredTimingWindow,
192
193
  toFps
193
- } from "./chunk-VEQ7DKPG.js";
194
+ } from "./chunk-AHS5QYKF.js";
194
195
  import {
195
196
  COMPOSITION_SOURCE_URL,
196
197
  HF_COLOR_GRADING_ATTR,
@@ -218,7 +219,7 @@ import {
218
219
  shouldClampResolvedMediaDuration,
219
220
  sourceTimeAt,
220
221
  styleElementsFor
221
- } from "./chunk-EEXE43II.js";
222
+ } from "./chunk-PLNQW2C6.js";
222
223
  import {
223
224
  MAX_AUDIO_GAIN
224
225
  } from "./chunk-UJKQWGFF.js";
@@ -236,10 +237,10 @@ import {
236
237
  import {
237
238
  redactKnownPaths,
238
239
  redactTelemetryString
239
- } from "./chunk-T76PLPD6.js";
240
+ } from "./chunk-634HSJ3J.js";
240
241
  import {
241
242
  chromeMajorCeiling
242
- } from "./chunk-TWWSYAKJ.js";
243
+ } from "./chunk-537IYAEE.js";
243
244
  import {
244
245
  lintHyperframeHtml
245
246
  } from "./chunk-GK2FIKIQ.js";
@@ -1582,7 +1583,6 @@ import { hostname, uptime } from "os";
1582
1583
  import { dirname as dirname3, join as join4 } from "path";
1583
1584
  var OWNER_FILE = "owner.json";
1584
1585
  var TRANSACTION_BACKUP = "backup";
1585
- var RENDER_TEMP_DIR = /^(work-[0-9a-f]{8}(-[0-9a-f]{4}){3}-[0-9a-f]{12}|hf-render|\..+\.hf-transaction)-[A-Za-z0-9]{6}$/;
1586
1586
  var RENDER_JOB_DIR = /^[0-9a-f]{8}(-[0-9a-f]{4}){3}-[0-9a-f]{12}$/;
1587
1587
  function pidScope() {
1588
1588
  const attempt = (read) => {
@@ -5756,7 +5756,7 @@ function detectShaderTransitionUsage(html) {
5756
5756
  }
5757
5757
  return false;
5758
5758
  }
5759
- async function resolveMediaDuration(src, mediaStart, playbackRate, baseDir, downloadDir, tagName, elementIdentity, log) {
5759
+ async function resolveMediaDuration(src, mediaStart, playbackRate, baseDir, downloadDir, tagName, elementIdentity, log, urlByAbsentLocalPath) {
5760
5760
  let filePath = src;
5761
5761
  if (isHttpUrl(src)) {
5762
5762
  if (!existsSync9(downloadDir)) mkdirSync5(downloadDir, { recursive: true });
@@ -5770,8 +5770,9 @@ async function resolveMediaDuration(src, mediaStart, playbackRate, baseDir, down
5770
5770
  } else {
5771
5771
  filePath = decodedUrlPath(src);
5772
5772
  if (!filePath.startsWith("/")) filePath = join12(baseDir, filePath);
5773
+ filePath = urlByAbsentLocalPath?.get(filePath) ?? filePath;
5773
5774
  }
5774
- if (!existsSync9(filePath)) {
5775
+ if (!isHttpUrl(filePath) && !existsSync9(filePath)) {
5775
5776
  return { duration: null, resolvedPath: filePath };
5776
5777
  }
5777
5778
  const withSrcContext = (error) => {
@@ -5834,7 +5835,7 @@ function markInferredVariableMediaDurations(html, unresolvedMedia) {
5834
5835
  }
5835
5836
  return changed ? document2.toString() : html;
5836
5837
  }
5837
- async function compileHtmlFile(html, baseDir, downloadDir, log) {
5838
+ async function compileHtmlFile(html, baseDir, downloadDir, log, urlByAbsentLocalPath) {
5838
5839
  const { html: staticCompiled, unresolved } = compileTimingAttrs(html);
5839
5840
  const mediaUnresolved = unresolved.filter(
5840
5841
  (el) => (el.tagName === "video" || el.tagName === "audio") && el.src
@@ -5850,7 +5851,8 @@ async function compileHtmlFile(html, baseDir, downloadDir, log) {
5850
5851
  downloadDir,
5851
5852
  el.tagName,
5852
5853
  el.id,
5853
- log
5854
+ log,
5855
+ urlByAbsentLocalPath
5854
5856
  ).then(({ duration }) => ({ id: el.id, duration }))
5855
5857
  )
5856
5858
  );
@@ -5892,7 +5894,7 @@ async function compileHtmlFile(html, baseDir, downloadDir, log) {
5892
5894
  compiledHtml = compiledHtml.replace(/(<audio\b[^>]*)\s+crossorigin(?:=["'][^"']*["'])?/gi, "$1");
5893
5895
  return { html: compiledHtml, unresolvedCompositions };
5894
5896
  }
5895
- async function parseSubCompositions(html, projectDir, downloadDir, visited = /* @__PURE__ */ new Set()) {
5897
+ async function parseSubCompositions(html, projectDir, downloadDir, visited = /* @__PURE__ */ new Set(), urlByAbsentLocalPath) {
5896
5898
  const subCompositions = /* @__PURE__ */ new Map();
5897
5899
  const { document: document2 } = parseHTML(html);
5898
5900
  const compEls = document2.querySelectorAll("[data-composition-src]");
@@ -5918,13 +5920,16 @@ async function parseSubCompositions(html, projectDir, downloadDir, visited = /*
5918
5920
  const { html: compiledSub } = await compileHtmlFile(
5919
5921
  item.rawSubHtml,
5920
5922
  dirname5(item.filePath),
5921
- downloadDir
5923
+ downloadDir,
5924
+ void 0,
5925
+ urlByAbsentLocalPath
5922
5926
  );
5923
5927
  const nested = await parseSubCompositions(
5924
5928
  compiledSub,
5925
5929
  projectDir,
5926
5930
  downloadDir,
5927
- item.nestedVisited
5931
+ item.nestedVisited,
5932
+ urlByAbsentLocalPath
5928
5933
  );
5929
5934
  return {
5930
5935
  srcPath: item.srcPath,
@@ -6682,9 +6687,16 @@ async function compileForRender(projectDir, htmlPath, downloadDir, options = {})
6682
6687
  rawHtml,
6683
6688
  projectDir,
6684
6689
  downloadDir,
6685
- options.log
6690
+ options.log,
6691
+ options.urlByAbsentLocalPath
6692
+ );
6693
+ const { subCompositions } = await parseSubCompositions(
6694
+ compiledHtml,
6695
+ projectDir,
6696
+ downloadDir,
6697
+ void 0,
6698
+ options.urlByAbsentLocalPath
6686
6699
  );
6687
- const { subCompositions } = await parseSubCompositions(compiledHtml, projectDir, downloadDir);
6688
6700
  const fullHtml = ensureFullDocument(compiledHtml);
6689
6701
  const inlinedHtml = inlineSubCompositions2(
6690
6702
  fullHtml,
@@ -7180,6 +7192,7 @@ async function runCompileStage(input) {
7180
7192
  allowSystemFontCapture,
7181
7193
  abortSignal,
7182
7194
  variables: input.variables,
7195
+ urlByAbsentLocalPath: input.urlByAbsentLocalPath,
7183
7196
  animatedGifCacheDir: cfg.extractCacheDir ? join13(cfg.extractCacheDir, "animated-gif") : void 0,
7184
7197
  ffmpegProcessTimeout: cfg.ffmpegProcessTimeout
7185
7198
  });
@@ -8148,7 +8161,8 @@ async function runExtractVideosStage(input) {
8148
8161
  abortSignal,
8149
8162
  assertNotAborted,
8150
8163
  materializeSymlinks,
8151
- deferRangeExtraction
8164
+ deferRangeExtraction,
8165
+ urlByAbsentLocalPath
8152
8166
  } = input;
8153
8167
  const stage2Start = Date.now();
8154
8168
  const extractionPolicy = resolveVideoExtractionPolicy();
@@ -8242,7 +8256,8 @@ async function runExtractVideosStage(input) {
8242
8256
  maxTransientRetries: extractionPolicy.maxTransientRetries,
8243
8257
  collectProbeFailures: extractionPolicy.failureMode === "enforce",
8244
8258
  deferRangeExtraction,
8245
- contentKeyedDirs: [join15(compiledDir, REMOTE_MEDIA_SUBDIR)]
8259
+ contentKeyedDirs: [join15(compiledDir, REMOTE_MEDIA_SUBDIR)],
8260
+ urlByAbsentLocalPath
8246
8261
  },
8247
8262
  abortSignal,
8248
8263
  cfg,
@@ -8320,6 +8335,7 @@ async function runAudioStage(input) {
8320
8335
  audioGain,
8321
8336
  audios,
8322
8337
  masterFxChain,
8338
+ urlByAbsentLocalPath,
8323
8339
  abortSignal,
8324
8340
  assertNotAborted,
8325
8341
  log
@@ -8339,7 +8355,7 @@ async function runAudioStage(input) {
8339
8355
  audioOutputPath,
8340
8356
  duration,
8341
8357
  abortSignal,
8342
- { ffmpegProcessTimeout, audioGain, masterFxChain },
8358
+ { ffmpegProcessTimeout, audioGain, masterFxChain, urlByAbsentLocalPath },
8343
8359
  compiledDir
8344
8360
  );
8345
8361
  } catch (err) {
@@ -13151,14 +13167,14 @@ async function executeRenderPipeline(input) {
13151
13167
  import {
13152
13168
  copyFileSync as copyFileSync4,
13153
13169
  cpSync as cpSync2,
13154
- existsSync as existsSync19,
13170
+ existsSync as existsSync20,
13155
13171
  linkSync,
13156
- mkdirSync as mkdirSync10,
13157
- renameSync as renameSync4,
13158
- rmSync as rmSync9,
13159
- writeFileSync as writeFileSync7
13172
+ mkdirSync as mkdirSync12,
13173
+ renameSync as renameSync5,
13174
+ rmSync as rmSync10,
13175
+ writeFileSync as writeFileSync8
13160
13176
  } from "fs";
13161
- import { extname as extname6, isAbsolute as isAbsolute2, join as join31, relative as relative5, sep as sep2 } from "path";
13177
+ import { extname as extname6, isAbsolute as isAbsolute2, join as join33, relative as relative5, sep as sep2 } from "path";
13162
13178
 
13163
13179
  // ../producer/src/services/distributed/planFrameCost.ts
13164
13180
  import { join as join28 } from "path";
@@ -13539,6 +13555,8 @@ function buildChunkCaptureOptions(input) {
13539
13555
  // The controller's snapshot, so the first capture sees the in-process renderer's `__hfVariables`.
13540
13556
  variables: encoder.variables,
13541
13557
  subTimelineWaitMemo: {},
13558
+ skipReadinessVideoIds: input.skipReadinessVideoIds,
13559
+ videoMetadataHints: input.videoMetadataHints,
13542
13560
  ...videoCaptureBeyondViewport !== void 0 ? { captureBeyondViewport: videoCaptureBeyondViewport } : {},
13543
13561
  // A fixed warmup count keeps `beginFrameTimeTicks` host-independent.
13544
13562
  lockWarmupTicks: true,
@@ -13547,6 +13565,19 @@ function buildChunkCaptureOptions(input) {
13547
13565
  }
13548
13566
  async function extractDeferredVideoFramesForChunk(input) {
13549
13567
  const deferred = input.planVideos.extracted.filter((video) => video.deferredRange);
13568
+ const remoteSources = input.remoteSources ?? {};
13569
+ for (const path of Object.keys(remoteSources)) {
13570
+ if (!deferred.some((video) => video.deferredRange.sourcePath === path)) {
13571
+ throw new RenderChunkValidationError(
13572
+ MISSING_PLAN_ARTIFACT,
13573
+ `[renderChunk] ${path} is read by URL but no deferred video uses it`
13574
+ );
13575
+ }
13576
+ defaultLogger.info("[renderChunk] reading video by URL", {
13577
+ path,
13578
+ url: remoteSources[path].split("?")[0]
13579
+ });
13580
+ }
13550
13581
  if (deferred.length === 0) return [];
13551
13582
  const table = createFrameLookupTable(
13552
13583
  input.planVideos.videos,
@@ -13574,8 +13605,9 @@ async function extractDeferredVideoFramesForChunk(input) {
13574
13605
  return { ...video, outputDir, framePaths: /* @__PURE__ */ new Map(), ownedByLookup: false };
13575
13606
  }
13576
13607
  const range = video.deferredRange;
13577
- const sourcePath = join27(input.planDir, ...range.sourcePath.split("/"));
13578
- if (!existsSync17(sourcePath)) {
13608
+ const remoteUrl = remoteSources[range.sourcePath];
13609
+ const sourcePath = remoteUrl ?? join27(input.planDir, ...range.sourcePath.split("/"));
13610
+ if (remoteUrl === void 0 && !existsSync17(sourcePath)) {
13579
13611
  throw new RenderChunkValidationError(
13580
13612
  MISSING_PLAN_ARTIFACT,
13581
13613
  `[renderChunk] planDir is missing the source for video ${JSON.stringify(video.videoId)}: ${range.sourcePath}`
@@ -13915,6 +13947,16 @@ function collectPlanAssetShas(planDir, digests) {
13915
13947
  }
13916
13948
  return { compositionHtml, assets };
13917
13949
  }
13950
+ var PLAN_REMOTE_FILES_RELATIVE_PATH = "meta/remote-files.json";
13951
+ function writePlanRemoteFiles(planDir, files) {
13952
+ if (files.length === 0) return;
13953
+ const sorted = [...files].sort((a, b) => a.path < b.path ? -1 : 1);
13954
+ writeFileSync6(join29(planDir, PLAN_REMOTE_FILES_RELATIVE_PATH), JSON.stringify(sorted), "utf-8");
13955
+ }
13956
+ function readPlanRemoteFiles(planDir) {
13957
+ const path = join29(planDir, PLAN_REMOTE_FILES_RELATIVE_PATH);
13958
+ return existsSync18(path) ? JSON.parse(readFileSync9(path, "utf-8")) : [];
13959
+ }
13918
13960
  function recomputePlanHashFromPlanDir(planDir, digests) {
13919
13961
  const planJsonPath = join29(planDir, "plan.json");
13920
13962
  const encoderJsonPath = join29(planDir, "meta", "encoder.json");
@@ -14030,170 +14072,679 @@ function applyRuntimeEnvSnapshot(snapshot, env = process.env) {
14030
14072
  };
14031
14073
  }
14032
14074
 
14033
- // ../producer/src/services/distributed/planSize.ts
14034
- import { createHash as createHash6 } from "crypto";
14035
- import { lstatSync as lstatSync2, readdirSync as readdirSync8 } from "fs";
14036
- import { extname as extname5, join as join30, relative as relative4 } from "path";
14037
- var MAX_TOP_COMPONENTS = 10;
14038
- var SOURCE_MEDIA_EXTENSIONS = /* @__PURE__ */ new Set([
14039
- ".aac",
14040
- ".aiff",
14041
- ".avi",
14042
- ".flac",
14043
- ".m4a",
14044
- ".m4v",
14045
- ".mkv",
14046
- ".mov",
14047
- ".mp3",
14048
- ".mp4",
14049
- ".mpeg",
14050
- ".mpg",
14051
- ".ogg",
14052
- ".ogv",
14053
- ".ts",
14054
- ".wav",
14055
- ".webm"
14056
- ]);
14057
- var PLAN_ROOT_CATEGORY = {
14058
- compiled: "compiled",
14059
- [PLAN_AUDIO_RELATIVE_PATH]: "audio",
14060
- meta: "metadata",
14061
- "plan.json": "metadata"
14062
- };
14063
- function hashComponent(value) {
14064
- return createHash6("sha256").update(value).digest("hex").slice(0, 12);
14075
+ // ../producer/src/services/distributed/chunkAudio.ts
14076
+ import { closeSync as closeSync6, existsSync as existsSync19, ftruncateSync, mkdirSync as mkdirSync11, openSync as openSync5, readFileSync as readFileSync10 } from "fs";
14077
+ import { writeFileSync as writeFileSync7, writeSync } from "fs";
14078
+ import { join as join31 } from "path";
14079
+
14080
+ // ../producer/src/services/distributed/mp4Stitch.ts
14081
+ import {
14082
+ createReadStream as createReadStream4,
14083
+ createWriteStream,
14084
+ mkdirSync as mkdirSync10,
14085
+ mkdtempSync as mkdtempSync4,
14086
+ renameSync as renameSync4,
14087
+ rmSync as rmSync9
14088
+ } from "fs";
14089
+ import { open } from "fs/promises";
14090
+ import { dirname as dirname11, join as join30 } from "path";
14091
+ import { finished, pipeline } from "stream/promises";
14092
+ function boxAt(src, off, at, limit) {
14093
+ let size = src.readUInt32BE(off);
14094
+ let bodyStart = at + 8;
14095
+ if (size === 1) {
14096
+ size = Number(src.readBigUInt64BE(off + 8));
14097
+ bodyStart = at + 16;
14098
+ }
14099
+ if (size < 8 || at + size > limit) throw new Error(`[mp4Stitch] malformed box at ${at}`);
14100
+ return { type: src.toString("latin1", off + 4, off + 8), start: at, bodyStart, end: at + size };
14065
14101
  }
14066
- function classifyFile(segments, rootKind) {
14067
- const rootName = segments[0] ?? "";
14068
- const extractedRootName = rootKind === "compiled" ? "__hyperframes_video_frames" : "video-frames";
14069
- if (rootName === extractedRootName) {
14070
- const videoKey = segments[1] ?? "unknown";
14071
- return {
14072
- category: "video-frames",
14073
- componentLabel: `video-frames:${hashComponent(videoKey)}`
14074
- };
14102
+ function childBoxes(buf, parent) {
14103
+ const boxes = [];
14104
+ for (let at = parent.bodyStart; at < parent.end; at = boxes.at(-1).end) {
14105
+ boxes.push(boxAt(buf, at, at, parent.end));
14075
14106
  }
14076
- const category = rootKind === "compiled" ? "compiled" : PLAN_ROOT_CATEGORY[rootName] ?? "other";
14077
- return { category, componentLabel: category };
14107
+ return boxes;
14078
14108
  }
14079
- function emptyBreakdown() {
14080
- return {
14081
- totalBytes: 0,
14082
- fileCount: 0,
14083
- compiledBytes: 0,
14084
- sourceMediaBytes: 0,
14085
- videoFramesBytes: 0,
14086
- videoFrameFileCount: 0,
14087
- audioBytes: 0,
14088
- metadataBytes: 0,
14089
- otherBytes: 0,
14090
- topComponents: []
14091
- };
14109
+ function findChild(buf, parent, type) {
14110
+ return childBoxes(buf, parent).find((box2) => box2.type === type);
14092
14111
  }
14093
- function addCategoryBytes(result, category, filePath, sizeBytes) {
14094
- if (category === "compiled") {
14095
- result.compiledBytes += sizeBytes;
14096
- if (SOURCE_MEDIA_EXTENSIONS.has(extname5(filePath).toLowerCase())) {
14097
- result.sourceMediaBytes += sizeBytes;
14098
- }
14099
- return;
14100
- }
14101
- if (category === "video-frames") {
14102
- result.videoFramesBytes += sizeBytes;
14103
- result.videoFrameFileCount += 1;
14104
- return;
14105
- }
14106
- if (category === "audio") {
14107
- result.audioBytes += sizeBytes;
14108
- return;
14109
- }
14110
- if (category === "metadata") {
14111
- result.metadataBytes += sizeBytes;
14112
- return;
14113
- }
14114
- result.otherBytes += sizeBytes;
14112
+ function child(buf, parent, type) {
14113
+ const found = findChild(buf, parent, type);
14114
+ if (!found) throw new Error(`[mp4Stitch] ${parent.type} has no ${type}`);
14115
+ return found;
14115
14116
  }
14116
- function recordFile(result, components, rootDir, rootKind, filePath) {
14117
- let sizeBytes;
14118
- try {
14119
- const stat = lstatSync2(filePath);
14120
- if (!stat.isFile()) return;
14121
- sizeBytes = stat.size;
14122
- } catch {
14123
- return;
14124
- }
14125
- const rel = relative4(rootDir, filePath);
14126
- const segments = rel.split(/[\\/]+/).filter(Boolean);
14127
- const classified = classifyFile(segments, rootKind);
14128
- result.totalBytes += sizeBytes;
14129
- result.fileCount += 1;
14130
- addCategoryBytes(result, classified.category, filePath, sizeBytes);
14131
- const component = components.get(classified.componentLabel) ?? {
14132
- label: classified.componentLabel,
14133
- sizeBytes: 0,
14134
- fileCount: 0
14117
+ var SCAN_BYTES = 64 * 1024;
14118
+ async function readMp4Layout(read) {
14119
+ const found = /* @__PURE__ */ new Map();
14120
+ let payload;
14121
+ let buf = Buffer.alloc(0);
14122
+ let bufAt = 0;
14123
+ const load = async (start, end) => {
14124
+ if (start >= bufAt && end <= bufAt + buf.length) return;
14125
+ buf = await read(start, Math.max(end, start + SCAN_BYTES));
14126
+ bufAt = start;
14135
14127
  };
14136
- component.sizeBytes += sizeBytes;
14137
- component.fileCount += 1;
14138
- components.set(classified.componentLabel, component);
14139
- }
14140
- function walkRegularFiles(dir, visit) {
14141
- let entries;
14142
- try {
14143
- entries = readdirSync8(dir, { withFileTypes: true });
14144
- } catch {
14145
- return;
14146
- }
14147
- for (const entry of entries) {
14148
- const full = join30(dir, entry.name);
14149
- if (entry.isDirectory()) {
14150
- walkRegularFiles(full, visit);
14151
- } else if (entry.isFile()) {
14152
- visit(full);
14128
+ for (let at = 0; !(found.has("ftyp") && found.has("moov") && payload); ) {
14129
+ await load(at, at + 16);
14130
+ if (at + 8 > bufAt + buf.length) {
14131
+ throw new Error(`[mp4Stitch] chunk needs ftyp, moov and mdat; found ${[...found.keys()]}`);
14132
+ }
14133
+ const box2 = boxAt(buf, at - bufAt, at, Number.MAX_SAFE_INTEGER);
14134
+ if (box2.type === "mdat") payload ??= box2;
14135
+ if (box2.type === "ftyp" || box2.type === "moov") {
14136
+ await load(at, box2.end);
14137
+ found.set(box2.type, buf.subarray(at - bufAt, box2.end - bufAt));
14153
14138
  }
14139
+ at = box2.end;
14154
14140
  }
14141
+ const [ftyp, moov] = [found.get("ftyp"), found.get("moov")];
14142
+ if (moov.length !== moovSize(moov)) throw new Error("[mp4Stitch] moov is cut off");
14143
+ return { ftyp, moov, payloadStart: payload.bodyStart, payloadEnd: payload.end };
14155
14144
  }
14156
- function measurePlanSizeBreakdown(rootDir, rootKind = "plan") {
14157
- const result = emptyBreakdown();
14158
- const components = /* @__PURE__ */ new Map();
14159
- walkRegularFiles(
14160
- rootDir,
14161
- (filePath) => recordFile(result, components, rootDir, rootKind, filePath)
14145
+ var moovSize = (moov) => boxAt(moov, 0, 0, Number.MAX_SAFE_INTEGER).end;
14146
+ function readTracks(moov) {
14147
+ const root = boxAt(moov, 0, 0, moov.length);
14148
+ return childBoxes(moov, root).filter((box2) => box2.type === "trak").map((trak) => readTrack(moov, trak));
14149
+ }
14150
+ function rows(buf, table, columns, countAt = 4) {
14151
+ const count = buf.readUInt32BE(table.bodyStart + countAt);
14152
+ const first = table.bodyStart + countAt + 4;
14153
+ return Array.from(
14154
+ { length: count },
14155
+ (_, row) => Array.from(
14156
+ { length: columns },
14157
+ (_2, col) => buf.readUInt32BE(first + (row * columns + col) * 4)
14158
+ )
14162
14159
  );
14163
- result.topComponents = [...components.values()].sort((a, b) => b.sizeBytes - a.sizeBytes || a.label.localeCompare(b.label)).slice(0, MAX_TOP_COMPONENTS);
14164
- return result;
14165
14160
  }
14166
-
14167
- // ../producer/src/services/distributed/plan.ts
14168
- function logPlanStage(log, stage, startedAt) {
14169
- log.info("[plan] stage", { stage, ms: Date.now() - startedAt });
14161
+ function readMediaTime(moov, trak) {
14162
+ const edts = findChild(moov, trak, "edts");
14163
+ if (!edts) return 0;
14164
+ const elst = child(moov, edts, "elst");
14165
+ const wide = moov[elst.bodyStart] === 1;
14166
+ const entries = moov.readUInt32BE(elst.bodyStart + 4);
14167
+ for (let i = 0, at = elst.bodyStart + 8; i < entries; i++, at += wide ? 20 : 12) {
14168
+ const offset = at + (wide ? 8 : 4);
14169
+ const mediaTime = wide ? Number(moov.readBigInt64BE(offset)) : moov.readInt32BE(offset);
14170
+ if (mediaTime !== -1) return mediaTime;
14171
+ }
14172
+ return 0;
14170
14173
  }
14171
- function applyDistributedAudioWarningPolicy(job, audioError, audioFailures = [], log = defaultLogger) {
14172
- const failureOwner = audioFailures.length === 0 ? void 0 : audioFailures.some((failure) => failure.owner === "system") ? "system" : "user";
14173
- const retryable = audioFailures.length === 0 ? void 0 : audioFailures.every((failure) => failure.retryable);
14174
- applyRenderWarningPolicy(
14175
- job,
14176
- [
14177
- {
14178
- code: "audio_processing_failed",
14179
- message: `Audio mix failed; output would be video-only: ${audioError}`,
14180
- details: {
14181
- mediaType: "audio",
14182
- audioFailures: audioFailureDiagnostics(audioFailures),
14183
- failureReasons: [...new Set(audioFailures.map((failure) => failure.reason))],
14184
- failureStages: [...new Set(audioFailures.map((failure) => failure.stage))],
14185
- failureOwner,
14186
- retryable
14187
- }
14188
- }
14189
- ],
14190
- log
14191
- );
14174
+ function readSizes(moov, stsz) {
14175
+ const fixedSize = moov.readUInt32BE(stsz.bodyStart + 4);
14176
+ if (!fixedSize) return rows(moov, stsz, 1, 8).map(([size]) => size);
14177
+ return Array(moov.readUInt32BE(stsz.bodyStart + 8)).fill(fixedSize);
14192
14178
  }
14193
- var PLAN_PROJECT_DIR_SKIP_SEGMENTS = /* @__PURE__ */ new Set([
14194
- "node_modules",
14195
- ".git",
14196
- ".cache",
14179
+ function readSync4(moov, stbl, sampleCount) {
14180
+ const stss = findChild(moov, stbl, "stss");
14181
+ if (!stss) return null;
14182
+ const sync = Array(sampleCount).fill(false);
14183
+ for (const [n] of rows(moov, stss, 1)) sync[n - 1] = true;
14184
+ return sync;
14185
+ }
14186
+ function readChunkOffsets(moov, stbl) {
14187
+ const stco = findChild(moov, stbl, "stco");
14188
+ if (stco) return rows(moov, stco, 1).map(([offset]) => offset);
14189
+ return rows(moov, child(moov, stbl, "co64"), 2).map(([hi, lo]) => hi * 2 ** 32 + lo);
14190
+ }
14191
+ function readSamplesPerChunk(moov, stbl, chunkCount) {
14192
+ const stsc = rows(moov, child(moov, stbl, "stsc"), 3);
14193
+ return stsc.flatMap(([firstChunk, perChunk, description], i) => {
14194
+ if (description !== 1) throw new Error("[mp4Stitch] chunk uses more than one sample entry");
14195
+ const nextFirst = stsc[i + 1]?.[0] ?? chunkCount + 1;
14196
+ return Array(nextFirst - firstChunk).fill(perChunk);
14197
+ });
14198
+ }
14199
+ function readTrack(moov, trak) {
14200
+ const mdia = child(moov, trak, "mdia");
14201
+ const hdlr = child(moov, mdia, "hdlr");
14202
+ const stbl = child(moov, child(moov, mdia, "minf"), "stbl");
14203
+ if (findChild(moov, stbl, "ctts")) throw new Error("[mp4Stitch] chunk has ctts (B-frames)");
14204
+ const stts = rows(moov, child(moov, stbl, "stts"), 2);
14205
+ const durations = stts.flatMap(([n, delta]) => Array(n).fill(delta));
14206
+ const sizes = readSizes(moov, child(moov, stbl, "stsz"));
14207
+ const chunkOffsets = readChunkOffsets(moov, stbl);
14208
+ const samplesPerChunk = readSamplesPerChunk(moov, stbl, chunkOffsets.length);
14209
+ if (durations.length !== sizes.length || sum(samplesPerChunk) !== sizes.length) {
14210
+ throw new Error("[mp4Stitch] sample tables disagree on the sample count");
14211
+ }
14212
+ const stsd = child(moov, stbl, "stsd");
14213
+ return {
14214
+ trak,
14215
+ handler: moov.toString("latin1", hdlr.bodyStart + 8, hdlr.bodyStart + 12),
14216
+ timescale: timescaleOf(moov, child(moov, mdia, "mdhd")),
14217
+ mediaTime: readMediaTime(moov, trak),
14218
+ stsd: moov.subarray(stsd.start, stsd.end),
14219
+ durations,
14220
+ sizes,
14221
+ sync: readSync4(moov, stbl, sizes.length),
14222
+ samplesPerChunk,
14223
+ chunkOffsets
14224
+ };
14225
+ }
14226
+ function sum(values) {
14227
+ return values.reduce((total, value) => total + value, 0);
14228
+ }
14229
+ function box(type, ...body) {
14230
+ const header = Buffer.alloc(8);
14231
+ header.writeUInt32BE(8 + sum(body.map((part) => part.length)));
14232
+ header.write(type, 4, "latin1");
14233
+ return Buffer.concat([header, ...body]);
14234
+ }
14235
+ function fullBox(type, version, body) {
14236
+ return box(type, Buffer.from([version, 0, 0, 0]), body);
14237
+ }
14238
+ function u32s(values) {
14239
+ const buf = Buffer.alloc(values.length * 4);
14240
+ values.forEach((value, i) => buf.writeUInt32BE(value, i * 4));
14241
+ return buf;
14242
+ }
14243
+ function runs(values) {
14244
+ const out = [];
14245
+ for (const value of values) {
14246
+ const last = out.at(-1);
14247
+ if (last && last[1] === value) last[0]++;
14248
+ else out.push([1, value]);
14249
+ }
14250
+ return out;
14251
+ }
14252
+ function rebuild(buf, parent, replace) {
14253
+ const parts = childBoxes(buf, parent).map(
14254
+ (box2) => replace[box2.type]?.(box2) ?? buf.subarray(box2.start, box2.end)
14255
+ );
14256
+ return box(parent.type, ...parts);
14257
+ }
14258
+ var DURATION_AT = {
14259
+ mvhd: [16, 24],
14260
+ mdhd: [16, 24],
14261
+ tkhd: [20, 28]
14262
+ };
14263
+ function withDuration(buf, target, duration) {
14264
+ const copy = Buffer.from(buf.subarray(target.start, target.end));
14265
+ const body = target.bodyStart - target.start;
14266
+ const wide = copy[body] === 1;
14267
+ const at = body + DURATION_AT[target.type][wide ? 1 : 0];
14268
+ if (wide) copy.writeBigUInt64BE(BigInt(duration), at);
14269
+ else if (duration <= 4294967295) copy.writeUInt32BE(duration, at);
14270
+ else throw new Error(`[mp4Stitch] ${target.type} duration ${duration} needs a version 1 box`);
14271
+ return copy;
14272
+ }
14273
+ function timescaleOf(buf, header) {
14274
+ return buf.readUInt32BE(header.bodyStart + (buf[header.bodyStart] === 1 ? 20 : 12));
14275
+ }
14276
+ function buildStbl(stsd, track) {
14277
+ const stts = runs(track.durations);
14278
+ const stsc = [];
14279
+ let firstChunk = 1;
14280
+ for (const [count, perChunk] of runs(track.samplesPerChunk)) {
14281
+ stsc.push([firstChunk, perChunk, 1]);
14282
+ firstChunk += count;
14283
+ }
14284
+ const co64 = Buffer.alloc(4 + track.chunkOffsets.length * 8);
14285
+ co64.writeUInt32BE(track.chunkOffsets.length);
14286
+ track.chunkOffsets.forEach((offset, i) => co64.writeBigUInt64BE(BigInt(offset), 4 + i * 8));
14287
+ const syncSamples = track.sync?.flatMap((isSync, i) => isSync ? [i + 1] : []) ?? null;
14288
+ return box(
14289
+ "stbl",
14290
+ stsd,
14291
+ fullBox("stts", 0, u32s([stts.length, ...stts.flat()])),
14292
+ ...syncSamples ? [fullBox("stss", 0, u32s([syncSamples.length, ...syncSamples]))] : [],
14293
+ fullBox("stsc", 0, u32s([stsc.length, ...stsc.flat()])),
14294
+ fullBox("stsz", 0, u32s([0, track.sizes.length, ...track.sizes])),
14295
+ fullBox("co64", 0, co64)
14296
+ );
14297
+ }
14298
+ var codecOf = (track) => track.stsd.toString("latin1", 20, 24);
14299
+ var sameTrack = (track, plan2) => track.handler === plan2.handler && track.timescale === plan2.timescale && codecOf(track) === codecOf(plan2);
14300
+ function planTracksOf(tracks, plan2, index) {
14301
+ let next = 0;
14302
+ const matched = plan2.map((planTrack) => {
14303
+ const track = tracks[next];
14304
+ if (track && sameTrack(track, planTrack)) return tracks[next++];
14305
+ if (planTrack.handler === "soun") return null;
14306
+ throw new Error(`[mp4Stitch] chunk ${index} tracks differ from chunk 0`);
14307
+ });
14308
+ if (next !== tracks.length)
14309
+ throw new Error(`[mp4Stitch] chunk ${index} tracks differ from chunk 0`);
14310
+ for (const track of tracks) {
14311
+ if (track.handler === "vide" && track.mediaTime !== 0) {
14312
+ throw new Error(`[mp4Stitch] chunk ${index} video starts at media time ${track.mediaTime}`);
14313
+ }
14314
+ }
14315
+ return matched;
14316
+ }
14317
+ function buildStitchedHeader(layouts) {
14318
+ const plan2 = readTracks(layouts[0].moov);
14319
+ const chunks = layouts.map((layout, i) => planTracksOf(readTracks(layout.moov), plan2, i));
14320
+ const payloadBases = [];
14321
+ let payloadBytes = 0;
14322
+ for (const layout of layouts) {
14323
+ payloadBases.push(payloadBytes);
14324
+ payloadBytes += layout.payloadEnd - layout.payloadStart;
14325
+ }
14326
+ const template = layouts[0].moov;
14327
+ const root = boxAt(template, 0, 0, template.length);
14328
+ const movieTimescale = timescaleOf(template, child(template, root, "mvhd"));
14329
+ const buildMoov = (dataStart2) => {
14330
+ const traks = plan2.map((track, t) => {
14331
+ const parts = chunks.flatMap((tracks, chunk) => {
14332
+ const part = tracks[t];
14333
+ return part ? [{ ...part, chunk }] : [];
14334
+ });
14335
+ const merged = {
14336
+ durations: parts.flatMap((part) => part.durations),
14337
+ sizes: parts.flatMap((part) => part.sizes),
14338
+ sync: parts.every((part) => !part.sync) ? null : parts.flatMap((part) => part.sync ?? part.sizes.map(() => true)),
14339
+ samplesPerChunk: parts.flatMap((part) => part.samplesPerChunk),
14340
+ chunkOffsets: parts.flatMap(
14341
+ (part) => part.chunkOffsets.map((offset) => {
14342
+ const i = part.chunk;
14343
+ const { payloadStart, payloadEnd } = layouts[i];
14344
+ if (offset < payloadStart || offset >= payloadEnd) {
14345
+ throw new Error(`[mp4Stitch] chunk ${i} has samples outside its mdat`);
14346
+ }
14347
+ return offset - payloadStart + payloadBases[i] + dataStart2;
14348
+ })
14349
+ )
14350
+ };
14351
+ const mediaDuration = sum(merged.durations);
14352
+ const shown = Math.round(
14353
+ (mediaDuration - track.mediaTime) * movieTimescale / track.timescale
14354
+ );
14355
+ const elst = Buffer.alloc(24);
14356
+ elst.writeUInt32BE(1);
14357
+ elst.writeBigUInt64BE(BigInt(shown), 4);
14358
+ elst.writeBigInt64BE(BigInt(track.mediaTime), 12);
14359
+ elst.writeUInt32BE(65536, 20);
14360
+ const edts = track.mediaTime ? box("edts", fullBox("elst", 1, elst)) : Buffer.alloc(0);
14361
+ const trak = rebuild(template, track.trak, {
14362
+ tkhd: (tkhd) => Buffer.concat([withDuration(template, tkhd, shown), edts]),
14363
+ edts: () => Buffer.alloc(0),
14364
+ mdia: (mdia) => rebuild(template, mdia, {
14365
+ mdhd: (mdhd) => withDuration(template, mdhd, mediaDuration),
14366
+ minf: (minf) => rebuild(template, minf, { stbl: () => buildStbl(track.stsd, merged) })
14367
+ })
14368
+ });
14369
+ return { trak, shown };
14370
+ });
14371
+ let next = 0;
14372
+ return rebuild(template, root, {
14373
+ mvhd: (mvhd) => withDuration(template, mvhd, Math.max(...traks.map((t) => t.shown))),
14374
+ trak: () => traks[next++].trak
14375
+ });
14376
+ };
14377
+ const ftyp = layouts[0].ftyp;
14378
+ const dataStart = ftyp.length + buildMoov(0).length + 16;
14379
+ const mdat = Buffer.alloc(16);
14380
+ mdat.writeUInt32BE(1);
14381
+ mdat.write("mdat", 4, "latin1");
14382
+ mdat.writeBigUInt64BE(BigInt(16 + payloadBytes), 8);
14383
+ return Buffer.concat([ftyp, buildMoov(dataStart), mdat]);
14384
+ }
14385
+ function fileRange(path) {
14386
+ return async (start, end) => {
14387
+ const file = await open(path, "r", 384);
14388
+ try {
14389
+ const buf = Buffer.alloc(end - start);
14390
+ const { bytesRead } = await file.read(buf, 0, buf.length, start);
14391
+ return buf.subarray(0, bytesRead);
14392
+ } finally {
14393
+ await file.close();
14394
+ }
14395
+ };
14396
+ }
14397
+ async function copyPayload(path, layout, index, out) {
14398
+ const { payloadStart, payloadEnd } = layout;
14399
+ const payload = createReadStream4(path, { start: payloadStart, end: payloadEnd - 1 });
14400
+ await pipeline(payload, out, { end: false });
14401
+ if (payload.bytesRead !== payloadEnd - payloadStart) {
14402
+ throw new Error(
14403
+ `[mp4Stitch] chunk ${index} mdat holds ${payload.bytesRead} of ${payloadEnd - payloadStart} payload bytes`
14404
+ );
14405
+ }
14406
+ }
14407
+ async function writeStitchedMp4(chunkPaths, outputPath) {
14408
+ const layouts = await Promise.all(chunkPaths.map((path) => readMp4Layout(fileRange(path))));
14409
+ const header = buildStitchedHeader(layouts);
14410
+ mkdirSync10(dirname11(outputPath), { recursive: true });
14411
+ const workDir = mkdtempSync4(`${outputPath}.stitch-`);
14412
+ const out = createWriteStream(join30(workDir, "out.mp4"));
14413
+ try {
14414
+ out.write(header);
14415
+ for (const [i, path] of chunkPaths.entries()) await copyPayload(path, layouts[i], i, out);
14416
+ out.end();
14417
+ await finished(out);
14418
+ renameSync4(join30(workDir, "out.mp4"), outputPath);
14419
+ } finally {
14420
+ out.destroy();
14421
+ rmSync9(workDir, { recursive: true, force: true });
14422
+ }
14423
+ }
14424
+
14425
+ // ../producer/src/services/distributed/chunkAudio.ts
14426
+ function audioCutForChunk(grid, chunks, index, fps) {
14427
+ const firstPacketAt = (frame) => Math.min(
14428
+ grid.packetCount,
14429
+ Math.max(
14430
+ 0,
14431
+ Math.ceil(
14432
+ (frame * fps.den * grid.sampleRate - grid.firstStart * fps.num) / (grid.packetSamples * fps.num)
14433
+ )
14434
+ )
14435
+ );
14436
+ const first = index === 0 ? 0 : firstPacketAt(chunks[index].startFrame);
14437
+ const end = index === chunks.length - 1 ? grid.packetCount : firstPacketAt(chunks[index + 1].startFrame);
14438
+ if (end <= first) return null;
14439
+ const cutSeconds = (packet) => (grid.firstStart + packet * grid.packetSamples - grid.packetSamples / 2) / grid.sampleRate;
14440
+ const seekSeconds = index === 0 ? null : cutSeconds(first);
14441
+ const durationSeconds = end === grid.packetCount ? null : cutSeconds(end) - (seekSeconds ?? 0);
14442
+ return { seekSeconds, durationSeconds, firstPacket: first, packetCount: end - first };
14443
+ }
14444
+ async function probeAacPacketGrid(audioPath) {
14445
+ const probe = await runFfprobeJson([
14446
+ "-v",
14447
+ "error",
14448
+ "-select_streams",
14449
+ "a:0",
14450
+ "-show_entries",
14451
+ "stream=sample_rate,time_base,nb_frames:packet=pts,duration",
14452
+ "-read_intervals",
14453
+ "%+#1",
14454
+ "-of",
14455
+ "json",
14456
+ "--",
14457
+ audioPath
14458
+ ]);
14459
+ const stream = probe.streams?.[0] ?? {};
14460
+ const packet = probe.packets?.[0] ?? {};
14461
+ const sampleRate = Number(stream.sample_rate);
14462
+ const [tbNum = NaN, tbDen = NaN] = String(stream.time_base).split("/").map(Number);
14463
+ const toSamples = (ticks) => Number(ticks) * sampleRate * tbNum / tbDen;
14464
+ const grid = {
14465
+ sampleRate,
14466
+ firstStart: toSamples(packet.pts),
14467
+ packetSamples: toSamples(packet.duration),
14468
+ packetCount: Number(stream.nb_frames)
14469
+ };
14470
+ const counts = [grid.sampleRate, grid.packetSamples, grid.packetCount];
14471
+ if (!Number.isFinite(grid.firstStart) || !counts.every((n) => Number.isFinite(n) && n > 0)) {
14472
+ throw new Error(`[chunkAudio] could not read the AAC packet grid: ${JSON.stringify(probe)}`);
14473
+ }
14474
+ return grid;
14475
+ }
14476
+ var SLICE_DIR = "audio-chunks";
14477
+ var SLICE_FRAME = `${SLICE_DIR}/frame.m4a`;
14478
+ var SLICE_LAYOUT = `${SLICE_DIR}/layout.json`;
14479
+ function chunkAudioSlicePath(index) {
14480
+ return `${SLICE_DIR}/${String(index).padStart(4, "0")}.bin`;
14481
+ }
14482
+ function chunkAudioSliceTarget(path) {
14483
+ if (path === SLICE_FRAME || path === SLICE_LAYOUT) return "all";
14484
+ const match = /^audio-chunks\/(\d{4,})\.bin$/.exec(path);
14485
+ return match ? Number(match[1]) : null;
14486
+ }
14487
+ async function writeChunkAudioSlices(input) {
14488
+ const grid = await probeAacPacketGrid(input.audioPath);
14489
+ const { payloadStart, payloadEnd, moov } = await readMp4Layout(fileRange(input.audioPath));
14490
+ const [track] = readTracks(moov);
14491
+ const offsets = [];
14492
+ track.chunkOffsets.forEach((offset, chunk) => {
14493
+ for (let i = 0; i < track.samplesPerChunk[chunk]; i++) {
14494
+ offsets.push(offset);
14495
+ offset += track.sizes[offsets.length - 1];
14496
+ }
14497
+ });
14498
+ const bytes = readFileSync10(input.audioPath);
14499
+ const layout = { sizeBytes: bytes.length, payloadStart, payloadEnd, partStarts: {} };
14500
+ mkdirSync11(join31(input.planDir, SLICE_DIR), { recursive: true });
14501
+ input.chunks.forEach((_, index) => {
14502
+ const cut = audioCutForChunk(grid, input.chunks, index, input.fps);
14503
+ if (!cut) return;
14504
+ const last = cut.firstPacket + cut.packetCount - 1;
14505
+ const start = offsets[cut.firstPacket];
14506
+ layout.partStarts[index] = start;
14507
+ const end = offsets[last] + track.sizes[last];
14508
+ writeFileSync7(join31(input.planDir, chunkAudioSlicePath(index)), bytes.subarray(start, end));
14509
+ });
14510
+ const frame = [bytes.subarray(0, payloadStart), bytes.subarray(payloadEnd)];
14511
+ writeFileSync7(join31(input.planDir, SLICE_FRAME), Buffer.concat(frame));
14512
+ writeFileSync7(join31(input.planDir, SLICE_LAYOUT), JSON.stringify(layout));
14513
+ }
14514
+ function chunkAudioSource(planDir, chunkIndex, scratchPath) {
14515
+ const layoutPath = join31(planDir, SLICE_LAYOUT);
14516
+ if (!existsSync19(layoutPath)) return resolvePlanAudioPath(planDir);
14517
+ const layout = JSON.parse(readFileSync10(layoutPath, "utf-8"));
14518
+ const frame = readFileSync10(join31(planDir, SLICE_FRAME));
14519
+ const fd = openSync5(scratchPath, "w");
14520
+ try {
14521
+ writeSync(fd, frame, 0, layout.payloadStart, 0);
14522
+ writeSync(
14523
+ fd,
14524
+ frame,
14525
+ layout.payloadStart,
14526
+ frame.length - layout.payloadStart,
14527
+ layout.payloadEnd
14528
+ );
14529
+ const start = layout.partStarts[chunkIndex];
14530
+ if (start !== void 0) {
14531
+ const part = readFileSync10(join31(planDir, chunkAudioSlicePath(chunkIndex)));
14532
+ writeSync(fd, part, 0, part.length, start);
14533
+ }
14534
+ ftruncateSync(fd, layout.sizeBytes);
14535
+ } finally {
14536
+ closeSync6(fd);
14537
+ }
14538
+ return scratchPath;
14539
+ }
14540
+ function chunkMuxArgs(input, cut) {
14541
+ if (!cut) return ["-i", input.videoPath, "-map", "0:v:0"];
14542
+ const seek = cut.seekSeconds === null ? [] : ["-ss", cut.seekSeconds.toFixed(6)];
14543
+ const length = cut.durationSeconds === null ? [] : ["-t", cut.durationSeconds.toFixed(6)];
14544
+ const exact = seek.length ? ["-copypriorss", "0"] : [];
14545
+ return [
14546
+ ...["-i", input.videoPath, ...seek, ...length, "-i", input.audioPath],
14547
+ ...["-map", "0:v:0", "-map", "1:a:0", ...exact]
14548
+ ];
14549
+ }
14550
+ async function muxChunkWithAudio(input) {
14551
+ const grid = input.audioPath ? await probeAacPacketGrid(input.audioPath) : null;
14552
+ const cut = grid ? audioCutForChunk(grid, input.chunks, input.chunkIndex, input.fps) : null;
14553
+ const args = [...chunkMuxArgs(input, cut), "-c", "copy"];
14554
+ appendRenderProvenanceArgs(args, input.outputPath);
14555
+ args.push("-y", input.outputPath);
14556
+ const result = await runFfmpeg(args, { signal: input.signal, timeout: input.timeoutMs });
14557
+ if (!result.success) {
14558
+ throw new Error(
14559
+ `[chunkAudio] chunk mux failed: ${describeFfmpegFailure(result, input.timeoutMs)}`
14560
+ );
14561
+ }
14562
+ const slice = input.chunks[input.chunkIndex];
14563
+ const expected = { video: slice.endFrame - slice.startFrame };
14564
+ if (cut) expected.audio = cut.packetCount;
14565
+ const probe = await runFfprobeJson([
14566
+ "-v",
14567
+ "error",
14568
+ "-count_packets",
14569
+ "-show_entries",
14570
+ "stream=codec_type,nb_read_packets",
14571
+ "-of",
14572
+ "json",
14573
+ "--",
14574
+ input.outputPath
14575
+ ]);
14576
+ for (const [type, count] of Object.entries(expected)) {
14577
+ const packets = Number(probe.streams?.find((s) => s.codec_type === type)?.nb_read_packets ?? 0);
14578
+ if (packets !== count) {
14579
+ throw new Error(`[chunkAudio] ${type} packets in chunk ${packets}, expected ${count}`);
14580
+ }
14581
+ }
14582
+ }
14583
+
14584
+ // ../producer/src/services/distributed/planSize.ts
14585
+ import { createHash as createHash6 } from "crypto";
14586
+ import { lstatSync as lstatSync2, readdirSync as readdirSync8 } from "fs";
14587
+ import { extname as extname5, join as join32, relative as relative4 } from "path";
14588
+ var MAX_TOP_COMPONENTS = 10;
14589
+ var SOURCE_MEDIA_EXTENSIONS = /* @__PURE__ */ new Set([
14590
+ ".aac",
14591
+ ".aiff",
14592
+ ".avi",
14593
+ ".flac",
14594
+ ".m4a",
14595
+ ".m4v",
14596
+ ".mkv",
14597
+ ".mov",
14598
+ ".mp3",
14599
+ ".mp4",
14600
+ ".mpeg",
14601
+ ".mpg",
14602
+ ".ogg",
14603
+ ".ogv",
14604
+ ".ts",
14605
+ ".wav",
14606
+ ".webm"
14607
+ ]);
14608
+ var PLAN_ROOT_CATEGORY = {
14609
+ compiled: "compiled",
14610
+ [PLAN_AUDIO_RELATIVE_PATH]: "audio",
14611
+ meta: "metadata",
14612
+ "plan.json": "metadata"
14613
+ };
14614
+ function hashComponent(value) {
14615
+ return createHash6("sha256").update(value).digest("hex").slice(0, 12);
14616
+ }
14617
+ function classifyFile(segments, rootKind) {
14618
+ const rootName = segments[0] ?? "";
14619
+ const extractedRootName = rootKind === "compiled" ? "__hyperframes_video_frames" : "video-frames";
14620
+ if (rootName === extractedRootName) {
14621
+ const videoKey = segments[1] ?? "unknown";
14622
+ return {
14623
+ category: "video-frames",
14624
+ componentLabel: `video-frames:${hashComponent(videoKey)}`
14625
+ };
14626
+ }
14627
+ const category = rootKind === "compiled" ? "compiled" : PLAN_ROOT_CATEGORY[rootName] ?? "other";
14628
+ return { category, componentLabel: category };
14629
+ }
14630
+ function emptyBreakdown() {
14631
+ return {
14632
+ totalBytes: 0,
14633
+ fileCount: 0,
14634
+ compiledBytes: 0,
14635
+ sourceMediaBytes: 0,
14636
+ videoFramesBytes: 0,
14637
+ videoFrameFileCount: 0,
14638
+ audioBytes: 0,
14639
+ metadataBytes: 0,
14640
+ otherBytes: 0,
14641
+ topComponents: []
14642
+ };
14643
+ }
14644
+ function addCategoryBytes(result, category, filePath, sizeBytes) {
14645
+ if (category === "compiled") {
14646
+ result.compiledBytes += sizeBytes;
14647
+ if (SOURCE_MEDIA_EXTENSIONS.has(extname5(filePath).toLowerCase())) {
14648
+ result.sourceMediaBytes += sizeBytes;
14649
+ }
14650
+ return;
14651
+ }
14652
+ if (category === "video-frames") {
14653
+ result.videoFramesBytes += sizeBytes;
14654
+ result.videoFrameFileCount += 1;
14655
+ return;
14656
+ }
14657
+ if (category === "audio") {
14658
+ result.audioBytes += sizeBytes;
14659
+ return;
14660
+ }
14661
+ if (category === "metadata") {
14662
+ result.metadataBytes += sizeBytes;
14663
+ return;
14664
+ }
14665
+ result.otherBytes += sizeBytes;
14666
+ }
14667
+ function recordFile(result, components, rootDir, rootKind, filePath) {
14668
+ let sizeBytes;
14669
+ try {
14670
+ const stat = lstatSync2(filePath);
14671
+ if (!stat.isFile()) return;
14672
+ sizeBytes = stat.size;
14673
+ } catch {
14674
+ return;
14675
+ }
14676
+ const rel = relative4(rootDir, filePath);
14677
+ const segments = rel.split(/[\\/]+/).filter(Boolean);
14678
+ const classified = classifyFile(segments, rootKind);
14679
+ result.totalBytes += sizeBytes;
14680
+ result.fileCount += 1;
14681
+ addCategoryBytes(result, classified.category, filePath, sizeBytes);
14682
+ const component = components.get(classified.componentLabel) ?? {
14683
+ label: classified.componentLabel,
14684
+ sizeBytes: 0,
14685
+ fileCount: 0
14686
+ };
14687
+ component.sizeBytes += sizeBytes;
14688
+ component.fileCount += 1;
14689
+ components.set(classified.componentLabel, component);
14690
+ }
14691
+ function walkRegularFiles(dir, visit) {
14692
+ let entries;
14693
+ try {
14694
+ entries = readdirSync8(dir, { withFileTypes: true });
14695
+ } catch {
14696
+ return;
14697
+ }
14698
+ for (const entry of entries) {
14699
+ const full = join32(dir, entry.name);
14700
+ if (entry.isDirectory()) {
14701
+ walkRegularFiles(full, visit);
14702
+ } else if (entry.isFile()) {
14703
+ visit(full);
14704
+ }
14705
+ }
14706
+ }
14707
+ function measurePlanSizeBreakdown(rootDir, rootKind = "plan") {
14708
+ const result = emptyBreakdown();
14709
+ const components = /* @__PURE__ */ new Map();
14710
+ walkRegularFiles(
14711
+ rootDir,
14712
+ (filePath) => recordFile(result, components, rootDir, rootKind, filePath)
14713
+ );
14714
+ result.topComponents = [...components.values()].sort((a, b) => b.sizeBytes - a.sizeBytes || a.label.localeCompare(b.label)).slice(0, MAX_TOP_COMPONENTS);
14715
+ return result;
14716
+ }
14717
+
14718
+ // ../producer/src/services/distributed/plan.ts
14719
+ function logPlanStage(log, stage, startedAt) {
14720
+ log.info("[plan] stage", { stage, ms: Date.now() - startedAt });
14721
+ }
14722
+ function applyDistributedAudioWarningPolicy(job, audioError, audioFailures = [], log = defaultLogger) {
14723
+ const failureOwner = audioFailures.length === 0 ? void 0 : audioFailures.some((failure) => failure.owner === "system") ? "system" : "user";
14724
+ const retryable = audioFailures.length === 0 ? void 0 : audioFailures.every((failure) => failure.retryable);
14725
+ applyRenderWarningPolicy(
14726
+ job,
14727
+ [
14728
+ {
14729
+ code: "audio_processing_failed",
14730
+ message: `Audio mix failed; output would be video-only: ${audioError}`,
14731
+ details: {
14732
+ mediaType: "audio",
14733
+ audioFailures: audioFailureDiagnostics(audioFailures),
14734
+ failureReasons: [...new Set(audioFailures.map((failure) => failure.reason))],
14735
+ failureStages: [...new Set(audioFailures.map((failure) => failure.stage))],
14736
+ failureOwner,
14737
+ retryable
14738
+ }
14739
+ }
14740
+ ],
14741
+ log
14742
+ );
14743
+ }
14744
+ var PLAN_PROJECT_DIR_SKIP_SEGMENTS = /* @__PURE__ */ new Set([
14745
+ "node_modules",
14746
+ ".git",
14747
+ ".cache",
14197
14748
  "output",
14198
14749
  "failures",
14199
14750
  "dist",
@@ -14355,13 +14906,13 @@ function assertPositiveInteger2(name, value) {
14355
14906
  }
14356
14907
  var FREEZE_OWNED_PLAN_FILES = [
14357
14908
  "plan.json",
14358
- join31("meta", "composition.json"),
14359
- join31("meta", "encoder.json"),
14360
- join31("meta", "chunks.json")
14909
+ join33("meta", "composition.json"),
14910
+ join33("meta", "encoder.json"),
14911
+ join33("meta", "chunks.json")
14361
14912
  ];
14362
14913
  function removeFreezeOwnedPlanFiles(planDir) {
14363
14914
  for (const relativePath of FREEZE_OWNED_PLAN_FILES) {
14364
- rmSync9(join31(planDir, relativePath), { force: true });
14915
+ rmSync10(join33(planDir, relativePath), { force: true });
14365
14916
  }
14366
14917
  }
14367
14918
  function buildChunkSlices(totalFrames, chunkCount, effectiveChunkSize) {
@@ -14492,19 +15043,19 @@ function createVideoSourceShipper(input) {
14492
15043
  const known = shipped.get(srcPath);
14493
15044
  if (known) return known;
14494
15045
  const stagedRel = pathInside(input.workVideoFramesDir, srcPath);
14495
- const current = stagedRel === null ? srcPath : join31(input.planDir, "video-frames", stagedRel);
15046
+ const current = stagedRel === null ? srcPath : join33(input.planDir, "video-frames", stagedRel);
14496
15047
  let result = null;
14497
15048
  for (const root of stagedRel === null ? [input.workCompiledDir, input.projectDir] : []) {
14498
15049
  const rel = pathInside(root, srcPath);
14499
- if (rel !== null && existsSync19(join31(input.planDir, "compiled", rel))) {
15050
+ if (rel !== null && (existsSync20(join33(input.planDir, "compiled", rel)) || input.isRemote(srcPath))) {
14500
15051
  result = ["compiled", ...rel.split(sep2)].join("/");
14501
15052
  break;
14502
15053
  }
14503
15054
  }
14504
15055
  if (result === null) {
14505
15056
  result = `video-sources/${shipped.size}${extname6(current)}`;
14506
- const destination = join31(input.planDir, ...result.split("/"));
14507
- mkdirSync10(join31(input.planDir, "video-sources"), { recursive: true });
15057
+ const destination = join33(input.planDir, ...result.split("/"));
15058
+ mkdirSync12(join33(input.planDir, "video-sources"), { recursive: true });
14508
15059
  try {
14509
15060
  linkSync(current, destination);
14510
15061
  } catch {
@@ -14522,7 +15073,7 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
14522
15073
  useGpu: false,
14523
15074
  browserGpuMode: "software"
14524
15075
  });
14525
- if (!existsSync19(planDir)) mkdirSync10(planDir, { recursive: true });
15076
+ if (!existsSync20(planDir)) mkdirSync12(planDir, { recursive: true });
14526
15077
  const log = config.logger ?? defaultLogger;
14527
15078
  const sizeLimitBytes = options.executionPlanSizeLimitBytes ?? config.planDirSizeLimitBytes ?? PLAN_DIR_SIZE_LIMIT_BYTES;
14528
15079
  const abortSignal = config.abortSignal;
@@ -14551,15 +15102,15 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
14551
15102
  variables: config.variables
14552
15103
  });
14553
15104
  const entryFile = config.entryFile ?? "index.html";
14554
- const htmlPath = join31(projectDir, entryFile);
14555
- if (!existsSync19(htmlPath)) {
15105
+ const htmlPath = join33(projectDir, entryFile);
15106
+ if (!existsSync20(htmlPath)) {
14556
15107
  throw new Error(`[plan] entry file not found: ${htmlPath}`);
14557
15108
  }
14558
- const workDir = join31(planDir, ".plan-work");
14559
- rmSync9(workDir, { recursive: true, force: true });
14560
- mkdirSync10(workDir, { recursive: true });
14561
- const compiledDir = join31(workDir, "compiled");
14562
- mkdirSync10(compiledDir, { recursive: true });
15109
+ const workDir = join33(planDir, ".plan-work");
15110
+ rmSync10(workDir, { recursive: true, force: true });
15111
+ mkdirSync12(workDir, { recursive: true });
15112
+ const compiledDir = join33(workDir, "compiled");
15113
+ mkdirSync12(compiledDir, { recursive: true });
14563
15114
  let stageStarted = Date.now();
14564
15115
  cpSync2(projectDir, compiledDir, {
14565
15116
  recursive: true,
@@ -14572,10 +15123,19 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
14572
15123
  }
14573
15124
  });
14574
15125
  logPlanStage(log, "copy", stageStarted);
14575
- const finalCompiledDir = join31(planDir, "compiled");
15126
+ const finalCompiledDir = join33(planDir, "compiled");
14576
15127
  const needsAlpha = outputNeedsAlpha(config.format);
15128
+ const remoteFiles = Object.entries(options.remoteFiles ?? {});
15129
+ const remoteLocalPath = (rel) => join33(projectDir, ...rel.split("/"));
15130
+ const urlByAbsentLocalPath = new Map(
15131
+ remoteFiles.map(([rel, file]) => [remoteLocalPath(rel), file.url])
15132
+ );
15133
+ const localPathByUrl = new Map(
15134
+ remoteFiles.map(([rel, file]) => [file.url, remoteLocalPath(rel)])
15135
+ );
14577
15136
  stageStarted = Date.now();
14578
15137
  const compileResult = await runCompileStage({
15138
+ urlByAbsentLocalPath,
14579
15139
  projectDir,
14580
15140
  workDir,
14581
15141
  htmlPath,
@@ -14635,7 +15195,7 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
14635
15195
  if (probeResult.fileServer) closeFileServerSafely(probeResult.fileServer, "plan", log);
14636
15196
  if (probeResult.probeSession) {
14637
15197
  try {
14638
- const { closeCaptureSession: closeCaptureSession2 } = await import("./src-JGPFLYBL.js");
15198
+ const { closeCaptureSession: closeCaptureSession2 } = await import("./src-XRFOS5LP.js");
14639
15199
  await closeCaptureSession2(probeResult.probeSession);
14640
15200
  } catch (err) {
14641
15201
  log.warn("[plan] probe session close failed", {
@@ -14663,30 +15223,32 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
14663
15223
  abortSignal,
14664
15224
  assertNotAborted,
14665
15225
  materializeSymlinks: true,
14666
- deferRangeExtraction: options.deferVideoExtraction
15226
+ deferRangeExtraction: options.deferVideoExtraction,
15227
+ urlByAbsentLocalPath
14667
15228
  });
14668
15229
  logPlanStage(log, "extract", stageStarted);
14669
15230
  if (extractResult.failureToEnforce) throw extractResult.failureToEnforce;
14670
15231
  if (extractResult.extractionResult) {
14671
15232
  assertVideoExtractionSucceeded(extractResult.extractionResult);
14672
15233
  }
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");
14677
- rmSync9(videoSourcesDir, { recursive: true, force: true });
14678
- if (existsSync19(stagedVideoFrames)) {
14679
- renameSync4(stagedVideoFrames, videoFramesDst);
15234
+ const stagedVideoFrames = join33(compiledDir, "__hyperframes_video_frames");
15235
+ const videoFramesDst = join33(planDir, "video-frames");
15236
+ if (existsSync20(videoFramesDst)) rmSync10(videoFramesDst, { recursive: true, force: true });
15237
+ const videoSourcesDir = join33(planDir, "video-sources");
15238
+ rmSync10(videoSourcesDir, { recursive: true, force: true });
15239
+ if (existsSync20(stagedVideoFrames)) {
15240
+ renameSync5(stagedVideoFrames, videoFramesDst);
14680
15241
  } else {
14681
- mkdirSync10(videoFramesDst, { recursive: true });
15242
+ mkdirSync12(videoFramesDst, { recursive: true });
14682
15243
  }
14683
- if (existsSync19(finalCompiledDir)) rmSync9(finalCompiledDir, { recursive: true, force: true });
14684
- renameSync4(compiledDir, finalCompiledDir);
15244
+ if (existsSync20(finalCompiledDir)) rmSync10(finalCompiledDir, { recursive: true, force: true });
15245
+ renameSync5(compiledDir, finalCompiledDir);
14685
15246
  const shipVideoSource = createVideoSourceShipper({
14686
15247
  planDir,
14687
15248
  projectDir,
14688
15249
  workCompiledDir: compiledDir,
14689
- workVideoFramesDir: stagedVideoFrames
15250
+ workVideoFramesDir: stagedVideoFrames,
15251
+ isRemote: (srcPath) => urlByAbsentLocalPath.has(srcPath)
14690
15252
  });
14691
15253
  const sourceRoots = [
14692
15254
  [stagedVideoFrames, "video-frames"],
@@ -14696,19 +15258,30 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
14696
15258
  const planVideosJson = buildPlanVideosJson({
14697
15259
  videos: composition.videos,
14698
15260
  compositionEnd: job.duration ?? Number.NaN,
14699
- extracted: (extractResult.extractionResult?.extracted ?? []).map((ext) => ({
14700
- videoId: ext.videoId,
14701
- srcPath: stableSourcePath(sourceRoots, ext.srcPath),
14702
- framePattern: ext.framePattern,
14703
- fps: ext.fps,
14704
- totalFrames: ext.totalFrames,
14705
- metadata: ext.metadata,
14706
- ...ext.deferredRange ? { deferredRange: { ...ext.deferredRange, sourcePath: shipVideoSource(ext.srcPath) } } : {}
14707
- }))
15261
+ extracted: (extractResult.extractionResult?.extracted ?? []).map((ext) => {
15262
+ const sourcePath = localPathByUrl.get(ext.srcPath) ?? ext.srcPath;
15263
+ return {
15264
+ videoId: ext.videoId,
15265
+ srcPath: stableSourcePath(sourceRoots, sourcePath),
15266
+ framePattern: ext.framePattern,
15267
+ fps: ext.fps,
15268
+ totalFrames: ext.totalFrames,
15269
+ metadata: ext.metadata,
15270
+ ...ext.deferredRange ? { deferredRange: { ...ext.deferredRange, sourcePath: shipVideoSource(sourcePath) } } : {}
15271
+ };
15272
+ })
14708
15273
  });
14709
- mkdirSync10(join31(planDir, "meta"), { recursive: true });
14710
- writeFileSync7(
14711
- join31(planDir, PLAN_VIDEOS_META_RELATIVE_PATH),
15274
+ mkdirSync12(join33(planDir, "meta"), { recursive: true });
15275
+ writePlanRemoteFiles(
15276
+ planDir,
15277
+ remoteFiles.map(([rel, { sha256, sizeBytes }]) => ({
15278
+ path: `compiled/${rel}`,
15279
+ sha256,
15280
+ sizeBytes
15281
+ }))
15282
+ );
15283
+ writeFileSync8(
15284
+ join33(planDir, PLAN_VIDEOS_META_RELATIVE_PATH),
14712
15285
  JSON.stringify(planVideosJson, null, 2),
14713
15286
  "utf-8"
14714
15287
  );
@@ -14721,6 +15294,7 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
14721
15294
  projectDir,
14722
15295
  workDir,
14723
15296
  compiledDir: finalCompiledDir,
15297
+ urlByAbsentLocalPath,
14724
15298
  duration: audioSeconds,
14725
15299
  ffmpegProcessTimeout: cfg.ffmpegProcessTimeout,
14726
15300
  audioGain: cfg.audioGain,
@@ -14781,11 +15355,11 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
14781
15355
  if (audioResult.audioError) {
14782
15356
  applyDistributedAudioWarningPolicy(job, audioResult.audioError, audioResult.audioFailures, log);
14783
15357
  }
14784
- const planAudioPath = join31(planDir, PLAN_AUDIO_RELATIVE_PATH);
14785
- if (audioResult.hasAudio && existsSync19(audioResult.audioOutputPath)) {
14786
- renameSync4(audioResult.audioOutputPath, planAudioPath);
15358
+ const planAudioPath = join33(planDir, PLAN_AUDIO_RELATIVE_PATH);
15359
+ if (audioResult.hasAudio && existsSync20(audioResult.audioOutputPath)) {
15360
+ renameSync5(audioResult.audioOutputPath, planAudioPath);
14787
15361
  }
14788
- if (chunksCarryAudio2 && existsSync19(planAudioPath)) {
15362
+ if (chunksCarryAudio2 && existsSync20(planAudioPath)) {
14789
15363
  stageStarted = Date.now();
14790
15364
  const peak = await constrainAacTruePeak({
14791
15365
  audioPath: planAudioPath,
@@ -14795,6 +15369,16 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
14795
15369
  });
14796
15370
  if (!peak.success) throw encoderFailureError("[plan] audio true-peak check failed", peak);
14797
15371
  logPlanStage(log, "audio-peak", stageStarted);
15372
+ if (options.sliceChunkAudio) {
15373
+ stageStarted = Date.now();
15374
+ await writeChunkAudioSlices({
15375
+ audioPath: planAudioPath,
15376
+ planDir,
15377
+ chunks,
15378
+ fps: { num: config.fps, den: 1 }
15379
+ });
15380
+ logPlanStage(log, "audio-slices", stageStarted);
15381
+ }
14798
15382
  }
14799
15383
  const ffmpegVersion = await readFfmpegVersion();
14800
15384
  const producerVersion = readProducerVersion();
@@ -14828,7 +15412,7 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
14828
15412
  format: config.format
14829
15413
  };
14830
15414
  try {
14831
- rmSync9(workDir, { recursive: true, force: true });
15415
+ rmSync10(workDir, { recursive: true, force: true });
14832
15416
  } catch (err) {
14833
15417
  log.warn("[plan] failed to remove temp work dir", {
14834
15418
  workDir,
@@ -15370,15 +15954,15 @@ function renderRequestFromDistributedConfig(input) {
15370
15954
 
15371
15955
  // ../producer/src/server.ts
15372
15956
  import {
15373
- existsSync as existsSync22,
15374
- mkdirSync as mkdirSync11,
15957
+ existsSync as existsSync23,
15958
+ mkdirSync as mkdirSync13,
15375
15959
  statSync as statSync7,
15376
- mkdtempSync as mkdtempSync4,
15377
- writeFileSync as writeFileSync8,
15378
- rmSync as rmSync10,
15379
- createReadStream as createReadStream4
15960
+ mkdtempSync as mkdtempSync5,
15961
+ writeFileSync as writeFileSync9,
15962
+ rmSync as rmSync11,
15963
+ createReadStream as createReadStream5
15380
15964
  } from "fs";
15381
- import { resolve as resolve9, dirname as dirname12, join as join34 } from "path";
15965
+ import { resolve as resolve9, dirname as dirname13, join as join36 } from "path";
15382
15966
  import { tmpdir as tmpdir3 } from "os";
15383
15967
  import { parseArgs } from "util";
15384
15968
  import crypto from "crypto";
@@ -15388,15 +15972,15 @@ import { serve as serve2 } from "@hono/node-server";
15388
15972
 
15389
15973
  // ../producer/src/services/hyperframeLint.ts
15390
15974
  import {
15391
- existsSync as existsSync20,
15392
- readFileSync as readFileSync10,
15975
+ existsSync as existsSync21,
15976
+ readFileSync as readFileSync11,
15393
15977
  statSync as statSync6,
15394
- openSync as openSync5,
15978
+ openSync as openSync6,
15395
15979
  fstatSync as fstatSync4,
15396
- closeSync as closeSync6,
15980
+ closeSync as closeSync7,
15397
15981
  constants as constants3
15398
15982
  } from "fs";
15399
- import { resolve as resolve8, join as join32, relative as relative6, isAbsolute as isAbsolute3, sep as sep3 } from "path";
15983
+ import { resolve as resolve8, join as join34, relative as relative6, isAbsolute as isAbsolute3, sep as sep3 } from "path";
15400
15984
  function isStringRecord(value) {
15401
15985
  if (!value || typeof value !== "object" || Array.isArray(value)) {
15402
15986
  return false;
@@ -15425,7 +16009,7 @@ function pickEntryFile(files, preferredEntryFile) {
15425
16009
  function readEntryHtml(filePath) {
15426
16010
  let fd;
15427
16011
  try {
15428
- fd = openSync5(filePath, constants3.O_RDONLY | constants3.O_NONBLOCK);
16012
+ fd = openSync6(filePath, constants3.O_RDONLY | constants3.O_NONBLOCK);
15429
16013
  } catch (error) {
15430
16014
  if (error instanceof Error && "code" in error && (error.code === "ENOENT" || error.code === "ENOTDIR"))
15431
16015
  return null;
@@ -15434,14 +16018,14 @@ function readEntryHtml(filePath) {
15434
16018
  }
15435
16019
  try {
15436
16020
  if (!fstatSync4(fd).isFile()) return null;
15437
- return readFileSync10(fd, "utf-8");
16021
+ return readFileSync11(fd, "utf-8");
15438
16022
  } finally {
15439
- closeSync6(fd);
16023
+ closeSync7(fd);
15440
16024
  }
15441
16025
  }
15442
16026
  function readProjectEntryFile(projectDir, preferredEntryFile) {
15443
16027
  const absProjectDir = resolve8(projectDir);
15444
- if (!existsSync20(absProjectDir) || !statSync6(absProjectDir).isDirectory()) {
16028
+ if (!existsSync21(absProjectDir) || !statSync6(absProjectDir).isDirectory()) {
15445
16029
  return { error: `Project directory not found: ${absProjectDir}` };
15446
16030
  }
15447
16031
  const entryCandidates = [preferredEntryFile, "index.html", "src/index.html"].filter(
@@ -15463,7 +16047,7 @@ function readProjectEntryFile(projectDir, preferredEntryFile) {
15463
16047
  }
15464
16048
  }
15465
16049
  return {
15466
- error: `No HTML entry file found in project directory: ${join32(absProjectDir, preferredEntryFile || "index.html")}`
16050
+ error: `No HTML entry file found in project directory: ${join34(absProjectDir, preferredEntryFile || "index.html")}`
15467
16051
  };
15468
16052
  }
15469
16053
  function prepareHyperframeLintBody(body) {
@@ -15509,8 +16093,8 @@ async function runHyperframeLint(prepared) {
15509
16093
  // ../producer/src/services/healthWorker.ts
15510
16094
  import { Worker as Worker2 } from "worker_threads";
15511
16095
  import { fileURLToPath as fileURLToPath6 } from "url";
15512
- import { dirname as dirname11, join as join33 } from "path";
15513
- import { existsSync as existsSync21 } from "fs";
16096
+ import { dirname as dirname12, join as join35 } from "path";
16097
+ import { existsSync as existsSync22 } from "fs";
15514
16098
  var DEFAULT_HEALTH_PORT = 9848;
15515
16099
  async function startHealthWorker(options = {}) {
15516
16100
  const log = options.logger ?? defaultLogger2();
@@ -15584,10 +16168,10 @@ function defaultLogger2() {
15584
16168
  };
15585
16169
  }
15586
16170
  function resolveWorkerEntry2() {
15587
- const here = dirname11(fileURLToPath6(import.meta.url));
15588
- const candidates = [join33(here, "healthWorkerThread.js"), join33(here, "healthWorkerThread.ts")];
16171
+ const here = dirname12(fileURLToPath6(import.meta.url));
16172
+ const candidates = [join35(here, "healthWorkerThread.js"), join35(here, "healthWorkerThread.ts")];
15589
16173
  for (const candidate of candidates) {
15590
- if (existsSync21(candidate)) return candidate;
16174
+ if (existsSync22(candidate)) return candidate;
15591
16175
  }
15592
16176
  return void 0;
15593
16177
  }
@@ -15725,8 +16309,8 @@ function buildRenderJobConfig(input, outputPath, log) {
15725
16309
  function resolvePreparedRenderOutput(prepared, rendersDir, log) {
15726
16310
  const { input, cleanupProjectDir } = prepared;
15727
16311
  const absoluteOutputPath = resolveOutputPath(input.projectDir, input.outputPath, rendersDir, log);
15728
- const outputDir = dirname12(absoluteOutputPath);
15729
- if (!existsSync22(outputDir)) mkdirSync11(outputDir, { recursive: true });
16312
+ const outputDir = dirname13(absoluteOutputPath);
16313
+ if (!existsSync23(outputDir)) mkdirSync13(outputDir, { recursive: true });
15730
16314
  return { input, cleanupProjectDir, absoluteOutputPath };
15731
16315
  }
15732
16316
  function validateRenderOverrides(body) {
@@ -15766,11 +16350,11 @@ function prepareProjectDirectory(projectDir, options) {
15766
16350
  const candidate = nonEmptyString(projectDir);
15767
16351
  if (!candidate) return null;
15768
16352
  const absProjectDir = resolve9(candidate);
15769
- if (!existsSync22(absProjectDir) || !statSync7(absProjectDir).isDirectory()) {
16353
+ if (!existsSync23(absProjectDir) || !statSync7(absProjectDir).isDirectory()) {
15770
16354
  return { error: `Project directory not found: ${absProjectDir}` };
15771
16355
  }
15772
16356
  const entry = options.entryFile || "index.html";
15773
- if (!existsSync22(resolve9(absProjectDir, entry))) {
16357
+ if (!existsSync23(resolve9(absProjectDir, entry))) {
15774
16358
  return { error: `Entry file "${entry}" not found in project directory: ${absProjectDir}` };
15775
16359
  }
15776
16360
  return { prepared: { input: { projectDir: absProjectDir, ...options } } };
@@ -15795,8 +16379,8 @@ async function resolveInlineRenderHtml(body) {
15795
16379
  }
15796
16380
  function materializeInlineProject(html, options) {
15797
16381
  const tempRoot = process.env.PRODUCER_TMP_PROJECT_DIR || tmpdir3();
15798
- const tempProjectDir = mkdtempSync4(join34(tempRoot, "producer-project-"));
15799
- writeFileSync8(join34(tempProjectDir, "index.html"), html, "utf-8");
16382
+ const tempProjectDir = mkdtempSync5(join36(tempRoot, "producer-project-"));
16383
+ writeFileSync9(join36(tempProjectDir, "index.html"), html, "utf-8");
15800
16384
  return {
15801
16385
  prepared: {
15802
16386
  input: { projectDir: tempProjectDir, ...options },
@@ -15853,7 +16437,7 @@ function createArtifactStore(ttlMs) {
15853
16437
  function cleanupTempDir(dir, log) {
15854
16438
  if (!dir) return;
15855
16439
  try {
15856
- rmSync10(dir, { recursive: true, force: true });
16440
+ rmSync11(dir, { recursive: true, force: true });
15857
16441
  } catch (error) {
15858
16442
  log.warn("Failed to cleanup temp project dir", {
15859
16443
  cleanupProjectDir: dir,
@@ -15862,7 +16446,7 @@ function cleanupTempDir(dir, log) {
15862
16446
  }
15863
16447
  }
15864
16448
  function outputFileSize(path) {
15865
- return existsSync22(path) ? statSync7(path).size : 0;
16449
+ return existsSync23(path) ? statSync7(path).size : 0;
15866
16450
  }
15867
16451
  function createBlockingProgressReporter(log, requestId) {
15868
16452
  let lastLoggedPct = -10;
@@ -16158,12 +16742,12 @@ function createRenderHandlers(options = {}) {
16158
16742
  if (!artifact) {
16159
16743
  return c.json({ success: false, error: "Output artifact not found or expired" }, 404);
16160
16744
  }
16161
- if (!existsSync22(artifact.path)) {
16745
+ if (!existsSync23(artifact.path)) {
16162
16746
  store.delete(token);
16163
16747
  return c.json({ success: false, error: "Output artifact file missing" }, 404);
16164
16748
  }
16165
16749
  const stats = statSync7(artifact.path);
16166
- return new Response(createReadStream4(artifact.path), {
16750
+ return new Response(createReadStream5(artifact.path), {
16167
16751
  headers: {
16168
16752
  "content-type": "video/mp4",
16169
16753
  "content-length": String(stats.size),
@@ -16205,7 +16789,7 @@ function startServer(options = {}) {
16205
16789
  });
16206
16790
  async function shutdown(signal) {
16207
16791
  log.info(`Received ${signal}, shutting down`);
16208
- const { drainBrowserPool } = await import("./src-JGPFLYBL.js");
16792
+ const { drainBrowserPool } = await import("./src-XRFOS5LP.js");
16209
16793
  await drainBrowserPool().catch(() => {
16210
16794
  });
16211
16795
  const handle = await Promise.race([
@@ -16243,33 +16827,33 @@ if (isPublicServerEntry) {
16243
16827
  // ../producer/src/services/distributed/planV2.ts
16244
16828
  import {
16245
16829
  copyFileSync as copyFileSync6,
16246
- existsSync as existsSync24,
16830
+ existsSync as existsSync25,
16247
16831
  lstatSync as lstatSync3,
16248
- mkdirSync as mkdirSync13,
16249
- mkdtempSync as mkdtempSync6,
16250
- readFileSync as readFileSync11,
16832
+ mkdirSync as mkdirSync15,
16833
+ mkdtempSync as mkdtempSync7,
16834
+ readFileSync as readFileSync12,
16251
16835
  readdirSync as readdirSync9,
16252
- renameSync as renameSync6,
16253
- rmSync as rmSync12,
16836
+ renameSync as renameSync7,
16837
+ rmSync as rmSync13,
16254
16838
  statSync as statSync9,
16255
- writeFileSync as writeFileSync10
16839
+ writeFileSync as writeFileSync11
16256
16840
  } from "fs";
16257
16841
  import { tmpdir as tmpdir4 } from "os";
16258
- import { dirname as dirname14, isAbsolute as isAbsolute4, join as join37, relative as relative7, resolve as resolve10, sep as sep4 } from "path";
16842
+ import { dirname as dirname15, isAbsolute as isAbsolute4, join as join39, relative as relative7, resolve as resolve10, sep as sep4 } from "path";
16259
16843
 
16260
16844
  // ../producer/src/services/distributed/planV2Publisher.ts
16261
16845
  import {
16262
16846
  copyFileSync as copyFileSync5,
16263
- existsSync as existsSync23,
16847
+ existsSync as existsSync24,
16264
16848
  linkSync as linkSync2,
16265
- mkdirSync as mkdirSync12,
16266
- mkdtempSync as mkdtempSync5,
16267
- renameSync as renameSync5,
16268
- rmSync as rmSync11,
16849
+ mkdirSync as mkdirSync14,
16850
+ mkdtempSync as mkdtempSync6,
16851
+ renameSync as renameSync6,
16852
+ rmSync as rmSync12,
16269
16853
  statSync as statSync8,
16270
- writeFileSync as writeFileSync9
16854
+ writeFileSync as writeFileSync10
16271
16855
  } from "fs";
16272
- import { dirname as dirname13, join as join36 } from "path";
16856
+ import { dirname as dirname14, join as join38 } from "path";
16273
16857
 
16274
16858
  // ../producer/src/services/distributed/planV2Errors.ts
16275
16859
  var PLAN_V2_INTEGRITY_UNRECOVERABLE = "PLAN_V2_INTEGRITY_UNRECOVERABLE";
@@ -16284,7 +16868,7 @@ var PlanV2IntegrityError = class extends Error {
16284
16868
  };
16285
16869
 
16286
16870
  // ../producer/src/services/distributed/planV2Layout.ts
16287
- import { join as join35 } from "path";
16871
+ import { join as join37 } from "path";
16288
16872
  var SHA256_PATTERN = /^[0-9a-f]{64}$/;
16289
16873
  function assertPlanV2Sha256(value, field) {
16290
16874
  if (typeof value !== "string" || !SHA256_PATTERN.test(value)) {
@@ -16294,7 +16878,7 @@ function assertPlanV2Sha256(value, field) {
16294
16878
  }
16295
16879
  function planV2BlobPath(root, sha256) {
16296
16880
  const digest = assertPlanV2Sha256(sha256, "artifact sha256");
16297
- return join35(root, "artifacts", "sha256", digest.slice(0, 2), digest);
16881
+ return join37(root, "artifacts", "sha256", digest.slice(0, 2), digest);
16298
16882
  }
16299
16883
 
16300
16884
  // ../producer/src/services/distributed/planV2Publisher.ts
@@ -16329,13 +16913,13 @@ var LocalPlanV2ArtifactPublisher = class {
16329
16913
  #linkFile;
16330
16914
  #committed = false;
16331
16915
  constructor(destinationDir, options = {}) {
16332
- if (existsSync23(destinationDir)) {
16916
+ if (existsSync24(destinationDir)) {
16333
16917
  throw new PlanV2IntegrityError(`output directory already exists: ${destinationDir}`);
16334
16918
  }
16335
16919
  this.destinationDir = destinationDir;
16336
16920
  this.#linkFile = options.linkFile ?? linkSync2;
16337
- mkdirSync12(dirname13(destinationDir), { recursive: true });
16338
- this.temporaryDir = mkdtempSync5(join36(dirname13(destinationDir), ".plan-v2-publish-"));
16921
+ mkdirSync14(dirname14(destinationDir), { recursive: true });
16922
+ this.temporaryDir = mkdtempSync6(join38(dirname14(destinationDir), ".plan-v2-publish-"));
16339
16923
  }
16340
16924
  async putBlob(blob) {
16341
16925
  const digest = assertPlanV2Sha256(blob.sha256, "published blob sha256");
@@ -16346,10 +16930,10 @@ var LocalPlanV2ArtifactPublisher = class {
16346
16930
  );
16347
16931
  }
16348
16932
  const destinationPath = planV2BlobPath(this.temporaryDir, digest);
16349
- if (existsSync23(destinationPath)) return;
16350
- mkdirSync12(dirname13(destinationPath), { recursive: true });
16351
- const stagingDir = mkdtempSync5(join36(dirname13(destinationPath), ".plan-v2-blob-"));
16352
- const temporaryPath = join36(stagingDir, "blob");
16933
+ if (existsSync24(destinationPath)) return;
16934
+ mkdirSync14(dirname14(destinationPath), { recursive: true });
16935
+ const stagingDir = mkdtempSync6(join38(dirname14(destinationPath), ".plan-v2-blob-"));
16936
+ const temporaryPath = join38(stagingDir, "blob");
16353
16937
  try {
16354
16938
  try {
16355
16939
  this.#linkFile(blob.sourcePath, temporaryPath);
@@ -16357,26 +16941,26 @@ var LocalPlanV2ArtifactPublisher = class {
16357
16941
  if (!canFallbackToCopy(error)) throw error;
16358
16942
  copyFileSync5(blob.sourcePath, temporaryPath);
16359
16943
  }
16360
- renameSync5(temporaryPath, destinationPath);
16944
+ renameSync6(temporaryPath, destinationPath);
16361
16945
  } finally {
16362
- rmSync11(stagingDir, { recursive: true, force: true });
16946
+ rmSync12(stagingDir, { recursive: true, force: true });
16363
16947
  }
16364
16948
  }
16365
16949
  async commitManifest(manifestBytes) {
16366
16950
  for (const digest of new Set(manifestDigests(manifestBytes))) {
16367
- if (!existsSync23(planV2BlobPath(this.temporaryDir, digest))) {
16951
+ if (!existsSync24(planV2BlobPath(this.temporaryDir, digest))) {
16368
16952
  throw new PlanV2IntegrityError(
16369
16953
  `cannot commit manifest before referenced blob is durable: ${digest}`
16370
16954
  );
16371
16955
  }
16372
16956
  }
16373
- writeFileSync9(join36(this.temporaryDir, "plan.json"), manifestBytes, "utf-8");
16374
- renameSync5(this.temporaryDir, this.destinationDir);
16957
+ writeFileSync10(join38(this.temporaryDir, "plan.json"), manifestBytes, "utf-8");
16958
+ renameSync6(this.temporaryDir, this.destinationDir);
16375
16959
  this.#committed = true;
16376
16960
  }
16377
16961
  async abort() {
16378
16962
  if (!this.#committed) {
16379
- rmSync11(this.temporaryDir, { recursive: true, force: true });
16963
+ rmSync12(this.temporaryDir, { recursive: true, force: true });
16380
16964
  }
16381
16965
  }
16382
16966
  };
@@ -16395,7 +16979,7 @@ function isNonNegativeIntegerArray(value) {
16395
16979
  return Array.isArray(value) && value.every((item) => typeof item === "number" && Number.isInteger(item) && item >= 0);
16396
16980
  }
16397
16981
  function readJsonFile(path, label) {
16398
- const contents = readFileSync11(path, "utf-8");
16982
+ const contents = readFileSync12(path, "utf-8");
16399
16983
  try {
16400
16984
  const parsed = JSON.parse(contents);
16401
16985
  return parsed;
@@ -16415,7 +16999,7 @@ function listFiles(root) {
16415
16999
  const rootResolved = resolve10(root);
16416
17000
  function walk(dir) {
16417
17001
  for (const entry of readdirSync9(dir, { withFileTypes: true })) {
16418
- const absolutePath = join37(dir, entry.name);
17002
+ const absolutePath = join39(dir, entry.name);
16419
17003
  if (entry.isDirectory()) {
16420
17004
  walk(absolutePath);
16421
17005
  } else if (entry.isFile()) {
@@ -16442,13 +17026,13 @@ function assertValidExtractionCacheCompleteSentinel(path) {
16442
17026
  }
16443
17027
  }
16444
17028
  function validateExtractionCacheCompleteSentinels(executionPlanDir) {
16445
- const videoRoot = join37(executionPlanDir, "video-frames");
16446
- if (!existsSync24(videoRoot)) return;
17029
+ const videoRoot = join39(executionPlanDir, "video-frames");
17030
+ if (!existsSync25(videoRoot)) return;
16447
17031
  for (const videoEntry of readdirSync9(videoRoot, { withFileTypes: true })) {
16448
17032
  if (!videoEntry.isDirectory()) continue;
16449
- const videoDir = join37(videoRoot, videoEntry.name);
17033
+ const videoDir = join39(videoRoot, videoEntry.name);
16450
17034
  if (readdirSync9(videoDir).includes(EXTRACTION_CACHE_COMPLETE_SENTINEL)) {
16451
- const sentinelPath = join37(videoDir, EXTRACTION_CACHE_COMPLETE_SENTINEL);
17035
+ const sentinelPath = join39(videoDir, EXTRACTION_CACHE_COMPLETE_SENTINEL);
16452
17036
  assertValidExtractionCacheCompleteSentinel(sentinelPath);
16453
17037
  }
16454
17038
  }
@@ -16465,31 +17049,37 @@ function resolveExtractedVideoOutputDir(planDir, videoId) {
16465
17049
  }
16466
17050
  return outputDir;
16467
17051
  }
16468
- function artifactTargets(path, videoDependencies, chunksCarryAudio2) {
17052
+ function artifactTargets(path, videoDependencies, chunkAudio) {
16469
17053
  if (isPlanAudioArtifactPath(path)) {
16470
- return chunksCarryAudio2 ? { chunks: "all", assembler: false } : { chunks: [], assembler: true };
17054
+ if (chunkAudio === "none") return { chunks: [], assembler: true };
17055
+ return { chunks: chunkAudio === "whole" ? "all" : [], assembler: false };
16471
17056
  }
17057
+ const slice = chunkAudioSliceTarget(path);
17058
+ if (slice !== null) return { chunks: slice === "all" ? "all" : [slice], assembler: false };
16472
17059
  if (path === "plan.json" || path === "meta/chunks.json" || path === "meta/encoder.json") {
16473
17060
  return { chunks: "all", assembler: true };
16474
17061
  }
16475
- if (path === "meta/composition.json" || path === "meta/videos.json" || path.startsWith("compiled/")) {
16476
- return { chunks: "all", assembler: false };
16477
- }
16478
- if (path.startsWith("video-frames/") || path.startsWith("video-sources/")) {
17062
+ if (path.startsWith("video-frames/") || path.startsWith("video-sources/") || path.startsWith("compiled/") && videoDependencies?.has(path)) {
16479
17063
  return {
16480
17064
  chunks: videoDependencies === null ? "all" : videoDependencies.get(path) ?? [],
16481
17065
  assembler: false
16482
17066
  };
16483
17067
  }
17068
+ if (path === "meta/composition.json" || path === "meta/videos.json" || path.startsWith("compiled/")) {
17069
+ return { chunks: "all", assembler: false };
17070
+ }
16484
17071
  return { chunks: "all", assembler: true };
16485
17072
  }
17073
+ function isPlanV2DeferredSourceArtifact(artifact) {
17074
+ return artifact.path.startsWith("compiled/") && artifact.chunks !== "all";
17075
+ }
16486
17076
  function listVideoFramePaths(executionPlanDir, videos) {
16487
17077
  return videos.extracted.map((video) => {
16488
17078
  const outputDir = resolveExtractedVideoOutputDir(executionPlanDir, video.videoId);
16489
17079
  if (video.deferredRange) {
16490
17080
  const framePaths2 = /* @__PURE__ */ new Map();
16491
17081
  for (let index = 0; index < video.totalFrames; index++) {
16492
- framePaths2.set(index, join37(outputDir, String(index)));
17082
+ framePaths2.set(index, join39(outputDir, String(index)));
16493
17083
  }
16494
17084
  return { ...video, outputDir, framePaths: framePaths2, ownedByLookup: false };
16495
17085
  }
@@ -16497,7 +17087,7 @@ function listVideoFramePaths(executionPlanDir, videos) {
16497
17087
  const framePaths = /* @__PURE__ */ new Map();
16498
17088
  for (const frameName of frameNames) {
16499
17089
  if (frameName === EXTRACTION_CACHE_COMPLETE_SENTINEL) {
16500
- assertValidExtractionCacheCompleteSentinel(join37(outputDir, frameName));
17090
+ assertValidExtractionCacheCompleteSentinel(join39(outputDir, frameName));
16501
17091
  continue;
16502
17092
  }
16503
17093
  const match = /(\d+)(?=\.[^.]+$)/.exec(frameName);
@@ -16516,7 +17106,7 @@ function listVideoFramePaths(executionPlanDir, videos) {
16516
17106
  `duplicate extracted frame index ${frameIndex} in ${outputDir}`
16517
17107
  );
16518
17108
  }
16519
- framePaths.set(frameIndex, join37(outputDir, frameName));
17109
+ framePaths.set(frameIndex, join39(outputDir, frameName));
16520
17110
  }
16521
17111
  return {
16522
17112
  ...video,
@@ -16537,11 +17127,11 @@ function parsePlanVideosJson2(value) {
16537
17127
  }
16538
17128
  }
16539
17129
  function materializeExtractedVideoDirectories(planDir) {
16540
- const videosPath = join37(planDir, PLAN_VIDEOS_META_RELATIVE_PATH);
16541
- if (!existsSync24(videosPath)) return;
17130
+ const videosPath = join39(planDir, PLAN_VIDEOS_META_RELATIVE_PATH);
17131
+ if (!existsSync25(videosPath)) return;
16542
17132
  const videos = parsePlanVideosJson2(readJsonFile(videosPath, PLAN_VIDEOS_META_RELATIVE_PATH));
16543
17133
  for (const video of videos.extracted) {
16544
- mkdirSync13(resolveExtractedVideoOutputDir(planDir, video.videoId), { recursive: true });
17134
+ mkdirSync15(resolveExtractedVideoOutputDir(planDir, video.videoId), { recursive: true });
16545
17135
  }
16546
17136
  }
16547
17137
  function parseChunkSlices(value) {
@@ -16566,13 +17156,13 @@ function parseChunkSlices(value) {
16566
17156
  });
16567
17157
  }
16568
17158
  function buildVideoChunkDependencies(executionPlanDir, dimensions) {
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)) {
17159
+ const videoRoot = join39(executionPlanDir, "video-frames");
17160
+ const hasExtractedFrames = existsSync25(videoRoot) && listFiles(videoRoot).length > 0;
17161
+ const videosPath = join39(executionPlanDir, PLAN_VIDEOS_META_RELATIVE_PATH);
17162
+ if (!existsSync25(videosPath)) {
16573
17163
  return hasExtractedFrames ? { mode: "full-source-pack", dependencies: null } : { mode: "exact-rendered-frames", dependencies: /* @__PURE__ */ new Map() };
16574
17164
  }
16575
- const chunksPath = join37(executionPlanDir, "meta", "chunks.json");
17165
+ const chunksPath = join39(executionPlanDir, "meta", "chunks.json");
16576
17166
  const videos = readJsonFile(videosPath, PLAN_VIDEOS_META_RELATIVE_PATH);
16577
17167
  const chunks = readJsonFile(chunksPath, "meta/chunks.json");
16578
17168
  const parsedVideos = parsePlanVideosJson2(videos);
@@ -16626,15 +17216,15 @@ function computeManifestHash(manifest) {
16626
17216
  return sha256Hex(`${PLAN_V2_HASH_PREFIX}${canonicalJsonStringify(manifestPayload(manifest))}`);
16627
17217
  }
16628
17218
  function writeBlob(sourcePath, destinationPath) {
16629
- if (existsSync24(destinationPath)) return;
16630
- mkdirSync13(dirname14(destinationPath), { recursive: true });
16631
- const temporaryDir = mkdtempSync6(join37(dirname14(destinationPath), ".plan-v2-blob-"));
16632
- const temporaryPath = join37(temporaryDir, "blob");
17219
+ if (existsSync25(destinationPath)) return;
17220
+ mkdirSync15(dirname15(destinationPath), { recursive: true });
17221
+ const temporaryDir = mkdtempSync7(join39(dirname15(destinationPath), ".plan-v2-blob-"));
17222
+ const temporaryPath = join39(temporaryDir, "blob");
16633
17223
  try {
16634
17224
  copyFileSync6(sourcePath, temporaryPath);
16635
- renameSync6(temporaryPath, destinationPath);
17225
+ renameSync7(temporaryPath, destinationPath);
16636
17226
  } finally {
16637
- rmSync12(temporaryDir, { recursive: true, force: true });
17227
+ rmSync13(temporaryDir, { recursive: true, force: true });
16638
17228
  }
16639
17229
  }
16640
17230
  function publishBlobFor(absolutePath, digests) {
@@ -16645,14 +17235,17 @@ function publishBlobFor(absolutePath, digests) {
16645
17235
  };
16646
17236
  }
16647
17237
  async function putBlobs(publisher, blobs) {
17238
+ await putTasks(blobs.map((blob) => () => publisher.putBlob(blob)));
17239
+ }
17240
+ async function putTasks(tasks) {
16648
17241
  const uploadSlots = 32;
16649
17242
  let next = 0;
16650
17243
  let failure;
16651
17244
  const upload = async () => {
16652
- while (!failure && next < blobs.length) {
16653
- const blob = blobs[next++];
17245
+ while (!failure && next < tasks.length) {
17246
+ const task = tasks[next++];
16654
17247
  try {
16655
- await publisher.putBlob(blob);
17248
+ await task();
16656
17249
  } catch (reason) {
16657
17250
  failure ??= { reason };
16658
17251
  }
@@ -16662,8 +17255,8 @@ async function putBlobs(publisher, blobs) {
16662
17255
  if (failure) throw failure.reason;
16663
17256
  }
16664
17257
  function buildPlanV2Publication(executionPlanDir, digests) {
16665
- const executionPlanPath = join37(executionPlanDir, "plan.json");
16666
- if (!existsSync24(executionPlanPath)) {
17258
+ const executionPlanPath = join39(executionPlanDir, "plan.json");
17259
+ if (!existsSync25(executionPlanPath)) {
16667
17260
  throw new PlanV2IntegrityError(`execution plan is missing plan.json: ${executionPlanPath}`);
16668
17261
  }
16669
17262
  const executionPlanValue = readJsonFile(executionPlanPath, "execution plan.json");
@@ -16690,11 +17283,12 @@ function buildPlanV2Publication(executionPlanDir, digests) {
16690
17283
  }
16691
17284
  validateExtractionCacheCompleteSentinels(executionPlanDir);
16692
17285
  const videoDependencyPlan = buildVideoChunkDependencies(executionPlanDir, dimensions);
16693
- const encoder = readJsonFile(join37(executionPlanDir, "meta", "encoder.json"), "meta/encoder.json");
16694
- const chunksCarryAudio2 = isRecord3(encoder) && encoder.chunksCarryAudio === true;
16695
- for (const file of listFiles(executionPlanDir)) {
17286
+ const encoder = readJsonFile(join39(executionPlanDir, "meta", "encoder.json"), "meta/encoder.json");
17287
+ const files = listFiles(executionPlanDir);
17288
+ const chunkAudio = !isRecord3(encoder) || encoder.chunksCarryAudio !== true ? "none" : files.some((file) => chunkAudioSliceTarget(file.path) !== null) ? "sliced" : "whole";
17289
+ for (const file of files) {
16696
17290
  if (isExtractionCacheCompleteSentinelPath(file.path)) continue;
16697
- const targets = artifactTargets(file.path, videoDependencyPlan.dependencies, chunksCarryAudio2);
17291
+ const targets = artifactTargets(file.path, videoDependencyPlan.dependencies, chunkAudio);
16698
17292
  if ((file.path.startsWith("video-frames/") || file.path.startsWith("video-sources/")) && targets.chunks !== "all" && targets.chunks.length === 0 && !targets.assembler) {
16699
17293
  continue;
16700
17294
  }
@@ -16707,6 +17301,15 @@ function buildPlanV2Publication(executionPlanDir, digests) {
16707
17301
  ...targets
16708
17302
  });
16709
17303
  }
17304
+ const storedBlobs = /* @__PURE__ */ new Map();
17305
+ for (const remote of readPlanRemoteFiles(executionPlanDir)) {
17306
+ const { sha256, sizeBytes } = remote;
17307
+ if (!blobs.has(sha256)) storedBlobs.set(sha256, { sha256, sizeBytes });
17308
+ artifacts.push({
17309
+ ...remote,
17310
+ ...artifactTargets(remote.path, videoDependencyPlan.dependencies, chunkAudio)
17311
+ });
17312
+ }
16710
17313
  const base = {
16711
17314
  protocol: PLAN_PROTOCOL_V2,
16712
17315
  // Retain the established manifest key byte-for-byte. It now acts as the
@@ -16728,708 +17331,397 @@ function buildPlanV2Publication(executionPlanDir, digests) {
16728
17331
  ...base,
16729
17332
  planHash: computeManifestHash(base)
16730
17333
  },
16731
- blobs: [...blobs.values()]
16732
- };
16733
- }
16734
- function createPlanV2FromExecutionPlan(executionPlanDir, planV2Dir) {
16735
- if (existsSync24(planV2Dir)) {
16736
- throw new PlanV2IntegrityError(`output directory already exists: ${planV2Dir}`);
16737
- }
16738
- const publication = buildPlanV2Publication(executionPlanDir);
16739
- mkdirSync13(dirname14(planV2Dir), { recursive: true });
16740
- const tempDir = mkdtempSync6(join37(dirname14(planV2Dir), ".plan-v2-build-"));
16741
- try {
16742
- for (const blob of publication.blobs) {
16743
- writeBlob(blob.sourcePath, planV2BlobPath(tempDir, blob.sha256));
16744
- }
16745
- writeFileSync10(
16746
- join37(tempDir, "plan.json"),
16747
- canonicalJsonStringify(publication.manifest),
16748
- "utf-8"
16749
- );
16750
- renameSync6(tempDir, planV2Dir);
16751
- return resultFromManifest(planV2Dir, publication.manifest);
16752
- } catch (error) {
16753
- rmSync12(tempDir, { recursive: true, force: true });
16754
- throw error;
16755
- }
16756
- }
16757
- function createPlanV2FromV1(executionPlanDir, planV2Dir) {
16758
- return createPlanV2FromExecutionPlan(executionPlanDir, planV2Dir);
16759
- }
16760
- async function publishPlanV2FromExecutionPlan(executionPlanDir, publisher) {
16761
- return publishExecutionPlan(executionPlanDir, publisher, {});
16762
- }
16763
- async function publishExecutionPlan(executionPlanDir, publisher, { digests, log = defaultLogger }) {
16764
- try {
16765
- let stageStarted = Date.now();
16766
- const publication = buildPlanV2Publication(executionPlanDir, digests);
16767
- logPlanStage(log, "publication", stageStarted);
16768
- stageStarted = Date.now();
16769
- await putBlobs(publisher, publication.blobs);
16770
- logPlanStage(log, "upload", stageStarted);
16771
- stageStarted = Date.now();
16772
- await publisher.commitManifest(canonicalJsonStringify(publication.manifest));
16773
- logPlanStage(log, "manifest", stageStarted);
16774
- return publication.manifest;
16775
- } catch (error) {
16776
- try {
16777
- await publisher.abort();
16778
- } catch {
16779
- }
16780
- throw error;
16781
- }
16782
- }
16783
- async function publishPlanV2FromV1(executionPlanDir, publisher) {
16784
- return publishPlanV2FromExecutionPlan(executionPlanDir, publisher);
16785
- }
16786
- async function planV2WithPublisher(projectDir, config, publisher, options = {}) {
16787
- const stagingRoot = mkdtempSync6(join37(options.stagingParentDir ?? tmpdir4(), ".execution-plan-"));
16788
- const digests = /* @__PURE__ */ new Map();
16789
- try {
16790
- await buildLocalExecutionPlan(projectDir, config, stagingRoot, {
16791
- executionPlanSizeLimitBytes: Number.MAX_SAFE_INTEGER,
16792
- deferVideoExtraction: true,
16793
- digests,
16794
- // artifactTargets ships every compiled/ file to all chunks, so each is in the manifest.
16795
- whileAudioMixes: () => putBlobs(
16796
- publisher,
16797
- listFiles(join37(stagingRoot, "compiled")).map(
16798
- (file) => publishBlobFor(file.absolutePath, digests)
16799
- )
16800
- ),
16801
- chunksCarryAudio: options.chunksCarryAudio
16802
- });
16803
- return await publishExecutionPlan(stagingRoot, publisher, { digests, log: config.logger });
16804
- } catch (error) {
16805
- try {
16806
- await publisher.abort();
16807
- } catch {
16808
- }
16809
- throw error;
16810
- } finally {
16811
- rmSync12(stagingRoot, { recursive: true, force: true });
16812
- }
16813
- }
16814
- async function planV2(projectDir, config, planV2Dir) {
16815
- if (existsSync24(planV2Dir)) {
16816
- throw new PlanV2IntegrityError(`output directory already exists: ${planV2Dir}`);
16817
- }
16818
- const publisher = new LocalPlanV2ArtifactPublisher(planV2Dir);
16819
- const manifest = await planV2WithPublisher(projectDir, config, publisher, {
16820
- stagingParentDir: dirname14(planV2Dir)
16821
- });
16822
- return resultFromManifest(planV2Dir, manifest);
16823
- }
16824
- function resultFromManifest(planV2Dir, manifest) {
16825
- return {
16826
- planDir: planV2Dir,
16827
- manifestPath: join37(planV2Dir, "plan.json"),
16828
- planProtocol: PLAN_PROTOCOL_V2,
16829
- planHash: manifest.planHash,
16830
- sourcePlanV1Hash: getPlanV2ExecutionPlanHash(manifest),
16831
- chunkCount: manifest.chunkCount,
16832
- totalFrames: manifest.totalFrames,
16833
- fps: manifest.fps,
16834
- width: manifest.width,
16835
- height: manifest.height,
16836
- format: manifest.format,
16837
- ffmpegVersion: manifest.ffmpegVersion,
16838
- producerVersion: manifest.producerVersion,
16839
- limitations: manifest.limitations
16840
- };
16841
- }
16842
- function getPlanV2ExecutionPlanHash(plan2) {
16843
- return plan2.sourcePlanV1Hash;
16844
- }
16845
- function readString2(value, field) {
16846
- if (typeof value !== "string" || value.length === 0) {
16847
- throw new PlanV2IntegrityError(`${field} must be a non-empty string`);
16848
- }
16849
- return value;
16850
- }
16851
- function readPositiveInteger2(value, field) {
16852
- if (typeof value !== "number" || !Number.isInteger(value) || value <= 0) {
16853
- throw new PlanV2IntegrityError(`${field} must be a positive integer`);
16854
- }
16855
- return value;
16856
- }
16857
- function readNonNegativeInteger2(value, field) {
16858
- if (typeof value !== "number" || !Number.isInteger(value) || value < 0) {
16859
- throw new PlanV2IntegrityError(`${field} must be a non-negative integer`);
16860
- }
16861
- return value;
16862
- }
16863
- function readSupportedFps(value) {
16864
- if (!isDistributedFps(value)) {
16865
- throw new PlanV2IntegrityError(
16866
- `dimensions.fpsNum must be one of ${DISTRIBUTED_FPS.join(", ")}`
16867
- );
16868
- }
16869
- return value;
16870
- }
16871
- function readExecutionPlanFps(dimensions) {
16872
- const fpsDen = readPositiveInteger2(dimensions.fpsDen, "dimensions.fpsDen");
16873
- if (fpsDen !== 1) {
16874
- throw new PlanV2IntegrityError("dimensions.fpsDen must be 1 for plan v2");
16875
- }
16876
- return readSupportedFps(dimensions.fpsNum);
16877
- }
16878
- function readDistributedFormat(value) {
16879
- if (value !== "mp4" && value !== "mov" && value !== "webm" && value !== "png-sequence") {
16880
- throw new PlanV2IntegrityError("dimensions.format is unsupported");
16881
- }
16882
- return value;
16883
- }
16884
- function parseArtifact(value, index) {
16885
- if (!isRecord3(value)) throw new PlanV2IntegrityError(`artifacts[${index}] must be an object`);
16886
- const path = readString2(value.path, `artifacts[${index}].path`);
16887
- assertSafeRelativePath(path);
16888
- if (!isSha256(value.sha256)) {
16889
- throw new PlanV2IntegrityError(`artifacts[${index}].sha256 must be a lowercase sha256`);
16890
- }
16891
- const sizeBytes = value.sizeBytes;
16892
- if (typeof sizeBytes !== "number" || !Number.isInteger(sizeBytes) || sizeBytes < 0) {
16893
- throw new PlanV2IntegrityError(`artifacts[${index}].sizeBytes must be a non-negative integer`);
16894
- }
16895
- let chunks;
16896
- if (value.chunks === "all") {
16897
- chunks = "all";
16898
- } else if (isNonNegativeIntegerArray(value.chunks)) {
16899
- chunks = value.chunks;
16900
- } else {
16901
- throw new PlanV2IntegrityError(`artifacts[${index}].chunks is invalid`);
16902
- }
16903
- if (typeof value.assembler !== "boolean") {
16904
- throw new PlanV2IntegrityError(`artifacts[${index}].assembler must be boolean`);
16905
- }
16906
- return {
16907
- path,
16908
- sha256: value.sha256,
16909
- sizeBytes,
16910
- chunks,
16911
- assembler: value.assembler
16912
- };
16913
- }
16914
- function parsePlanV2Manifest(value) {
16915
- if (!isRecord3(value)) throw new PlanV2IntegrityError("manifest must be an object");
16916
- if (!isRecord3(value.protocol) || value.protocol.schemaVersion !== PLAN_PROTOCOL_V2.schemaVersion || value.protocol.artifactLayout !== PLAN_PROTOCOL_V2.artifactLayout || value.protocol.hashSchema !== PLAN_PROTOCOL_V2.hashSchema) {
16917
- throw new PlanV2IntegrityError("manifest protocol is not the supported v2 descriptor");
16918
- }
16919
- if (!isSha256(value.planHash) || !isSha256(value.sourcePlanV1Hash)) {
16920
- throw new PlanV2IntegrityError("manifest hashes must be lowercase sha256 digests");
16921
- }
16922
- if (!Array.isArray(value.artifacts)) throw new PlanV2IntegrityError("artifacts must be an array");
16923
- const artifacts = value.artifacts.map(parseArtifact);
16924
- const paths = /* @__PURE__ */ new Set();
16925
- for (const artifact of artifacts) {
16926
- if (paths.has(artifact.path))
16927
- throw new PlanV2IntegrityError(`duplicate artifact path: ${artifact.path}`);
16928
- paths.add(artifact.path);
16929
- }
16930
- if (!paths.has("plan.json"))
16931
- throw new PlanV2IntegrityError("manifest must include the local execution plan.json artifact");
16932
- if (!isRecord3(value.limitations) || value.limitations.videoDependencyMode !== "exact-rendered-frames" && value.limitations.videoDependencyMode !== "full-source-pack" && value.limitations.videoDependencyMode !== "source-extract") {
16933
- throw new PlanV2IntegrityError("unsupported video dependency mode");
16934
- }
16935
- const parsed = {
16936
- protocol: PLAN_PROTOCOL_V2,
16937
- planHash: value.planHash,
16938
- sourcePlanV1Hash: value.sourcePlanV1Hash,
16939
- chunkCount: readPositiveInteger2(value.chunkCount, "chunkCount"),
16940
- totalFrames: readPositiveInteger2(value.totalFrames, "totalFrames"),
16941
- fps: readSupportedFps(value.fps),
16942
- width: readPositiveInteger2(value.width, "width"),
16943
- height: readPositiveInteger2(value.height, "height"),
16944
- format: readDistributedFormat(value.format),
16945
- ffmpegVersion: readString2(value.ffmpegVersion, "ffmpegVersion"),
16946
- producerVersion: readString2(value.producerVersion, "producerVersion"),
16947
- limitations: { videoDependencyMode: value.limitations.videoDependencyMode },
16948
- artifacts
16949
- };
16950
- const { planHash: _planHash, ...payload } = parsed;
16951
- const expectedHash = computeManifestHash(payload);
16952
- if (expectedHash !== parsed.planHash) {
16953
- throw new PlanV2IntegrityError("manifest integrity hash mismatch");
16954
- }
16955
- for (const [index, artifact] of parsed.artifacts.entries()) {
16956
- if (artifact.chunks !== "all" && artifact.chunks.some((chunkIndex) => chunkIndex >= parsed.chunkCount)) {
16957
- throw new PlanV2IntegrityError(`artifacts[${index}].chunks contains an out-of-range index`);
16958
- }
16959
- }
16960
- return parsed;
16961
- }
16962
- function readPlanV2Manifest(planV2Dir) {
16963
- const manifestPath = join37(planV2Dir, "plan.json");
16964
- if (!existsSync24(manifestPath)) {
16965
- throw new PlanV2IntegrityError(`missing v2 manifest: ${manifestPath}`);
16966
- }
16967
- const value = readJsonFile(manifestPath, "v2 manifest");
16968
- return parsePlanV2Manifest(value);
16969
- }
16970
- function listPlanV2ArtifactsForTarget(manifest, target) {
16971
- if (target.role === "chunk") {
16972
- if (!Number.isInteger(target.chunkIndex) || target.chunkIndex < 0 || target.chunkIndex >= manifest.chunkCount) {
16973
- throw new PlanV2IntegrityError(`chunkIndex ${String(target.chunkIndex)} is out of range`);
16974
- }
16975
- return manifest.artifacts.filter(
16976
- (artifact) => artifact.chunks === "all" || artifact.chunks.some((chunkIndex) => chunkIndex === target.chunkIndex)
16977
- );
16978
- }
16979
- return manifest.artifacts.filter((artifact) => artifact.assembler);
17334
+ blobs: [...blobs.values()],
17335
+ storedBlobs: [...storedBlobs.values()]
17336
+ };
16980
17337
  }
16981
- function verifyBlob(planV2Dir, artifact) {
16982
- const sourcePath = planV2BlobPath(planV2Dir, artifact.sha256);
16983
- if (!existsSync24(sourcePath)) {
16984
- throw new PlanV2IntegrityError(`missing content-addressed artifact ${artifact.sha256}`);
16985
- }
16986
- const stats = statSync9(sourcePath);
16987
- if (!stats.isFile() || stats.size !== artifact.sizeBytes) {
16988
- throw new PlanV2IntegrityError(`artifact size mismatch for ${artifact.path}`);
16989
- }
16990
- if (sha256File(sourcePath) !== artifact.sha256) {
16991
- throw new PlanV2IntegrityError(`artifact hash mismatch for ${artifact.path}`);
17338
+ function createPlanV2FromExecutionPlan(executionPlanDir, planV2Dir) {
17339
+ if (existsSync25(planV2Dir)) {
17340
+ throw new PlanV2IntegrityError(`output directory already exists: ${planV2Dir}`);
16992
17341
  }
16993
- return sourcePath;
16994
- }
16995
- function materializePlanV2Target(planV2Dir, target, destinationDir) {
16996
- if (existsSync24(destinationDir)) {
16997
- throw new PlanV2IntegrityError(`materialization destination already exists: ${destinationDir}`);
17342
+ const publication = buildPlanV2Publication(executionPlanDir);
17343
+ if (publication.storedBlobs.length > 0) {
17344
+ throw new PlanV2IntegrityError("a local plan v2 directory cannot reference remote files");
16998
17345
  }
16999
- const manifest = readPlanV2Manifest(planV2Dir);
17000
- const artifacts = listPlanV2ArtifactsForTarget(manifest, target);
17001
- const audioArtifact = artifacts.find((artifact) => isPlanAudioArtifactPath(artifact.path));
17002
- const verified = artifacts.map((artifact) => ({
17003
- artifact,
17004
- sourcePath: verifyBlob(planV2Dir, artifact)
17005
- }));
17006
- mkdirSync13(dirname14(destinationDir), { recursive: true });
17007
- const tempDir = mkdtempSync6(join37(dirname14(destinationDir), ".plan-v2-materialize-"));
17346
+ mkdirSync15(dirname15(planV2Dir), { recursive: true });
17347
+ const tempDir = mkdtempSync7(join39(dirname15(planV2Dir), ".plan-v2-build-"));
17008
17348
  try {
17009
- for (const { artifact, sourcePath } of verified) {
17010
- const destinationPath = join37(tempDir, ...artifact.path.split("/"));
17011
- mkdirSync13(dirname14(destinationPath), { recursive: true });
17012
- copyFileSync6(sourcePath, destinationPath);
17349
+ for (const blob of publication.blobs) {
17350
+ writeBlob(blob.sourcePath, planV2BlobPath(tempDir, blob.sha256));
17013
17351
  }
17014
- if (target.role === "chunk") materializeExtractedVideoDirectories(tempDir);
17015
- writeFileSync10(
17016
- join37(tempDir, PLAN_V2_MATERIALIZATION_MARKER),
17017
- canonicalJsonStringify({ manifest, target }),
17352
+ writeFileSync11(
17353
+ join39(tempDir, "plan.json"),
17354
+ canonicalJsonStringify(publication.manifest),
17018
17355
  "utf-8"
17019
17356
  );
17020
- renameSync6(tempDir, destinationDir);
17357
+ renameSync7(tempDir, planV2Dir);
17358
+ return resultFromManifest(planV2Dir, publication.manifest);
17021
17359
  } catch (error) {
17022
- rmSync12(tempDir, { recursive: true, force: true });
17360
+ rmSync13(tempDir, { recursive: true, force: true });
17023
17361
  throw error;
17024
17362
  }
17025
- return {
17026
- planDir: destinationDir,
17027
- target,
17028
- planHash: manifest.planHash,
17029
- sourcePlanV1Hash: getPlanV2ExecutionPlanHash(manifest),
17030
- artifactCount: artifacts.length,
17031
- sizeBytes: artifacts.reduce((sum2, artifact) => sum2 + artifact.sizeBytes, 0),
17032
- // Join the artifact's OWN name, not the current constant: a plan written
17033
- // before the container change carries the legacy name, and materializing it
17034
- // under the new one would point at a file that was never written.
17035
- audioPath: target.role === "assembler" ? audioArtifact ? join37(destinationDir, audioArtifact.path) : null : null
17036
- };
17037
- }
17038
- function validatePlanV2MaterializedTarget(planDir, expectedTarget) {
17039
- const markerPath = join37(planDir, PLAN_V2_MATERIALIZATION_MARKER);
17040
- if (!existsSync24(markerPath)) return null;
17041
- const marker = readJsonFile(markerPath, "materialization marker");
17042
- if (!isRecord3(marker) || !isRecord3(marker.manifest) || !isRecord3(marker.target)) {
17043
- throw new PlanV2IntegrityError("malformed materialization marker");
17044
- }
17045
- const target = marker.target;
17046
- if (target.role !== expectedTarget.role || expectedTarget.role === "chunk" && target.chunkIndex !== expectedTarget.chunkIndex) {
17047
- throw new PlanV2IntegrityError(
17048
- "materialization target does not match requested execution role"
17049
- );
17050
- }
17051
- const manifest = parsePlanV2Manifest(marker.manifest);
17052
- const required = listPlanV2ArtifactsForTarget(manifest, expectedTarget);
17053
- for (const artifact of required) {
17054
- const path = join37(planDir, ...artifact.path.split("/"));
17055
- if (!existsSync24(path) || statSync9(path).size !== artifact.sizeBytes) {
17056
- throw new PlanV2IntegrityError(
17057
- `materialized artifact missing or truncated: ${artifact.path}`
17058
- );
17059
- }
17060
- if (sha256File(path) !== artifact.sha256) {
17061
- throw new PlanV2IntegrityError(`materialized artifact hash mismatch: ${artifact.path}`);
17062
- }
17063
- }
17064
- return manifest;
17065
- }
17066
-
17067
- // ../producer/src/services/distributed/planV2Execution.ts
17068
- import { mkdtempSync as mkdtempSync9, rmSync as rmSync16 } from "fs";
17069
- import { tmpdir as tmpdir5 } from "os";
17070
- import { join as join41 } from "path";
17071
-
17072
- // ../producer/src/services/distributed/assemble.ts
17073
- import {
17074
- cpSync as cpSync3,
17075
- existsSync as existsSync25,
17076
- mkdirSync as mkdirSync15,
17077
- mkdtempSync as mkdtempSync8,
17078
- readFileSync as readFileSync12,
17079
- readdirSync as readdirSync10,
17080
- renameSync as renameSync8,
17081
- rmSync as rmSync14,
17082
- statSync as statSync10,
17083
- writeFileSync as writeFileSync11
17084
- } from "fs";
17085
- import { dirname as dirname16, join as join39 } from "path";
17086
-
17087
- // ../producer/src/services/distributed/mp4Stitch.ts
17088
- import {
17089
- createReadStream as createReadStream5,
17090
- createWriteStream,
17091
- mkdirSync as mkdirSync14,
17092
- mkdtempSync as mkdtempSync7,
17093
- renameSync as renameSync7,
17094
- rmSync as rmSync13
17095
- } from "fs";
17096
- import { open } from "fs/promises";
17097
- import { dirname as dirname15, join as join38 } from "path";
17098
- import { finished, pipeline } from "stream/promises";
17099
- function boxAt(src, off, at, limit) {
17100
- let size = src.readUInt32BE(off);
17101
- let bodyStart = at + 8;
17102
- if (size === 1) {
17103
- size = Number(src.readBigUInt64BE(off + 8));
17104
- bodyStart = at + 16;
17105
- }
17106
- if (size < 8 || at + size > limit) throw new Error(`[mp4Stitch] malformed box at ${at}`);
17107
- return { type: src.toString("latin1", off + 4, off + 8), start: at, bodyStart, end: at + size };
17108
17363
  }
17109
- function childBoxes(buf, parent) {
17110
- const boxes = [];
17111
- for (let at = parent.bodyStart; at < parent.end; at = boxes.at(-1).end) {
17112
- boxes.push(boxAt(buf, at, at, parent.end));
17113
- }
17114
- return boxes;
17115
- }
17116
- function findChild(buf, parent, type) {
17117
- return childBoxes(buf, parent).find((box2) => box2.type === type);
17364
+ function createPlanV2FromV1(executionPlanDir, planV2Dir) {
17365
+ return createPlanV2FromExecutionPlan(executionPlanDir, planV2Dir);
17118
17366
  }
17119
- function child(buf, parent, type) {
17120
- const found = findChild(buf, parent, type);
17121
- if (!found) throw new Error(`[mp4Stitch] ${parent.type} has no ${type}`);
17122
- return found;
17367
+ async function publishPlanV2FromExecutionPlan(executionPlanDir, publisher) {
17368
+ return publishExecutionPlan(executionPlanDir, publisher, {});
17123
17369
  }
17124
- var SCAN_BYTES = 64 * 1024;
17125
- async function readMp4Layout(read) {
17126
- const found = /* @__PURE__ */ new Map();
17127
- let payload;
17128
- let buf = Buffer.alloc(0);
17129
- let bufAt = 0;
17130
- const load = async (start, end) => {
17131
- if (start >= bufAt && end <= bufAt + buf.length) return;
17132
- buf = await read(start, Math.max(end, start + SCAN_BYTES));
17133
- bufAt = start;
17134
- };
17135
- for (let at = 0; !(found.has("ftyp") && found.has("moov") && payload); ) {
17136
- await load(at, at + 16);
17137
- if (at + 8 > bufAt + buf.length) {
17138
- throw new Error(`[mp4Stitch] chunk needs ftyp, moov and mdat; found ${[...found.keys()]}`);
17370
+ async function publishExecutionPlan(executionPlanDir, publisher, { digests, log = defaultLogger }) {
17371
+ try {
17372
+ let stageStarted = Date.now();
17373
+ const publication = buildPlanV2Publication(executionPlanDir, digests);
17374
+ logPlanStage(log, "publication", stageStarted);
17375
+ if (publication.storedBlobs.length > 0 && !publisher.referenceStoredBlob) {
17376
+ throw new PlanV2IntegrityError("this publisher cannot reference remote files");
17139
17377
  }
17140
- const box2 = boxAt(buf, at - bufAt, at, Number.MAX_SAFE_INTEGER);
17141
- if (box2.type === "mdat") payload ??= box2;
17142
- if (box2.type === "ftyp" || box2.type === "moov") {
17143
- await load(at, box2.end);
17144
- found.set(box2.type, buf.subarray(at - bufAt, box2.end - bufAt));
17378
+ stageStarted = Date.now();
17379
+ await putTasks([
17380
+ ...publication.blobs.map((blob) => () => publisher.putBlob(blob)),
17381
+ ...publication.storedBlobs.map((blob) => () => publisher.referenceStoredBlob(blob))
17382
+ ]);
17383
+ logPlanStage(log, "upload", stageStarted);
17384
+ stageStarted = Date.now();
17385
+ await publisher.commitManifest(canonicalJsonStringify(publication.manifest));
17386
+ logPlanStage(log, "manifest", stageStarted);
17387
+ return publication.manifest;
17388
+ } catch (error) {
17389
+ try {
17390
+ await publisher.abort();
17391
+ } catch {
17145
17392
  }
17146
- at = box2.end;
17393
+ throw error;
17147
17394
  }
17148
- const [ftyp, moov] = [found.get("ftyp"), found.get("moov")];
17149
- if (moov.length !== moovSize(moov)) throw new Error("[mp4Stitch] moov is cut off");
17150
- return { ftyp, moov, payloadStart: payload.bodyStart, payloadEnd: payload.end };
17151
- }
17152
- var moovSize = (moov) => boxAt(moov, 0, 0, Number.MAX_SAFE_INTEGER).end;
17153
- function readTracks(moov) {
17154
- const root = boxAt(moov, 0, 0, moov.length);
17155
- return childBoxes(moov, root).filter((box2) => box2.type === "trak").map((trak) => readTrack(moov, trak));
17156
17395
  }
17157
- function rows(buf, table, columns, countAt = 4) {
17158
- const count = buf.readUInt32BE(table.bodyStart + countAt);
17159
- const first = table.bodyStart + countAt + 4;
17160
- return Array.from(
17161
- { length: count },
17162
- (_, row) => Array.from(
17163
- { length: columns },
17164
- (_2, col) => buf.readUInt32BE(first + (row * columns + col) * 4)
17165
- )
17166
- );
17396
+ async function publishPlanV2FromV1(executionPlanDir, publisher) {
17397
+ return publishPlanV2FromExecutionPlan(executionPlanDir, publisher);
17167
17398
  }
17168
- function readMediaTime(moov, trak) {
17169
- const edts = findChild(moov, trak, "edts");
17170
- if (!edts) return 0;
17171
- const elst = child(moov, edts, "elst");
17172
- const wide = moov[elst.bodyStart] === 1;
17173
- const entries = moov.readUInt32BE(elst.bodyStart + 4);
17174
- for (let i = 0, at = elst.bodyStart + 8; i < entries; i++, at += wide ? 20 : 12) {
17175
- const offset = at + (wide ? 8 : 4);
17176
- const mediaTime = wide ? Number(moov.readBigInt64BE(offset)) : moov.readInt32BE(offset);
17177
- if (mediaTime !== -1) return mediaTime;
17399
+ async function planV2WithPublisher(projectDir, config, publisher, options = {}) {
17400
+ const stagingRoot = mkdtempSync7(join39(options.stagingParentDir ?? tmpdir4(), ".execution-plan-"));
17401
+ const digests = /* @__PURE__ */ new Map();
17402
+ try {
17403
+ await buildLocalExecutionPlan(projectDir, config, stagingRoot, {
17404
+ executionPlanSizeLimitBytes: Number.MAX_SAFE_INTEGER,
17405
+ deferVideoExtraction: true,
17406
+ remoteFiles: options.remoteFiles,
17407
+ digests,
17408
+ // artifactTargets ships every compiled/ file to all chunks, so each is in the manifest.
17409
+ whileAudioMixes: () => putBlobs(
17410
+ publisher,
17411
+ listFiles(join39(stagingRoot, "compiled")).map(
17412
+ (file) => publishBlobFor(file.absolutePath, digests)
17413
+ )
17414
+ ),
17415
+ chunksCarryAudio: options.chunksCarryAudio,
17416
+ sliceChunkAudio: true
17417
+ });
17418
+ return await publishExecutionPlan(stagingRoot, publisher, { digests, log: config.logger });
17419
+ } catch (error) {
17420
+ try {
17421
+ await publisher.abort();
17422
+ } catch {
17423
+ }
17424
+ throw error;
17425
+ } finally {
17426
+ rmSync13(stagingRoot, { recursive: true, force: true });
17178
17427
  }
17179
- return 0;
17180
- }
17181
- function readSizes(moov, stsz) {
17182
- const fixedSize = moov.readUInt32BE(stsz.bodyStart + 4);
17183
- if (!fixedSize) return rows(moov, stsz, 1, 8).map(([size]) => size);
17184
- return Array(moov.readUInt32BE(stsz.bodyStart + 8)).fill(fixedSize);
17185
- }
17186
- function readSync4(moov, stbl, sampleCount) {
17187
- const stss = findChild(moov, stbl, "stss");
17188
- if (!stss) return null;
17189
- const sync = Array(sampleCount).fill(false);
17190
- for (const [n] of rows(moov, stss, 1)) sync[n - 1] = true;
17191
- return sync;
17192
17428
  }
17193
- function readChunkOffsets(moov, stbl) {
17194
- const stco = findChild(moov, stbl, "stco");
17195
- if (stco) return rows(moov, stco, 1).map(([offset]) => offset);
17196
- return rows(moov, child(moov, stbl, "co64"), 2).map(([hi, lo]) => hi * 2 ** 32 + lo);
17197
- }
17198
- function readSamplesPerChunk(moov, stbl, chunkCount) {
17199
- const stsc = rows(moov, child(moov, stbl, "stsc"), 3);
17200
- return stsc.flatMap(([firstChunk, perChunk, description], i) => {
17201
- if (description !== 1) throw new Error("[mp4Stitch] chunk uses more than one sample entry");
17202
- const nextFirst = stsc[i + 1]?.[0] ?? chunkCount + 1;
17203
- return Array(nextFirst - firstChunk).fill(perChunk);
17429
+ async function planV2(projectDir, config, planV2Dir) {
17430
+ if (existsSync25(planV2Dir)) {
17431
+ throw new PlanV2IntegrityError(`output directory already exists: ${planV2Dir}`);
17432
+ }
17433
+ const publisher = new LocalPlanV2ArtifactPublisher(planV2Dir);
17434
+ const manifest = await planV2WithPublisher(projectDir, config, publisher, {
17435
+ stagingParentDir: dirname15(planV2Dir)
17204
17436
  });
17437
+ return resultFromManifest(planV2Dir, manifest);
17205
17438
  }
17206
- function readTrack(moov, trak) {
17207
- const mdia = child(moov, trak, "mdia");
17208
- const hdlr = child(moov, mdia, "hdlr");
17209
- const stbl = child(moov, child(moov, mdia, "minf"), "stbl");
17210
- if (findChild(moov, stbl, "ctts")) throw new Error("[mp4Stitch] chunk has ctts (B-frames)");
17211
- const stts = rows(moov, child(moov, stbl, "stts"), 2);
17212
- const durations = stts.flatMap(([n, delta]) => Array(n).fill(delta));
17213
- const sizes = readSizes(moov, child(moov, stbl, "stsz"));
17214
- const chunkOffsets = readChunkOffsets(moov, stbl);
17215
- const samplesPerChunk = readSamplesPerChunk(moov, stbl, chunkOffsets.length);
17216
- if (durations.length !== sizes.length || sum(samplesPerChunk) !== sizes.length) {
17217
- throw new Error("[mp4Stitch] sample tables disagree on the sample count");
17218
- }
17219
- const stsd = child(moov, stbl, "stsd");
17439
+ function resultFromManifest(planV2Dir, manifest) {
17220
17440
  return {
17221
- trak,
17222
- handler: moov.toString("latin1", hdlr.bodyStart + 8, hdlr.bodyStart + 12),
17223
- timescale: timescaleOf(moov, child(moov, mdia, "mdhd")),
17224
- mediaTime: readMediaTime(moov, trak),
17225
- stsd: moov.subarray(stsd.start, stsd.end),
17226
- durations,
17227
- sizes,
17228
- sync: readSync4(moov, stbl, sizes.length),
17229
- samplesPerChunk,
17230
- chunkOffsets
17441
+ planDir: planV2Dir,
17442
+ manifestPath: join39(planV2Dir, "plan.json"),
17443
+ planProtocol: PLAN_PROTOCOL_V2,
17444
+ planHash: manifest.planHash,
17445
+ sourcePlanV1Hash: getPlanV2ExecutionPlanHash(manifest),
17446
+ chunkCount: manifest.chunkCount,
17447
+ totalFrames: manifest.totalFrames,
17448
+ fps: manifest.fps,
17449
+ width: manifest.width,
17450
+ height: manifest.height,
17451
+ format: manifest.format,
17452
+ ffmpegVersion: manifest.ffmpegVersion,
17453
+ producerVersion: manifest.producerVersion,
17454
+ limitations: manifest.limitations
17231
17455
  };
17232
17456
  }
17233
- function sum(values) {
17234
- return values.reduce((total, value) => total + value, 0);
17457
+ function getPlanV2ExecutionPlanHash(plan2) {
17458
+ return plan2.sourcePlanV1Hash;
17235
17459
  }
17236
- function box(type, ...body) {
17237
- const header = Buffer.alloc(8);
17238
- header.writeUInt32BE(8 + sum(body.map((part) => part.length)));
17239
- header.write(type, 4, "latin1");
17240
- return Buffer.concat([header, ...body]);
17460
+ function readString2(value, field) {
17461
+ if (typeof value !== "string" || value.length === 0) {
17462
+ throw new PlanV2IntegrityError(`${field} must be a non-empty string`);
17463
+ }
17464
+ return value;
17241
17465
  }
17242
- function fullBox(type, version, body) {
17243
- return box(type, Buffer.from([version, 0, 0, 0]), body);
17466
+ function readPositiveInteger2(value, field) {
17467
+ if (typeof value !== "number" || !Number.isInteger(value) || value <= 0) {
17468
+ throw new PlanV2IntegrityError(`${field} must be a positive integer`);
17469
+ }
17470
+ return value;
17244
17471
  }
17245
- function u32s(values) {
17246
- const buf = Buffer.alloc(values.length * 4);
17247
- values.forEach((value, i) => buf.writeUInt32BE(value, i * 4));
17248
- return buf;
17472
+ function readNonNegativeInteger2(value, field) {
17473
+ if (typeof value !== "number" || !Number.isInteger(value) || value < 0) {
17474
+ throw new PlanV2IntegrityError(`${field} must be a non-negative integer`);
17475
+ }
17476
+ return value;
17249
17477
  }
17250
- function runs(values) {
17251
- const out = [];
17252
- for (const value of values) {
17253
- const last = out.at(-1);
17254
- if (last && last[1] === value) last[0]++;
17255
- else out.push([1, value]);
17478
+ function readSupportedFps(value) {
17479
+ if (!isDistributedFps(value)) {
17480
+ throw new PlanV2IntegrityError(
17481
+ `dimensions.fpsNum must be one of ${DISTRIBUTED_FPS.join(", ")}`
17482
+ );
17256
17483
  }
17257
- return out;
17484
+ return value;
17258
17485
  }
17259
- function rebuild(buf, parent, replace) {
17260
- const parts = childBoxes(buf, parent).map(
17261
- (box2) => replace[box2.type]?.(box2) ?? buf.subarray(box2.start, box2.end)
17262
- );
17263
- return box(parent.type, ...parts);
17486
+ function readExecutionPlanFps(dimensions) {
17487
+ const fpsDen = readPositiveInteger2(dimensions.fpsDen, "dimensions.fpsDen");
17488
+ if (fpsDen !== 1) {
17489
+ throw new PlanV2IntegrityError("dimensions.fpsDen must be 1 for plan v2");
17490
+ }
17491
+ return readSupportedFps(dimensions.fpsNum);
17264
17492
  }
17265
- var DURATION_AT = {
17266
- mvhd: [16, 24],
17267
- mdhd: [16, 24],
17268
- tkhd: [20, 28]
17269
- };
17270
- function withDuration(buf, target, duration) {
17271
- const copy = Buffer.from(buf.subarray(target.start, target.end));
17272
- const body = target.bodyStart - target.start;
17273
- const wide = copy[body] === 1;
17274
- const at = body + DURATION_AT[target.type][wide ? 1 : 0];
17275
- if (wide) copy.writeBigUInt64BE(BigInt(duration), at);
17276
- else if (duration <= 4294967295) copy.writeUInt32BE(duration, at);
17277
- else throw new Error(`[mp4Stitch] ${target.type} duration ${duration} needs a version 1 box`);
17278
- return copy;
17493
+ function readDistributedFormat(value) {
17494
+ if (value !== "mp4" && value !== "mov" && value !== "webm" && value !== "png-sequence") {
17495
+ throw new PlanV2IntegrityError("dimensions.format is unsupported");
17496
+ }
17497
+ return value;
17279
17498
  }
17280
- function timescaleOf(buf, header) {
17281
- return buf.readUInt32BE(header.bodyStart + (buf[header.bodyStart] === 1 ? 20 : 12));
17499
+ function parseArtifact(value, index) {
17500
+ if (!isRecord3(value)) throw new PlanV2IntegrityError(`artifacts[${index}] must be an object`);
17501
+ const path = readString2(value.path, `artifacts[${index}].path`);
17502
+ assertSafeRelativePath(path);
17503
+ if (!isSha256(value.sha256)) {
17504
+ throw new PlanV2IntegrityError(`artifacts[${index}].sha256 must be a lowercase sha256`);
17505
+ }
17506
+ const sizeBytes = value.sizeBytes;
17507
+ if (typeof sizeBytes !== "number" || !Number.isInteger(sizeBytes) || sizeBytes < 0) {
17508
+ throw new PlanV2IntegrityError(`artifacts[${index}].sizeBytes must be a non-negative integer`);
17509
+ }
17510
+ let chunks;
17511
+ if (value.chunks === "all") {
17512
+ chunks = "all";
17513
+ } else if (isNonNegativeIntegerArray(value.chunks)) {
17514
+ chunks = value.chunks;
17515
+ } else {
17516
+ throw new PlanV2IntegrityError(`artifacts[${index}].chunks is invalid`);
17517
+ }
17518
+ if (typeof value.assembler !== "boolean") {
17519
+ throw new PlanV2IntegrityError(`artifacts[${index}].assembler must be boolean`);
17520
+ }
17521
+ return {
17522
+ path,
17523
+ sha256: value.sha256,
17524
+ sizeBytes,
17525
+ chunks,
17526
+ assembler: value.assembler
17527
+ };
17282
17528
  }
17283
- function buildStbl(stsd, track) {
17284
- const stts = runs(track.durations);
17285
- const stsc = [];
17286
- let firstChunk = 1;
17287
- for (const [count, perChunk] of runs(track.samplesPerChunk)) {
17288
- stsc.push([firstChunk, perChunk, 1]);
17289
- firstChunk += count;
17529
+ function parsePlanV2Manifest(value) {
17530
+ if (!isRecord3(value)) throw new PlanV2IntegrityError("manifest must be an object");
17531
+ if (!isRecord3(value.protocol) || value.protocol.schemaVersion !== PLAN_PROTOCOL_V2.schemaVersion || value.protocol.artifactLayout !== PLAN_PROTOCOL_V2.artifactLayout || value.protocol.hashSchema !== PLAN_PROTOCOL_V2.hashSchema) {
17532
+ throw new PlanV2IntegrityError("manifest protocol is not the supported v2 descriptor");
17290
17533
  }
17291
- const co64 = Buffer.alloc(4 + track.chunkOffsets.length * 8);
17292
- co64.writeUInt32BE(track.chunkOffsets.length);
17293
- track.chunkOffsets.forEach((offset, i) => co64.writeBigUInt64BE(BigInt(offset), 4 + i * 8));
17294
- const syncSamples = track.sync?.flatMap((isSync, i) => isSync ? [i + 1] : []) ?? null;
17295
- return box(
17296
- "stbl",
17297
- stsd,
17298
- fullBox("stts", 0, u32s([stts.length, ...stts.flat()])),
17299
- ...syncSamples ? [fullBox("stss", 0, u32s([syncSamples.length, ...syncSamples]))] : [],
17300
- fullBox("stsc", 0, u32s([stsc.length, ...stsc.flat()])),
17301
- fullBox("stsz", 0, u32s([0, track.sizes.length, ...track.sizes])),
17302
- fullBox("co64", 0, co64)
17303
- );
17534
+ if (!isSha256(value.planHash) || !isSha256(value.sourcePlanV1Hash)) {
17535
+ throw new PlanV2IntegrityError("manifest hashes must be lowercase sha256 digests");
17536
+ }
17537
+ if (!Array.isArray(value.artifacts)) throw new PlanV2IntegrityError("artifacts must be an array");
17538
+ const artifacts = value.artifacts.map(parseArtifact);
17539
+ const paths = /* @__PURE__ */ new Set();
17540
+ for (const artifact of artifacts) {
17541
+ if (paths.has(artifact.path))
17542
+ throw new PlanV2IntegrityError(`duplicate artifact path: ${artifact.path}`);
17543
+ paths.add(artifact.path);
17544
+ }
17545
+ if (!paths.has("plan.json"))
17546
+ throw new PlanV2IntegrityError("manifest must include the local execution plan.json artifact");
17547
+ if (!isRecord3(value.limitations) || value.limitations.videoDependencyMode !== "exact-rendered-frames" && value.limitations.videoDependencyMode !== "full-source-pack" && value.limitations.videoDependencyMode !== "source-extract") {
17548
+ throw new PlanV2IntegrityError("unsupported video dependency mode");
17549
+ }
17550
+ const parsed = {
17551
+ protocol: PLAN_PROTOCOL_V2,
17552
+ planHash: value.planHash,
17553
+ sourcePlanV1Hash: value.sourcePlanV1Hash,
17554
+ chunkCount: readPositiveInteger2(value.chunkCount, "chunkCount"),
17555
+ totalFrames: readPositiveInteger2(value.totalFrames, "totalFrames"),
17556
+ fps: readSupportedFps(value.fps),
17557
+ width: readPositiveInteger2(value.width, "width"),
17558
+ height: readPositiveInteger2(value.height, "height"),
17559
+ format: readDistributedFormat(value.format),
17560
+ ffmpegVersion: readString2(value.ffmpegVersion, "ffmpegVersion"),
17561
+ producerVersion: readString2(value.producerVersion, "producerVersion"),
17562
+ limitations: { videoDependencyMode: value.limitations.videoDependencyMode },
17563
+ artifacts
17564
+ };
17565
+ const { planHash: _planHash, ...payload } = parsed;
17566
+ const expectedHash = computeManifestHash(payload);
17567
+ if (expectedHash !== parsed.planHash) {
17568
+ throw new PlanV2IntegrityError("manifest integrity hash mismatch");
17569
+ }
17570
+ for (const [index, artifact] of parsed.artifacts.entries()) {
17571
+ if (artifact.chunks !== "all" && artifact.chunks.some((chunkIndex) => chunkIndex >= parsed.chunkCount)) {
17572
+ throw new PlanV2IntegrityError(`artifacts[${index}].chunks contains an out-of-range index`);
17573
+ }
17574
+ }
17575
+ return parsed;
17304
17576
  }
17305
- var codecOf = (track) => track.stsd.toString("latin1", 20, 24);
17306
- var sameTrack = (track, plan2) => track.handler === plan2.handler && track.timescale === plan2.timescale && codecOf(track) === codecOf(plan2);
17307
- function planTracksOf(tracks, plan2, index) {
17308
- let next = 0;
17309
- const matched = plan2.map((planTrack) => {
17310
- const track = tracks[next];
17311
- if (track && sameTrack(track, planTrack)) return tracks[next++];
17312
- if (planTrack.handler === "soun") return null;
17313
- throw new Error(`[mp4Stitch] chunk ${index} tracks differ from chunk 0`);
17314
- });
17315
- if (next !== tracks.length)
17316
- throw new Error(`[mp4Stitch] chunk ${index} tracks differ from chunk 0`);
17317
- for (const track of tracks) {
17318
- if (track.handler === "vide" && track.mediaTime !== 0) {
17319
- throw new Error(`[mp4Stitch] chunk ${index} video starts at media time ${track.mediaTime}`);
17577
+ function readPlanV2Manifest(planV2Dir) {
17578
+ const manifestPath = join39(planV2Dir, "plan.json");
17579
+ if (!existsSync25(manifestPath)) {
17580
+ throw new PlanV2IntegrityError(`missing v2 manifest: ${manifestPath}`);
17581
+ }
17582
+ const value = readJsonFile(manifestPath, "v2 manifest");
17583
+ return parsePlanV2Manifest(value);
17584
+ }
17585
+ function listPlanV2ArtifactsForTarget(manifest, target) {
17586
+ if (target.role === "chunk") {
17587
+ if (!Number.isInteger(target.chunkIndex) || target.chunkIndex < 0 || target.chunkIndex >= manifest.chunkCount) {
17588
+ throw new PlanV2IntegrityError(`chunkIndex ${String(target.chunkIndex)} is out of range`);
17320
17589
  }
17590
+ return manifest.artifacts.filter(
17591
+ (artifact) => artifact.chunks === "all" || artifact.chunks.some((chunkIndex) => chunkIndex === target.chunkIndex)
17592
+ );
17321
17593
  }
17322
- return matched;
17594
+ return manifest.artifacts.filter((artifact) => artifact.assembler);
17323
17595
  }
17324
- function buildStitchedHeader(layouts) {
17325
- const plan2 = readTracks(layouts[0].moov);
17326
- const chunks = layouts.map((layout, i) => planTracksOf(readTracks(layout.moov), plan2, i));
17327
- const payloadBases = [];
17328
- let payloadBytes = 0;
17329
- for (const layout of layouts) {
17330
- payloadBases.push(payloadBytes);
17331
- payloadBytes += layout.payloadEnd - layout.payloadStart;
17596
+ function verifyBlob(planV2Dir, artifact) {
17597
+ const sourcePath = planV2BlobPath(planV2Dir, artifact.sha256);
17598
+ if (!existsSync25(sourcePath)) {
17599
+ throw new PlanV2IntegrityError(`missing content-addressed artifact ${artifact.sha256}`);
17600
+ }
17601
+ const stats = statSync9(sourcePath);
17602
+ if (!stats.isFile() || stats.size !== artifact.sizeBytes) {
17603
+ throw new PlanV2IntegrityError(`artifact size mismatch for ${artifact.path}`);
17604
+ }
17605
+ if (sha256File(sourcePath) !== artifact.sha256) {
17606
+ throw new PlanV2IntegrityError(`artifact hash mismatch for ${artifact.path}`);
17607
+ }
17608
+ return sourcePath;
17609
+ }
17610
+ function materializePlanV2Target(planV2Dir, target, destinationDir, options = {}) {
17611
+ if (existsSync25(destinationDir)) {
17612
+ throw new PlanV2IntegrityError(`materialization destination already exists: ${destinationDir}`);
17613
+ }
17614
+ const manifest = readPlanV2Manifest(planV2Dir);
17615
+ const artifacts = listPlanV2ArtifactsForTarget(manifest, target);
17616
+ const remoteSources = options.remoteSources ?? {};
17617
+ assertRemoteSources(remoteSources, artifacts);
17618
+ const audioArtifact = artifacts.find((artifact) => isPlanAudioArtifactPath(artifact.path));
17619
+ const verified = artifacts.filter((artifact) => !Object.hasOwn(remoteSources, artifact.path)).map((artifact) => ({
17620
+ artifact,
17621
+ sourcePath: verifyBlob(planV2Dir, artifact)
17622
+ }));
17623
+ mkdirSync15(dirname15(destinationDir), { recursive: true });
17624
+ const tempDir = mkdtempSync7(join39(dirname15(destinationDir), ".plan-v2-materialize-"));
17625
+ try {
17626
+ for (const { artifact, sourcePath } of verified) {
17627
+ const destinationPath = join39(tempDir, ...artifact.path.split("/"));
17628
+ mkdirSync15(dirname15(destinationPath), { recursive: true });
17629
+ copyFileSync6(sourcePath, destinationPath);
17630
+ }
17631
+ if (target.role === "chunk") materializeExtractedVideoDirectories(tempDir);
17632
+ writeFileSync11(
17633
+ join39(tempDir, PLAN_V2_MATERIALIZATION_MARKER),
17634
+ canonicalJsonStringify({
17635
+ manifest,
17636
+ target,
17637
+ ...Object.keys(remoteSources).length > 0 ? { remoteSources } : {}
17638
+ }),
17639
+ "utf-8"
17640
+ );
17641
+ renameSync7(tempDir, destinationDir);
17642
+ } catch (error) {
17643
+ rmSync13(tempDir, { recursive: true, force: true });
17644
+ throw error;
17332
17645
  }
17333
- const template = layouts[0].moov;
17334
- const root = boxAt(template, 0, 0, template.length);
17335
- const movieTimescale = timescaleOf(template, child(template, root, "mvhd"));
17336
- const buildMoov = (dataStart2) => {
17337
- const traks = plan2.map((track, t) => {
17338
- const parts = chunks.flatMap((tracks, chunk) => {
17339
- const part = tracks[t];
17340
- return part ? [{ ...part, chunk }] : [];
17341
- });
17342
- const merged = {
17343
- durations: parts.flatMap((part) => part.durations),
17344
- sizes: parts.flatMap((part) => part.sizes),
17345
- sync: parts.every((part) => !part.sync) ? null : parts.flatMap((part) => part.sync ?? part.sizes.map(() => true)),
17346
- samplesPerChunk: parts.flatMap((part) => part.samplesPerChunk),
17347
- chunkOffsets: parts.flatMap(
17348
- (part) => part.chunkOffsets.map((offset) => {
17349
- const i = part.chunk;
17350
- const { payloadStart, payloadEnd } = layouts[i];
17351
- if (offset < payloadStart || offset >= payloadEnd) {
17352
- throw new Error(`[mp4Stitch] chunk ${i} has samples outside its mdat`);
17353
- }
17354
- return offset - payloadStart + payloadBases[i] + dataStart2;
17355
- })
17356
- )
17357
- };
17358
- const mediaDuration = sum(merged.durations);
17359
- const shown = Math.round(
17360
- (mediaDuration - track.mediaTime) * movieTimescale / track.timescale
17361
- );
17362
- const elst = Buffer.alloc(24);
17363
- elst.writeUInt32BE(1);
17364
- elst.writeBigUInt64BE(BigInt(shown), 4);
17365
- elst.writeBigInt64BE(BigInt(track.mediaTime), 12);
17366
- elst.writeUInt32BE(65536, 20);
17367
- const edts = track.mediaTime ? box("edts", fullBox("elst", 1, elst)) : Buffer.alloc(0);
17368
- const trak = rebuild(template, track.trak, {
17369
- tkhd: (tkhd) => Buffer.concat([withDuration(template, tkhd, shown), edts]),
17370
- edts: () => Buffer.alloc(0),
17371
- mdia: (mdia) => rebuild(template, mdia, {
17372
- mdhd: (mdhd) => withDuration(template, mdhd, mediaDuration),
17373
- minf: (minf) => rebuild(template, minf, { stbl: () => buildStbl(track.stsd, merged) })
17374
- })
17375
- });
17376
- return { trak, shown };
17377
- });
17378
- let next = 0;
17379
- return rebuild(template, root, {
17380
- mvhd: (mvhd) => withDuration(template, mvhd, Math.max(...traks.map((t) => t.shown))),
17381
- trak: () => traks[next++].trak
17382
- });
17646
+ return {
17647
+ planDir: destinationDir,
17648
+ target,
17649
+ planHash: manifest.planHash,
17650
+ sourcePlanV1Hash: getPlanV2ExecutionPlanHash(manifest),
17651
+ artifactCount: artifacts.length,
17652
+ sizeBytes: artifacts.reduce((sum2, artifact) => sum2 + artifact.sizeBytes, 0),
17653
+ // Join the artifact's OWN name, not the current constant: a plan written
17654
+ // before the container change carries the legacy name, and materializing it
17655
+ // under the new one would point at a file that was never written.
17656
+ audioPath: target.role === "assembler" ? audioArtifact ? join39(destinationDir, audioArtifact.path) : null : null
17383
17657
  };
17384
- const ftyp = layouts[0].ftyp;
17385
- const dataStart = ftyp.length + buildMoov(0).length + 16;
17386
- const mdat = Buffer.alloc(16);
17387
- mdat.writeUInt32BE(1);
17388
- mdat.write("mdat", 4, "latin1");
17389
- mdat.writeBigUInt64BE(BigInt(16 + payloadBytes), 8);
17390
- return Buffer.concat([ftyp, buildMoov(dataStart), mdat]);
17391
17658
  }
17392
- function fileRange(path) {
17393
- return async (start, end) => {
17394
- const file = await open(path, "r", 384);
17395
- try {
17396
- const buf = Buffer.alloc(end - start);
17397
- const { bytesRead } = await file.read(buf, 0, buf.length, start);
17398
- return buf.subarray(0, bytesRead);
17399
- } finally {
17400
- await file.close();
17659
+ function assertRemoteSources(remoteSources, artifacts) {
17660
+ if (!isRecord3(remoteSources)) throw new PlanV2IntegrityError("remoteSources must be an object");
17661
+ for (const [path, url] of Object.entries(remoteSources)) {
17662
+ const artifact = artifacts.find((candidate) => candidate.path === path);
17663
+ if (!artifact || !isPlanV2DeferredSourceArtifact(artifact)) {
17664
+ throw new PlanV2IntegrityError(`remote source is not a selected deferred video: ${path}`);
17401
17665
  }
17402
- };
17666
+ if (typeof url !== "string" || !/^https?:\/\//.test(url)) {
17667
+ throw new PlanV2IntegrityError(`remote source for ${path} must be an http(s) URL`);
17668
+ }
17669
+ }
17403
17670
  }
17404
- async function copyPayload(path, layout, index, out) {
17405
- const { payloadStart, payloadEnd } = layout;
17406
- const payload = createReadStream5(path, { start: payloadStart, end: payloadEnd - 1 });
17407
- await pipeline(payload, out, { end: false });
17408
- if (payload.bytesRead !== payloadEnd - payloadStart) {
17409
- throw new Error(
17410
- `[mp4Stitch] chunk ${index} mdat holds ${payload.bytesRead} of ${payloadEnd - payloadStart} payload bytes`
17671
+ function validatePlanV2MaterializedTarget(planDir, expectedTarget) {
17672
+ return readValidatedPlanV2Materialization(planDir, expectedTarget)?.manifest ?? null;
17673
+ }
17674
+ function readValidatedPlanV2Materialization(planDir, expectedTarget) {
17675
+ const markerPath = join39(planDir, PLAN_V2_MATERIALIZATION_MARKER);
17676
+ if (!existsSync25(markerPath)) return null;
17677
+ const marker = readJsonFile(markerPath, "materialization marker");
17678
+ if (!isRecord3(marker) || !isRecord3(marker.manifest) || !isRecord3(marker.target)) {
17679
+ throw new PlanV2IntegrityError("malformed materialization marker");
17680
+ }
17681
+ const target = marker.target;
17682
+ if (target.role !== expectedTarget.role || expectedTarget.role === "chunk" && target.chunkIndex !== expectedTarget.chunkIndex) {
17683
+ throw new PlanV2IntegrityError(
17684
+ "materialization target does not match requested execution role"
17411
17685
  );
17412
17686
  }
17413
- }
17414
- async function writeStitchedMp4(chunkPaths, outputPath) {
17415
- const layouts = await Promise.all(chunkPaths.map((path) => readMp4Layout(fileRange(path))));
17416
- const header = buildStitchedHeader(layouts);
17417
- mkdirSync14(dirname15(outputPath), { recursive: true });
17418
- const workDir = mkdtempSync7(`${outputPath}.stitch-`);
17419
- const out = createWriteStream(join38(workDir, "out.mp4"));
17420
- try {
17421
- out.write(header);
17422
- for (const [i, path] of chunkPaths.entries()) await copyPayload(path, layouts[i], i, out);
17423
- out.end();
17424
- await finished(out);
17425
- renameSync7(join38(workDir, "out.mp4"), outputPath);
17426
- } finally {
17427
- out.destroy();
17428
- rmSync13(workDir, { recursive: true, force: true });
17687
+ const manifest = parsePlanV2Manifest(marker.manifest);
17688
+ const required = listPlanV2ArtifactsForTarget(manifest, expectedTarget);
17689
+ const remoteSources = marker.remoteSources ?? {};
17690
+ assertRemoteSources(remoteSources, required);
17691
+ for (const artifact of required) {
17692
+ if (Object.hasOwn(remoteSources, artifact.path)) continue;
17693
+ const path = join39(planDir, ...artifact.path.split("/"));
17694
+ if (!existsSync25(path) || statSync9(path).size !== artifact.sizeBytes) {
17695
+ throw new PlanV2IntegrityError(
17696
+ `materialized artifact missing or truncated: ${artifact.path}`
17697
+ );
17698
+ }
17699
+ if (sha256File(path) !== artifact.sha256) {
17700
+ throw new PlanV2IntegrityError(`materialized artifact hash mismatch: ${artifact.path}`);
17701
+ }
17429
17702
  }
17703
+ return { manifest, remoteSources };
17430
17704
  }
17431
17705
 
17706
+ // ../producer/src/services/distributed/planV2Execution.ts
17707
+ import { mkdtempSync as mkdtempSync9, rmSync as rmSync16 } from "fs";
17708
+ import { tmpdir as tmpdir5 } from "os";
17709
+ import { join as join42 } from "path";
17710
+
17432
17711
  // ../producer/src/services/distributed/assemble.ts
17712
+ import {
17713
+ cpSync as cpSync3,
17714
+ existsSync as existsSync26,
17715
+ mkdirSync as mkdirSync16,
17716
+ mkdtempSync as mkdtempSync8,
17717
+ readFileSync as readFileSync13,
17718
+ readdirSync as readdirSync10,
17719
+ renameSync as renameSync8,
17720
+ rmSync as rmSync14,
17721
+ statSync as statSync10,
17722
+ writeFileSync as writeFileSync12
17723
+ } from "fs";
17724
+ import { dirname as dirname16, join as join40 } from "path";
17433
17725
  function cfrRateControlArgs(locked) {
17434
17726
  const preset = getEncoderPreset(locked.quality ?? "standard", "mp4");
17435
17727
  if (locked.crf == null && locked.bitrate) {
@@ -17456,8 +17748,8 @@ async function assemble(planDir, chunkPaths, audioPath, outputPath, options) {
17456
17748
  }
17457
17749
  const format = plan2.dimensions.format;
17458
17750
  const cfrEncoder = cfr ? readCfrEncoder(planDir, format) : null;
17459
- if (!existsSync25(dirname16(outputPath))) {
17460
- mkdirSync15(dirname16(outputPath), { recursive: true });
17751
+ if (!existsSync26(dirname16(outputPath))) {
17752
+ mkdirSync16(dirname16(outputPath), { recursive: true });
17461
17753
  }
17462
17754
  const workDir = mkdtempSync8(`${outputPath}.assemble-work-`);
17463
17755
  const fps = { num: plan2.dimensions.fpsNum, den: plan2.dimensions.fpsDen };
@@ -17479,8 +17771,8 @@ async function assemble(planDir, chunkPaths, audioPath, outputPath, options) {
17479
17771
  };
17480
17772
  try {
17481
17773
  const videoSources = cfrEncoder ? [await reencodeCfr(ctx, chunkPaths, cfrEncoder)] : chunkPaths;
17482
- const finalPath = join39(workDir, `output.${format}`);
17483
- const audio = audioPath !== null && existsSync25(audioPath) ? audioPath : null;
17774
+ const finalPath = join40(workDir, `output.${format}`);
17775
+ const audio = audioPath !== null && existsSync26(audioPath) ? audioPath : null;
17484
17776
  await writeJoined(ctx, videoSources, audio, finalPath);
17485
17777
  renameSync8(finalPath, outputPath);
17486
17778
  log.info("[assemble] stage timings", {
@@ -17498,7 +17790,7 @@ async function assemble(planDir, chunkPaths, audioPath, outputPath, options) {
17498
17790
  });
17499
17791
  }
17500
17792
  }
17501
- const fileSize = existsSync25(outputPath) ? statSync10(outputPath).size : 0;
17793
+ const fileSize = existsSync26(outputPath) ? statSync10(outputPath).size : 0;
17502
17794
  return {
17503
17795
  outputPath,
17504
17796
  durationMs: Date.now() - start,
@@ -17507,25 +17799,25 @@ async function assemble(planDir, chunkPaths, audioPath, outputPath, options) {
17507
17799
  };
17508
17800
  }
17509
17801
  function readAssemblePlan(planDir, chunkPaths) {
17510
- const planJsonPath = join39(planDir, "plan.json");
17511
- const chunksJsonPath = join39(planDir, "meta", "chunks.json");
17512
- if (!existsSync25(planJsonPath)) {
17802
+ const planJsonPath = join40(planDir, "plan.json");
17803
+ const chunksJsonPath = join40(planDir, "meta", "chunks.json");
17804
+ if (!existsSync26(planJsonPath)) {
17513
17805
  throw new Error(`[assemble] planDir missing plan.json: ${planJsonPath}`);
17514
17806
  }
17515
- const plan2 = JSON.parse(readFileSync12(planJsonPath, "utf-8"));
17807
+ const plan2 = JSON.parse(readFileSync13(planJsonPath, "utf-8"));
17516
17808
  readPlanProtocolV1(plan2, DISTRIBUTED_RENDER_CAPABILITIES.roles.assembler);
17517
17809
  validatePlanV2MaterializedTarget(planDir, { role: "assembler" });
17518
- if (!existsSync25(chunksJsonPath)) {
17810
+ if (!existsSync26(chunksJsonPath)) {
17519
17811
  throw new Error(`[assemble] planDir missing meta/chunks.json: ${chunksJsonPath}`);
17520
17812
  }
17521
- const chunks = JSON.parse(readFileSync12(chunksJsonPath, "utf-8"));
17813
+ const chunks = JSON.parse(readFileSync13(chunksJsonPath, "utf-8"));
17522
17814
  if (chunkPaths.length !== chunks.length) {
17523
17815
  throw new Error(
17524
17816
  `[assemble] chunkPaths length (${chunkPaths.length}) does not match chunks.json length (${chunks.length}). Adapters must pass one path per chunk, ordered by index.`
17525
17817
  );
17526
17818
  }
17527
17819
  for (const path of chunkPaths) {
17528
- if (!existsSync25(path)) {
17820
+ if (!existsSync26(path)) {
17529
17821
  throw new Error(`[assemble] chunk path does not exist: ${path}`);
17530
17822
  }
17531
17823
  }
@@ -17535,7 +17827,7 @@ function videoInputArgs(ctx, videoSources) {
17535
17827
  return videoSources.length === 1 ? ["-i", videoSources[0]] : concatDemuxerInput(videoSources, ctx.workDir, ctx.fpsArg);
17536
17828
  }
17537
17829
  async function reencodeCfr(ctx, chunkPaths, encoder) {
17538
- const cfrOutputPath = join39(ctx.workDir, `cfr.${ctx.format}`);
17830
+ const cfrOutputPath = join40(ctx.workDir, `cfr.${ctx.format}`);
17539
17831
  const cfrArgs = [
17540
17832
  ...videoInputArgs(ctx, chunkPaths),
17541
17833
  "-c:v",
@@ -17573,7 +17865,7 @@ async function writeJoined(ctx, videoSources, audioPath, finalPath) {
17573
17865
  );
17574
17866
  return;
17575
17867
  }
17576
- const paddedAudioPath = join39(ctx.workDir, "audio-padded.m4a");
17868
+ const paddedAudioPath = join40(ctx.workDir, "audio-padded.m4a");
17577
17869
  const padTrimResult = await ctx.timed(
17578
17870
  "audioMs",
17579
17871
  () => padOrTrimAudioToVideoFrameCount({
@@ -17613,9 +17905,9 @@ async function writeJoined(ctx, videoSources, audioPath, finalPath) {
17613
17905
  }
17614
17906
  }
17615
17907
  function concatDemuxerInput(chunkPaths, workDir, fpsArg) {
17616
- const listPath = join39(workDir, "concat-list.txt");
17908
+ const listPath = join40(workDir, "concat-list.txt");
17617
17909
  const body = chunkPaths.map((path) => `file '${path.replace(/'/g, "'\\''")}'`).join("\n");
17618
- writeFileSync11(listPath, `${body}
17910
+ writeFileSync12(listPath, `${body}
17619
17911
  `, "utf-8");
17620
17912
  return ["-r", fpsArg, "-f", "concat", "-safe", "0", "-i", listPath];
17621
17913
  }
@@ -17646,9 +17938,9 @@ function chunksAreStitchedMp4(planDir, cfr) {
17646
17938
  return true;
17647
17939
  }
17648
17940
  function chunksCarryAudio(planDir) {
17649
- const encoderJsonPath = join39(planDir, "meta", "encoder.json");
17650
- if (!existsSync25(encoderJsonPath)) return false;
17651
- return JSON.parse(readFileSync12(encoderJsonPath, "utf-8")).chunksCarryAudio === true;
17941
+ const encoderJsonPath = join40(planDir, "meta", "encoder.json");
17942
+ if (!existsSync26(encoderJsonPath)) return false;
17943
+ return JSON.parse(readFileSync13(encoderJsonPath, "utf-8")).chunksCarryAudio === true;
17652
17944
  }
17653
17945
  function readCfrEncoder(planDir, format) {
17654
17946
  if (format !== "mp4") {
@@ -17656,11 +17948,11 @@ function readCfrEncoder(planDir, format) {
17656
17948
  `[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.`
17657
17949
  );
17658
17950
  }
17659
- const encoderJsonPath = join39(planDir, "meta", "encoder.json");
17660
- if (!existsSync25(encoderJsonPath)) {
17951
+ const encoderJsonPath = join40(planDir, "meta", "encoder.json");
17952
+ if (!existsSync26(encoderJsonPath)) {
17661
17953
  throw new Error(`[assemble] planDir missing meta/encoder.json: ${encoderJsonPath}`);
17662
17954
  }
17663
- const encoderJson = JSON.parse(readFileSync12(encoderJsonPath, "utf-8"));
17955
+ const encoderJson = JSON.parse(readFileSync13(encoderJsonPath, "utf-8"));
17664
17956
  if (encoderJson.encoder === "libx265-software") {
17665
17957
  throw new Error(
17666
17958
  `[assemble] cfr=true is not yet supported with codec: "h265". The cfr re-encode pass uses libx264 and would silently transcode the h265 chunks. Either disable cfr or render with codec: "h264".`
@@ -17669,8 +17961,8 @@ function readCfrEncoder(planDir, format) {
17669
17961
  return encoderJson;
17670
17962
  }
17671
17963
  function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, startTimeMs) {
17672
- if (existsSync25(outputPath)) rmSync14(outputPath, { recursive: true, force: true });
17673
- mkdirSync15(outputPath, { recursive: true });
17964
+ if (existsSync26(outputPath)) rmSync14(outputPath, { recursive: true, force: true });
17965
+ mkdirSync16(outputPath, { recursive: true });
17674
17966
  let globalIdx = 0;
17675
17967
  for (const chunkDir of chunkPaths) {
17676
17968
  if (!statSync10(chunkDir).isDirectory()) {
@@ -17683,8 +17975,8 @@ function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, start
17683
17975
  throw new Error(`[assemble] png-sequence chunk has no frames: ${chunkDir}`);
17684
17976
  }
17685
17977
  for (const frame of frames) {
17686
- const dst = join39(outputPath, formatExportFrameName(globalIdx, "png"));
17687
- cpSync3(join39(chunkDir, frame), dst);
17978
+ const dst = join40(outputPath, formatExportFrameName(globalIdx, "png"));
17979
+ cpSync3(join40(chunkDir, frame), dst);
17688
17980
  globalIdx += 1;
17689
17981
  }
17690
17982
  }
@@ -17693,14 +17985,14 @@ function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, start
17693
17985
  `[assemble] png-sequence frame count mismatch: merged ${globalIdx} frames vs plan.totalFrames=${totalFrames}. Using on-disk count.`
17694
17986
  );
17695
17987
  }
17696
- if (audioPath !== null && existsSync25(audioPath)) {
17697
- const sidecar = join39(outputPath, MIXED_AUDIO_FILENAME);
17988
+ if (audioPath !== null && existsSync26(audioPath)) {
17989
+ const sidecar = join40(outputPath, MIXED_AUDIO_FILENAME);
17698
17990
  cpSync3(audioPath, sidecar);
17699
17991
  }
17700
17992
  let fileSize = 0;
17701
17993
  for (const name of readdirSync10(outputPath)) {
17702
17994
  try {
17703
- fileSize += statSync10(join39(outputPath, name)).size;
17995
+ fileSize += statSync10(join40(outputPath, name)).size;
17704
17996
  } catch {
17705
17997
  }
17706
17998
  }
@@ -17714,105 +18006,8 @@ function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, start
17714
18006
 
17715
18007
  // ../producer/src/services/distributed/renderChunk.ts
17716
18008
  import { randomBytes } from "crypto";
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";
17719
-
17720
- // ../producer/src/services/distributed/chunkAudio.ts
17721
- function audioCutForChunk(grid, chunks, index, fps) {
17722
- const firstPacketAt = (frame) => Math.min(
17723
- grid.packetCount,
17724
- Math.max(
17725
- 0,
17726
- Math.ceil(
17727
- (frame * fps.den * grid.sampleRate - grid.firstStart * fps.num) / (grid.packetSamples * fps.num)
17728
- )
17729
- )
17730
- );
17731
- const first = index === 0 ? 0 : firstPacketAt(chunks[index].startFrame);
17732
- const end = index === chunks.length - 1 ? grid.packetCount : firstPacketAt(chunks[index + 1].startFrame);
17733
- if (end <= first) return null;
17734
- const cutSeconds = (packet) => (grid.firstStart + packet * grid.packetSamples - grid.packetSamples / 2) / grid.sampleRate;
17735
- const seekSeconds = index === 0 ? null : cutSeconds(first);
17736
- const durationSeconds = end === grid.packetCount ? null : cutSeconds(end) - (seekSeconds ?? 0);
17737
- return { seekSeconds, durationSeconds, firstPacket: first, packetCount: end - first };
17738
- }
17739
- async function probeAacPacketGrid(audioPath) {
17740
- const probe = await runFfprobeJson([
17741
- "-v",
17742
- "error",
17743
- "-select_streams",
17744
- "a:0",
17745
- "-show_entries",
17746
- "stream=sample_rate,time_base,nb_frames:packet=pts,duration",
17747
- "-read_intervals",
17748
- "%+#1",
17749
- "-of",
17750
- "json",
17751
- "--",
17752
- audioPath
17753
- ]);
17754
- const stream = probe.streams?.[0] ?? {};
17755
- const packet = probe.packets?.[0] ?? {};
17756
- const sampleRate = Number(stream.sample_rate);
17757
- const [tbNum = NaN, tbDen = NaN] = String(stream.time_base).split("/").map(Number);
17758
- const toSamples = (ticks) => Number(ticks) * sampleRate * tbNum / tbDen;
17759
- const grid = {
17760
- sampleRate,
17761
- firstStart: toSamples(packet.pts),
17762
- packetSamples: toSamples(packet.duration),
17763
- packetCount: Number(stream.nb_frames)
17764
- };
17765
- const counts = [grid.sampleRate, grid.packetSamples, grid.packetCount];
17766
- if (!Number.isFinite(grid.firstStart) || !counts.every((n) => Number.isFinite(n) && n > 0)) {
17767
- throw new Error(`[chunkAudio] could not read the AAC packet grid: ${JSON.stringify(probe)}`);
17768
- }
17769
- return grid;
17770
- }
17771
- function chunkMuxArgs(input, cut) {
17772
- if (!cut) return ["-i", input.videoPath, "-map", "0:v:0"];
17773
- const seek = cut.seekSeconds === null ? [] : ["-ss", cut.seekSeconds.toFixed(6)];
17774
- const length = cut.durationSeconds === null ? [] : ["-t", cut.durationSeconds.toFixed(6)];
17775
- const exact = seek.length ? ["-copypriorss", "0"] : [];
17776
- return [
17777
- ...["-i", input.videoPath, ...seek, ...length, "-i", input.audioPath],
17778
- ...["-map", "0:v:0", "-map", "1:a:0", ...exact]
17779
- ];
17780
- }
17781
- async function muxChunkWithAudio(input) {
17782
- const grid = input.audioPath ? await probeAacPacketGrid(input.audioPath) : null;
17783
- const cut = grid ? audioCutForChunk(grid, input.chunks, input.chunkIndex, input.fps) : null;
17784
- const args = [...chunkMuxArgs(input, cut), "-c", "copy"];
17785
- appendRenderProvenanceArgs(args, input.outputPath);
17786
- args.push("-y", input.outputPath);
17787
- const result = await runFfmpeg(args, { signal: input.signal, timeout: input.timeoutMs });
17788
- if (!result.success) {
17789
- throw new Error(
17790
- `[chunkAudio] chunk mux failed: ${describeFfmpegFailure(result, input.timeoutMs)}`
17791
- );
17792
- }
17793
- const slice = input.chunks[input.chunkIndex];
17794
- const expected = { video: slice.endFrame - slice.startFrame };
17795
- if (cut) expected.audio = cut.packetCount;
17796
- const probe = await runFfprobeJson([
17797
- "-v",
17798
- "error",
17799
- "-count_packets",
17800
- "-show_entries",
17801
- "stream=codec_type,nb_read_packets",
17802
- "-of",
17803
- "json",
17804
- "--",
17805
- input.outputPath
17806
- ]);
17807
- for (const [type, count] of Object.entries(expected)) {
17808
- const packets = Number(probe.streams?.find((s) => s.codec_type === type)?.nb_read_packets ?? 0);
17809
- if (packets !== count) {
17810
- throw new Error(`[chunkAudio] ${type} packets in chunk ${packets}, expected ${count}`);
17811
- }
17812
- }
17813
- }
17814
-
17815
- // ../producer/src/services/distributed/renderChunk.ts
18009
+ import { existsSync as existsSync27, mkdirSync as mkdirSync17, readFileSync as readFileSync14, readdirSync as readdirSync11, rmSync as rmSync15, writeFileSync as writeFileSync13 } from "fs";
18010
+ import { join as join41 } from "path";
17816
18011
  var LEGACY_DISTRIBUTED_VP9_CPU_USED = 2;
17817
18012
  function validatePlanVideosForChunk(value) {
17818
18013
  try {
@@ -17877,10 +18072,10 @@ async function beginFrameSessionNeedsScreenshotFallback(session, probe = probeBe
17877
18072
  return !await probe(session.page, timeoutMs, probeTick, session.beginFrameIntervalMs);
17878
18073
  }
17879
18074
  function hashChunkOutput(outputPath, kind) {
17880
- if (kind === "file") return sha256Hex(readFileSync13(outputPath));
18075
+ if (kind === "file") return sha256Hex(readFileSync14(outputPath));
17881
18076
  const entries = readdirSync11(outputPath).filter((name) => /\.(png|jpg|jpeg)$/i.test(name)).sort();
17882
18077
  const lines = entries.map(
17883
- (name) => `${name}\0${sha256Hex(readFileSync13(join40(outputPath, name)))}`
18078
+ (name) => `${name}\0${sha256Hex(readFileSync14(join41(outputPath, name)))}`
17884
18079
  );
17885
18080
  return sha256Hex(lines.join("\0"));
17886
18081
  }
@@ -17897,37 +18092,39 @@ function resolveLockedVp9CpuUsed(lockedEncoder) {
17897
18092
  async function renderChunk(planDir, chunkIndex, outputChunkPath) {
17898
18093
  const start = Date.now();
17899
18094
  const log = defaultLogger;
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)) {
18095
+ const planJsonPath = join41(planDir, "plan.json");
18096
+ const encoderJsonPath = join41(planDir, "meta", "encoder.json");
18097
+ const chunksJsonPath = join41(planDir, "meta", "chunks.json");
18098
+ if (!existsSync27(planJsonPath)) {
17904
18099
  throw new RenderChunkValidationError(
17905
18100
  MISSING_PLAN_ARTIFACT,
17906
18101
  `[renderChunk] planDir is missing required artifact: ${planJsonPath}`
17907
18102
  );
17908
18103
  }
17909
- const plan2 = JSON.parse(readFileSync13(planJsonPath, "utf-8"));
18104
+ const plan2 = JSON.parse(readFileSync14(planJsonPath, "utf-8"));
17910
18105
  readPlanProtocolV1(plan2, DISTRIBUTED_RENDER_CAPABILITIES.roles.chunk);
17911
18106
  const planHashStarted = Date.now();
17912
- const v2Manifest = validatePlanV2MaterializedTarget(planDir, {
18107
+ const v2Materialization = readValidatedPlanV2Materialization(planDir, {
17913
18108
  role: "chunk",
17914
18109
  chunkIndex
17915
18110
  });
18111
+ const v2Manifest = v2Materialization?.manifest ?? null;
18112
+ const remoteSources = v2Materialization?.remoteSources ?? {};
17916
18113
  for (const required of [encoderJsonPath, chunksJsonPath]) {
17917
- if (!existsSync26(required)) {
18114
+ if (!existsSync27(required)) {
17918
18115
  throw new RenderChunkValidationError(
17919
18116
  MISSING_PLAN_ARTIFACT,
17920
18117
  `[renderChunk] planDir is missing required artifact: ${required}`
17921
18118
  );
17922
18119
  }
17923
18120
  }
17924
- const encoder = JSON.parse(readFileSync13(encoderJsonPath, "utf-8"));
17925
- const chunks = JSON.parse(readFileSync13(chunksJsonPath, "utf-8"));
17926
- const videosJsonPath = join40(planDir, PLAN_VIDEOS_META_RELATIVE_PATH);
18121
+ const encoder = JSON.parse(readFileSync14(encoderJsonPath, "utf-8"));
18122
+ const chunks = JSON.parse(readFileSync14(chunksJsonPath, "utf-8"));
18123
+ const videosJsonPath = join41(planDir, PLAN_VIDEOS_META_RELATIVE_PATH);
17927
18124
  let planVideos = null;
17928
- if (existsSync26(videosJsonPath)) {
18125
+ if (existsSync27(videosJsonPath)) {
17929
18126
  try {
17930
- planVideos = validatePlanVideosForChunk(JSON.parse(readFileSync13(videosJsonPath, "utf-8")));
18127
+ planVideos = validatePlanVideosForChunk(JSON.parse(readFileSync14(videosJsonPath, "utf-8")));
17931
18128
  } catch (err) {
17932
18129
  if (err instanceof RenderChunkValidationError) throw err;
17933
18130
  throw new RenderChunkValidationError(
@@ -17953,8 +18150,8 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath) {
17953
18150
  `[renderChunk] chunk ${chunkIndex} has non-positive frame count: ${framesInChunk}`
17954
18151
  );
17955
18152
  }
17956
- const compiledDir = join40(planDir, "compiled");
17957
- if (!existsSync26(compiledDir)) {
18153
+ const compiledDir = join41(planDir, "compiled");
18154
+ if (!existsSync27(compiledDir)) {
17958
18155
  throw new RenderChunkValidationError(
17959
18156
  MISSING_PLAN_ARTIFACT,
17960
18157
  `[renderChunk] planDir missing compiled/ directory: ${compiledDir}`
@@ -18010,10 +18207,28 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath) {
18010
18207
  forceScreenshotExplicitlyOptedOut: !encoder.forceScreenshot
18011
18208
  };
18012
18209
  const workDir = `${outputChunkPath}.work.${process.pid}.${randomBytes(4).toString("hex")}`;
18013
- mkdirSync16(workDir, { recursive: true });
18014
- const framesDir = join40(workDir, "captured-frames");
18015
- mkdirSync16(framesDir, { recursive: true });
18210
+ mkdirSync17(workDir, { recursive: true });
18211
+ const framesDir = join41(workDir, "captured-frames");
18212
+ mkdirSync17(framesDir, { recursive: true });
18016
18213
  const videoFramesDir = `${workDir}.video-frames`;
18214
+ const deferredVideoFrames = planVideos ? await extractDeferredVideoFramesForChunk({
18215
+ planDir,
18216
+ planVideos,
18217
+ frames: Array.from(
18218
+ { length: slice.endFrame - slice.startFrame },
18219
+ (_, i) => slice.startFrame + i
18220
+ ),
18221
+ fps: { num: plan2.dimensions.fpsNum, den: plan2.dimensions.fpsDen },
18222
+ outputRoot: videoFramesDir,
18223
+ cfg,
18224
+ remoteSources
18225
+ }) : [];
18226
+ const remoteVideoIds = new Set(
18227
+ planVideos?.extracted.filter(
18228
+ (video) => video.deferredRange && Object.hasOwn(remoteSources, video.deferredRange.sourcePath)
18229
+ ).map((video) => video.videoId)
18230
+ );
18231
+ const remoteVideos = deferredVideoFrames.filter((video) => remoteVideoIds.has(video.videoId));
18017
18232
  const videoFrameLookup = planVideos && planVideos.extracted.length > 0 ? createFrameLookupTable(
18018
18233
  planVideos.videos,
18019
18234
  [
@@ -18022,17 +18237,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath) {
18022
18237
  planVideos.extracted.filter((video) => !video.deferredRange),
18023
18238
  v2Manifest === null ? "dense-v1" : "sparse-v2"
18024
18239
  ),
18025
- ...await extractDeferredVideoFramesForChunk({
18026
- planDir,
18027
- planVideos,
18028
- frames: Array.from(
18029
- { length: slice.endFrame - slice.startFrame },
18030
- (_, i) => slice.startFrame + i
18031
- ),
18032
- fps: { num: plan2.dimensions.fpsNum, den: plan2.dimensions.fpsDen },
18033
- outputRoot: videoFramesDir,
18034
- cfg
18035
- })
18240
+ ...deferredVideoFrames
18036
18241
  ],
18037
18242
  resolveRenderFpsConfig(job.config.fps).value
18038
18243
  ) : null;
@@ -18050,7 +18255,10 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath) {
18050
18255
  dimensions: plan2.dimensions,
18051
18256
  encoder,
18052
18257
  videoCount: planVideos?.videos.length ?? 0,
18053
- html: readFileSync13(join40(compiledDir, "index.html"), "utf-8")
18258
+ html: readFileSync14(join41(compiledDir, "index.html"), "utf-8"),
18259
+ // The plan ships a deferred source only to chunks that show it; don't wait on the rest.
18260
+ skipReadinessVideoIds: deferredVideoFrames.filter((video) => video.framePaths.size === 0 || remoteVideoIds.has(video.videoId)).map((video) => video.videoId),
18261
+ videoMetadataHints: collectVideoMetadataHints(remoteVideos)
18054
18262
  });
18055
18263
  const chunkWorkerCount = calculateOptimalWorkers(framesInChunk, void 0, cfg);
18056
18264
  let session = null;
@@ -18181,7 +18389,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath) {
18181
18389
  },
18182
18390
  resetForScreenshotRetry: () => {
18183
18391
  rmSync15(workDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
18184
- mkdirSync16(framesDir, { recursive: true });
18392
+ mkdirSync17(framesDir, { recursive: true });
18185
18393
  capturePerfs.length = 0;
18186
18394
  captureAttempts.length = 0;
18187
18395
  job.framesRendered = 0;
@@ -18212,10 +18420,10 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath) {
18212
18420
  const effectiveBitrate = encoder.crf != null ? void 0 : encoder.bitrate;
18213
18421
  const videoOnlyPath = encoder.chunksCarryAudio ? `${outputChunkPath}.video.mp4` : outputChunkPath;
18214
18422
  if (isPngSequence) {
18215
- if (!existsSync26(outputChunkPath)) mkdirSync16(outputChunkPath, { recursive: true });
18423
+ if (!existsSync27(outputChunkPath)) mkdirSync17(outputChunkPath, { recursive: true });
18216
18424
  } else {
18217
- const outDir = join40(outputChunkPath, "..");
18218
- if (!existsSync26(outDir)) mkdirSync16(outDir, { recursive: true });
18425
+ const outDir = join41(outputChunkPath, "..");
18426
+ if (!existsSync27(outDir)) mkdirSync17(outDir, { recursive: true });
18219
18427
  }
18220
18428
  const encodeStarted = Date.now();
18221
18429
  await runEncodeStage({
@@ -18258,9 +18466,10 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath) {
18258
18466
  gopSize: framesInChunk
18259
18467
  });
18260
18468
  if (encoder.chunksCarryAudio) {
18469
+ const audioScratchPath = `${outputChunkPath}.audio.m4a`;
18261
18470
  await muxChunkWithAudio({
18262
18471
  videoPath: videoOnlyPath,
18263
- audioPath: resolvePlanAudioPath(planDir),
18472
+ audioPath: chunkAudioSource(planDir, chunkIndex, audioScratchPath),
18264
18473
  outputPath: outputChunkPath,
18265
18474
  chunks,
18266
18475
  chunkIndex,
@@ -18268,6 +18477,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath) {
18268
18477
  timeoutMs: cfg.ffmpegProcessTimeout
18269
18478
  });
18270
18479
  rmSync15(videoOnlyPath, { force: true });
18480
+ rmSync15(audioScratchPath, { force: true });
18271
18481
  }
18272
18482
  encodeStageMs = Date.now() - encodeStarted;
18273
18483
  } finally {
@@ -18306,7 +18516,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath) {
18306
18516
  producerVersion: plan2.producerVersion,
18307
18517
  ffmpegVersion
18308
18518
  };
18309
- writeFileSync12(perfPath, `${JSON.stringify(perfPayload, null, 2)}
18519
+ writeFileSync13(perfPath, `${JSON.stringify(perfPayload, null, 2)}
18310
18520
  `, "utf-8");
18311
18521
  try {
18312
18522
  rmSync15(workDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
@@ -18338,8 +18548,8 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath) {
18338
18548
 
18339
18549
  // ../producer/src/services/distributed/planV2Execution.ts
18340
18550
  async function renderChunkV2(planV2Dir, chunkIndex, outputChunkPath) {
18341
- const workRoot = mkdtempSync9(join41(tmpdir5(), "hf-plan-v2-chunk-"));
18342
- const materializedPlanDir = join41(workRoot, "plan");
18551
+ const workRoot = mkdtempSync9(join42(tmpdir5(), "hf-plan-v2-chunk-"));
18552
+ const materializedPlanDir = join42(workRoot, "plan");
18343
18553
  try {
18344
18554
  materializePlanV2Target(planV2Dir, { role: "chunk", chunkIndex }, materializedPlanDir);
18345
18555
  return await renderChunk(materializedPlanDir, chunkIndex, outputChunkPath);
@@ -18348,8 +18558,8 @@ async function renderChunkV2(planV2Dir, chunkIndex, outputChunkPath) {
18348
18558
  }
18349
18559
  }
18350
18560
  async function assembleV2(planV2Dir, chunkPaths, outputPath, options) {
18351
- const workRoot = mkdtempSync9(join41(tmpdir5(), "hf-plan-v2-assembler-"));
18352
- const materializedPlanDir = join41(workRoot, "plan");
18561
+ const workRoot = mkdtempSync9(join42(tmpdir5(), "hf-plan-v2-assembler-"));
18562
+ const materializedPlanDir = join42(workRoot, "plan");
18353
18563
  try {
18354
18564
  const materialized = materializePlanV2Target(
18355
18565
  planV2Dir,
@@ -18369,9 +18579,9 @@ async function assembleV2(planV2Dir, chunkPaths, outputPath, options) {
18369
18579
  }
18370
18580
 
18371
18581
  // ../producer/src/services/distributed/projectHash.ts
18372
- import { readdirSync as readdirSync12, readFileSync as readFileSync14 } from "fs";
18582
+ import { readdirSync as readdirSync12, readFileSync as readFileSync15, realpathSync as realpathSync2, statSync as statSync11 } from "fs";
18373
18583
  import { createHash as createHash7 } from "crypto";
18374
- import { join as join42, relative as relative8 } from "path";
18584
+ import { join as join43 } from "path";
18375
18585
 
18376
18586
  export {
18377
18587
  createConsoleLogger,