hyperframes 0.8.181 → 0.8.184

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 (218) hide show
  1. package/bin/hyperframes.mjs +3 -1
  2. package/dist/{add-AWQ2EGBU.js → add-5ZTD3MUZ.js} +22 -22
  3. package/dist/{animatedGifPrep-SENXJ7TN.js → animatedGifPrep-AGASIWVA.js} +14 -13
  4. package/dist/api.js +29 -28
  5. package/dist/{asset-XBH3MN3Y.js → asset-LSTEMPIB.js} +3 -3
  6. package/dist/{assetCataloger-KH4X2CRO.js → assetCataloger-BKBMNL3A.js} +16 -13
  7. package/dist/{audio-Q7I473UN.js → audio-KGK44XE4.js} +31 -30
  8. package/dist/{auth-I7CBTEUS.js → auth-MJWKYK77.js} +4 -4
  9. package/dist/{autoUpdate-KY5TLSN7.js → autoUpdate-2GGDKYR2.js} +2 -2
  10. package/dist/{backgroundChecks-4Q3VVK5W.js → backgroundChecks-7GRX5VQY.js} +3 -3
  11. package/dist/backgroundChecksWorker.js +3 -3
  12. package/dist/{batchRender-3AYDQRBI.js → batchRender-BCV2LELR.js} +7 -7
  13. package/dist/{beats-P4LENNVT.js → beats-7BCFRBAL.js} +12 -12
  14. package/dist/{benchmark-AXOAECN3.js → benchmark-2R6CN5MQ.js} +24 -24
  15. package/dist/{browser-XZKB5HGJ.js → browser-6AFGOI7G.js} +20 -20
  16. package/dist/{bundleWithLocalizedFonts-2WCR57IN.js → bundleWithLocalizedFonts-2EGPA5ZO.js} +2 -2
  17. package/dist/{capture-NWARHRHR.js → capture-HA2UHLIU.js} +8 -5
  18. package/dist/{capture-4RN4APDF.js → capture-HOIAW6MO.js} +50 -41
  19. package/dist/{captureCompositionFrame-QOF5BRF4.js → captureCompositionFrame-4236FVWJ.js} +22 -20
  20. package/dist/{captureTimeout-K2JNVVCI.js → captureTimeout-RIMG6IYH.js} +1 -1
  21. package/dist/{catalog-CRPOPVD5.js → catalog-A6CZAWW4.js} +42 -42
  22. package/dist/{catch-up-MHWGUQ42.js → catch-up-TNA4DJOA.js} +15 -15
  23. package/dist/check-5E5U2EQZ.js +117 -0
  24. package/dist/checkBrowser-FNINXBMZ.js +118 -0
  25. package/dist/{chunk-OJTDUF4W.js → chunk-2PVE4WS2.js} +1 -1
  26. package/dist/{checkBrowser-VES2NF3K.js → chunk-2ULXZ4PG.js} +187 -114
  27. package/dist/{chunk-WITHUCCB.js → chunk-2XA7KXQO.js} +2 -2
  28. package/dist/{chunk-HODBPO4A.js → chunk-3MDJIWOV.js} +626 -328
  29. package/dist/{chunk-JBCU6NR5.js → chunk-3QVMJ7GX.js} +56 -55
  30. package/dist/{chunk-OR5WCFWZ.js → chunk-3TXNNBI2.js} +2 -2
  31. package/dist/{chunk-IZM562YJ.js → chunk-4KKKBEP3.js} +4 -4
  32. package/dist/{chunk-XEWHUPFS.js → chunk-4N4P3ZWL.js} +2 -2
  33. package/dist/{chunk-537IYAEE.js → chunk-4UMPIPCQ.js} +9 -1
  34. package/dist/{chunk-J4L5TPLS.js → chunk-5UDRPPN2.js} +48 -6
  35. package/dist/{chunk-L3YOGIPG.js → chunk-66LREXGS.js} +1 -1
  36. package/dist/{chunk-6I7V6Q7F.js → chunk-74JMKLVU.js} +103 -20
  37. package/dist/{chunk-3PULCIY5.js → chunk-7HJALUIO.js} +5 -5
  38. package/dist/{chunk-GZVP4TWN.js → chunk-7JAD4MEW.js} +1 -1
  39. package/dist/{chunk-NAMGMP3R.js → chunk-7KVVNF7B.js} +1 -1
  40. package/dist/{chunk-TO6JNTML.js → chunk-7MB2ZAQF.js} +13 -8
  41. package/dist/{chunk-55IG255C.js → chunk-7NRI7HYX.js} +1 -1
  42. package/dist/{chunk-VAJIPSN6.js → chunk-ABZYYUUH.js} +1 -1
  43. package/dist/{chunk-GK2FIKIQ.js → chunk-ADDHW2FS.js} +214 -37
  44. package/dist/{chunk-ZUOSILUE.js → chunk-BCHSTE7U.js} +1 -1
  45. package/dist/{chunk-AOWDVB2T.js → chunk-CXABAXB3.js} +1 -1
  46. package/dist/{chunk-E24HSMZV.js → chunk-CYBXXSC4.js} +65 -14
  47. package/dist/{chunk-NZZYJOMJ.js → chunk-CZUAX3BV.js} +6 -5
  48. package/dist/{chunk-GV5R5B7Q.js → chunk-DGECWPTL.js} +1 -1
  49. package/dist/{chunk-BRHI7EIA.js → chunk-DKPNNYBZ.js} +4 -0
  50. package/dist/{chunk-UIDZRCBG.js → chunk-DWUGKEHR.js} +1 -1
  51. package/dist/{chunk-D3ZWBHZP.js → chunk-DYAPHNV6.js} +3 -2
  52. package/dist/{chunk-T7UHTUUE.js → chunk-E32ZQ2WO.js} +9 -9
  53. package/dist/{chunk-RGHQID2E.js → chunk-EHSBBBR2.js} +5 -3
  54. package/dist/{chunk-DJJ7SK4F.js → chunk-EM7C7H7S.js} +8 -8
  55. package/dist/{chunk-XSRUVMXK.js → chunk-ES426XRJ.js} +4 -4
  56. package/dist/chunk-FG4BBFZI.js +122 -0
  57. package/dist/{chunk-JMFJI26U.js → chunk-FGJEUV4S.js} +2 -2
  58. package/dist/{check-DJCUOUDI.js → chunk-GDEV42FA.js} +198 -142
  59. package/dist/{chunk-RXQHSGAQ.js → chunk-HR23IKLZ.js} +2 -2
  60. package/dist/{chunk-KMAAOHLO.js → chunk-HRPWRZGF.js} +1 -1
  61. package/dist/{chunk-UPFUC4DB.js → chunk-HTIUNMTP.js} +1 -1
  62. package/dist/{chunk-V2UNZ7QP.js → chunk-ICJEAFPD.js} +25 -14
  63. package/dist/{chunk-EOADENKV.js → chunk-IWTNTUPQ.js} +16 -14
  64. package/dist/chunk-IZCW2NKT.js +29 -0
  65. package/dist/{chunk-XFCLWWL3.js → chunk-J36JVBLR.js} +11 -4
  66. package/dist/{chunk-LBQFLUIY.js → chunk-J3QM7AHP.js} +9 -49
  67. package/dist/{chunk-DUN63LHY.js → chunk-JB6ARMQS.js} +2 -2
  68. package/dist/{chunk-5E5EYPZO.js → chunk-JIUTXKWA.js} +7 -7
  69. package/dist/{chunk-YJDB4FLT.js → chunk-JQYJXUJW.js} +1 -1
  70. package/dist/{chunk-PBN6GBJE.js → chunk-JVBRJMOP.js} +14 -1
  71. package/dist/{chunk-RHWJKSGH.js → chunk-KANNRHY7.js} +1 -1
  72. package/dist/{chunk-IL3GTCQY.js → chunk-KN46TGHV.js} +72 -140
  73. package/dist/{chunk-OFY6JTZC.js → chunk-LLC4CS6I.js} +2 -2
  74. package/dist/{chunk-OJDXABUF.js → chunk-LRGYPXHF.js} +2 -2
  75. package/dist/{chunk-GNE57O7U.js → chunk-MFAETWIF.js} +1 -1
  76. package/dist/{chunk-32GKETR7.js → chunk-MMQ52NIZ.js} +74 -0
  77. package/dist/chunk-MOEGCMKI.js +180 -0
  78. package/dist/{chunk-E6RPWJVC.js → chunk-NAUKCKCJ.js} +5 -1
  79. package/dist/{chunk-B4VT6YU2.js → chunk-OLZENUPI.js} +50 -7
  80. package/dist/{chunk-H4YZZ2VQ.js → chunk-PFD25NAC.js} +1 -1
  81. package/dist/{chunk-XH262OWN.js → chunk-PFYWVUHA.js} +1 -1
  82. package/dist/{chunk-S3RYRF3W.js → chunk-PSKEKWVU.js} +1 -1
  83. package/dist/{chunk-N5SXQCJL.js → chunk-Q33P4EAC.js} +3 -3
  84. package/dist/{chunk-3DKN5WKL.js → chunk-QH3T4WI2.js} +77 -6
  85. package/dist/{chunk-O5AE7HOP.js → chunk-QL2PNIXC.js} +37 -33
  86. package/dist/{chunk-WUUCEFL5.js → chunk-R2BFTECP.js} +5 -5
  87. package/dist/chunk-R2SQBPMH.js +3805 -0
  88. package/dist/{chunk-YPBIAONI.js → chunk-RGXVEYHI.js} +230 -162
  89. package/dist/{chunk-YMS6CJCD.js → chunk-SAX7HQ5E.js} +1 -1
  90. package/dist/{chunk-KIO3R4QN.js → chunk-SN7KI5JA.js} +27 -14
  91. package/dist/{chunk-4USWWI2Y.js → chunk-TE427YID.js} +2 -2
  92. package/dist/{chunk-ICMXC3MI.js → chunk-U6RXZNZE.js} +31 -18
  93. package/dist/{chunk-X7CCSMRR.js → chunk-UJKUB2LH.js} +16 -16
  94. package/dist/{chunk-K26FR4SM.js → chunk-UKOTVJFV.js} +12 -12
  95. package/dist/{chunk-4OT3FWVV.js → chunk-UNNXFKVG.js} +2 -2
  96. package/dist/{chunk-PF7OWVTL.js → chunk-VDM3FEDP.js} +2 -2
  97. package/dist/{chunk-Y5GY6Y7A.js → chunk-VNVQGDPC.js} +2 -2
  98. package/dist/{chunk-22QHO26F.js → chunk-W64L7IFS.js} +1 -1
  99. package/dist/{chunk-Y2RXRPL2.js → chunk-WHPN2TIT.js} +5 -5
  100. package/dist/{chunk-65WIUIHL.js → chunk-WTFUDNW2.js} +7 -15
  101. package/dist/{chunk-3XDOGSRD.js → chunk-X3CG5YQA.js} +3 -3
  102. package/dist/{chunk-UZOZ2W2E.js → chunk-XGHHVYC7.js} +96 -34
  103. package/dist/{chunk-4IEH6EAC.js → chunk-YALM6XN4.js} +203 -58
  104. package/dist/{chunk-B6JLCPT3.js → chunk-YOZPQCSR.js} +6 -6
  105. package/dist/{chunk-5JMZLQT6.js → chunk-YWHCXIMD.js} +16 -16
  106. package/dist/{chunk-UVEQLDKB.js → chunk-YWNJTZGN.js} +3 -2
  107. package/dist/{chunk-WUQUR33W.js → chunk-ZOCHDXDG.js} +1 -1
  108. package/dist/{chunk-IWTRHPTP.js → chunk-ZRHO6YMM.js} +8 -8
  109. package/dist/{clean-GJNDVVDI.js → clean-HGASSMWI.js} +41 -40
  110. package/dist/cli.js +68 -64
  111. package/dist/{cloud-JCWNMKXC.js → cloud-JPDVNXHN.js} +4 -4
  112. package/dist/{cloudrun-2BKGY53X.js → cloudrun-AMMGSDRP.js} +8 -8
  113. package/dist/commands/layout-audit.browser.js +15 -4
  114. package/dist/commands/motion-signature.browser.js +52 -0
  115. package/dist/{compare-XWM7FAYM.js → compare-LHHVHUN3.js} +51 -47
  116. package/dist/{compiler-U3W6CJW7.js → compiler-Q3N6QXMT.js} +6 -6
  117. package/dist/{component-CBRAE325.js → component-D72J32Q7.js} +4 -4
  118. package/dist/{compositions-EZYJEHQ3.js → compositions-UQRJ5ROG.js} +29 -29
  119. package/dist/{contactSheet-27SRU7A4.js → contactSheet-CAGMKI3E.js} +3 -1
  120. package/dist/{delete-JWWMOITI.js → delete-P42VIDDA.js} +9 -9
  121. package/dist/{deploy-UGOV76OH.js → deploy-WZTH57O4.js} +11 -11
  122. package/dist/{destroy-QBLMYDBF.js → destroy-4B5HW5FH.js} +5 -5
  123. package/dist/{deterministicFonts-L7YEEFGI.js → deterministicFonts-VQ5UBMXY.js} +2 -2
  124. package/dist/{dist-VO7OXOFK.js → dist-DYS24AMA.js} +5 -5
  125. package/dist/{dist-2FMTAVGL.js → dist-SENYPQGW.js} +4 -4
  126. package/dist/{docs-MPOM3PHH.js → docs-UEWLIP6D.js} +1 -1
  127. package/dist/{doctor-XSKSZRR7.js → doctor-KEJIG2SK.js} +28 -27
  128. package/dist/{events-RGS4MAH4.js → events-CYKSNR7N.js} +6 -6
  129. package/dist/{events-AIZODZMP.js → events-UZXFQPMT.js} +9 -9
  130. package/dist/{feedback-GE3VDZOH.js → feedback-LXNQF7CA.js} +35 -35
  131. package/dist/{ffmpeg-SCXZTR6X.js → ffmpeg-BYHON577.js} +16 -15
  132. package/dist/{figma-3R4XST4Y.js → figma-EIQ35PS6.js} +3 -3
  133. package/dist/fontLocalizeCli.js +24 -23
  134. package/dist/{get-4FKJP6IM.js → get-CC2YL7QO.js} +9 -9
  135. package/dist/{gpuPolicy-4NNXESM7.js → gpuPolicy-VFSACUQV.js} +14 -13
  136. package/dist/{grade-compare-FAYVPXUR.js → grade-compare-TUQL7IV2.js} +56 -50
  137. package/dist/{help-C3UQMYXK.js → help-ROROO5K6.js} +1 -1
  138. package/dist/{history-LDGQ2IZJ.js → history-PNELYLXX.js} +15 -15
  139. package/dist/hyperframe.manifest.json +1 -1
  140. package/dist/hyperframe.runtime.iife.js +59 -59
  141. package/dist/hyperframes-player.global.js +8 -3
  142. package/dist/{info-KMXFLD3Q.js → info-4FZTH6AS.js} +20 -20
  143. package/dist/{init-D7GYHSX5.js → init-I43GHHVP.js} +69 -68
  144. package/dist/{inspect-CUKWHCGS.js → inspect-IT4TYIZ3.js} +46 -44
  145. package/dist/{installer-EPRSH6QI.js → installer-CWOJVK5K.js} +7 -7
  146. package/dist/{keyframes-XCUPHBW3.js → keyframes-3FBOGVD4.js} +16 -16
  147. package/dist/{lambda-IMORFJHN.js → lambda-H3V32I7M.js} +15 -15
  148. package/dist/{layout-JD2IOBMS.js → layout-BZ5LYJ5C.js} +46 -44
  149. package/dist/{lint-FHLER36Y.js → lint-ZTNSGP2S.js} +23 -23
  150. package/dist/{lintProject-ARKJXDQF.js → lintProject-C2ONSH2U.js} +4 -4
  151. package/dist/{list-MGCFG5MA.js → list-XM4GWJYY.js} +10 -10
  152. package/dist/{login-QX6M4UTR.js → login-H6HYKMWX.js} +7 -7
  153. package/dist/{logout-DKJANASY.js → logout-DDC4QLVK.js} +3 -3
  154. package/dist/{manager-IETAVBZS.js → manager-TWVDSJXO.js} +17 -16
  155. package/dist/{manager-V2QCCPOJ.js → manager-UHWEKPXC.js} +8 -3
  156. package/dist/{media-treatment-MDAQRITV.js → media-treatment-N7MY5NXB.js} +34 -33
  157. package/dist/{media-use-4XXGABIL.js → media-use-JBN57JE3.js} +1 -1
  158. package/dist/{models-NZAOSI5A.js → models-WEMIMYBD.js} +25 -24
  159. package/dist/{motionShot-6ZDUM2K4.js → motionShot-RHICTVPU.js} +29 -26
  160. package/dist/{normalize-audio-7PRYX6QM.js → normalize-audio-ZFUZCJE3.js} +12 -12
  161. package/dist/{open-I254NY5J.js → open-IBJ4FL3G.js} +24 -24
  162. package/dist/{parakeetRunner-TL3TOGUS.js → parakeetRunner-XXO4WSLR.js} +26 -25
  163. package/dist/{pipeline-YPERVOQM.js → pipeline-6ML3LUVK.js} +19 -18
  164. package/dist/{play-MYI7BYKS.js → play-RV4NAVF5.js} +40 -38
  165. package/dist/{policies-5NTZOHJA.js → policies-XXNGXWHV.js} +1 -1
  166. package/dist/{preflight-HRCSZ52D.js → preflight-R3DEFMLA.js} +19 -18
  167. package/dist/{present-WYXQDV4Q.js → present-5TBJGE6B.js} +34 -33
  168. package/dist/{preview-535HTWQO.js → preview-FLKZ5CXJ.js} +56 -55
  169. package/dist/{producer-C76CRJ5O.js → producer-7XYSPVKH.js} +1 -1
  170. package/dist/{progress-I37JB7R4.js → progress-NHJIHAAG.js} +2 -2
  171. package/dist/{projectConfig-5QCNTGQT.js → projectConfig-TBU2VWZX.js} +9 -9
  172. package/dist/{publication-2QT7VVE2.js → publication-CWWUJYJ5.js} +5 -5
  173. package/dist/{publish-53EBL7OO.js → publish-FSNE4FEB.js} +28 -27
  174. package/dist/{refresh-MGCVWX56.js → refresh-RXTOPRBB.js} +3 -3
  175. package/dist/{remove-background-OBYHMPD4.js → remove-background-MDFWRWCT.js} +32 -31
  176. package/dist/render-DBGQZAZT.js +1516 -0
  177. package/dist/{render-3MELF6Y3.js → render-F4JQ553X.js} +102 -96
  178. package/dist/{render-75CRB3BW.js → render-LWZKXCLY.js} +8 -8
  179. package/dist/{render-batch-CEVWUYBP.js → render-batch-PU7NMLIJ.js} +8 -8
  180. package/dist/renderSetupWorker.js +12 -9
  181. package/dist/{resolver-4ICE2AT6.js → resolver-RUBGFXPC.js} +7 -7
  182. package/dist/{screenshotCapture-HXEZ3TNJ.js → screenshotCapture-UBY6TT75.js} +33 -6
  183. package/dist/{sherpa-L4UACXT3.js → sherpa-X2TBNO6P.js} +25 -24
  184. package/dist/{sites-3ZBYB5X4.js → sites-MAVH57MO.js} +2 -2
  185. package/dist/skills/hyperframes/references/review-loop.md +1 -1
  186. package/dist/{skills-DXTQPCAO.js → skills-WHKMWO2D.js} +17 -17
  187. package/dist/{skillsUpdateCheck-ULXRGLDU.js → skillsUpdateCheck-XQGZGJJG.js} +3 -3
  188. package/dist/{snapshot-PWFHQU5E.js → snapshot-M4NUUSNU.js} +73 -71
  189. package/dist/{src-RDSWHMJS.js → src-4UYM2K3S.js} +34 -26
  190. package/dist/{src-S7NVL5VF.js → src-6MSC62U2.js} +23 -19
  191. package/dist/{state-KAXGHF77.js → state-ORRPOLMQ.js} +2 -2
  192. package/dist/{staticProjectServer-GYPUQGI7.js → staticProjectServer-ERDTFP45.js} +21 -20
  193. package/dist/{status-BV7QAUQD.js → status-CM7IQTU2.js} +6 -6
  194. package/dist/studio/assets/{hyperframes-player-DcD3NXpX.js → hyperframes-player-DEncBxR7.js} +7 -2
  195. package/dist/studio/assets/{index-CFrZAwKo.js → index-BABXr78Q.js} +4 -4
  196. package/dist/studio/assets/{index-BIDKYk0h.js → index-Cc22mgvY.js} +1 -1
  197. package/dist/studio/index.html +1 -1
  198. package/dist/{studio-api-IXGX5ZFI.js → studio-api-SAKD3VKC.js} +18 -17
  199. package/dist/{studioServer-LA5Z7RSS.js → studioServer-IRI7EVOM.js} +91 -84
  200. package/dist/{telemetry-STEMAMAX.js → telemetry-F4XADVAF.js} +10 -10
  201. package/dist/{telemetry-CQYHCCPL.js → telemetry-QG5AANZ4.js} +1 -1
  202. package/dist/{timeline-KLD24EN6.js → timeline-5I444RRE.js} +42 -41
  203. package/dist/{tokens-ICLORAA4.js → tokens-LRRYM35A.js} +3 -3
  204. package/dist/{transcribe-A6IKK7VS.js → transcribe-44XDFNVU.js} +48 -47
  205. package/dist/{transcribe-CTHZLRXF.js → transcribe-WHYHWAB2.js} +20 -19
  206. package/dist/{tts-EDK3QS7M.js → tts-A2WILYGN.js} +6 -6
  207. package/dist/{updateCheck-FHCAAAXA.js → updateCheck-7CPH3CAZ.js} +2 -2
  208. package/dist/{upgrade-7EV4F2R7.js → upgrade-WW4GQ2RI.js} +3 -3
  209. package/dist/{urlDownloader-MIMGVASJ.js → urlDownloader-OHLLP45A.js} +13 -12
  210. package/dist/{validate-LZF46W3V.js → validate-VNCDH2LW.js} +40 -38
  211. package/dist/{video-ZC47NFOL.js → video-5OHTLL2D.js} +17 -14
  212. package/dist/warmCheck.js +228 -0
  213. package/dist/warmCheckClient.js +139 -0
  214. package/package.json +4 -2
  215. package/dist/chunk-BAZ5LCR3.js +0 -3805
  216. package/dist/hyperframe-runtime.js +0 -1125
  217. package/dist/render-5BXTEEG7.js +0 -719
  218. package/dist/{synthesize-I2C24XJ5.js → synthesize-PKTB5ATG.js} +1 -1
@@ -5,12 +5,10 @@ var require = __hf_createRequire(import.meta.url);
5
5
  var __filename = __hf_fileURLToPath(import.meta.url);
6
6
  var __dirname = __hf_dirname(__filename);
7
7
  import {
8
- parseEncoderOptions
9
- } from "./chunk-KLIKCX6Q.js";
10
- import {
11
- RENDER_TEMP_DIR,
12
- createStudioPositionSeekReapplyScript
13
- } from "./chunk-EVMY7IPL.js";
8
+ EncoderInterruptedError,
9
+ encoderFailureError,
10
+ prepareAnimatedGifInputs
11
+ } from "./chunk-TE427YID.js";
14
12
  import {
15
13
  GENERIC_FAMILIES,
16
14
  collectFontFamilyCustomProperties,
@@ -18,12 +16,14 @@ import {
18
16
  iterateFontFamilyDeclarations,
19
17
  normalizeSystemFontPrimaryFamilies,
20
18
  resolveFontFamilyDeclarationFamilies
21
- } from "./chunk-B4VT6YU2.js";
19
+ } from "./chunk-OLZENUPI.js";
22
20
  import {
23
- EncoderInterruptedError,
24
- encoderFailureError,
25
- prepareAnimatedGifInputs
26
- } from "./chunk-4USWWI2Y.js";
21
+ parseEncoderOptions
22
+ } from "./chunk-KLIKCX6Q.js";
23
+ import {
24
+ RENDER_TEMP_DIR,
25
+ createStudioPositionSeekReapplyScript
26
+ } from "./chunk-EVMY7IPL.js";
27
27
  import {
28
28
  BROWSER_GPU_NOT_SOFTWARE,
29
29
  CaptureFailure,
@@ -80,6 +80,7 @@ import {
80
80
  distributeFrames,
81
81
  distributeFramesInterleaved,
82
82
  downloadToTemp,
83
+ encodeAac,
83
84
  encodeFramesChunkedConcat,
84
85
  encodeFramesFromDir,
85
86
  executeParallelCapture,
@@ -113,6 +114,7 @@ import {
113
114
  isVideoFrameFormat,
114
115
  isVideoHiddenBeforeStreamStart,
115
116
  isVideoPastTimelineEnd,
117
+ measureVideoShownBoxes,
116
118
  mergeWorkerFrames,
117
119
  muxVideoWithAudio,
118
120
  normalizeObjectFit,
@@ -139,7 +141,6 @@ import {
139
141
  resolveFinalFrameExtractionWindow,
140
142
  resolveHeadlessShellPath,
141
143
  resolvePlayableVideoDuration,
142
- resolveReferencedStart,
143
144
  resolveVideoExtractionWindow,
144
145
  runFfmpeg,
145
146
  runVideoExtractionWithRetry,
@@ -153,46 +154,35 @@ import {
153
154
  validateEngineConfigSnapshot,
154
155
  verifyDiskDrawElementSamples,
155
156
  writeCapturedFrame
156
- } from "./chunk-YPBIAONI.js";
157
- import {
158
- assignBundledRuntimeCompositionIds,
159
- buildVariablesByCompScript,
160
- emitMountedModuleScripts,
161
- emitRootCompositionVariableStyles,
162
- ensureExternalLinkTag,
163
- ensureExternalScriptTag,
164
- extractStandaloneEntryFromIndex,
165
- inlineSubCompositions,
166
- isTemplateEntry,
167
- parsesAsScript,
168
- prepareFlattenedInnerRoot
169
- } from "./chunk-4IEH6EAC.js";
170
- import {
171
- LOW_MEMORY_TOTAL_MB_THRESHOLD,
172
- getCgroupMemoryLimitMb,
173
- getSystemTotalMb
174
- } from "./chunk-W2LGJSEN.js";
157
+ } from "./chunk-RGXVEYHI.js";
175
158
  import {
176
159
  DISTRIBUTED_FPS,
177
160
  HF_VFX_ATTR,
178
- MEDIA_START_BASIS_ATTR,
179
161
  chainCapture,
180
162
  clearGifFramesBeforeNext,
181
- compositionOriginSeconds,
182
- cutToHostSlots,
183
163
  fpsToFfmpegArg,
184
164
  fpsToNumber,
185
165
  gifClearsAfterLeavingFrameInPlace,
186
166
  gsapCdnDist,
187
- hostInPointSeconds,
188
167
  isDistributedFps,
189
168
  parseFps,
190
169
  parseVfxChain,
191
- readMediaStartBasis,
192
- resolveAbsoluteMediaStartSeconds,
193
170
  resolveAuthoredTimingWindow,
194
171
  toFps
195
- } from "./chunk-LBQFLUIY.js";
172
+ } from "./chunk-J3QM7AHP.js";
173
+ import {
174
+ assignBundledRuntimeCompositionIds,
175
+ buildVariablesByCompScript,
176
+ emitMountedModuleScripts,
177
+ emitRootCompositionVariableStyles,
178
+ ensureExternalLinkTag,
179
+ ensureExternalScriptTag,
180
+ extractStandaloneEntryFromIndex,
181
+ inlineSubCompositions,
182
+ isTemplateEntry,
183
+ parsesAsScript,
184
+ prepareFlattenedInnerRoot
185
+ } from "./chunk-YALM6XN4.js";
196
186
  import {
197
187
  COMPOSITION_SOURCE_URL,
198
188
  HF_COLOR_GRADING_ATTR,
@@ -220,7 +210,7 @@ import {
220
210
  shouldClampResolvedMediaDuration,
221
211
  sourceTimeAt,
222
212
  styleElementsFor
223
- } from "./chunk-BAZ5LCR3.js";
213
+ } from "./chunk-R2SQBPMH.js";
224
214
  import {
225
215
  MAX_AUDIO_GAIN
226
216
  } from "./chunk-UJKQWGFF.js";
@@ -235,24 +225,36 @@ import {
235
225
  normalizeResolutionFlag,
236
226
  suggestMatchingPreset
237
227
  } from "./chunk-YMS5FOMC.js";
228
+ import {
229
+ TRANSITIONS,
230
+ convertTransfer,
231
+ crossfade
232
+ } from "./chunk-3GXQPCJC.js";
238
233
  import {
239
234
  redactKnownPaths,
240
235
  redactTelemetryString
241
236
  } from "./chunk-634HSJ3J.js";
242
237
  import {
243
238
  chromeMajorCeiling
244
- } from "./chunk-537IYAEE.js";
239
+ } from "./chunk-4UMPIPCQ.js";
245
240
  import {
246
- lintHyperframeHtml
247
- } from "./chunk-GK2FIKIQ.js";
241
+ lintHyperframeHtml,
242
+ resolveReferencedStart
243
+ } from "./chunk-ADDHW2FS.js";
248
244
  import {
249
245
  isUnresolvedAssetPlaceholder,
250
246
  readProjectFile,
251
247
  resolveProjectRelativeSrc
252
248
  } from "./chunk-6BNTGNGD.js";
253
249
  import {
254
- checkSubCompositionUsability
255
- } from "./chunk-32GKETR7.js";
250
+ MEDIA_START_BASIS_ATTR,
251
+ checkSubCompositionUsability,
252
+ compositionOriginSeconds,
253
+ cutToHostSlots,
254
+ hostInPointSeconds,
255
+ readMediaStartBasis,
256
+ resolveAbsoluteMediaStartSeconds
257
+ } from "./chunk-MMQ52NIZ.js";
256
258
  import {
257
259
  CSS_URL_RE,
258
260
  decodedUrlPath,
@@ -265,10 +267,10 @@ import {
265
267
  parseHTML
266
268
  } from "./chunk-SDYDNHEE.js";
267
269
  import {
268
- TRANSITIONS,
269
- convertTransfer,
270
- crossfade
271
- } from "./chunk-3GXQPCJC.js";
270
+ LOW_MEMORY_TOTAL_MB_THRESHOLD,
271
+ getCgroupMemoryLimitMb,
272
+ getSystemTotalMb
273
+ } from "./chunk-W2LGJSEN.js";
272
274
 
273
275
  // ../producer/src/services/renderOrchestrator.ts
274
276
  import {
@@ -1596,7 +1598,7 @@ function pidScope() {
1596
1598
  return {
1597
1599
  host: hostname(),
1598
1600
  // Without /proc (macOS, Windows) the boot time, to the minute, stands in for the boot id.
1599
- boot: attempt(() => readFileSync2("/proc/sys/kernel/random/boot_id", "utf-8").trim()) ?? String(Math.round(Date.now() / 6e4 - uptime() / 60)),
1601
+ boot: attempt(() => readFileSync2("/proc/sys/kernel/random/boot_id", "utf-8").trim()) ?? attempt(() => String(Math.round(Date.now() / 6e4 - uptime() / 60))),
1600
1602
  pidns: attempt(() => readlinkSync("/proc/self/ns/pid"))
1601
1603
  };
1602
1604
  }
@@ -1614,9 +1616,14 @@ function alive(pid) {
1614
1616
  return error.code === "EPERM";
1615
1617
  }
1616
1618
  }
1617
- function inThisPidScope(owner) {
1619
+ function knownPidScope() {
1618
1620
  const scope = pidScope();
1619
- return owner.host === scope.host && owner.boot === scope.boot && owner.pidns === scope.pidns;
1621
+ const unknown = scope.boot === void 0 || process.platform === "linux" && scope.pidns === void 0;
1622
+ return unknown ? void 0 : scope;
1623
+ }
1624
+ function inThisPidScope(owner) {
1625
+ const scope = knownPidScope();
1626
+ return scope !== void 0 && owner.host === scope.host && owner.boot === scope.boot && owner.pidns === scope.pidns;
1620
1627
  }
1621
1628
  function ownerState(dir) {
1622
1629
  let owner;
@@ -1681,7 +1688,9 @@ async function defaultArtifactDurationProbe(path) {
1681
1688
  const meta = await extractMediaMetadata(path);
1682
1689
  return {
1683
1690
  durationSeconds: meta.durationSeconds,
1684
- frames: meta.frames
1691
+ frames: meta.frames,
1692
+ width: meta.width,
1693
+ height: meta.height
1685
1694
  };
1686
1695
  }
1687
1696
  function buildArtifactExpectation(input) {
@@ -1690,7 +1699,9 @@ function buildArtifactExpectation(input) {
1690
1699
  const expectation = {
1691
1700
  expectedDurationSeconds: input.durationSeconds,
1692
1701
  fps: input.fps,
1693
- expectedFrames: input.expectedFrames
1702
+ expectedFrames: input.expectedFrames,
1703
+ expectedWidth: input.width,
1704
+ expectedHeight: input.height
1694
1705
  };
1695
1706
  if (input.outputFormat !== "hls") return expectation;
1696
1707
  return {
@@ -1758,6 +1769,16 @@ function assertFrameCountWithinTolerance(expectedFrames, probedFrames, stagingPa
1758
1769
  `Render artifact is truncated: expected ${expectedFrames} frames, probed ${probedFrames} frames (shortfall ${shortfall} frames). Artifact: ${stagingPath}`
1759
1770
  );
1760
1771
  }
1772
+ function assertSizeMatches(expected, probed, stagingPath) {
1773
+ const { expectedWidth: width, expectedHeight: height } = expected;
1774
+ if (width === void 0 || height === void 0) return;
1775
+ if (probed.width === void 0 || probed.height === void 0) return;
1776
+ const fits = (want, got) => got === want || got === want + want % 2;
1777
+ if (fits(width, probed.width) && fits(height, probed.height)) return;
1778
+ throw new Error(
1779
+ `Render artifact has the wrong size: expected ${width}x${height}, probed ${probed.width}x${probed.height}. Artifact: ${stagingPath}`
1780
+ );
1781
+ }
1761
1782
  var ArtifactTransaction = class {
1762
1783
  constructor(destinationPath, kind, fileSystem = defaultFileSystem, durationProbe = defaultArtifactDurationProbe) {
1763
1784
  this.kind = kind;
@@ -1830,6 +1851,7 @@ var ArtifactTransaction = class {
1830
1851
  if (expected.expectedFrames !== void 0) {
1831
1852
  assertFrameCountWithinTolerance(expected.expectedFrames, probed.frames, probePath);
1832
1853
  }
1854
+ assertSizeMatches(expected, probed, probePath);
1833
1855
  }
1834
1856
  async runDurationProbe(probePath) {
1835
1857
  try {
@@ -3308,7 +3330,9 @@ function aggregateDrawElement(perfs, de) {
3308
3330
  if (perfs.length === 0) return void 0;
3309
3331
  const modes = [...new Set(perfs.map((p) => p.captureMode).filter(Boolean))].sort();
3310
3332
  const gateReasons = [
3311
- ...new Set(perfs.map((p) => p.deGateReason).filter((r) => !!r))
3333
+ ...new Set(
3334
+ [de.probeGateReason, ...perfs.map((p) => p.deGateReason)].filter((r) => !!r)
3335
+ )
3312
3336
  ].sort();
3313
3337
  const gpuRenderers = [
3314
3338
  ...new Set(perfs.map((p) => p.gpuRenderer).filter((r) => !!r))
@@ -6580,7 +6604,7 @@ async function readLocalFont(absPath) {
6580
6604
  return { kind: "inline", buffer: Buffer.concat(chunks, totalBytes) };
6581
6605
  }
6582
6606
  async function embedLocalFontFaces(html, projectDir) {
6583
- const { fontToDataUri: toDataUri } = await import("./deterministicFonts-L7YEEFGI.js");
6607
+ const { fontToDataUri: toDataUri } = await import("./deterministicFonts-VQ5UBMXY.js");
6584
6608
  const styleBlockRe = /<style\b[^>]*>([\s\S]*?)<\/style>/gi;
6585
6609
  const fontFaceRe = /@font-face\s*\{([^}]*)\}/gi;
6586
6610
  let result = html;
@@ -8337,6 +8361,7 @@ async function runAudioStage(input) {
8337
8361
  audios,
8338
8362
  masterFxChain,
8339
8363
  urlByAbsentLocalPath,
8364
+ keepEncoderInputAt,
8340
8365
  abortSignal,
8341
8366
  assertNotAborted,
8342
8367
  log
@@ -8356,7 +8381,13 @@ async function runAudioStage(input) {
8356
8381
  audioOutputPath,
8357
8382
  duration,
8358
8383
  abortSignal,
8359
- { ffmpegProcessTimeout, audioGain, masterFxChain, urlByAbsentLocalPath },
8384
+ {
8385
+ ffmpegProcessTimeout,
8386
+ audioGain,
8387
+ masterFxChain,
8388
+ urlByAbsentLocalPath,
8389
+ keepEncoderInputAt
8390
+ },
8360
8391
  compiledDir
8361
8392
  );
8362
8393
  } catch (err) {
@@ -8456,13 +8487,29 @@ function inspectDiskCaptureHeadroom(framesDir, totalFrames, captureOptions, free
8456
8487
  }
8457
8488
  return { available: false, estimatedBytes, freeBytes };
8458
8489
  }
8459
- function diskCaptureShortfallError(needBytes, freeBytes, framesDir, basis) {
8490
+ function diskCaptureRemedy(cfg, outputFormat, durationSeconds, workerCount) {
8491
+ const oneWorker = explainStreamingEncodeGate(cfg, outputFormat, 1, durationSeconds);
8492
+ if (workerCount > 1 && oneWorker.enabled) {
8493
+ return `This render stores frames because it captures with ${workerCount} workers. Free up disk space, or re-run this render with --workers 1 to stream frames into the encoder instead. One worker is slower, so keep the default worker count for renders that fit on disk.`;
8494
+ }
8495
+ switch (oneWorker.reason) {
8496
+ case "format_excluded":
8497
+ return `${outputFormat} output stores every frame at any worker count. Free up disk space or render to mp4.`;
8498
+ case "disabled_by_config":
8499
+ return "PRODUCER_ENABLE_STREAMING_ENCODE=false sends every render to disk. Free up disk space or unset it.";
8500
+ case "duration_cap":
8501
+ return "The streaming duration cap (PRODUCER_STREAMING_ENCODE_DURATION_CAP_ENABLED=true) sends renders longer than PRODUCER_STREAMING_ENCODE_MAX_DURATION_SECONDS to disk at any worker count. Free up disk space, unset the cap, or use --low-memory-mode, which streams past it on one worker.";
8502
+ default:
8503
+ return "Free up disk space.";
8504
+ }
8505
+ }
8506
+ function diskCaptureShortfallError(needBytes, freeBytes, framesDir, basis, remedy) {
8460
8507
  const basisNote = basis === "measured" ? ` (measured from the first ${DISK_PROJECTION_SAMPLE_FRAMES} frames)` : "";
8461
8508
  return new Error(
8462
- `Disk capture may need ~${(needBytes / 1e6).toFixed(1)} MB of temporary frame storage${basisNote}, but only ${(freeBytes / 1e6).toFixed(1)} MB is free at ${framesDir}. Disk capture stores every frame as an image file (JPEG, or PNG for alpha). mp4/mov renders stream instead: always at --workers 1, and multi-worker on Linux BeginFrame capture. This render landed on disk because the output is png-sequence or gif, because it is multi-worker screenshot capture (opt in with HF_CAPTURE_PARALLEL_STREAM=true), or because streaming was switched off: HF_CAPTURE_PARALLEL_STREAM=false, PRODUCER_ENABLE_STREAMING_ENCODE=false, or the duration cap (PRODUCER_STREAMING_ENCODE_DURATION_CAP_ENABLED=true) on a render longer than PRODUCER_STREAMING_ENCODE_MAX_DURATION_SECONDS. HDR and shader-transition renders never reach this check; they run their own compositor. Re-run with --workers 1, use --low-memory-mode, or free up disk space.`
8509
+ `Disk capture may need ~${(needBytes / 1e6).toFixed(1)} MB of temporary frame storage${basisNote}, but only ${(freeBytes / 1e6).toFixed(1)} MB is free at ${framesDir}. Disk capture stores every frame as an image file (JPEG, or PNG for alpha). ${remedy}`
8463
8510
  );
8464
8511
  }
8465
- function assertDiskCaptureHeadroom(framesDir, totalFrames, captureOptions, freeDiskBytes) {
8512
+ function assertDiskCaptureHeadroom(framesDir, totalFrames, captureOptions, remedy, freeDiskBytes) {
8466
8513
  const headroom = inspectDiskCaptureHeadroom(
8467
8514
  framesDir,
8468
8515
  totalFrames,
@@ -8474,7 +8521,8 @@ function assertDiskCaptureHeadroom(framesDir, totalFrames, captureOptions, freeD
8474
8521
  headroom.estimatedBytes,
8475
8522
  headroom.freeBytes,
8476
8523
  framesDir,
8477
- "estimate"
8524
+ "estimate",
8525
+ remedy
8478
8526
  );
8479
8527
  }
8480
8528
  function measureCaptureFrameBytes(workDir, framesDir) {
@@ -8494,7 +8542,7 @@ function measureCaptureFrameBytes(workDir, framesDir) {
8494
8542
  return total;
8495
8543
  }
8496
8544
  function createDiskCaptureProjection(input) {
8497
- const { framesDir, totalFrames, measureBytes, freeDiskBytes = freeDiskBytesAt } = input;
8545
+ const { framesDir, totalFrames, measureBytes, remedy, freeDiskBytes = freeDiskBytesAt } = input;
8498
8546
  let checked = false;
8499
8547
  return (capturedFrames) => {
8500
8548
  if (checked || capturedFrames < DISK_PROJECTION_SAMPLE_FRAMES) return;
@@ -8506,7 +8554,7 @@ function createDiskCaptureProjection(input) {
8506
8554
  if (remaining <= 0 || freeBytes === null) return;
8507
8555
  const needBytes = Math.ceil(measured / capturedFrames * remaining);
8508
8556
  if (needBytes > freeBytes * 0.9) {
8509
- throw diskCaptureShortfallError(needBytes, freeBytes, framesDir, "measured");
8557
+ throw diskCaptureShortfallError(needBytes, freeBytes, framesDir, "measured", remedy);
8510
8558
  }
8511
8559
  };
8512
8560
  }
@@ -8548,11 +8596,13 @@ async function runCaptureStage(input) {
8548
8596
  }
8549
8597
  }
8550
8598
  const captureOptions = buildCaptureOptions();
8551
- assertDiskCaptureHeadroom(framesDir, totalFrames, captureOptions);
8599
+ const remedy = diskCaptureRemedy(cfg, job.config.format ?? "mp4", job.duration ?? 0, workerCount);
8600
+ assertDiskCaptureHeadroom(framesDir, totalFrames, captureOptions, remedy);
8552
8601
  const checkDiskProjection = createDiskCaptureProjection({
8553
8602
  framesDir,
8554
8603
  totalFrames,
8555
- measureBytes: () => measureCaptureFrameBytes(workDir, framesDir)
8604
+ measureBytes: () => measureCaptureFrameBytes(workDir, framesDir),
8605
+ remedy
8556
8606
  });
8557
8607
  if (workerCount > 1) {
8558
8608
  const attempts = await executeDiskCaptureWithAdaptiveRetry({
@@ -8573,6 +8623,7 @@ async function runCaptureStage(input) {
8573
8623
  if (progress.capturedFrames === 0) reportWorkerStartup(job, progress, onProgress);
8574
8624
  return;
8575
8625
  }
8626
+ if (progress.landedFrame) input.onFrameLanded?.(progress.landedFrame);
8576
8627
  job.framesRendered = progress.capturedFrames;
8577
8628
  checkDiskProjection(progress.capturedFrames);
8578
8629
  const frameProgress = progress.capturedFrames / progress.totalFrames;
@@ -9719,6 +9770,7 @@ import {
9719
9770
  statSync as statSync4,
9720
9771
  writeFileSync as writeFileSync4
9721
9772
  } from "fs";
9773
+ import { readFile as readFile2 } from "fs/promises";
9722
9774
  import { dirname as dirname7, join as join23 } from "path";
9723
9775
 
9724
9776
  // ../producer/src/services/render/stages/gifEncodeArgs.ts
@@ -9728,8 +9780,10 @@ function fpsToFfmpegArg2(fps) {
9728
9780
  }
9729
9781
  var KEEP_FILTER_GRAPH_WHEN_FRAMES_DROP_ALPHA = ["-reinit_filter", "0"];
9730
9782
  var WRITE_EVERY_FRAME_WHOLE = ["-gifflags", "0"];
9783
+ var FAIL_ON_UNREADABLE_FRAME_INSTEAD_OF_ENDING_THERE = ["-xerror"];
9731
9784
  function framesInput(input, fpsArg) {
9732
9785
  return [
9786
+ ...FAIL_ON_UNREADABLE_FRAME_INSTEAD_OF_ENDING_THERE,
9733
9787
  "-framerate",
9734
9788
  fpsArg,
9735
9789
  ...KEEP_FILTER_GRAPH_WHEN_FRAMES_DROP_ALPHA,
@@ -9848,6 +9902,124 @@ function startEncodeProgress(job, stage, onProgress) {
9848
9902
  });
9849
9903
  return (frames = total) => reportEncodeProgress(job, frames, total, onProgress, 75);
9850
9904
  }
9905
+ function resolveEngineConfig(input) {
9906
+ return input.engineConfig ?? input.job.config.producerConfig ?? resolveConfig();
9907
+ }
9908
+ function videoEncoderOptions(input, engineCfg) {
9909
+ const { job, preset } = input;
9910
+ return {
9911
+ fps: job.config.fps,
9912
+ width: input.width,
9913
+ height: input.height,
9914
+ codec: preset.codec,
9915
+ preset: preset.preset,
9916
+ quality: input.effectiveQuality,
9917
+ bitrate: input.effectiveBitrate,
9918
+ pixelFormat: preset.pixelFormat,
9919
+ vp9CpuUsed: engineCfg.vp9CpuUsed,
9920
+ useGpu: job.config.useGpu,
9921
+ hdr: preset.hdr,
9922
+ // Distributed chunk renders pass these so the encoder writes closed-GOP
9923
+ // keyframes that survive `-f concat -c copy` at assemble time. In-process
9924
+ // renders leave both undefined → preserves the existing open-GOP output.
9925
+ lockGopForChunkConcat: input.lockGopForChunkConcat === true,
9926
+ gopSize: input.gopSize
9927
+ };
9928
+ }
9929
+ function canEncodeWhileCapturing(input, workerCount, cpuCount) {
9930
+ return workerCount > 1 && workerCount < cpuCount && !input.isPngSequence && !input.isGif && !input.enableChunkedEncode && input.preset.codec === "h264" && !input.preset.hdr && input.job.config.useGpu !== true && input.captureImageFormat === "jpeg" && input.lockGopForChunkConcat !== true;
9931
+ }
9932
+ async function readLandedFrame(workerPath, mergedPath) {
9933
+ if (workerPath !== void 0) {
9934
+ try {
9935
+ return await readFile2(workerPath);
9936
+ } catch (error) {
9937
+ if (error.code !== "ENOENT") throw error;
9938
+ }
9939
+ }
9940
+ return readFile2(mergedPath);
9941
+ }
9942
+ function encodeWhileCapturing(input, totalFrames) {
9943
+ const engineCfg = resolveEngineConfig(input);
9944
+ const stop = new AbortController();
9945
+ const signal = input.abortSignal ? AbortSignal.any([input.abortSignal, stop.signal]) : stop.signal;
9946
+ const landed = /* @__PURE__ */ new Map();
9947
+ let captureDone = false;
9948
+ let framesFed = 0;
9949
+ let wake = null;
9950
+ const nudge = () => {
9951
+ wake?.();
9952
+ wake = null;
9953
+ };
9954
+ signal.addEventListener("abort", nudge, { once: true });
9955
+ let reportFed = null;
9956
+ const encoder = spawnStreamingEncoder(
9957
+ input.videoOnlyPath,
9958
+ { ...videoEncoderOptions(input, engineCfg), imageFormat: "jpeg" },
9959
+ signal,
9960
+ engineCfg
9961
+ );
9962
+ const fed = (async () => {
9963
+ const stream = await encoder;
9964
+ for (let next = 0; next < totalFrames; next++) {
9965
+ while (!landed.has(next) && !captureDone && !signal.aborted) {
9966
+ await new Promise((resolve11) => wake = resolve11);
9967
+ }
9968
+ if (signal.aborted) return;
9969
+ const mergedPath = join23(input.framesDir, formatCaptureFrameName(next, "jpg"));
9970
+ const frame = await readLandedFrame(landed.get(next), mergedPath);
9971
+ landed.delete(next);
9972
+ if (!await stream.writeFrame(frame)) {
9973
+ throw new Error(stream.getExitError() ?? "encoder exited before the last frame");
9974
+ }
9975
+ framesFed++;
9976
+ reportFed?.(framesFed);
9977
+ }
9978
+ })().then(
9979
+ () => void 0,
9980
+ (error) => error ?? new Error("encode failed")
9981
+ );
9982
+ const closeEncoder = async () => (await encoder.catch(() => null))?.close();
9983
+ return {
9984
+ onFrameLanded: (frame) => {
9985
+ landed.set(frame.index, frame.path);
9986
+ nudge();
9987
+ },
9988
+ finish: async () => {
9989
+ const start = Date.now();
9990
+ captureDone = true;
9991
+ input.log.info("[Render] Encoded frames while capturing", {
9992
+ framesFed,
9993
+ totalFrames
9994
+ });
9995
+ const encoded = startEncodeProgress(input.job, "Encoding video", input.onProgress);
9996
+ reportFed = encoded;
9997
+ nudge();
9998
+ const failure = await fed;
9999
+ const result = await closeEncoder();
10000
+ input.assertNotAborted();
10001
+ if (failure === void 0 && result?.success) {
10002
+ encoded();
10003
+ return { encodeMs: Date.now() - start };
10004
+ }
10005
+ input.log.warn(
10006
+ "[Render] Encoding while capturing failed; encoding the frames after capture",
10007
+ {
10008
+ error: String(failure ?? result?.error)
10009
+ }
10010
+ );
10011
+ return runEncodeStage(input);
10012
+ },
10013
+ abort: async () => {
10014
+ stop.abort();
10015
+ await fed;
10016
+ await closeEncoder();
10017
+ },
10018
+ get framesFed() {
10019
+ return framesFed;
10020
+ }
10021
+ };
10022
+ }
9851
10023
  async function runEncodeStage(input) {
9852
10024
  const {
9853
10025
  job,
@@ -9855,16 +10027,11 @@ async function runEncodeStage(input) {
9855
10027
  outputPath,
9856
10028
  framesDir,
9857
10029
  videoOnlyPath,
9858
- width,
9859
- height,
9860
10030
  needsAlpha,
9861
10031
  hasAudio,
9862
10032
  audioOutputPath,
9863
10033
  isPngSequence,
9864
10034
  isGif,
9865
- preset,
9866
- effectiveQuality,
9867
- effectiveBitrate,
9868
10035
  enableChunkedEncode,
9869
10036
  chunkedEncodeSize,
9870
10037
  abortSignal,
@@ -9894,7 +10061,7 @@ async function runEncodeStage(input) {
9894
10061
  encoded();
9895
10062
  return { encodeMs: Date.now() - stage5Start };
9896
10063
  }
9897
- const engineCfg = input.engineConfig ?? job.config.producerConfig ?? resolveConfig();
10064
+ const engineCfg = resolveEngineConfig(input);
9898
10065
  if (isGif) {
9899
10066
  const encoded = startEncodeProgress(job, "Encoding GIF", onProgress);
9900
10067
  if (hasAudio) {
@@ -9924,24 +10091,7 @@ async function runEncodeStage(input) {
9924
10091
  const videoEngineCfg = scaledEncodeTimeout > engineCfg.ffmpegEncodeTimeout ? { ...engineCfg, ffmpegEncodeTimeout: scaledEncodeTimeout } : engineCfg;
9925
10092
  const frameExt = frameFileExtension(input.captureImageFormat);
9926
10093
  const framePattern = `frame_%06d.${frameExt}`;
9927
- const encoderOpts = {
9928
- fps: job.config.fps,
9929
- width,
9930
- height,
9931
- codec: preset.codec,
9932
- preset: preset.preset,
9933
- quality: effectiveQuality,
9934
- bitrate: effectiveBitrate,
9935
- pixelFormat: preset.pixelFormat,
9936
- vp9CpuUsed: engineCfg.vp9CpuUsed,
9937
- useGpu: job.config.useGpu,
9938
- hdr: preset.hdr,
9939
- // Distributed chunk renders pass these so the encoder writes closed-GOP
9940
- // keyframes that survive `-f concat -c copy` at assemble time. In-process
9941
- // renders leave both undefined → preserves the existing open-GOP output.
9942
- lockGopForChunkConcat: input.lockGopForChunkConcat === true,
9943
- gopSize: input.gopSize
9944
- };
10094
+ const encoderOpts = videoEncoderOptions(input, engineCfg);
9945
10095
  const encodeResult = enableChunkedEncode ? await encodeFramesChunkedConcat(
9946
10096
  framesDir,
9947
10097
  framePattern,
@@ -10014,12 +10164,13 @@ async function scanTruePeak(audioPath, trim, timeoutMs, signal) {
10014
10164
  };
10015
10165
  }
10016
10166
  async function measureTruePeakDbfs(audioPath, options) {
10017
- const { timeoutMs, signal, segments } = options;
10167
+ const { timeoutMs, signal } = options;
10018
10168
  const wholeFile = async () => (await scanTruePeak(audioPath, "", timeoutMs, signal)).peak;
10169
+ const { durationSeconds, sampleRate } = await extractAudioMetadata(audioPath, { signal });
10019
10170
  const block = 4800;
10020
- const count = segments?.sampleRate === 48e3 ? aacSegmentCount(segments.durationSeconds) : 1;
10021
- if (!segments || count === 1) return wholeFile();
10022
- const cut = (k) => k * Math.floor(segments.durationSeconds * 10 / count) * block;
10171
+ const count = sampleRate === 48e3 && Number.isFinite(durationSeconds) ? aacSegmentCount(durationSeconds) : 1;
10172
+ if (count === 1) return wholeFile();
10173
+ const cut = (k) => k * Math.floor(durationSeconds * 10 / count) * block;
10023
10174
  const peers = new AbortController();
10024
10175
  const segmentSignal = signal ? AbortSignal.any([signal, peers.signal]) : peers.signal;
10025
10176
  const scans = await Promise.all(
@@ -10048,24 +10199,8 @@ var AUDIO_DURATION_TOLERANCE_SECONDS = 1e-3;
10048
10199
  var AAC_DELIVERY_TRUE_PEAK_DBFS = -1;
10049
10200
  var AAC_TRUE_PEAK_CORRECTION_HEADROOM_DB = 0.5;
10050
10201
  var MAX_TRUE_PEAK_CORRECTION_PASSES = 3;
10051
- function buildAacTruePeakCorrectionArgs(inputPath, outputPath, targetDurationSeconds, attenuationDb) {
10052
- return [
10053
- "-i",
10054
- inputPath,
10055
- "-map",
10056
- "0:a:0",
10057
- "-vn",
10058
- "-af",
10059
- `volume=${attenuationDb.toFixed(3)}dB`,
10060
- "-t",
10061
- formatSeconds(targetDurationSeconds),
10062
- "-c:a",
10063
- "aac",
10064
- "-b:a",
10065
- "192k",
10066
- "-y",
10067
- outputPath
10068
- ];
10202
+ function aacTruePeakCorrectionFilter(targetDurationSeconds, attenuationDb) {
10203
+ return `volume=${attenuationDb.toFixed(3)}dB,apad,atrim=0:${formatSeconds(targetDurationSeconds)}`;
10069
10204
  }
10070
10205
  function buildPadTrimAudioPlan(audioPath, outputPath, sourceDurationSeconds, targetDurationSeconds) {
10071
10206
  const delta = targetDurationSeconds - sourceDurationSeconds;
@@ -10080,53 +10215,13 @@ function buildPadTrimAudioPlan(audioPath, outputPath, sourceDurationSeconds, tar
10080
10215
  if (delta > 0) {
10081
10216
  return {
10082
10217
  operation: "pad",
10083
- steps: [
10084
- {
10085
- kind: "normalize",
10086
- args: [
10087
- "-i",
10088
- audioPath,
10089
- "-af",
10090
- `apad,atrim=0:${targetSec}`,
10091
- "-t",
10092
- targetSec,
10093
- "-c:a",
10094
- "aac",
10095
- "-b:a",
10096
- "192k",
10097
- "-y",
10098
- outputPath
10099
- ]
10100
- }
10101
- ],
10218
+ steps: [{ kind: "normalize", filter: `apad,atrim=0:${targetSec}` }],
10102
10219
  cleanupPaths: []
10103
10220
  };
10104
10221
  }
10105
10222
  return {
10106
10223
  operation: "trim",
10107
- steps: [
10108
- {
10109
- kind: "trim",
10110
- args: [
10111
- "-i",
10112
- audioPath,
10113
- "-af",
10114
- `atrim=duration=${targetSec},asetpts=PTS-STARTPTS`,
10115
- // `atrim` limits decoded samples but does not cap muxer timestamps
10116
- // introduced by encoder delay/flush. The output duration contract
10117
- // is enforced at the container boundary as well; without `-t`, a
10118
- // long primed source can retain its original tail after re-encode.
10119
- "-t",
10120
- targetSec,
10121
- "-c:a",
10122
- "aac",
10123
- "-b:a",
10124
- "192k",
10125
- "-y",
10126
- outputPath
10127
- ]
10128
- }
10129
- ],
10224
+ steps: [{ kind: "trim", filter: `atrim=duration=${targetSec},asetpts=PTS-STARTPTS` }],
10130
10225
  cleanupPaths: []
10131
10226
  };
10132
10227
  }
@@ -10143,7 +10238,8 @@ async function padOrTrimAudioToVideoFrameCount(input) {
10143
10238
  const probeAudio = input.probeAudioInfo ?? defaultProbeAudioInfo;
10144
10239
  const timeoutMs = input.timeoutMs ?? DEFAULT_CONFIG.ffmpegProcessTimeout;
10145
10240
  const runner = input.runFfmpeg ?? ((args) => defaultRunFfmpeg(args, timeoutMs, input.signal));
10146
- const probeTruePeak = input.probeAudioTruePeakDbfs ?? (input.runFfmpeg ? void 0 : (audioPath, signal) => measureTruePeakDbfs(audioPath, { timeoutMs, signal }));
10241
+ const encode = input.encodeAac ?? defaultAacEncoder(timeoutMs, input.signal);
10242
+ const probeTruePeak = input.probeAudioTruePeakDbfs ?? (input.runFfmpeg || input.encodeAac ? void 0 : (audioPath, signal) => measureTruePeakDbfs(audioPath, { timeoutMs, signal }));
10147
10243
  const [videoResult, audioResult] = await Promise.allSettled([
10148
10244
  probeVideo(input.videoPath),
10149
10245
  probeAudio(input.audioPath, input.signal)
@@ -10190,7 +10286,7 @@ async function padOrTrimAudioToVideoFrameCount(input) {
10190
10286
  );
10191
10287
  try {
10192
10288
  for (const step of plan2.steps) {
10193
- const ffmpegResult = await runner(step.args);
10289
+ const ffmpegResult = step.kind === "copy" ? await runner(step.args) : await encode(input.audioPath, input.outputPath, targetDurationSeconds, step.filter);
10194
10290
  if (!ffmpegResult.success) {
10195
10291
  return {
10196
10292
  success: false,
@@ -10222,7 +10318,7 @@ async function padOrTrimAudioToVideoFrameCount(input) {
10222
10318
  targetDurationSeconds,
10223
10319
  signal: input.signal,
10224
10320
  probeTruePeak,
10225
- runner
10321
+ encode
10226
10322
  });
10227
10323
  if (!correction.success) {
10228
10324
  return {
@@ -10252,12 +10348,8 @@ async function constrainAacTruePeak(input) {
10252
10348
  audioPath: input.audioPath,
10253
10349
  targetDurationSeconds: input.targetDurationSeconds,
10254
10350
  signal: input.signal,
10255
- probeTruePeak: async (audioPath, signal) => {
10256
- const { sampleRate } = await extractAudioMetadata(audioPath, { signal });
10257
- const segments = { durationSeconds: input.targetDurationSeconds, sampleRate };
10258
- return measureTruePeakDbfs(audioPath, { timeoutMs, signal, segments });
10259
- },
10260
- runner: (args) => defaultRunFfmpeg(args, timeoutMs, input.signal)
10351
+ probeTruePeak: (audioPath, signal) => measureTruePeakDbfs(audioPath, { timeoutMs, signal }),
10352
+ encode: defaultAacEncoder(timeoutMs, input.signal, input.sourcePath)
10261
10353
  });
10262
10354
  }
10263
10355
  async function enforceAacTruePeak(input) {
@@ -10284,13 +10376,11 @@ async function enforceAacTruePeak(input) {
10284
10376
  }
10285
10377
  if (pass === MAX_TRUE_PEAK_CORRECTION_PASSES) break;
10286
10378
  attenuationDb += correctionTargetDbfs - truePeakDbfs;
10287
- const result = await input.runner(
10288
- buildAacTruePeakCorrectionArgs(
10289
- input.audioPath,
10290
- correctedPath,
10291
- input.targetDurationSeconds,
10292
- attenuationDb
10293
- )
10379
+ const result = await input.encode(
10380
+ input.audioPath,
10381
+ correctedPath,
10382
+ input.targetDurationSeconds,
10383
+ aacTruePeakCorrectionFilter(input.targetDurationSeconds, attenuationDb)
10294
10384
  );
10295
10385
  if (!result.success) {
10296
10386
  return {
@@ -10394,7 +10484,18 @@ async function defaultProbeAudioInfo(audioPath, signal) {
10394
10484
  };
10395
10485
  }
10396
10486
  async function defaultRunFfmpeg(args, timeoutMs, signal) {
10397
- const result = await runFfmpeg(args, { signal, timeout: timeoutMs });
10487
+ return toStepResult(await runFfmpeg(args, { signal, timeout: timeoutMs }), timeoutMs);
10488
+ }
10489
+ function defaultAacEncoder(timeoutMs, signal, pcmSourcePath) {
10490
+ return async (inputPath, outputPath, durationSeconds, filter) => {
10491
+ const options = pcmSourcePath ? { signal, timeout: timeoutMs, filter } : { signal, timeout: timeoutMs, filter, segments: 1 };
10492
+ return toStepResult(
10493
+ await encodeAac(pcmSourcePath ?? inputPath, outputPath, durationSeconds, options),
10494
+ timeoutMs
10495
+ );
10496
+ };
10497
+ }
10498
+ function toStepResult(result, timeoutMs) {
10398
10499
  if (result.success) return { success: true };
10399
10500
  return {
10400
10501
  success: false,
@@ -10582,7 +10683,7 @@ function assertOutputScaleSupported(deviceScaleFactor, route) {
10582
10683
  const reason = UNSUPPORTED_REASON2[route];
10583
10684
  if (reason === null) return;
10584
10685
  throw new Error(
10585
- `[Render] --resolution other than the composition's size is not supported on the "${route}" capture route: ${reason}. Render at the composition's size, or as mp4 with page-side compositing.`
10686
+ `[Render] --resolution other than the composition's size is not supported on the "${route}" capture route: ${reason}. Render at the composition's size; or, for HDR footage, add --sdr to render this size in standard colour; or, for shader transitions, render as mp4.`
10586
10687
  );
10587
10688
  }
10588
10689
 
@@ -10800,7 +10901,7 @@ function getNextRetryWorkerCount(currentWorkers) {
10800
10901
  return Math.max(1, Math.floor(currentWorkers / 2));
10801
10902
  }
10802
10903
  function resolveRenderWorkDirPrefix(outputPath, jobId, platform2 = process.platform, systemTempDir = tmpdir2()) {
10803
- if (platform2 === "win32") return join25(systemTempDir, "hf-render-");
10904
+ if (platform2 === "win32" || platform2 === "darwin") return join25(systemTempDir, "hf-render-");
10804
10905
  return join25(dirname9(outputPath), `work-${jobId}-`);
10805
10906
  }
10806
10907
  var MAX_TRANSIENT_CAPTURE_RETRIES = 1;
@@ -11294,6 +11395,9 @@ function resolveDeShortBand(args) {
11294
11395
  function shouldPreferSingleWorkerDrawElement(args) {
11295
11396
  return args.workerCount > 1 && typeof args.requestedWorkers !== "number" && args.useDrawElement && !args.deCompileGate && !args.forceScreenshot && outputUsesH264Pipeline(args.outputFormat) && args.minFrames > 0 && args.totalFrames >= args.minFrames && args.singleWorkerStreamingOk && !args.layeredOrEffectRoute && !args.supersampling && !args.probeDeGated && !args.experimentalParallelDeOptIn;
11296
11397
  }
11398
+ function isProbeDeGated(session) {
11399
+ return session !== null && session.captureMode !== "drawelement" && !session.deInitDeferred;
11400
+ }
11297
11401
  function resolveInversionRetryPlan(args) {
11298
11402
  if (args.deWorkerInversion !== "inverted") return null;
11299
11403
  const workerCount = args.isMemoryExhaustion ? 1 : args.preInversionWorkerCount;
@@ -11375,7 +11479,8 @@ async function closeOrphanedProbeForRetry(probe, closer, log, retryContext) {
11375
11479
  }
11376
11480
  function resolveParallelCaptureMode(args) {
11377
11481
  const supersampling = (args.deviceScaleFactor ?? 1) !== 1;
11378
- return args.headlessShell && args.platform === "linux" && !args.forceScreenshot && !supersampling ? "beginframe" : "screenshot";
11482
+ const headlessShell = args.headlessShellPath !== void 0;
11483
+ return headlessShell && args.platform === "linux" && !args.forceScreenshot && !supersampling ? "beginframe" : "screenshot";
11379
11484
  }
11380
11485
  function isCaptureParallelStreamRouterEnabled(env, resolvedMode) {
11381
11486
  const raw = env.HF_CAPTURE_PARALLEL_STREAM?.trim().toLowerCase();
@@ -11983,6 +12088,7 @@ async function executeRenderPipeline(input) {
11983
12088
  );
11984
12089
  const nativeHdrIds = /* @__PURE__ */ new Set([...nativeHdrVideoIds, ...nativeHdrImageIds]);
11985
12090
  const hasHdrContent = Boolean(effectiveHdr && nativeHdrIds.size > 0);
12091
+ if (hasHdrContent) assertOutputScaleSupported(deviceScaleFactor, "hdr_layered");
11986
12092
  const deSingleMinFramesRaw = process.env.HF_DE_SINGLE_MIN_FRAMES;
11987
12093
  const deSingleMinFramesNum = deSingleMinFramesRaw === void 0 || deSingleMinFramesRaw.trim() === "" ? 900 : Number(deSingleMinFramesRaw);
11988
12094
  const deSingleMinFrames = Number.isFinite(deSingleMinFramesNum) ? deSingleMinFramesNum : 900;
@@ -12023,7 +12129,7 @@ async function executeRenderPipeline(input) {
12023
12129
  singleWorkerStreamingOk: shouldUseStreamingEncode(cfg, outputFormat, 1, job.duration),
12024
12130
  layeredOrEffectRoute: hasHdrContent || compiled.hasShaderTransitions,
12025
12131
  supersampling: deviceScaleFactor !== 1,
12026
- probeDeGated: probeSession !== null && probeSession.captureMode !== "drawelement" && !probeSession.deInitDeferred,
12132
+ probeDeGated: isProbeDeGated(probeSession),
12027
12133
  experimentalParallelDeOptIn: process.env.PRODUCER_EXPERIMENTAL_FAST_CAPTURE === "true" || // Verified parallel DE streaming (opt-in) wants its parallelism kept.
12028
12134
  process.env.HF_DE_PARALLEL_STREAM === "true"
12029
12135
  };
@@ -12032,14 +12138,14 @@ async function executeRenderPipeline(input) {
12032
12138
  ...deInversionArgs,
12033
12139
  minFrames: Math.min(deSingleMinFrames, deShortBandMinFrames)
12034
12140
  });
12035
- const deShortBand = resolveDeShortBand({
12141
+ let deShortBand = resolveDeShortBand({
12036
12142
  invertAtBaseFloor,
12037
12143
  invertAtBandFloor,
12038
12144
  bandEnabled: deShortBandEnabled,
12039
12145
  bandOpen: deShortBandOpen,
12040
12146
  elementCountSource: compositionElementCountSource
12041
12147
  });
12042
- const deInversionEligible = deShortBandRoute && deShortBand === "applied" ? invertAtBandFloor : invertAtBaseFloor;
12148
+ let deInversionEligible = deShortBandRoute && deShortBand === "applied" ? invertAtBandFloor : invertAtBaseFloor;
12043
12149
  const deInversionEffectiveMinFrames = deShortBandRoute && deShortBand === "applied" ? Math.min(deSingleMinFrames, deShortBandMinFrames) : deSingleMinFrames;
12044
12150
  const deParallelRouterEnabled = isDeParallelRouterEnabled(process.env);
12045
12151
  const deParallelMinFramesRaw = process.env.HF_DE_PARALLEL_MIN_FRAMES;
@@ -12048,7 +12154,7 @@ async function executeRenderPipeline(input) {
12048
12154
  const deParallelMinMemRaw = process.env.HF_DE_PARALLEL_MIN_MEM_MB;
12049
12155
  const deParallelMinMemNum = deParallelMinMemRaw === void 0 || deParallelMinMemRaw.trim() === "" ? 24576 : Number(deParallelMinMemRaw);
12050
12156
  const deParallelMinMemoryMb = Number.isFinite(deParallelMinMemNum) ? deParallelMinMemNum : 24576;
12051
- const deParallelRouterEligible = shouldPreferParallelDrawElement({
12157
+ let deParallelRouterEligible = shouldPreferParallelDrawElement({
12052
12158
  workerCount: WOULD_RESOLVE_MULTI_WORKER,
12053
12159
  requestedWorkers: job.config.workers,
12054
12160
  useDrawElement: cfg.useDrawElement,
@@ -12059,7 +12165,7 @@ async function executeRenderPipeline(input) {
12059
12165
  minFrames: deParallelMinFrames,
12060
12166
  layeredOrEffectRoute: hasHdrContent || compiled.hasShaderTransitions,
12061
12167
  supersampling: deviceScaleFactor !== 1,
12062
- probeDeGated: probeSession !== null && probeSession.captureMode !== "drawelement" && !probeSession.deInitDeferred,
12168
+ probeDeGated: isProbeDeGated(probeSession),
12063
12169
  experimentalParallelDeOptIn: process.env.PRODUCER_EXPERIMENTAL_FAST_CAPTURE === "true" || process.env.HF_DE_PARALLEL_STREAM === "true",
12064
12170
  routerEnabled: deParallelRouterEnabled,
12065
12171
  // Router pins 3 workers for the streaming path; don't pin when a
@@ -12075,6 +12181,35 @@ async function executeRenderPipeline(input) {
12075
12181
  totalMemoryMb: Math.round(totalmem2() / (1024 * 1024)),
12076
12182
  minMemoryMb: deParallelMinMemoryMb
12077
12183
  });
12184
+ if (probeSession === null && (deInversionEligible || deParallelRouterEligible)) {
12185
+ try {
12186
+ probeSession = await createCaptureSession(
12187
+ activeFileServer.url,
12188
+ framesDir,
12189
+ buildCaptureOptions(),
12190
+ null,
12191
+ { ...cfg, forceScreenshot: captureForceScreenshot }
12192
+ );
12193
+ await initializeSession(probeSession);
12194
+ } catch (error) {
12195
+ assertNotAborted();
12196
+ if (probeSession) {
12197
+ lastBrowserConsole = probeSession.browserConsoleBuffer;
12198
+ await closeCaptureSession(probeSession).catch(() => {
12199
+ });
12200
+ probeSession = null;
12201
+ }
12202
+ log.warn("[Render] Fast capture gate check failed; routing without it.", {
12203
+ error: error instanceof Error ? error.message : String(error)
12204
+ });
12205
+ }
12206
+ if (isProbeDeGated(probeSession)) {
12207
+ deInversionEligible = false;
12208
+ deParallelRouterEligible = false;
12209
+ deShortBand = void 0;
12210
+ }
12211
+ }
12212
+ const deProbeGateReason = isProbeDeGated(probeSession) ? probeSession.deGateReason : void 0;
12078
12213
  let workerCount;
12079
12214
  const captureStageObservationData = (extra = {}) => ({
12080
12215
  stagePhase: "capturing",
@@ -12262,7 +12397,7 @@ async function executeRenderPipeline(input) {
12262
12397
  }
12263
12398
  const parallelCaptureMode = resolveParallelCaptureMode({
12264
12399
  platform: process.platform,
12265
- headlessShell: resolveHeadlessShellPath(cfg) !== null,
12400
+ headlessShellPath: resolveHeadlessShellPath(cfg),
12266
12401
  forceScreenshot: captureForceScreenshot,
12267
12402
  deviceScaleFactor: captureOptions.deviceScaleFactor
12268
12403
  });
@@ -12864,6 +12999,34 @@ async function executeRenderPipeline(input) {
12864
12999
  throw new Error(`Disk capture requires sdr_disk plan; got ${capturePlan.kind}`);
12865
13000
  }
12866
13001
  resetCaptureAttemptProgress(job);
13002
+ const encodeInput = {
13003
+ job,
13004
+ log,
13005
+ outputPath: stagedOutputPath,
13006
+ framesDir,
13007
+ videoOnlyPath,
13008
+ width,
13009
+ height,
13010
+ needsAlpha,
13011
+ captureImageFormat: captureOptions.format ?? "jpeg",
13012
+ hasAudio,
13013
+ audioOutputPath,
13014
+ isPngSequence,
13015
+ isGif,
13016
+ preset,
13017
+ effectiveQuality,
13018
+ effectiveBitrate,
13019
+ enableChunkedEncode,
13020
+ chunkedEncodeSize,
13021
+ engineConfig: cfg,
13022
+ // Same value the streaming encoder above spreads — the disk and
13023
+ // chunked-concat encoders need an identical GOP lock for HLS.
13024
+ ...hlsEncoderGopLock,
13025
+ abortSignal: executionSignal,
13026
+ assertNotAborted,
13027
+ onProgress
13028
+ };
13029
+ const overlap = !cfg.useDrawElement && workerSizing !== void 0 && canEncodeWhileCapturing(encodeInput, capturePlan.workerCount, workerSizing.cpuCount) ? encodeWhileCapturing(encodeInput, totalFrames) : null;
12867
13030
  const captureFrameStart = Date.now();
12868
13031
  const invokeDiskCapture = (diskPlan) => observeRenderStage(
12869
13032
  observability,
@@ -12885,13 +13048,15 @@ async function executeRenderPipeline(input) {
12885
13048
  createRenderVideoFrameInjector,
12886
13049
  abortSignal: executionSignal,
12887
13050
  assertNotAborted,
12888
- onProgress
13051
+ onProgress,
13052
+ onFrameLanded: (frame) => overlap?.onFrameLanded(frame)
12889
13053
  })
12890
13054
  );
12891
13055
  let captureRes;
12892
13056
  try {
12893
13057
  captureRes = await invokeDiskCapture(capturePlan);
12894
13058
  } catch (err) {
13059
+ await overlap?.abort();
12895
13060
  if (!isDrawElementVerificationError(err) && !isDrawElementCaptureError(err) || err instanceof RenderCancelledError || executionSignal?.aborted === true) {
12896
13061
  throw err;
12897
13062
  }
@@ -12960,33 +13125,7 @@ async function executeRenderPipeline(input) {
12960
13125
  isGif,
12961
13126
  chunkedEncode: enableChunkedEncode
12962
13127
  }),
12963
- () => runEncodeStage({
12964
- job,
12965
- log,
12966
- outputPath: stagedOutputPath,
12967
- framesDir,
12968
- videoOnlyPath,
12969
- width,
12970
- height,
12971
- needsAlpha,
12972
- captureImageFormat: captureOptions.format ?? "jpeg",
12973
- hasAudio,
12974
- audioOutputPath,
12975
- isPngSequence,
12976
- isGif,
12977
- preset,
12978
- effectiveQuality,
12979
- effectiveBitrate,
12980
- enableChunkedEncode,
12981
- chunkedEncodeSize,
12982
- engineConfig: cfg,
12983
- // Same value the streaming encoder above spreads — the disk and
12984
- // chunked-concat encoders need an identical GOP lock for HLS.
12985
- ...hlsEncoderGopLock,
12986
- abortSignal: executionSignal,
12987
- assertNotAborted,
12988
- onProgress
12989
- })
13128
+ () => overlap ? overlap.finish() : runEncodeStage(encodeInput)
12990
13129
  );
12991
13130
  perfStages.encodeMs = encodeRes.encodeMs;
12992
13131
  }
@@ -13034,7 +13173,9 @@ async function executeRenderPipeline(input) {
13034
13173
  outputFormat,
13035
13174
  durationSeconds: job.duration,
13036
13175
  fps: fpsToNumber(job.config.fps),
13037
- expectedFrames: captureTotalFrames
13176
+ expectedFrames: captureTotalFrames,
13177
+ width: outputWidth,
13178
+ height: outputHeight
13038
13179
  })
13039
13180
  );
13040
13181
  const totalElapsed = Date.now() - pipelineStart;
@@ -13070,6 +13211,7 @@ async function executeRenderPipeline(input) {
13070
13211
  drawElement: {
13071
13212
  compileGate: deCompileGate,
13072
13213
  clampReason: deClampReason,
13214
+ probeGateReason: deProbeGateReason,
13073
13215
  workerInversion: deWorkerInversion,
13074
13216
  preInversionWorkers: deWorkerInversion ? preRoutingWorkerCount : void 0,
13075
13217
  compositionElementCount,
@@ -13622,6 +13764,29 @@ function buildChunkCaptureOptions(input) {
13622
13764
  requiresWebGpu: compositionRequiresWebGpu(input.html)
13623
13765
  };
13624
13766
  }
13767
+ function shownDeferredFrameIndexes(planVideos, frames, fps) {
13768
+ const shown = /* @__PURE__ */ new Map();
13769
+ const deferred = planVideos.extracted.filter((video) => video.deferredRange);
13770
+ if (deferred.length === 0) return shown;
13771
+ const table = createFrameLookupTable(
13772
+ planVideos.videos,
13773
+ deferred.map((video) => ({
13774
+ ...video,
13775
+ outputDir: "",
13776
+ framePaths: new Map(Array.from({ length: video.totalFrames }, (_, i) => [i, String(i)]))
13777
+ })),
13778
+ resolveRenderFpsConfig(fps).value
13779
+ );
13780
+ for (const frame of frames) {
13781
+ const globalTime = frame * fps.den / fps.num;
13782
+ for (const [videoId, payload] of table.getActiveFramePayloads(globalTime)) {
13783
+ const indexes = shown.get(videoId) ?? /* @__PURE__ */ new Set();
13784
+ indexes.add(payload.frameIndex);
13785
+ shown.set(videoId, indexes);
13786
+ }
13787
+ }
13788
+ return shown;
13789
+ }
13625
13790
  async function extractDeferredVideoFramesForChunk(input) {
13626
13791
  const deferred = input.planVideos.extracted.filter((video) => video.deferredRange);
13627
13792
  const remoteSources = input.remoteSources ?? {};
@@ -13638,24 +13803,7 @@ async function extractDeferredVideoFramesForChunk(input) {
13638
13803
  });
13639
13804
  }
13640
13805
  if (deferred.length === 0) return [];
13641
- const table = createFrameLookupTable(
13642
- input.planVideos.videos,
13643
- deferred.map((video) => ({
13644
- ...video,
13645
- outputDir: input.outputRoot,
13646
- framePaths: new Map(Array.from({ length: video.totalFrames }, (_, i) => [i, String(i)]))
13647
- })),
13648
- resolveRenderFpsConfig(input.fps).value
13649
- );
13650
- const shown = /* @__PURE__ */ new Map();
13651
- for (const frame of input.frames) {
13652
- const globalTime = frame * input.fps.den / input.fps.num;
13653
- for (const [videoId, payload] of table.getActiveFramePayloads(globalTime)) {
13654
- const indexes = shown.get(videoId) ?? /* @__PURE__ */ new Set();
13655
- indexes.add(payload.frameIndex);
13656
- shown.set(videoId, indexes);
13657
- }
13658
- }
13806
+ const shown = shownDeferredFrameIndexes(input.planVideos, input.frames, input.fps);
13659
13807
  return Promise.all(
13660
13808
  deferred.map(async (video) => {
13661
13809
  const outputDir = join27(input.outputRoot, video.videoId);
@@ -13681,7 +13829,8 @@ async function extractDeferredVideoFramesForChunk(input) {
13681
13829
  fps: input.fps,
13682
13830
  outputDir: input.outputRoot,
13683
13831
  format: range.format,
13684
- frameRanges: contiguousRuns(indexes)
13832
+ frameRanges: contiguousRuns(indexes),
13833
+ shownBox: input.shownBoxes?.get(video.videoId)
13685
13834
  },
13686
13835
  void 0,
13687
13836
  input.cfg
@@ -14333,6 +14482,20 @@ function withDuration(buf, target, duration) {
14333
14482
  function timescaleOf(buf, header) {
14334
14483
  return buf.readUInt32BE(header.bodyStart + (buf[header.bodyStart] === 1 ? 20 : 12));
14335
14484
  }
14485
+ var TRACK_ID_AT = { tkhd: [12, 20], mvhd: [96, 108] };
14486
+ function idOf(buf, target) {
14487
+ return buf.readUInt32BE(
14488
+ target.bodyStart + TRACK_ID_AT[target.type][buf[target.bodyStart] === 1 ? 1 : 0]
14489
+ );
14490
+ }
14491
+ function withId(boxBytes, type, id) {
14492
+ const header = boxAt(boxBytes, 0, 0, boxBytes.length);
14493
+ boxBytes.writeUInt32BE(
14494
+ id,
14495
+ header.bodyStart + TRACK_ID_AT[type][boxBytes[header.bodyStart] === 1 ? 1 : 0]
14496
+ );
14497
+ return boxBytes;
14498
+ }
14336
14499
  function buildStbl(stsd, track) {
14337
14500
  const stts = runs(track.durations);
14338
14501
  const stsc = [];
@@ -14374,24 +14537,48 @@ function planTracksOf(tracks, plan2, index) {
14374
14537
  }
14375
14538
  return matched;
14376
14539
  }
14377
- function buildStitchedHeader(layouts) {
14378
- const plan2 = readTracks(layouts[0].moov);
14379
- const chunks = layouts.map((layout, i) => planTracksOf(readTracks(layout.moov), plan2, i));
14540
+ function sourceTracks(layouts, audio) {
14541
+ const chunkPlan = readTracks(layouts[0].moov);
14542
+ const audioTracks = audio ? readTracks(audio.moov) : [];
14543
+ if (audio && (audioTracks.length !== 1 || audioTracks[0].handler !== "soun")) {
14544
+ throw new Error("[mp4Stitch] the separate audio file must hold one audio track");
14545
+ }
14546
+ const plan2 = [...chunkPlan, ...audioTracks];
14547
+ const chunks = layouts.map((layout, i) => {
14548
+ const tracks = readTracks(layout.moov);
14549
+ if (audio && tracks.some((track) => track.handler === "soun")) {
14550
+ throw new Error(`[mp4Stitch] chunk ${i} carries its own audio`);
14551
+ }
14552
+ return planTracksOf(tracks, plan2, i);
14553
+ });
14554
+ return {
14555
+ plan: plan2,
14556
+ chunkTrackCount: chunkPlan.length,
14557
+ sources: audio ? [[...chunkPlan.map(() => null), audioTracks[0]], ...chunks] : chunks
14558
+ };
14559
+ }
14560
+ function buildStitchedHeader(layouts, audio) {
14561
+ const { plan: plan2, chunkTrackCount, sources } = sourceTracks(layouts, audio);
14562
+ const sourceLayouts = audio ? [audio, ...layouts] : layouts;
14380
14563
  const payloadBases = [];
14381
14564
  let payloadBytes = 0;
14382
- for (const layout of layouts) {
14565
+ for (const layout of sourceLayouts) {
14383
14566
  payloadBases.push(payloadBytes);
14384
14567
  payloadBytes += layout.payloadEnd - layout.payloadStart;
14385
14568
  }
14386
14569
  const template = layouts[0].moov;
14387
14570
  const root = boxAt(template, 0, 0, template.length);
14388
- const movieTimescale = timescaleOf(template, child(template, root, "mvhd"));
14571
+ const mvhd = child(template, root, "mvhd");
14572
+ const movieTimescale = timescaleOf(template, mvhd);
14573
+ const chunkTrackIds = plan2.slice(0, chunkTrackCount).map((track) => idOf(template, child(template, track.trak, "tkhd")));
14574
+ const audioTrackId = Math.max(...chunkTrackIds) + 1;
14389
14575
  const buildMoov = (dataStart2) => {
14390
14576
  const traks = plan2.map((track, t) => {
14391
- const parts = chunks.flatMap((tracks, chunk) => {
14577
+ const parts = sources.flatMap((tracks, chunk) => {
14392
14578
  const part = tracks[t];
14393
14579
  return part ? [{ ...part, chunk }] : [];
14394
14580
  });
14581
+ const moov = t < chunkTrackCount ? template : audio.moov;
14395
14582
  const merged = {
14396
14583
  durations: parts.flatMap((part) => part.durations),
14397
14584
  sizes: parts.flatMap((part) => part.sizes),
@@ -14400,9 +14587,9 @@ function buildStitchedHeader(layouts) {
14400
14587
  chunkOffsets: parts.flatMap(
14401
14588
  (part) => part.chunkOffsets.map((offset) => {
14402
14589
  const i = part.chunk;
14403
- const { payloadStart, payloadEnd } = layouts[i];
14590
+ const { payloadStart, payloadEnd } = sourceLayouts[i];
14404
14591
  if (offset < payloadStart || offset >= payloadEnd) {
14405
- throw new Error(`[mp4Stitch] chunk ${i} has samples outside its mdat`);
14592
+ throw new Error(`[mp4Stitch] source ${i} has samples outside its mdat`);
14406
14593
  }
14407
14594
  return offset - payloadStart + payloadBases[i] + dataStart2;
14408
14595
  })
@@ -14418,20 +14605,25 @@ function buildStitchedHeader(layouts) {
14418
14605
  elst.writeBigInt64BE(BigInt(track.mediaTime), 12);
14419
14606
  elst.writeUInt32BE(65536, 20);
14420
14607
  const edts = track.mediaTime ? box("edts", fullBox("elst", 1, elst)) : Buffer.alloc(0);
14421
- const trak = rebuild(template, track.trak, {
14422
- tkhd: (tkhd) => Buffer.concat([withDuration(template, tkhd, shown), edts]),
14608
+ const tkhdOf = (tkhd) => {
14609
+ const timed = withDuration(moov, tkhd, shown);
14610
+ return t < chunkTrackCount ? timed : withId(timed, "tkhd", audioTrackId);
14611
+ };
14612
+ const trak = rebuild(moov, track.trak, {
14613
+ tkhd: (tkhd) => Buffer.concat([tkhdOf(tkhd), edts]),
14423
14614
  edts: () => Buffer.alloc(0),
14424
- mdia: (mdia) => rebuild(template, mdia, {
14425
- mdhd: (mdhd) => withDuration(template, mdhd, mediaDuration),
14426
- minf: (minf) => rebuild(template, minf, { stbl: () => buildStbl(track.stsd, merged) })
14615
+ mdia: (mdia) => rebuild(moov, mdia, {
14616
+ mdhd: (mdhd) => withDuration(moov, mdhd, mediaDuration),
14617
+ minf: (minf) => rebuild(moov, minf, { stbl: () => buildStbl(track.stsd, merged) })
14427
14618
  })
14428
14619
  });
14429
14620
  return { trak, shown };
14430
14621
  });
14431
14622
  let next = 0;
14623
+ const movieHeader = withDuration(template, mvhd, Math.max(...traks.map((t) => t.shown)));
14432
14624
  return rebuild(template, root, {
14433
- mvhd: (mvhd) => withDuration(template, mvhd, Math.max(...traks.map((t) => t.shown))),
14434
- trak: () => traks[next++].trak
14625
+ mvhd: () => audio ? withId(movieHeader, "mvhd", audioTrackId + 1) : movieHeader,
14626
+ trak: () => ++next < chunkTrackCount ? traks[next - 1].trak : Buffer.concat(traks.slice(next - 1).map((t) => t.trak))
14435
14627
  });
14436
14628
  };
14437
14629
  const ftyp = layouts[0].ftyp;
@@ -14464,15 +14656,16 @@ async function copyPayload(path, layout, index, out) {
14464
14656
  );
14465
14657
  }
14466
14658
  }
14467
- async function writeStitchedMp4(chunkPaths, outputPath) {
14468
- const layouts = await Promise.all(chunkPaths.map((path) => readMp4Layout(fileRange(path))));
14469
- const header = buildStitchedHeader(layouts);
14659
+ async function writeStitchedMp4(chunkPaths, outputPath, audioPath) {
14660
+ const sourcePaths = audioPath ? [audioPath, ...chunkPaths] : chunkPaths;
14661
+ const layouts = await Promise.all(sourcePaths.map((path) => readMp4Layout(fileRange(path))));
14662
+ const header = audioPath ? buildStitchedHeader(layouts.slice(1), layouts[0]) : buildStitchedHeader(layouts);
14470
14663
  mkdirSync10(dirname11(outputPath), { recursive: true });
14471
14664
  const workDir = mkdtempSync4(`${outputPath}.stitch-`);
14472
14665
  const out = createWriteStream(join30(workDir, "out.mp4"));
14473
14666
  try {
14474
14667
  out.write(header);
14475
- for (const [i, path] of chunkPaths.entries()) await copyPayload(path, layouts[i], i, out);
14668
+ for (const [i, path] of sourcePaths.entries()) await copyPayload(path, layouts[i], i, out);
14476
14669
  out.end();
14477
14670
  await finished(out);
14478
14671
  renameSync4(join30(workDir, "out.mp4"), outputPath);
@@ -15043,7 +15236,8 @@ function buildLockedRenderConfig(input) {
15043
15236
  chunkCount: input.chunkCount,
15044
15237
  runtimeEnv: input.runtimeEnv,
15045
15238
  variables: config.variables,
15046
- ...input.chunksCarryAudio ? { chunksCarryAudio: true } : {}
15239
+ ...input.chunksCarryAudio ? { chunksCarryAudio: true } : {},
15240
+ ...input.separateAudio ? { separateAudio: true } : {}
15047
15241
  };
15048
15242
  }
15049
15243
  function carriesChunkAudio(config, options) {
@@ -15138,8 +15332,10 @@ function createVideoSourceShipper(input) {
15138
15332
  return result;
15139
15333
  };
15140
15334
  }
15141
- async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, options = {}) {
15142
- const planDir = executionPlanDir;
15335
+ function plansSeparateAudio(config, options) {
15336
+ return options.deferAudio === true && config.format === "mp4" && config.cfr !== true;
15337
+ }
15338
+ async function compilePlanComposition(projectDir, config, planDir, options) {
15143
15339
  rejectUnsupportedDistributedFormat(config);
15144
15340
  validateNoGpuEncode({
15145
15341
  useGpu: false,
@@ -15267,7 +15463,7 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
15267
15463
  if (probeResult.fileServer) closeFileServerSafely(probeResult.fileServer, "plan", log);
15268
15464
  if (probeResult.probeSession) {
15269
15465
  try {
15270
- const { closeCaptureSession: closeCaptureSession2 } = await import("./src-RDSWHMJS.js");
15466
+ const { closeCaptureSession: closeCaptureSession2 } = await import("./src-4UYM2K3S.js");
15271
15467
  await closeCaptureSession2(probeResult.probeSession);
15272
15468
  } catch (err) {
15273
15469
  log.warn("[plan] probe session close failed", {
@@ -15275,6 +15471,100 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
15275
15471
  });
15276
15472
  }
15277
15473
  }
15474
+ return {
15475
+ projectDir,
15476
+ log,
15477
+ sizeLimitBytes,
15478
+ abortSignal,
15479
+ assertNotAborted,
15480
+ cfg,
15481
+ job,
15482
+ workDir,
15483
+ compiledDir,
15484
+ finalCompiledDir,
15485
+ remoteFiles,
15486
+ urlByAbsentLocalPath,
15487
+ localPathByUrl,
15488
+ compileResult,
15489
+ compiled,
15490
+ composition,
15491
+ deviceScaleFactor,
15492
+ forceScreenshot,
15493
+ width,
15494
+ height,
15495
+ probeResult,
15496
+ totalFrames
15497
+ };
15498
+ }
15499
+ function mixPlanAudio(c, compiledDir, seconds, pcmPath) {
15500
+ return runAudioStage({
15501
+ projectDir: c.projectDir,
15502
+ workDir: c.workDir,
15503
+ compiledDir,
15504
+ urlByAbsentLocalPath: c.urlByAbsentLocalPath,
15505
+ duration: seconds,
15506
+ ffmpegProcessTimeout: c.cfg.ffmpegProcessTimeout,
15507
+ audioGain: c.cfg.audioGain,
15508
+ audios: c.composition.audios,
15509
+ masterFxChain: c.composition.masterFxChain,
15510
+ ...pcmPath ? { keepEncoderInputAt: pcmPath } : {},
15511
+ abortSignal: c.abortSignal,
15512
+ assertNotAborted: c.assertNotAborted
15513
+ });
15514
+ }
15515
+ async function extractPlanVideos(c, deferRangeExtraction) {
15516
+ const extractResult = await runExtractVideosStage({
15517
+ projectDir: c.projectDir,
15518
+ compiledDir: c.compiledDir,
15519
+ job: c.job,
15520
+ cfg: c.cfg,
15521
+ composition: c.composition,
15522
+ abortSignal: c.abortSignal,
15523
+ assertNotAborted: c.assertNotAborted,
15524
+ materializeSymlinks: true,
15525
+ deferRangeExtraction,
15526
+ urlByAbsentLocalPath: c.urlByAbsentLocalPath
15527
+ });
15528
+ if (extractResult.failureToEnforce) throw extractResult.failureToEnforce;
15529
+ if (extractResult.extractionResult) {
15530
+ assertVideoExtractionSucceeded(extractResult.extractionResult);
15531
+ }
15532
+ return extractResult;
15533
+ }
15534
+ async function holdPlanAudioPeak(c, audioPath, pcmPath, seconds) {
15535
+ const peak = await constrainAacTruePeak({
15536
+ audioPath,
15537
+ ...existsSync20(pcmPath) ? { sourcePath: pcmPath } : {},
15538
+ targetDurationSeconds: seconds,
15539
+ signal: c.abortSignal,
15540
+ timeoutMs: c.cfg.ffmpegProcessTimeout
15541
+ }).finally(() => rmSync10(pcmPath, { force: true }));
15542
+ if (!peak.success) throw encoderFailureError("[plan] audio true-peak check failed", peak);
15543
+ }
15544
+ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, options = {}) {
15545
+ const planDir = executionPlanDir;
15546
+ const c = await compilePlanComposition(projectDir, config, planDir, options);
15547
+ const {
15548
+ log,
15549
+ sizeLimitBytes,
15550
+ cfg,
15551
+ job,
15552
+ workDir,
15553
+ compiledDir,
15554
+ finalCompiledDir,
15555
+ remoteFiles,
15556
+ urlByAbsentLocalPath,
15557
+ localPathByUrl,
15558
+ compileResult,
15559
+ compiled,
15560
+ composition,
15561
+ deviceScaleFactor,
15562
+ forceScreenshot,
15563
+ width,
15564
+ height,
15565
+ probeResult,
15566
+ totalFrames
15567
+ } = c;
15278
15568
  const megapixels = compileResult.outputWidth * compileResult.outputHeight / 1e6;
15279
15569
  let maxParallel = resolveMaxParallelChunks(config, totalFrames, megapixels, log);
15280
15570
  const measureFrameCost = chunkSizingUsesFrameCost(config) && planNeedsFrameCostMeasurement(compiled.html);
@@ -15285,24 +15575,9 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
15285
15575
  observedAt: "pre-extract",
15286
15576
  log
15287
15577
  });
15288
- stageStarted = Date.now();
15289
- const extractResult = await runExtractVideosStage({
15290
- projectDir,
15291
- compiledDir,
15292
- job,
15293
- cfg,
15294
- composition,
15295
- abortSignal,
15296
- assertNotAborted,
15297
- materializeSymlinks: true,
15298
- deferRangeExtraction: options.deferVideoExtraction,
15299
- urlByAbsentLocalPath
15300
- });
15578
+ let stageStarted = Date.now();
15579
+ const extractResult = await extractPlanVideos(c, options.deferVideoExtraction);
15301
15580
  logPlanStage(log, "extract", stageStarted);
15302
- if (extractResult.failureToEnforce) throw extractResult.failureToEnforce;
15303
- if (extractResult.extractionResult) {
15304
- assertVideoExtractionSucceeded(extractResult.extractionResult);
15305
- }
15306
15581
  const stagedVideoFrames = join33(compiledDir, "__hyperframes_video_frames");
15307
15582
  const videoFramesDst = join33(planDir, "video-frames");
15308
15583
  if (existsSync20(videoFramesDst)) rmSync10(videoFramesDst, { recursive: true, force: true });
@@ -15358,23 +15633,18 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
15358
15633
  "utf-8"
15359
15634
  );
15360
15635
  const digests = options.digests ?? /* @__PURE__ */ new Map();
15361
- const chunksCarryAudio2 = carriesChunkAudio(config, options);
15362
- const audioSeconds = chunksCarryAudio2 ? totalFrames / config.fps : probeResult.duration;
15636
+ const separateAudio = plansSeparateAudio(config, options);
15637
+ const chunksCarryAudio = !separateAudio && carriesChunkAudio(config, options);
15638
+ const audioSeconds = chunksCarryAudio ? totalFrames / config.fps : probeResult.duration;
15639
+ const planMixPcmPath = join33(workDir, "plan-mix.wav");
15363
15640
  stageStarted = Date.now();
15364
15641
  const [audioOutcome, overlapOutcome, frameCostOutcome] = await Promise.allSettled([
15365
- runAudioStage({
15366
- projectDir,
15367
- workDir,
15368
- compiledDir: finalCompiledDir,
15369
- urlByAbsentLocalPath,
15370
- duration: audioSeconds,
15371
- ffmpegProcessTimeout: cfg.ffmpegProcessTimeout,
15372
- audioGain: cfg.audioGain,
15373
- audios: composition.audios,
15374
- masterFxChain: composition.masterFxChain,
15375
- abortSignal,
15376
- assertNotAborted
15377
- }).finally(() => logPlanStage(log, "audio", stageStarted)),
15642
+ separateAudio ? null : mixPlanAudio(
15643
+ c,
15644
+ finalCompiledDir,
15645
+ audioSeconds,
15646
+ chunksCarryAudio ? planMixPcmPath : void 0
15647
+ ).finally(() => logPlanStage(log, "audio", stageStarted)),
15378
15648
  (async () => {
15379
15649
  for (const dir of [finalCompiledDir, videoFramesDst, videoSourcesDir]) {
15380
15650
  await prehashPlanFiles(dir, digests);
@@ -15424,22 +15694,16 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
15424
15694
  );
15425
15695
  const chunks = buildChunkSlices(totalFrames, chunkCount, effectiveChunkSize);
15426
15696
  const audioResult = audioOutcome.value;
15427
- if (audioResult.audioError) {
15697
+ if (audioResult?.audioError) {
15428
15698
  applyDistributedAudioWarningPolicy(job, audioResult.audioError, audioResult.audioFailures, log);
15429
15699
  }
15430
15700
  const planAudioPath = join33(planDir, PLAN_AUDIO_RELATIVE_PATH);
15431
- if (audioResult.hasAudio && existsSync20(audioResult.audioOutputPath)) {
15701
+ if (audioResult?.hasAudio && existsSync20(audioResult.audioOutputPath)) {
15432
15702
  renameSync5(audioResult.audioOutputPath, planAudioPath);
15433
15703
  }
15434
- if (chunksCarryAudio2 && existsSync20(planAudioPath)) {
15704
+ if (chunksCarryAudio && existsSync20(planAudioPath)) {
15435
15705
  stageStarted = Date.now();
15436
- const peak = await constrainAacTruePeak({
15437
- audioPath: planAudioPath,
15438
- targetDurationSeconds: audioSeconds,
15439
- signal: abortSignal,
15440
- timeoutMs: cfg.ffmpegProcessTimeout
15441
- });
15442
- if (!peak.success) throw encoderFailureError("[plan] audio true-peak check failed", peak);
15706
+ await holdPlanAudioPeak(c, planAudioPath, planMixPcmPath, audioSeconds);
15443
15707
  logPlanStage(log, "audio-peak", stageStarted);
15444
15708
  if (options.sliceChunkAudio) {
15445
15709
  stageStarted = Date.now();
@@ -15465,7 +15729,8 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
15465
15729
  effectiveChunkSize,
15466
15730
  chunkCount,
15467
15731
  runtimeEnv,
15468
- chunksCarryAudio: chunksCarryAudio2
15732
+ chunksCarryAudio,
15733
+ separateAudio
15469
15734
  });
15470
15735
  const compositionJson = {
15471
15736
  durationSeconds: job.duration ?? 0,
@@ -15510,7 +15775,7 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
15510
15775
  fontSnapshotSha,
15511
15776
  durationSeconds: job.duration ?? 0,
15512
15777
  totalFrames,
15513
- hasAudio: audioResult.hasAudio,
15778
+ hasAudio: audioResult?.hasAudio ?? false,
15514
15779
  digests
15515
15780
  });
15516
15781
  logPlanStage(log, "freeze", stageStarted);
@@ -16861,7 +17126,7 @@ function startServer(options = {}) {
16861
17126
  });
16862
17127
  async function shutdown(signal) {
16863
17128
  log.info(`Received ${signal}, shutting down`);
16864
- const { drainBrowserPool } = await import("./src-RDSWHMJS.js");
17129
+ const { drainBrowserPool } = await import("./src-4UYM2K3S.js");
16865
17130
  await drainBrowserPool().catch(() => {
16866
17131
  });
16867
17132
  const handle = await Promise.race([
@@ -17485,7 +17750,8 @@ async function planV2WithPublisher(projectDir, config, publisher, options = {})
17485
17750
  )
17486
17751
  ),
17487
17752
  chunksCarryAudio: options.chunksCarryAudio,
17488
- sliceChunkAudio: true
17753
+ sliceChunkAudio: true,
17754
+ deferAudio: options.deferAudio
17489
17755
  });
17490
17756
  return await publishExecutionPlan(stagingRoot, publisher, { digests, log: config.logger });
17491
17757
  } catch (error) {
@@ -17811,7 +18077,8 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
17811
18077
  return mergePngFrameDirs(chunkPaths, outputPath, plan2.totalFrames, audioPath, start);
17812
18078
  }
17813
18079
  if (chunksAreStitchedMp4(planDir, cfr)) {
17814
- await writeStitchedMp4(chunkPaths, outputPath);
18080
+ const separateAudio = stitchesSeparateAudio(planDir) && audioPath !== null;
18081
+ await writeStitchedMp4(chunkPaths, outputPath, separateAudio ? audioPath : void 0);
17815
18082
  return {
17816
18083
  outputPath,
17817
18084
  durationMs: Date.now() - start,
@@ -18009,16 +18276,20 @@ async function runAssembleFfmpeg(args, step, signal, timeout) {
18009
18276
  }
18010
18277
  }
18011
18278
  function chunksAreStitchedMp4(planDir, cfr) {
18012
- if (!chunksCarryAudio(planDir)) return false;
18279
+ const { chunksCarryAudio, separateAudio } = lockedAudio(planDir);
18280
+ if (!chunksCarryAudio && !separateAudio) return false;
18013
18281
  if (cfr) {
18014
- throw new Error("[assemble] cfr=true cannot re-encode a plan whose mp4 chunks carry audio");
18282
+ throw new Error("[assemble] cfr=true cannot re-encode a plan whose mp4 chunks are stitched");
18015
18283
  }
18016
18284
  return true;
18017
18285
  }
18018
- function chunksCarryAudio(planDir) {
18286
+ function stitchesSeparateAudio(planDir) {
18287
+ return lockedAudio(planDir).separateAudio === true;
18288
+ }
18289
+ function lockedAudio(planDir) {
18019
18290
  const encoderJsonPath = join40(planDir, "meta", "encoder.json");
18020
- if (!existsSync26(encoderJsonPath)) return false;
18021
- return JSON.parse(readFileSync13(encoderJsonPath, "utf-8")).chunksCarryAudio === true;
18291
+ if (!existsSync26(encoderJsonPath)) return {};
18292
+ return JSON.parse(readFileSync13(encoderJsonPath, "utf-8"));
18022
18293
  }
18023
18294
  function readCfrEncoder(planDir, format) {
18024
18295
  if (format !== "mp4") {
@@ -18097,6 +18368,12 @@ function validatePlanVideosForChunk(value) {
18097
18368
  );
18098
18369
  }
18099
18370
  }
18371
+ var MIN_FRAMES_PER_CHUNK_BROWSER = 20;
18372
+ function resolveChunkCaptureWorkers(framesInChunk, machineSized, maxCaptureWorkers) {
18373
+ if (maxCaptureWorkers === void 0) return machineSized;
18374
+ const byFrames = Math.floor(framesInChunk / MIN_FRAMES_PER_CHUNK_BROWSER);
18375
+ return Math.max(machineSized, Math.floor(Math.min(maxCaptureWorkers, byFrames)));
18376
+ }
18100
18377
  function resolveChunkFrameRange(slice, chunkIndex, frameRange) {
18101
18378
  if (frameRange === void 0) return { startFrame: slice.startFrame, endFrame: slice.endFrame };
18102
18379
  const { startFrame, endFrame } = frameRange;
@@ -18302,34 +18579,15 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
18302
18579
  const framesDir = join41(workDir, "captured-frames");
18303
18580
  mkdirSync17(framesDir, { recursive: true });
18304
18581
  const videoFramesDir = `${workDir}.video-frames`;
18305
- const deferredVideoFrames = planVideos ? await extractDeferredVideoFramesForChunk({
18306
- planDir,
18307
- planVideos,
18308
- frames: Array.from({ length: framesInChunk }, (_, i) => range.startFrame + i),
18309
- fps: { num: plan2.dimensions.fpsNum, den: plan2.dimensions.fpsDen },
18310
- outputRoot: videoFramesDir,
18311
- cfg,
18312
- remoteSources
18313
- }) : [];
18582
+ const chunkFrameNumbers = Array.from({ length: framesInChunk }, (_, i) => range.startFrame + i);
18583
+ const chunkFps = { num: plan2.dimensions.fpsNum, den: plan2.dimensions.fpsDen };
18584
+ const deferredVideos = planVideos?.extracted.filter((video) => video.deferredRange) ?? [];
18585
+ const shownFrames = planVideos ? shownDeferredFrameIndexes(planVideos, chunkFrameNumbers, chunkFps) : /* @__PURE__ */ new Map();
18314
18586
  const remoteVideoIds = new Set(
18315
18587
  planVideos?.extracted.filter(
18316
18588
  (video) => video.deferredRange && Object.hasOwn(remoteSources, video.deferredRange.sourcePath)
18317
18589
  ).map((video) => video.videoId)
18318
18590
  );
18319
- const remoteVideos = deferredVideoFrames.filter((video) => remoteVideoIds.has(video.videoId));
18320
- const videoFrameLookup = planVideos && planVideos.extracted.length > 0 ? createFrameLookupTable(
18321
- planVideos.videos,
18322
- [
18323
- ...rebuildExtractedFramesFromPlanDir(
18324
- planDir,
18325
- planVideos.extracted.filter((video) => !video.deferredRange),
18326
- v2Manifest === null ? "dense-v1" : "sparse-v2"
18327
- ),
18328
- ...deferredVideoFrames
18329
- ],
18330
- resolveRenderFpsConfig(job.config.fps).value
18331
- ) : null;
18332
- const createChunkVideoFrameInjector = createChunkVideoFrameInjectorFactory(videoFrameLookup);
18333
18591
  const fileServer = await createFileServer({
18334
18592
  projectDir: compiledDir,
18335
18593
  compiledDir,
@@ -18345,10 +18603,18 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
18345
18603
  videoCount: planVideos?.videos.length ?? 0,
18346
18604
  html: readFileSync14(join41(compiledDir, "index.html"), "utf-8"),
18347
18605
  // The plan ships a deferred source only to chunks that show it; don't wait on the rest.
18348
- skipReadinessVideoIds: deferredVideoFrames.filter((video) => video.framePaths.size === 0 || remoteVideoIds.has(video.videoId)).map((video) => video.videoId),
18349
- videoMetadataHints: collectVideoMetadataHints(remoteVideos)
18606
+ skipReadinessVideoIds: deferredVideos.filter(
18607
+ (video) => !shownFrames.get(video.videoId)?.size || remoteVideoIds.has(video.videoId)
18608
+ ).map((video) => video.videoId),
18609
+ videoMetadataHints: collectVideoMetadataHints(
18610
+ deferredVideos.filter((video) => remoteVideoIds.has(video.videoId))
18611
+ )
18350
18612
  });
18351
- const chunkWorkerCount = calculateOptimalWorkers(framesInChunk, void 0, cfg);
18613
+ const chunkWorkerCount = resolveChunkCaptureWorkers(
18614
+ framesInChunk,
18615
+ calculateOptimalWorkers(framesInChunk, void 0, cfg),
18616
+ options?.maxCaptureWorkers
18617
+ );
18352
18618
  let session = null;
18353
18619
  let outputKind;
18354
18620
  let framesEncoded = 0;
@@ -18359,6 +18625,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
18359
18625
  const capturePerfs = [];
18360
18626
  const captureAttempts = [];
18361
18627
  let forceScreenshotForChunk = encoder.forceScreenshot;
18628
+ let shownBoxes;
18362
18629
  try {
18363
18630
  if (chunkWorkerCount === 1 || !forceScreenshotForChunk) {
18364
18631
  const bootStarted = Date.now();
@@ -18369,6 +18636,11 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
18369
18636
  cfg
18370
18637
  );
18371
18638
  sessionBootMs = Date.now() - bootStarted;
18639
+ shownBoxes = await measureVideoShownBoxes(
18640
+ session.page,
18641
+ [...shownFrames.keys()],
18642
+ chunkFrameNumbers.map((frame) => frame * chunkFps.den / chunkFps.num)
18643
+ );
18372
18644
  const browserSelectedScreenshot = session.launchCaptureMode === "screenshot";
18373
18645
  const beginFrameStalled = !browserSelectedScreenshot && await beginFrameSessionNeedsScreenshotFallback(session);
18374
18646
  if (browserSelectedScreenshot) {
@@ -18410,6 +18682,29 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
18410
18682
  session = null;
18411
18683
  }
18412
18684
  }
18685
+ const deferredVideoFrames = planVideos ? await extractDeferredVideoFramesForChunk({
18686
+ planDir,
18687
+ planVideos,
18688
+ frames: chunkFrameNumbers,
18689
+ fps: chunkFps,
18690
+ outputRoot: videoFramesDir,
18691
+ cfg,
18692
+ remoteSources,
18693
+ shownBoxes
18694
+ }) : [];
18695
+ const videoFrameLookup = planVideos && planVideos.extracted.length > 0 ? createFrameLookupTable(
18696
+ planVideos.videos,
18697
+ [
18698
+ ...rebuildExtractedFramesFromPlanDir(
18699
+ planDir,
18700
+ planVideos.extracted.filter((video) => !video.deferredRange),
18701
+ v2Manifest === null ? "dense-v1" : "sparse-v2"
18702
+ ),
18703
+ ...deferredVideoFrames
18704
+ ],
18705
+ resolveRenderFpsConfig(job.config.fps).value
18706
+ ) : null;
18707
+ const createChunkVideoFrameInjector = createChunkVideoFrameInjectorFactory(videoFrameLookup);
18413
18708
  const captureStarted = Date.now();
18414
18709
  captureMode = await runCaptureWithScreenshotFallback({
18415
18710
  forceScreenshot: forceScreenshotForChunk,
@@ -18600,6 +18895,8 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
18600
18895
  encodeStageMs,
18601
18896
  workers: chunkWorkerCount,
18602
18897
  captureMode,
18898
+ // Device-pixel box each video's frames were extracted to cover; a video not listed kept full size.
18899
+ videoShownBoxes: Object.fromEntries(shownBoxes ?? []),
18603
18900
  sha256,
18604
18901
  outputKind,
18605
18902
  producerVersion: plan2.producerVersion,
@@ -18669,9 +18966,10 @@ async function assembleV2(planV2Dir, chunkPaths, outputPath, options) {
18669
18966
  }
18670
18967
 
18671
18968
  // ../producer/src/services/distributed/projectHash.ts
18672
- import { readdirSync as readdirSync12, readFileSync as readFileSync15, realpathSync as realpathSync2, statSync as statSync11 } from "fs";
18969
+ import { closeSync as closeSync8, openSync as openSync7, readdirSync as readdirSync12, readSync as readSync5, realpathSync as realpathSync2, statSync as statSync11 } from "fs";
18673
18970
  import { createHash as createHash7 } from "crypto";
18674
18971
  import { join as join43 } from "path";
18972
+ var READ_CHUNK_BYTES = 256 * 1024;
18675
18973
 
18676
18974
  export {
18677
18975
  createConsoleLogger,