hyperframes 0.8.142 → 0.8.144

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 (222) hide show
  1. package/dist/{add-YS2RCWEK.js → add-CWRTVGDB.js} +18 -17
  2. package/dist/{animatedGifPrep-SAMCDT7T.js → animatedGifPrep-JTOHIWI4.js} +15 -14
  3. package/dist/{asset-BLWC2NWB.js → asset-KU2Z4UFM.js} +4 -4
  4. package/dist/{assetCataloger-FVNBBRP3.js → assetCataloger-3CS2P7PA.js} +8 -7
  5. package/dist/{auth-DRZB7OAC.js → auth-IDSOIQWG.js} +1 -1
  6. package/dist/{autoUpdate-I5I3BOJK.js → autoUpdate-373B4BA2.js} +2 -2
  7. package/dist/{backgroundChecks-VR2QHGNY.js → backgroundChecks-53Q3NAMG.js} +3 -3
  8. package/dist/backgroundChecksWorker.js +3 -3
  9. package/dist/{batchRender-4YUHQI6X.js → batchRender-UKZA6BME.js} +11 -10
  10. package/dist/{beats-7OT7ALV3.js → beats-N5JGEOFR.js} +33 -28
  11. package/dist/{benchmark-Q2XQCRTR.js → benchmark-CUN3LN4V.js} +15 -14
  12. package/dist/{browser-7VAWQQZC.js → browser-NCZT5AP6.js} +40 -56
  13. package/dist/{bundleWithLocalizedFonts-DDMLLL2R.js → bundleWithLocalizedFonts-FWC7PE2D.js} +2 -2
  14. package/dist/{capture-HEJS6M5A.js → capture-QBLG3VJR.js} +26 -25
  15. package/dist/{capture-U7ABPKS5.js → capture-YLS32UHT.js} +2 -2
  16. package/dist/{catalog-DVJ3XPDN.js → catalog-HIWNAFHW.js} +19 -18
  17. package/dist/{catch-up-DF6AVSN2.js → catch-up-HGS7KIAC.js} +5 -5
  18. package/dist/{check-KXORRWAE.js → check-FH6UYYLO.js} +78 -66
  19. package/dist/{checkBrowser-J7HZJTRH.js → checkBrowser-RNWNOMCZ.js} +93 -41
  20. package/dist/{chunk-R2JEKLDZ.js → chunk-2DA2BBNY.js} +3 -3
  21. package/dist/{chunk-TQFG7HTI.js → chunk-3RM2ETQT.js} +0 -168
  22. package/dist/{chunk-YFT3BL7N.js → chunk-3VYNNWVA.js} +30 -12
  23. package/dist/{chunk-7FCDA4RH.js → chunk-3XTEXWNN.js} +8 -5
  24. package/dist/{chunk-PFCFR3KZ.js → chunk-442JNXRM.js} +1 -1
  25. package/dist/{chunk-BPZVDBHY.js → chunk-4LAR447G.js} +1 -1
  26. package/dist/{chunk-AM5L5YIA.js → chunk-4OA4LDTU.js} +1 -1
  27. package/dist/{chunk-DZEVA7DI.js → chunk-4OC7MQNB.js} +2 -2
  28. package/dist/{chunk-75JJVANE.js → chunk-54QISXOC.js} +3 -3
  29. package/dist/{chunk-FZK35GT4.js → chunk-5HJ343GH.js} +1 -1
  30. package/dist/{chunk-IDX52YJN.js → chunk-5VMR4WBS.js} +458 -159
  31. package/dist/{chunk-OSUKETIH.js → chunk-5WADG7PR.js} +1 -1
  32. package/dist/{chunk-JYYOAKHS.js → chunk-6IIGT7BX.js} +1 -1
  33. package/dist/{chunk-6UYZB4TY.js → chunk-7RAYSCYT.js} +4 -4
  34. package/dist/{chunk-WTMUCJ6Z.js → chunk-7SDCIWHQ.js} +11 -2
  35. package/dist/{chunk-LAOCA3MQ.js → chunk-A4Q7W4LV.js} +13 -8
  36. package/dist/{chunk-B6SMSALD.js → chunk-A53CBD24.js} +18 -12
  37. package/dist/{chunk-4PFZPP2N.js → chunk-A6S35NYA.js} +54 -16
  38. package/dist/{chunk-JOTLPC6S.js → chunk-AUBOF2PN.js} +1 -1
  39. package/dist/{chunk-WCJGTLPF.js → chunk-BHVFTYSI.js} +6 -3
  40. package/dist/chunk-BSQ7XANM.js +210 -0
  41. package/dist/{chunk-LCSMOLGX.js → chunk-BYSBRF24.js} +69 -8
  42. package/dist/{chunk-U7E736AJ.js → chunk-DDIVWXN7.js} +32 -14
  43. package/dist/{chunk-KOEWEFHK.js → chunk-DIRJ322I.js} +10 -5
  44. package/dist/{chunk-HMUF7FEO.js → chunk-EX6C672Y.js} +2 -2
  45. package/dist/{chunk-QGRFOOLE.js → chunk-F2SMS24Q.js} +288 -85
  46. package/dist/{chunk-5Z63TBTX.js → chunk-FH7WODJH.js} +1 -1
  47. package/dist/{chunk-XTLBLWOV.js → chunk-GEWKFRFI.js} +1 -1
  48. package/dist/{chunk-RVCWIIZ7.js → chunk-GV5R5B7Q.js} +1 -1
  49. package/dist/{chunk-TY6XOHEM.js → chunk-GZM2EFOE.js} +3 -3
  50. package/dist/chunk-GZVP4TWN.js +76 -0
  51. package/dist/{chunk-QXPCARFE.js → chunk-I5KY7EZP.js} +1 -1
  52. package/dist/{chunk-IW2CKH6A.js → chunk-IEIJRMLZ.js} +94 -59
  53. package/dist/chunk-IR35BCLK.js +13 -0
  54. package/dist/{chunk-JFKL3WAE.js → chunk-IWFLMU2N.js} +104 -19
  55. package/dist/{chunk-EFDXGAAD.js → chunk-JEHFZQYT.js} +1 -1
  56. package/dist/{chunk-IHFUKJOR.js → chunk-K2FLNJVB.js} +8 -2
  57. package/dist/{chunk-KDPYVTEU.js → chunk-KBB4RG6F.js} +6 -1
  58. package/dist/{chunk-LJAU4TPS.js → chunk-KFKZ3ISO.js} +2 -2
  59. package/dist/{sherpa-ZYTNFHD5.js → chunk-L5TUCXWR.js} +11 -44
  60. package/dist/{chunk-KYN364DU.js → chunk-LC3RQURJ.js} +7 -9
  61. package/dist/{chunk-IMY4QQTQ.js → chunk-LFX2HEYP.js} +8 -8
  62. package/dist/{chunk-PXSM4HPB.js → chunk-MAYCCGMQ.js} +12 -3
  63. package/dist/{chunk-OZUQF4NB.js → chunk-MQZVBHEY.js} +123 -8
  64. package/dist/{chunk-FWRNQOLT.js → chunk-MXNDKDFT.js} +3 -3
  65. package/dist/{chunk-A4KQZXZI.js → chunk-NE55C6S5.js} +4 -2
  66. package/dist/{chunk-GYE63V4P.js → chunk-NOPBBSLA.js} +24 -19
  67. package/dist/{chunk-H2X52H2N.js → chunk-NV4RXIQ7.js} +3 -3
  68. package/dist/{chunk-WGVOWWVK.js → chunk-OIDN7G42.js} +3 -3
  69. package/dist/{chunk-DPHJKWP5.js → chunk-OUSLVRO4.js} +107 -108
  70. package/dist/{chunk-JLKMWY2T.js → chunk-P6WH6QTV.js} +8 -5
  71. package/dist/{chunk-7M4KAPAA.js → chunk-PUGIRFPX.js} +2 -2
  72. package/dist/{chunk-6RAD4BCC.js → chunk-QATWIE4K.js} +2 -2
  73. package/dist/{chunk-D2KNWIQ6.js → chunk-QFSCCQIR.js} +1 -1
  74. package/dist/{chunk-XGMNQQTJ.js → chunk-RG3QWWIE.js} +1 -1
  75. package/dist/{chunk-MVTUV4TP.js → chunk-RI53TS2Y.js} +14 -6
  76. package/dist/{chunk-LQASVU3I.js → chunk-RVFEDAUV.js} +12 -4
  77. package/dist/{chunk-GK4AIFIH.js → chunk-SDYDNHEE.js} +34 -3
  78. package/dist/{chunk-WSGY5CDQ.js → chunk-SLCFKB3Y.js} +14 -10
  79. package/dist/{chunk-2NCHQUPX.js → chunk-SQLK7SJL.js} +1 -1
  80. package/dist/{chunk-PMRBLBUP.js → chunk-T42U2W2R.js} +14 -2
  81. package/dist/chunk-UPFUC4DB.js +136 -0
  82. package/dist/{chunk-IXCTG2WY.js → chunk-VYSUZEJG.js} +5 -5
  83. package/dist/chunk-WJB7WI4J.js +3753 -0
  84. package/dist/{chunk-YEIFI443.js → chunk-WPNE5NEP.js} +1 -1
  85. package/dist/{chunk-YHRZQJXH.js → chunk-XGFHYAYN.js} +265 -68
  86. package/dist/{chunk-LBKDQKWV.js → chunk-XU5GGV6R.js} +1 -1
  87. package/dist/{chunk-LTGGTEMA.js → chunk-Y6UPYWJS.js} +2 -2
  88. package/dist/{chunk-OKCR3JTO.js → chunk-YJXHMIZA.js} +13 -10
  89. package/dist/{chunk-JU6Y5TTZ.js → chunk-YUAXFWSQ.js} +3 -3
  90. package/dist/{chunk-TVCRUG2S.js → chunk-ZEUONDQG.js} +1 -1
  91. package/dist/{chunk-THXFSPLV.js → chunk-ZIRIDSDB.js} +1 -1
  92. package/dist/{chunk-6UM3R7PP.js → chunk-ZOA2SC4P.js} +129 -60
  93. package/dist/{clean-KTHTV5G5.js → clean-KPX27IKJ.js} +27 -26
  94. package/dist/cli.js +70 -160
  95. package/dist/{cloud-ARSZWALC.js → cloud-P7DAOISN.js} +4 -4
  96. package/dist/{cloudrun-NM7RXOCL.js → cloudrun-XM56TBOH.js} +13 -12
  97. package/dist/commands/contrast-audit.browser.js +60 -4
  98. package/dist/commands/layout-audit.browser.js +68 -0
  99. package/dist/{compare-CY4UO3RW.js → compare-F6YAFZZM.js} +37 -36
  100. package/dist/{compiler-VTH2D3ID.js → compiler-JZ4GNFMG.js} +16 -10
  101. package/dist/{component-FRUIAXIR.js → component-3CFOGIQA.js} +5 -5
  102. package/dist/{compositions-CUSY6ZYY.js → compositions-CPCMWF24.js} +38 -33
  103. package/dist/{delete-KT3VHP4L.js → delete-GYVKOKB6.js} +4 -4
  104. package/dist/{deterministicFonts-F6WIVL2W.js → deterministicFonts-U2BIMGPG.js} +3 -3
  105. package/dist/{dist-7SZU7MA7.js → dist-KDJTFJHA.js} +7 -6
  106. package/dist/{dist-2GC6XKRQ.js → dist-YPWQDWJ4.js} +42 -40
  107. package/dist/{doctor-4ROTBL5U.js → doctor-GWI3B6JT.js} +21 -20
  108. package/dist/{dom-IROKGVJI.js → dom-37K6YOH4.js} +2 -2
  109. package/dist/{events-QJDVMKSR.js → events-BRRUDPPT.js} +3 -3
  110. package/dist/{events-Q5PXT5UC.js → events-V2QFN4YM.js} +2 -2
  111. package/dist/{feedback-PQ7TWESA.js → feedback-CXMD7FWL.js} +14 -13
  112. package/dist/{ffmpeg-QIUFEFTY.js → ffmpeg-T5SH2EFJ.js} +3 -2
  113. package/dist/{figma-JN5DDBDS.js → figma-YABDWD66.js} +3 -3
  114. package/dist/fontLocalizeCli.js +18 -17
  115. package/dist/{get-KT6OTERO.js → get-L2SOQKHX.js} +4 -4
  116. package/dist/{gpuPolicy-LCBWVGEH.js → gpuPolicy-NBJNWHYS.js} +19 -16
  117. package/dist/{grade-compare-VS3RP73G.js → grade-compare-STXQPKYQ.js} +39 -38
  118. package/dist/{help-AD6FIZIO.js → help-4H6BSFLB.js} +2 -2
  119. package/dist/{history-ZRWYRY7R.js → history-4IQOLTHJ.js} +6 -6
  120. package/dist/hyperframe-runtime.js +56 -56
  121. package/dist/hyperframe.manifest.json +1 -1
  122. package/dist/hyperframe.runtime.iife.js +56 -56
  123. package/dist/hyperframes-player.global.js +3 -3
  124. package/dist/hyperframes-slideshow.global.js +23 -23
  125. package/dist/{info-WBBQA2AX.js → info-2CZOTNBC.js} +17 -16
  126. package/dist/{init-A2XBDFGF.js → init-6BXK42VS.js} +46 -46
  127. package/dist/{inspect-CLTZHA5O.js → inspect-VKU47TBH.js} +38 -37
  128. package/dist/{installer-WQDRVXH4.js → installer-T2BSSUDY.js} +11 -10
  129. package/dist/{keyframes-3FYOK6H6.js → keyframes-IEYVHRPI.js} +10 -10
  130. package/dist/{lambda-Y2PA2BGS.js → lambda-TIKG25CK.js} +12 -11
  131. package/dist/{layout-4KP5O7IJ.js → layout-XOI4PT7A.js} +38 -37
  132. package/dist/{lint-V4GRFHOJ.js → lint-DWCMVIX5.js} +14 -13
  133. package/dist/{lintProject-RG2KGAXB.js → lintProject-UBZ3TDHU.js} +7 -6
  134. package/dist/{list-OQ4VED6E.js → list-7GXKG5QM.js} +5 -5
  135. package/dist/{login-W47NKN5S.js → login-BQ5L2UHX.js} +4 -4
  136. package/dist/{manager-CT3D67CG.js → manager-BPSR6D3V.js} +2 -2
  137. package/dist/{manager-524RPYO7.js → manager-RFZNG5YT.js} +2 -2
  138. package/dist/{manager-DXB7JDHU.js → manager-YKMHR6LN.js} +9 -6
  139. package/dist/{media-treatment-HJYL3EME.js → media-treatment-IFB7OSZ3.js} +24 -22
  140. package/dist/{models-DYBAD2GQ.js → models-TY4PJ545.js} +68 -27
  141. package/dist/{motionShot-7NE2YBB3.js → motionShot-WXMFHICW.js} +33 -33
  142. package/dist/{normalize-IGLHCWTF.js → normalize-U3SL5VG3.js} +19 -8
  143. package/dist/{normalize-audio-INKCLMS2.js → normalize-audio-RFD776HQ.js} +10 -6
  144. package/dist/{open-TYC5THVY.js → open-7IXXGO2E.js} +5 -5
  145. package/dist/parakeetRunner-IHLYYMTZ.js +67 -0
  146. package/dist/{pipeline-EVKEPQEW.js → pipeline-TXKPLL2U.js} +20 -18
  147. package/dist/{play-QMZEM6X3.js → play-J2KP64UK.js} +31 -29
  148. package/dist/{preflight-Y5DN5D37.js → preflight-B4AFK3RG.js} +4 -4
  149. package/dist/{present-Y7IK3C4W.js → present-MHHNRVRA.js} +26 -25
  150. package/dist/{preview-5OQRYE6A.js → preview-CONUOFFQ.js} +28 -27
  151. package/dist/{producer-Q6G6JU7B.js → producer-ZI46K7HH.js} +1 -1
  152. package/dist/{projectConfig-RJN3DKDB.js → projectConfig-FGGVG5MV.js} +13 -12
  153. package/dist/{publication-QCPGGN3U.js → publication-TXFBMW6B.js} +8 -7
  154. package/dist/{publish-VZLFIGYY.js → publish-YZSPBFRD.js} +18 -17
  155. package/dist/{remove-background-TKOEHBA5.js → remove-background-4ATVLHLG.js} +22 -20
  156. package/dist/{render-KIIIF6PS.js → render-6FBYLYGJ.js} +20 -19
  157. package/dist/{render-XTXE5CRU.js → render-GFW5BPES.js} +13 -12
  158. package/dist/{render-X3NFNVEQ.js → render-MH4IUPWB.js} +41 -40
  159. package/dist/{render-batch-M55YZLZ5.js → render-batch-XUCZ2PUJ.js} +13 -12
  160. package/dist/{renderCancellation-X5BEHAZU.js → renderCancellation-XF7M7IUP.js} +18 -16
  161. package/dist/renderSetupWorker.js +37 -48
  162. package/dist/{resolver-GAMXRA42.js → resolver-W7AYCQGS.js} +10 -9
  163. package/dist/sherpa-LDUB5NLY.js +75 -0
  164. package/dist/sherpaWorker.js +2 -1
  165. package/dist/skills/hyperframes-cli/references/doctor-browser.md +24 -1
  166. package/dist/skills/hyperframes-cli/references/lint-validate-inspect.md +1 -1
  167. package/dist/skills/media-use/scripts/lib/cache.mjs +28 -14
  168. package/dist/skills/media-use/scripts/lib/cache.test.mjs +110 -0
  169. package/dist/skills/media-use/scripts/lib/duck.mjs +2 -1
  170. package/dist/skills/media-use/scripts/lib/duck.test.mjs +87 -0
  171. package/dist/skills/media-use/scripts/lib/probe.mjs +6 -1
  172. package/dist/skills/media-use/scripts/lib/probe.test.mjs +73 -0
  173. package/dist/skills/media-use/scripts/lib/words.mjs +1 -1
  174. package/dist/{skills-CZHVAD3J.js → skills-VENERS5X.js} +7 -7
  175. package/dist/{skillsUpdateCheck-6WLLX53A.js → skillsUpdateCheck-UXFYTACG.js} +3 -3
  176. package/dist/{slideshow-EJ4IMY6P.js → slideshow-4OJEPWNZ.js} +1 -1
  177. package/dist/{snapshot-K7BTJPYQ.js → snapshot-C6T74HTI.js} +44 -40
  178. package/dist/{src-YUW26JU5.js → src-FS5RSVEI.js} +17 -16
  179. package/dist/{src-3I3RH3V5.js → src-VUJ2FUFK.js} +18 -13
  180. package/dist/{staticProjectServer-IKCSGN2B.js → staticProjectServer-V2RZ4DWD.js} +23 -22
  181. package/dist/studio/assets/hyperframes-player-Bw_jIaSX.js +473 -0
  182. package/dist/studio/assets/{index-B-wffzRH.js → index-BVM9NuWl.js} +153 -153
  183. package/dist/studio/assets/{index-D3Vij9t1.js → index-Cj7W_c7p.js} +1 -1
  184. package/dist/studio/assets/{index-D5q2_7Gw.js → index-DyH9dT7b.js} +1 -1
  185. package/dist/studio/{chunk-TFLU2MGI.js → chunk-3GHTHPZD.js} +8 -1
  186. package/dist/studio/chunk-3GHTHPZD.js.map +1 -0
  187. package/dist/studio/{chunk-H2DUOXTF.js → chunk-G3EDKRZH.js} +3 -3
  188. package/dist/studio/{chunk-UYWON5AX.js → chunk-PEAKCOUW.js} +7 -3
  189. package/dist/studio/chunk-PEAKCOUW.js.map +1 -0
  190. package/dist/studio/{chunk-LOVBLSMR.js → chunk-Y2OHHNM2.js} +35 -19
  191. package/dist/studio/chunk-Y2OHHNM2.js.map +1 -0
  192. package/dist/studio/{domEditingLayers-KBTPQKGA.js → domEditingLayers-PYST4G45.js} +3 -3
  193. package/dist/studio/index.html +1 -1
  194. package/dist/studio/index.js +261 -230
  195. package/dist/studio/index.js.map +1 -1
  196. package/dist/studio/player.js +3 -3
  197. package/dist/studio/ui.d.ts +4 -5
  198. package/dist/studio/ui.js +1 -1
  199. package/dist/{studio-api-SQ3NXFSM.js → studio-api-TQE7KWKO.js} +22 -17
  200. package/dist/{studioServer-VVEO7XCH.js → studioServer-CYKSQK7L.js} +81 -64
  201. package/dist/{synthesize-77PTU6C5.js → synthesize-UCI4X5O7.js} +6 -6
  202. package/dist/{telemetry-FQDKR5XA.js → telemetry-A23PPRD6.js} +3 -3
  203. package/dist/{timeline-N6ZASFNE.js → timeline-T3QSGT3A.js} +143 -99
  204. package/dist/{tokens-2OIIBBCW.js → tokens-RT5HWA6C.js} +4 -4
  205. package/dist/{transcribe-7YTUCXDX.js → transcribe-FOLNOC5G.js} +116 -52
  206. package/dist/{transcribe-4F6MK73N.js → transcribe-HCF64LQK.js} +23 -20
  207. package/dist/{tts-TOHSPWEG.js → tts-B6AHFQEO.js} +6 -6
  208. package/dist/{updateCheck-AZ2COZFR.js → updateCheck-27PIP3AA.js} +2 -2
  209. package/dist/{upgrade-QAJEYB4X.js → upgrade-TVGXBUPJ.js} +2 -2
  210. package/dist/{urlDownloader-LSMFEO3R.js → urlDownloader-KBLBJKRL.js} +14 -13
  211. package/dist/{validate-GW5MDL36.js → validate-ZMYGK4QI.js} +40 -27
  212. package/dist/{video-PRNOKUK3.js → video-AVE5GIGM.js} +15 -14
  213. package/package.json +1 -1
  214. package/dist/chunk-J4UX3RS3.js +0 -3717
  215. package/dist/studio/assets/hyperframes-player-sAsRuV73.js +0 -473
  216. package/dist/studio/chunk-LOVBLSMR.js.map +0 -1
  217. package/dist/studio/chunk-TFLU2MGI.js.map +0 -1
  218. package/dist/studio/chunk-UYWON5AX.js.map +0 -1
  219. package/dist/{chunk-HBBJFK6I.js → chunk-APDLWWFG.js} +0 -0
  220. package/dist/{screenshotCapture-TEUIWXWN.js → screenshotCapture-3JXXUZND.js} +3 -3
  221. /package/dist/studio/{chunk-H2DUOXTF.js.map → chunk-G3EDKRZH.js.map} +0 -0
  222. /package/dist/studio/{domEditingLayers-KBTPQKGA.js.map → domEditingLayers-PYST4G45.js.map} +0 -0
@@ -26,7 +26,7 @@ import {
26
26
  renderFrameIdForRenderId,
27
27
  resolveAuthoredTimingWindow,
28
28
  toFps
29
- } from "./chunk-YFT3BL7N.js";
29
+ } from "./chunk-3VYNNWVA.js";
30
30
  import {
31
31
  AUDIO_GROUP_RENDER_ID_ATTR,
32
32
  COLOR_GRADING_SOURCE_HIDDEN_ATTR,
@@ -55,7 +55,7 @@ import {
55
55
  shiftRateLane,
56
56
  sourceTimeAt,
57
57
  timeAtSourceTime
58
- } from "./chunk-J4UX3RS3.js";
58
+ } from "./chunk-WJB7WI4J.js";
59
59
  import {
60
60
  clampAudioGain,
61
61
  isAudibleVideoElement
@@ -70,7 +70,7 @@ import {
70
70
  import {
71
71
  isWithinProjectRoot,
72
72
  resolveProjectRelativeSrc
73
- } from "./chunk-JFKL3WAE.js";
73
+ } from "./chunk-IWFLMU2N.js";
74
74
  import {
75
75
  FFMPEG_PATH_ENV,
76
76
  FFPROBE_PATH_ENV,
@@ -79,10 +79,10 @@ import {
79
79
  import {
80
80
  parseNumeric,
81
81
  parseStartExpression
82
- } from "./chunk-TQFG7HTI.js";
82
+ } from "./chunk-3RM2ETQT.js";
83
83
  import {
84
84
  parseHTML
85
- } from "./chunk-GK4AIFIH.js";
85
+ } from "./chunk-SDYDNHEE.js";
86
86
 
87
87
  // ../engine/src/config.ts
88
88
  import { tmpdir } from "os";
@@ -553,9 +553,97 @@ function applyConcreteGpuScreenshotClamp(currentForceScreenshot, resolvedGpuMode
553
553
  });
554
554
  }
555
555
 
556
+ // ../engine/src/utils/sdrCaptureColor.ts
557
+ var SDR_RGB_TO_BT709_FILTER = "scale=out_color_matrix=bt709:out_range=tv:flags=neighbor";
558
+ var SDR_CAPTURE_TO_BT709_FILTER = `scale=in_color_matrix=bt601:in_range=pc:flags=neighbor,format=gbrp,${SDR_RGB_TO_BT709_FILTER}`;
559
+
560
+ // ../parsers/dist/htmlAttributeSpans.js
561
+ function readAttributeValue(html, at) {
562
+ while (/[\t\n\f\r ]/.test(html.charAt(at))) at++;
563
+ const quote = ['"', "'"].includes(html.charAt(at)) ? html.charAt(at) : "";
564
+ if (quote) at++;
565
+ const valueStart = at;
566
+ if (quote) {
567
+ const closing = html.indexOf(quote, at);
568
+ at = closing === -1 ? html.length : closing;
569
+ } else {
570
+ while (at < html.length && !/[\t\n\f\r >]/.test(html[at])) at++;
571
+ }
572
+ const valueEnd = at;
573
+ const end = quote ? Math.min(valueEnd + 1, html.length) : valueEnd;
574
+ return { value: html.slice(valueStart, valueEnd), valueStart, valueEnd, quote, end };
575
+ }
576
+ function readAttribute(html, start) {
577
+ let at = start;
578
+ while (at < html.length && !/[\t\n\f\r =/>]/.test(html[at])) at++;
579
+ if (at === start) return null;
580
+ const name = html.slice(start, at).toLowerCase();
581
+ const nameEnd = at;
582
+ while (/[\t\n\f\r ]/.test(html[at] ?? "")) at++;
583
+ if (html[at] !== "=") return { kind: "boolean", name, start, end: nameEnd };
584
+ return { kind: "value", name, start, ...readAttributeValue(html, at + 1) };
585
+ }
586
+ function readOpeningTag(html, start, name, at) {
587
+ const attributes = [];
588
+ while (at < html.length) {
589
+ while (/[\t\n\f\r /]/.test(html[at] ?? "")) at++;
590
+ if (at >= html.length || html[at] === ">") break;
591
+ const attribute = readAttribute(html, at);
592
+ if (attribute === null) {
593
+ at++;
594
+ continue;
595
+ }
596
+ attributes.push(attribute);
597
+ at = attribute.end;
598
+ }
599
+ const closed = html[at] === ">";
600
+ return {
601
+ name: name.toLowerCase(),
602
+ start,
603
+ bodyEnd: at,
604
+ end: closed ? at + 1 : at,
605
+ closed,
606
+ rawTextEnd: null,
607
+ attributes
608
+ };
609
+ }
610
+ function scanHtmlOpeningTags(html) {
611
+ const tags = [];
612
+ const opening = /<!--[\s\S]*?(?:--!?>|$)|<([a-z][^\t\n\f\r />]*)/gi;
613
+ let match;
614
+ while ((match = opening.exec(html)) !== null) {
615
+ if (!match[1]) continue;
616
+ const tag = readOpeningTag(html, match.index, match[1], opening.lastIndex);
617
+ tags.push(tag);
618
+ opening.lastIndex = tag.end;
619
+ if ([
620
+ "script",
621
+ "style",
622
+ "textarea",
623
+ "title",
624
+ "xmp",
625
+ "iframe",
626
+ "noembed",
627
+ "noframes",
628
+ "plaintext"
629
+ ].includes(tag.name)) {
630
+ if (tag.name === "plaintext") {
631
+ tag.rawTextEnd = html.length;
632
+ break;
633
+ }
634
+ const closing = new RegExp(`</${tag.name}(?=[\\t\\n\\f\\r />])`, "gi");
635
+ closing.lastIndex = tag.end;
636
+ const end = closing.exec(html);
637
+ tag.rawTextEnd = end?.index ?? html.length;
638
+ opening.lastIndex = tag.rawTextEnd;
639
+ }
640
+ }
641
+ return tags;
642
+ }
643
+
556
644
  // ../engine/src/services/browserManager.ts
557
645
  import { execSync } from "child_process";
558
- import { existsSync, readdirSync } from "fs";
646
+ import { existsSync, readdirSync, statSync } from "fs";
559
647
  import { join as join2 } from "path";
560
648
  import { homedir } from "os";
561
649
 
@@ -797,6 +885,7 @@ async function getPuppeteer() {
797
885
  if (!_puppeteer) throw new Error("Neither puppeteer nor puppeteer-core found");
798
886
  return _puppeteer;
799
887
  }
888
+ var GPU_PROBE_TIMEOUT_MS = 15e3;
800
889
  async function probeHardwareWebGlInfo(ppt, options) {
801
890
  let probeBrowser;
802
891
  try {
@@ -806,37 +895,44 @@ async function probeHardwareWebGlInfo(ppt, options) {
806
895
  args: options.args,
807
896
  defaultViewport: { width: 64, height: 64 },
808
897
  executablePath: options.executablePath,
809
- timeout: options.browserTimeout
810
- });
811
- const page = await probeBrowser.newPage();
812
- return await page.evaluate(() => {
813
- const unavailable = { hasWebGL: false, vendor: "", renderer: "" };
814
- const c = document.createElement("canvas");
815
- let gl = c.getContext("webgl");
816
- if (gl === null) {
817
- gl = c.getContext("experimental-webgl");
818
- }
819
- if (gl === null) return unavailable;
820
- const ext = gl.getExtension("WEBGL_debug_renderer_info");
821
- let vendorParam = gl.VENDOR;
822
- let rendererParam = gl.RENDERER;
823
- if (ext !== null) {
824
- vendorParam = ext.UNMASKED_VENDOR_WEBGL;
825
- rendererParam = ext.UNMASKED_RENDERER_WEBGL;
826
- }
827
- const vendor = gl.getParameter(vendorParam);
828
- const renderer = gl.getParameter(rendererParam);
829
- return {
830
- hasWebGL: true,
831
- vendor: vendor == null ? "" : String(vendor),
832
- renderer: renderer == null ? "" : String(renderer)
833
- };
898
+ timeout: options.browserTimeout,
899
+ waitForInitialPage: false
834
900
  });
901
+ return await rejectAfter(
902
+ readWebGlInfo(probeBrowser),
903
+ GPU_PROBE_TIMEOUT_MS,
904
+ `GPU probe page did not answer within ${GPU_PROBE_TIMEOUT_MS}ms`
905
+ );
835
906
  } finally {
836
- await probeBrowser?.close().catch(() => {
837
- });
907
+ if (probeBrowser) await closeBrowserAfterFailedProbe(probeBrowser);
838
908
  }
839
909
  }
910
+ async function readWebGlInfo(browser) {
911
+ const page = await browser.newPage();
912
+ return await page.evaluate(() => {
913
+ const unavailable = { hasWebGL: false, vendor: "", renderer: "" };
914
+ const c = document.createElement("canvas");
915
+ let gl = c.getContext("webgl");
916
+ if (gl === null) {
917
+ gl = c.getContext("experimental-webgl");
918
+ }
919
+ if (gl === null) return unavailable;
920
+ const ext = gl.getExtension("WEBGL_debug_renderer_info");
921
+ let vendorParam = gl.VENDOR;
922
+ let rendererParam = gl.RENDERER;
923
+ if (ext !== null) {
924
+ vendorParam = ext.UNMASKED_VENDOR_WEBGL;
925
+ rendererParam = ext.UNMASKED_RENDERER_WEBGL;
926
+ }
927
+ const vendor = gl.getParameter(vendorParam);
928
+ const renderer = gl.getParameter(rendererParam);
929
+ return {
930
+ hasWebGL: true,
931
+ vendor: vendor == null ? "" : String(vendor),
932
+ renderer: renderer == null ? "" : String(renderer)
933
+ };
934
+ });
935
+ }
840
936
  function compareBrowserVersionsDescending(left, right) {
841
937
  const parse = (value) => {
842
938
  const version = value.slice(value.indexOf("-") + 1);
@@ -867,6 +963,14 @@ var CACHED_HEADLESS_SHELL_EXECUTABLES = {
867
963
  function cachedHeadlessShellExecutable(hostPlatform = process.platform, hostArch = process.arch) {
868
964
  return CACHED_HEADLESS_SHELL_EXECUTABLES[`${hostPlatform}/${hostArch}`];
869
965
  }
966
+ function isNonEmptyFile(path) {
967
+ try {
968
+ const stat = statSync(path, { throwIfNoEntry: false });
969
+ return stat?.isFile() === true && stat.size > 0;
970
+ } catch {
971
+ return false;
972
+ }
973
+ }
870
974
  function findCachedHeadlessShell(baseDir) {
871
975
  if (!existsSync(baseDir)) return void 0;
872
976
  const executable = cachedHeadlessShellExecutable();
@@ -877,7 +981,7 @@ function findCachedHeadlessShell(baseDir) {
877
981
  for (const version of versions) {
878
982
  if (exceedsChromeCeiling(version, ceiling)) continue;
879
983
  const binary = join2(baseDir, version, ...executable);
880
- if (existsSync(binary)) return binary;
984
+ if (isNonEmptyFile(binary)) return binary;
881
985
  }
882
986
  } catch {
883
987
  }
@@ -933,15 +1037,15 @@ async function awaitBeforeDeadline(operation, deadline, label) {
933
1037
  if (remainingMs <= 0) {
934
1038
  throw new Error(`beginFrame probe timeout before ${label}`);
935
1039
  }
1040
+ return rejectAfter(operation, remainingMs, `beginFrame probe timeout during ${label}`);
1041
+ }
1042
+ async function rejectAfter(operation, timeoutMs, message) {
936
1043
  let timeout;
937
1044
  try {
938
1045
  return await Promise.race([
939
1046
  operation,
940
1047
  new Promise((_, reject) => {
941
- timeout = setTimeout(
942
- () => reject(new Error(`beginFrame probe timeout during ${label}`)),
943
- remainingMs
944
- );
1048
+ timeout = setTimeout(() => reject(new Error(message)), timeoutMs);
945
1049
  })
946
1050
  ]);
947
1051
  } finally {
@@ -1355,8 +1459,9 @@ function buildChromeArgs(options, config) {
1355
1459
  return chromeArgs;
1356
1460
  }
1357
1461
  function compositionRequiresWebGpu(html) {
1358
- for (const [tag] of html.matchAll(/<(?:[^<>"']|"[^"]*"|'[^']*')*>/g)) {
1359
- if (/\bdata-composition-id\b/i.test(tag)) return /\bdata-requires-webgpu(?:\s|=|>)/i.test(tag);
1462
+ for (const tag of scanHtmlOpeningTags(html)) {
1463
+ const names = tag.attributes.map((attribute) => attribute.name);
1464
+ if (names.includes("data-composition-id")) return names.includes("data-requires-webgpu");
1360
1465
  }
1361
1466
  return false;
1362
1467
  }
@@ -1959,7 +2064,7 @@ function parseObjectPositionAxis(value, axis) {
1959
2064
  if (lower === "center" || lower === "") return 0.5;
1960
2065
  if (lower.endsWith("%")) {
1961
2066
  const pct = parseFloat(lower) / 100;
1962
- return Number.isFinite(pct) ? Math.max(0, Math.min(1, pct)) : 0.5;
2067
+ return Number.isFinite(pct) ? pct : 0.5;
1963
2068
  }
1964
2069
  if (axis === "x" || axis === "y") return 0.5;
1965
2070
  return 0.5;
@@ -5166,6 +5271,7 @@ async function constructCaptureSession(input) {
5166
5271
  browserConsoleBuffer: [],
5167
5272
  scriptLoadFailures: [],
5168
5273
  pageErrors: [],
5274
+ subTimelineWaitMemo: sessionOptions.subTimelineWaitMemo ?? {},
5169
5275
  warnings: [],
5170
5276
  capturePerf: {
5171
5277
  frames: 0,
@@ -5314,15 +5420,52 @@ async function pollHfReady(page, timeoutMs, intervalMs = 100) {
5314
5420
  State: __hf=${diag.hasHf}, seek=${diag.hasSeek}, player=${diag.hasPlayer}, renderReady=${diag.renderReady}, duration=${diag.duration}`
5315
5421
  );
5316
5422
  }
5317
- async function pollSubCompositionTimelines(page, timeoutMs, intervalMs = 150, getScriptLoadFailures, scriptFailureGraceMs = 2e3, onPending, shouldStop) {
5423
+ async function listUnregisteredTimelineIds(page) {
5424
+ const evaluated = await page.evaluate(`(function() {
5425
+ var hosts = document.querySelectorAll("[data-composition-id]");
5426
+ var timelines = window.__timelines || {};
5427
+ var m = [];
5428
+ for (var i = 0; i < hosts.length; i++) {
5429
+ if (hosts[i].hasAttribute("data-no-timeline")) continue;
5430
+ var id = hosts[i].getAttribute("data-composition-id");
5431
+ if (id && !timelines[id]) m.push(id);
5432
+ }
5433
+ return m;
5434
+ })()`);
5435
+ return Array.isArray(evaluated) ? evaluated.map((id) => String(id)) : [];
5436
+ }
5437
+ async function waitForSubCompositionTimelines(session, page, timeoutMs) {
5438
+ const memo = session.subTimelineWaitMemo;
5439
+ session.subTimelineWaitOutcome = await pollSubCompositionTimelines(page, timeoutMs, {
5440
+ getScriptLoadFailures: () => session.scriptLoadFailures,
5441
+ onPending: (ids) => {
5442
+ session.pendingTimelineIds = [...ids];
5443
+ },
5444
+ shouldStop: () => session.vfxFailure !== void 0,
5445
+ knownUnregisteredIds: memo.unregisteredIds
5446
+ });
5447
+ const cleanTimeout = session.subTimelineWaitOutcome === "timeout" && session.vfxFailure === void 0 && session.pageErrors.length === 0 && session.scriptLoadFailures.length === 0;
5448
+ if (cleanTimeout) {
5449
+ memo.unregisteredIds = session.pendingTimelineIds ?? [];
5450
+ }
5451
+ }
5452
+ async function pollSubCompositionTimelines(page, timeoutMs, {
5453
+ intervalMs = 150,
5454
+ getScriptLoadFailures,
5455
+ scriptFailureGraceMs = 2e3,
5456
+ onPending,
5457
+ shouldStop,
5458
+ knownUnregisteredIds = []
5459
+ } = {}) {
5318
5460
  const expression = `(function() {
5319
5461
  var hosts = document.querySelectorAll("[data-composition-id]");
5320
5462
  if (hosts.length === 0) return true;
5321
5463
  var timelines = window.__timelines || {};
5464
+ var known = ${JSON.stringify(knownUnregisteredIds)};
5322
5465
  for (var i = 0; i < hosts.length; i++) {
5323
5466
  if (hosts[i].hasAttribute("data-no-timeline")) continue;
5324
5467
  var id = hosts[i].getAttribute("data-composition-id");
5325
- if (!id) continue;
5468
+ if (!id || known.indexOf(id) !== -1) continue;
5326
5469
  if (!timelines[id]) return false;
5327
5470
  }
5328
5471
  return true;
@@ -5346,7 +5489,11 @@ async function pollSubCompositionTimelines(page, timeoutMs, intervalMs = 150, ge
5346
5489
  }
5347
5490
  await new Promise((resolve) => setTimeout(resolve, intervalMs));
5348
5491
  }
5349
- if (ready) {
5492
+ const pendingIds = ready && knownUnregisteredIds.length === 0 ? [] : await listUnregisteredTimelineIds(page);
5493
+ if (ready && pendingIds.length > 0 && (getScriptLoadFailures?.() ?? []).length > 0) {
5494
+ scriptFailureBail = true;
5495
+ }
5496
+ if (ready && pendingIds.length === 0) {
5350
5497
  await page.evaluate(`(function() {
5351
5498
  if (typeof window.__hfForceTimelineRebind === "function") {
5352
5499
  window.__hfForceTimelineRebind();
@@ -5354,18 +5501,6 @@ async function pollSubCompositionTimelines(page, timeoutMs, intervalMs = 150, ge
5354
5501
  })()`);
5355
5502
  return "ready";
5356
5503
  }
5357
- const evaluated = await page.evaluate(`(function() {
5358
- var hosts = document.querySelectorAll("[data-composition-id]");
5359
- var timelines = window.__timelines || {};
5360
- var m = [];
5361
- for (var i = 0; i < hosts.length; i++) {
5362
- if (hosts[i].hasAttribute("data-no-timeline")) continue;
5363
- var id = hosts[i].getAttribute("data-composition-id");
5364
- if (id && !timelines[id]) m.push(id);
5365
- }
5366
- return m;
5367
- })()`);
5368
- const pendingIds = Array.isArray(evaluated) ? evaluated.map((id) => String(id)) : [];
5369
5504
  onPending?.(pendingIds);
5370
5505
  const missing = pendingIds.join(", ");
5371
5506
  if (scriptFailureBail) {
@@ -5735,17 +5870,7 @@ async function initializeSession(session) {
5735
5870
  const pageReadyTimeout = session.config?.playerReadyTimeout ?? DEFAULT_CONFIG.playerReadyTimeout;
5736
5871
  await pollHfReady(page, pageReadyTimeout);
5737
5872
  logInitPhase("pollHfReady complete");
5738
- session.subTimelineWaitOutcome = await pollSubCompositionTimelines(
5739
- page,
5740
- pageReadyTimeout,
5741
- void 0,
5742
- () => session.scriptLoadFailures,
5743
- void 0,
5744
- (ids) => {
5745
- session.pendingTimelineIds = [...ids];
5746
- },
5747
- () => session.vfxFailure !== void 0
5748
- );
5873
+ await waitForSubCompositionTimelines(session, page, pageReadyTimeout);
5749
5874
  logInitPhase(`pollSubCompositionTimelines complete (${session.subTimelineWaitOutcome})`);
5750
5875
  assertVfxIntact(session);
5751
5876
  recordSubTimelineWarning(session, pageReadyTimeout);
@@ -5851,17 +5976,7 @@ async function initializeSession(session) {
5851
5976
  const pageReadyTimeout = session.config?.playerReadyTimeout ?? DEFAULT_CONFIG.playerReadyTimeout;
5852
5977
  await pollHfReady(page, pageReadyTimeout);
5853
5978
  logInitPhase("pollHfReady complete");
5854
- session.subTimelineWaitOutcome = await pollSubCompositionTimelines(
5855
- page,
5856
- pageReadyTimeout,
5857
- void 0,
5858
- () => session.scriptLoadFailures,
5859
- void 0,
5860
- (ids) => {
5861
- session.pendingTimelineIds = [...ids];
5862
- },
5863
- () => session.vfxFailure !== void 0
5864
- );
5979
+ await waitForSubCompositionTimelines(session, page, pageReadyTimeout);
5865
5980
  logInitPhase(`pollSubCompositionTimelines complete (${session.subTimelineWaitOutcome})`);
5866
5981
  assertVfxIntact(session);
5867
5982
  recordSubTimelineWarning(session, pageReadyTimeout);
@@ -6298,7 +6413,10 @@ async function createStaticVerificationPage(session) {
6298
6413
  });
6299
6414
  await page.evaluate(`window.__hfFlushSync?.()`);
6300
6415
  await pollHfReady(page, pageReadyTimeout);
6301
- await pollSubCompositionTimelines(page, pageReadyTimeout);
6416
+ await pollSubCompositionTimelines(page, pageReadyTimeout, {
6417
+ getScriptLoadFailures: () => session.scriptLoadFailures,
6418
+ knownUnregisteredIds: session.subTimelineWaitMemo.unregisteredIds
6419
+ });
6302
6420
  await applyVideoMetadataHints(page, session.options.videoMetadataHints);
6303
6421
  const skipVideoIds = session.options.skipReadinessVideoIds ?? [];
6304
6422
  await Promise.all([
@@ -7006,7 +7124,7 @@ async function captureFramesBatchPipelined(session, frameIndices, times) {
7006
7124
  return results;
7007
7125
  }
7008
7126
  async function discardWarmupCapture(session, frameIndex = 0, time = 0, innerCapture = captureFrameCore) {
7009
- const perfBefore = { ...session.capturePerf };
7127
+ const perfBefore = { ...session.capturePerf, frameMs: [...session.capturePerf.frameMs] };
7010
7128
  const hasDamageBefore = session.beginFrameHasDamageCount;
7011
7129
  const noDamageBefore = session.beginFrameNoDamageCount;
7012
7130
  const dedupCountBefore = session.staticDedupCount;
@@ -7227,7 +7345,7 @@ import {
7227
7345
  openSync,
7228
7346
  readFileSync as readFileSync3,
7229
7347
  readdirSync as readdirSync2,
7230
- statSync as statSync2,
7348
+ statSync as statSync3,
7231
7349
  writeFileSync as writeFileSync2,
7232
7350
  writeSync
7233
7351
  } from "fs";
@@ -7421,7 +7539,7 @@ var H264_ENCODER_BY_GPU = {
7421
7539
  qsv: "h264_qsv",
7422
7540
  amf: "h264_amf"
7423
7541
  };
7424
- var GPU_PROBE_TIMEOUT_MS = 2e3;
7542
+ var GPU_PROBE_TIMEOUT_MS2 = 2e3;
7425
7543
  var GPU_PROBE_KILL_GRACE_MS = 1e3;
7426
7544
  function getCompiledGpuEncoders(ffmpegEncodersStdout) {
7427
7545
  return GPU_ENCODER_CANDIDATES.filter(
@@ -7538,7 +7656,7 @@ async function canUseGpuEncoder(encoder) {
7538
7656
  });
7539
7657
  trackChildProcess(ffmpeg);
7540
7658
  const outcome = await new ManagedChildProcess(ffmpeg, {
7541
- deadlineAtMs: Date.now() + GPU_PROBE_TIMEOUT_MS,
7659
+ deadlineAtMs: Date.now() + GPU_PROBE_TIMEOUT_MS2,
7542
7660
  terminationGraceMs: GPU_PROBE_KILL_GRACE_MS
7543
7661
  }).wait();
7544
7662
  const usable = outcome.reason === "exit" && outcome.exitCode === 0;
@@ -7554,7 +7672,7 @@ async function canUseGpuEncoder(encoder) {
7554
7672
  function logGpuProbeFailure(encoder, result) {
7555
7673
  if (!isGpuProbeDebugEnabled()) return;
7556
7674
  if (result.code === 0 && !result.error && !result.timedOut) return;
7557
- const reason = result.error ? result.error.message : result.timedOut ? `timed out after ${GPU_PROBE_TIMEOUT_MS}ms` : `exit=${String(result.code)} signal=${String(result.signal ?? "")}`;
7675
+ const reason = result.error ? result.error.message : result.timedOut ? `timed out after ${GPU_PROBE_TIMEOUT_MS2}ms` : `exit=${String(result.code)} signal=${String(result.signal ?? "")}`;
7558
7676
  const stderr = result.stderr?.trim();
7559
7677
  console.warn(`[gpuEncoder] ${encoder} probe failed: ${reason}${stderr ? `
7560
7678
  ${stderr}` : ""}`);
@@ -7661,10 +7779,6 @@ function withEvenDimensionPad(vfChain, pixelFormat, width, height) {
7661
7779
  return vfChain ? `${vfChain},${EVEN_DIMENSION_PAD}` : EVEN_DIMENSION_PAD;
7662
7780
  }
7663
7781
 
7664
- // ../engine/src/utils/sdrCaptureColor.ts
7665
- var SDR_RGB_TO_BT709_FILTER = "scale=out_color_matrix=bt709:out_range=tv:flags=neighbor";
7666
- var SDR_CAPTURE_TO_BT709_FILTER = `scale=in_color_matrix=bt601:in_range=pc:flags=neighbor,format=gbrp,${SDR_RGB_TO_BT709_FILTER}`;
7667
-
7668
7782
  // ../engine/src/utils/runFfmpeg.ts
7669
7783
  import { spawn as spawn2 } from "child_process";
7670
7784
  var FFMPEG_SIGTERM_EXIT_LINE = /^Exiting normally, received signal 15\.?\r?$/m;
@@ -7783,7 +7897,7 @@ async function runFfmpegPipeline(producerArgs, consumerArgs, opts) {
7783
7897
 
7784
7898
  // ../engine/src/utils/ffprobe.ts
7785
7899
  import { spawn as spawn3 } from "child_process";
7786
- import { createReadStream, readFileSync, statSync } from "fs";
7900
+ import { createReadStream, readFileSync, statSync as statSync2 } from "fs";
7787
7901
  import { StringDecoder } from "string_decoder";
7788
7902
  import { basename } from "path";
7789
7903
  var FFPROBE_STDERR_MAX_BYTES = 8 * 1024;
@@ -7886,7 +8000,8 @@ var audioMetadataCache = /* @__PURE__ */ new Map();
7886
8000
  var mediaProbeOutputCache = /* @__PURE__ */ new Map();
7887
8001
  var mediaProbeOutputSignalCaches = /* @__PURE__ */ new WeakMap();
7888
8002
  var MEDIA_PROBE_OUTPUT_CACHE_MAX_ENTRIES = 128;
7889
- var AAC_LC_SAMPLES_PER_PACKET = 1024;
8003
+ var AUDIO_DURATION_PROBE_MARGIN_SECONDS = 0.05;
8004
+ var AUDIO_DURATION_PROBE_TAIL_SECONDS = 1;
7890
8005
  var STILL_IMAGE_DEMUXERS = /* @__PURE__ */ new Set([
7891
8006
  "apng",
7892
8007
  "bmp_pipe",
@@ -7938,12 +8053,28 @@ function isPngImageProbe(output) {
7938
8053
  }
7939
8054
  function mediaFileIdentity(filePath) {
7940
8055
  try {
7941
- const stat = statSync(filePath, { bigint: true });
8056
+ const stat = statSync2(filePath, { bigint: true });
7942
8057
  return [stat.dev, stat.ino, stat.size, stat.mtimeNs, stat.ctimeNs].join(":");
7943
8058
  } catch {
7944
8059
  return null;
7945
8060
  }
7946
8061
  }
8062
+ function readMetadataCache(cache, key, identity) {
8063
+ const cached = cache.get(key);
8064
+ return cached?.identity === identity ? cached.promise : void 0;
8065
+ }
8066
+ function writeMetadataCache(cache, key, identity, promise) {
8067
+ const entry = { identity, promise };
8068
+ cache.set(key, entry);
8069
+ void promise.then(
8070
+ () => {
8071
+ if (identity === null && cache.get(key) === entry) cache.delete(key);
8072
+ },
8073
+ () => {
8074
+ if (cache.get(key) === entry) cache.delete(key);
8075
+ }
8076
+ );
8077
+ }
7947
8078
  async function probeMediaOutput(filePath, signal) {
7948
8079
  let cache = mediaProbeOutputCache;
7949
8080
  if (signal) {
@@ -7995,7 +8126,7 @@ async function readFileRange(filePath, start, length, signal) {
7995
8126
  async function hasCompletePngStructure(filePath, signal) {
7996
8127
  try {
7997
8128
  signal?.throwIfAborted();
7998
- const fileSize = statSync(filePath).size;
8129
+ const fileSize = statSync2(filePath).size;
7999
8130
  const signature = await readFileRange(filePath, 0, 8, signal);
8000
8131
  signal?.throwIfAborted();
8001
8132
  if (signature.length !== 8 || !signature.equals(Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]))) {
@@ -8126,7 +8257,8 @@ function parseFrameRate(frameRateStr) {
8126
8257
  return rounded > 0 ? rounded : 0.01;
8127
8258
  }
8128
8259
  async function extractMediaMetadata(filePath) {
8129
- const cached = videoMetadataCache.get(filePath);
8260
+ const identity = mediaFileIdentity(filePath);
8261
+ const cached = readMetadataCache(videoMetadataCache, filePath, identity);
8130
8262
  if (cached) return cached;
8131
8263
  const probePromise = (async () => {
8132
8264
  let stillImageMetaMemo;
@@ -8210,12 +8342,7 @@ async function extractMediaMetadata(filePath) {
8210
8342
  frames
8211
8343
  };
8212
8344
  })();
8213
- videoMetadataCache.set(filePath, probePromise);
8214
- probePromise.catch(() => {
8215
- if (videoMetadataCache.get(filePath) === probePromise) {
8216
- videoMetadataCache.delete(filePath);
8217
- }
8218
- });
8345
+ writeMetadataCache(videoMetadataCache, filePath, identity, probePromise);
8219
8346
  return probePromise;
8220
8347
  }
8221
8348
  function resolveStreamWindow(filePath, metadata) {
@@ -8231,12 +8358,13 @@ async function extractFinalVideoFrameTimestamp(filePath, metadata, signal) {
8231
8358
  videoStreamDurationSeconds: videoDurationSeconds,
8232
8359
  cacheKey
8233
8360
  } = resolveStreamWindow(filePath, metadata);
8361
+ const identity = mediaFileIdentity(filePath);
8234
8362
  let probeCache = finalVideoFrameTimestampCache;
8235
8363
  if (signal) {
8236
8364
  probeCache = finalVideoFrameTimestampSignalCaches.get(signal) ?? /* @__PURE__ */ new Map();
8237
8365
  finalVideoFrameTimestampSignalCaches.set(signal, probeCache);
8238
8366
  }
8239
- const cached = probeCache.get(cacheKey);
8367
+ const cached = readMetadataCache(probeCache, cacheKey, identity);
8240
8368
  if (cached) return cached;
8241
8369
  const probePromise = (async () => {
8242
8370
  if (!(videoDurationSeconds > 0) || !Number.isFinite(videoDurationSeconds)) {
@@ -8271,17 +8399,13 @@ async function extractFinalVideoFrameTimestamp(filePath, metadata, signal) {
8271
8399
  }
8272
8400
  return Math.min(Math.max(timestamp - videoStreamStartSeconds, 0), videoDurationSeconds);
8273
8401
  })();
8274
- probeCache.set(cacheKey, probePromise);
8275
- probePromise.catch(() => {
8276
- if (probeCache.get(cacheKey) === probePromise) {
8277
- probeCache.delete(cacheKey);
8278
- }
8279
- });
8402
+ writeMetadataCache(probeCache, cacheKey, identity, probePromise);
8280
8403
  return probePromise;
8281
8404
  }
8282
8405
  var extractVideoMetadata = extractMediaMetadata;
8283
8406
  async function extractAudioMetadata(filePath, options) {
8284
- const cached = options?.signal ? void 0 : audioMetadataCache.get(filePath);
8407
+ const identity = mediaFileIdentity(filePath);
8408
+ const cached = options?.signal ? void 0 : readMetadataCache(audioMetadataCache, filePath, identity);
8285
8409
  if (cached) return cached;
8286
8410
  const probePromise = (async () => {
8287
8411
  const output = await probeMediaOutput(filePath, options?.signal);
@@ -8291,29 +8415,16 @@ async function extractAudioMetadata(filePath, options) {
8291
8415
  const streamDuration = audioStream.duration ? parseFloat(audioStream.duration) : void 0;
8292
8416
  const sampleRate = audioStream.sample_rate ? parseInt(audioStream.sample_rate) : 44100;
8293
8417
  const audioCodec = audioStream.codec_name || "unknown";
8294
- const isAacLc = /^\s*LC\s*$/i.test(audioStream.profile ?? "");
8295
- if (audioCodec === "aac" && isAacLc && sampleRate > 0) {
8296
- try {
8297
- const packetStdout = await runFfprobe(
8298
- filePath,
8299
- [
8300
- "-select_streams",
8301
- "a:0",
8302
- "-count_packets",
8303
- "-show_entries",
8304
- "stream=nb_read_packets",
8305
- "-print_format",
8306
- "json"
8307
- ],
8308
- options?.signal
8309
- );
8310
- const packetOutput = parseProbeJson(packetStdout);
8311
- const packetCount = Number(packetOutput.streams[0]?.nb_read_packets);
8312
- if (Number.isFinite(packetCount) && packetCount > 0) {
8313
- durationSeconds = packetCount * AAC_LC_SAMPLES_PER_PACKET / sampleRate;
8314
- }
8315
- } catch (error) {
8316
- if (options?.signal?.aborted) throw error;
8418
+ if (sampleRate > 0) {
8419
+ const decoded = await probeDecodedAudioDuration(
8420
+ filePath,
8421
+ sampleRate,
8422
+ audioStartSeconds(audioStream, output.format),
8423
+ durationSeconds,
8424
+ options?.signal
8425
+ );
8426
+ if (decoded !== null) {
8427
+ durationSeconds = reconcileAudioDuration(durationSeconds, decoded, output.format);
8317
8428
  }
8318
8429
  }
8319
8430
  return {
@@ -8326,14 +8437,58 @@ async function extractAudioMetadata(filePath, options) {
8326
8437
  };
8327
8438
  })();
8328
8439
  if (options?.signal) return probePromise;
8329
- audioMetadataCache.set(filePath, probePromise);
8330
- probePromise.catch(() => {
8331
- if (audioMetadataCache.get(filePath) === probePromise) {
8332
- audioMetadataCache.delete(filePath);
8333
- }
8334
- });
8440
+ writeMetadataCache(audioMetadataCache, filePath, identity, probePromise);
8335
8441
  return probePromise;
8336
8442
  }
8443
+ function audioStartSeconds(stream, format) {
8444
+ const start = parseFloat(stream.start_time ?? format.start_time ?? "");
8445
+ return Number.isFinite(start) ? start : 0;
8446
+ }
8447
+ function reconcileAudioDuration(containerSeconds, decodedSeconds, format) {
8448
+ const difference = decodedSeconds - containerSeconds;
8449
+ const trustShorter = format.format_name === "aac";
8450
+ if (difference > AUDIO_DURATION_PROBE_MARGIN_SECONDS) return decodedSeconds;
8451
+ if (trustShorter && difference < -AUDIO_DURATION_PROBE_MARGIN_SECONDS) return decodedSeconds;
8452
+ return containerSeconds;
8453
+ }
8454
+ async function probeDecodedAudioDuration(filePath, sampleRate, startSeconds, containerDurationSeconds, signal) {
8455
+ const probe = async (readInterval) => {
8456
+ const args = [
8457
+ "-select_streams",
8458
+ "a:0",
8459
+ "-show_entries",
8460
+ "frame=best_effort_timestamp_time,nb_samples",
8461
+ "-of",
8462
+ "csv=p=0"
8463
+ ];
8464
+ if (readInterval) args.unshift("-read_intervals", readInterval);
8465
+ try {
8466
+ const stdout = await runFfprobe(
8467
+ filePath,
8468
+ args,
8469
+ signal,
8470
+ // CSV, not JSON: a tail truncated under the size cap still parses.
8471
+ { retainTail: true, maxChars: 64 * 1024 }
8472
+ );
8473
+ return stdout.split("\n").map((line) => {
8474
+ const [timestampText, samplesText] = line.trim().split(",");
8475
+ const timestamp = Number(timestampText);
8476
+ const nbSamples = Number(samplesText);
8477
+ return Number.isFinite(timestamp) && Number.isFinite(nbSamples) ? { timestamp, nbSamples } : void 0;
8478
+ }).filter((frame) => frame !== void 0).at(-1);
8479
+ } catch (error) {
8480
+ if (signal?.aborted) throw signal.reason ?? error;
8481
+ return void 0;
8482
+ }
8483
+ };
8484
+ const tailStart = Math.max(
8485
+ startSeconds,
8486
+ startSeconds + containerDurationSeconds - AUDIO_DURATION_PROBE_TAIL_SECONDS
8487
+ );
8488
+ const lastFrame = await probe(`${tailStart}%`) ?? await probe();
8489
+ if (!lastFrame) return null;
8490
+ return lastFrame.timestamp + lastFrame.nbSamples / sampleRate - startSeconds;
8491
+ }
8337
8492
  var keyframeCache = /* @__PURE__ */ new Map();
8338
8493
  var PROBLEMATIC_KEYFRAME_INTERVAL_SECONDS = 2;
8339
8494
  async function analyzeKeyframeIntervals(filePath, metadata) {
@@ -8781,7 +8936,7 @@ async function encodeFramesFromDir(framesDir, framePattern, outputPath, options,
8781
8936
  failureReason: isExternalFfmpegInterruption(result) ? "external_interruption" : void 0
8782
8937
  };
8783
8938
  }
8784
- const fileSize = existsSync4(outputPath) ? statSync2(outputPath).size : 0;
8939
+ const fileSize = existsSync4(outputPath) ? statSync3(outputPath).size : 0;
8785
8940
  return {
8786
8941
  success: true,
8787
8942
  outputPath,
@@ -8915,7 +9070,7 @@ async function encodeFramesChunkedConcat(framesDir, framePattern, outputPath, op
8915
9070
  failureReason: concatResult.externalInterruption ? "external_interruption" : void 0
8916
9071
  };
8917
9072
  }
8918
- const fileSize = existsSync4(outputPath) ? statSync2(outputPath).size : 0;
9073
+ const fileSize = existsSync4(outputPath) ? statSync3(outputPath).size : 0;
8919
9074
  return {
8920
9075
  success: true,
8921
9076
  outputPath,
@@ -9147,7 +9302,7 @@ function quantizationTableError(buffer, start, end) {
9147
9302
  // ../engine/src/services/streamingEncoder.ts
9148
9303
  import { spawn as spawn4 } from "child_process";
9149
9304
  import { once } from "events";
9150
- import { existsSync as existsSync5, mkdirSync as mkdirSync3, statSync as statSync3 } from "fs";
9305
+ import { existsSync as existsSync5, mkdirSync as mkdirSync3, statSync as statSync4 } from "fs";
9151
9306
  import { dirname as dirname3 } from "path";
9152
9307
  function createFrameReorderBuffer(startFrame, endFrame) {
9153
9308
  let cursor = startFrame;
@@ -9512,7 +9667,7 @@ FFmpeg stopped after ${streamingTimeout} ms without consuming a frame.` : "";
9512
9667
  }) ? "external_interruption" : void 0
9513
9668
  };
9514
9669
  }
9515
- const fileSize = existsSync5(outputPath) ? statSync3(outputPath).size : 0;
9670
+ const fileSize = existsSync5(outputPath) ? statSync4(outputPath).size : 0;
9516
9671
  return { success: true, durationMs, fileSize };
9517
9672
  },
9518
9673
  getExitStatus: () => exitStatus,
@@ -9608,6 +9763,7 @@ function isMemberGroupHidden(groupsById, el) {
9608
9763
  // ../engine/src/services/videoFrameExtractor.ts
9609
9764
  import { copyFileSync as copyFileSync2, existsSync as existsSync8, linkSync as linkSync2, mkdirSync as mkdirSync6, rmSync as rmSync4 } from "fs";
9610
9765
  import { join as join10 } from "path";
9766
+ import { cpus } from "os";
9611
9767
 
9612
9768
  // ../engine/src/services/referenceResolver.ts
9613
9769
  function findReferenceTargetEl(doc, refId) {
@@ -9731,7 +9887,7 @@ import {
9731
9887
  readdirSync as readdirSync3,
9732
9888
  rmdirSync,
9733
9889
  rmSync as rmSync2,
9734
- statSync as statSync4,
9890
+ statSync as statSync5,
9735
9891
  unlinkSync
9736
9892
  } from "fs";
9737
9893
  import { createHash, randomUUID } from "crypto";
@@ -10341,7 +10497,7 @@ async function fetchToPartial(url, partialPath, controller, options) {
10341
10497
  const readableStream = Readable.fromWeb(response.body);
10342
10498
  const fileStream = createWriteStream(partialPath, { flags: "wx" });
10343
10499
  await pipeline(readableStream, inspector, fileStream, { signal: controller.signal });
10344
- const localSize = statSync4(partialPath).size;
10500
+ const localSize = statSync5(partialPath).size;
10345
10501
  const sha256Bytes = sha256.digest();
10346
10502
  const localSha256 = sha256Bytes.toString("hex");
10347
10503
  const md5Base64 = md5.digest("base64");
@@ -10755,7 +10911,7 @@ import {
10755
10911
  readdirSync as readdirSync5,
10756
10912
  renameSync,
10757
10913
  rmSync as rmSync3,
10758
- statSync as statSync5,
10914
+ statSync as statSync6,
10759
10915
  utimesSync,
10760
10916
  writeFileSync as writeFileSync3
10761
10917
  } from "fs";
@@ -10782,7 +10938,7 @@ function extractedFrameIndex(file, format) {
10782
10938
  }
10783
10939
  return ordinal - 1;
10784
10940
  }
10785
- function framePathsFromDirectory(outputDir, format) {
10941
+ function framePathsFromDirectory(outputDir, format, allowGaps = false) {
10786
10942
  const suffix = `.${format}`;
10787
10943
  const indexed = /* @__PURE__ */ new Map();
10788
10944
  for (const file of readdirSync4(outputDir)) {
@@ -10795,6 +10951,7 @@ function framePathsFromDirectory(outputDir, format) {
10795
10951
  }
10796
10952
  indexed.set(index, join8(outputDir, file));
10797
10953
  }
10954
+ if (allowGaps) return new Map([...indexed].sort(([a], [b]) => a - b));
10798
10955
  const ordered = /* @__PURE__ */ new Map();
10799
10956
  for (let index = 0; index < indexed.size; index += 1) {
10800
10957
  const path = indexed.get(index);
@@ -10815,7 +10972,7 @@ var SCHEMA_PREFIX = "hfcache-v6-";
10815
10972
  var KEY_HEX_CHARS = 16;
10816
10973
  function readKeyStat(videoPath) {
10817
10974
  try {
10818
- const stat = statSync5(videoPath);
10975
+ const stat = statSync6(videoPath);
10819
10976
  return { mtimeMs: Math.floor(stat.mtimeMs), size: stat.size };
10820
10977
  } catch {
10821
10978
  return null;
@@ -10949,7 +11106,7 @@ function removeDir(path) {
10949
11106
  }
10950
11107
  function collectGcEntry(dir, name, now, minAgeMs, stats, remove) {
10951
11108
  try {
10952
- const dirStat = statSync5(dir);
11109
+ const dirStat = statSync6(dir);
10953
11110
  if (isPartialChild(name) && now - dirStat.mtimeMs >= minAgeMs) {
10954
11111
  remove(dir);
10955
11112
  stats.agedPartialsRemoved += 1;
@@ -10957,7 +11114,7 @@ function collectGcEntry(dir, name, now, minAgeMs, stats, remove) {
10957
11114
  }
10958
11115
  let lastUseMs = dirStat.mtimeMs;
10959
11116
  try {
10960
- lastUseMs = statSync5(join9(dir, COMPLETE_SENTINEL)).mtimeMs;
11117
+ lastUseMs = statSync6(join9(dir, COMPLETE_SENTINEL)).mtimeMs;
10961
11118
  } catch {
10962
11119
  }
10963
11120
  return { dir, size: directorySizeBytes(dir), lastUseMs, ageMs: now - lastUseMs };
@@ -10967,7 +11124,7 @@ function collectGcEntry(dir, name, now, minAgeMs, stats, remove) {
10967
11124
  }
10968
11125
  function gcSweepDue(rootDir, maxAgeMs) {
10969
11126
  try {
10970
- return Date.now() - statSync5(join9(rootDir, GC_MARKER)).mtimeMs > maxAgeMs;
11127
+ return Date.now() - statSync6(join9(rootDir, GC_MARKER)).mtimeMs > maxAgeMs;
10971
11128
  } catch {
10972
11129
  return true;
10973
11130
  }
@@ -11465,6 +11622,69 @@ function restoreSourceColourFilter(metadata) {
11465
11622
  ].filter(([, value]) => value && value !== "unknown" && value !== "reserved");
11466
11623
  return tags.length > 0 ? [`setparams=${tags.map(([key, value]) => `${key}=${value}`).join(":")}`] : [];
11467
11624
  }
11625
+ var EXTRACTION_SEGMENT_SECONDS = 120;
11626
+ function splitFrameRanges(ranges, segmentFrames) {
11627
+ const segments = [];
11628
+ for (const range of ranges) {
11629
+ for (let offset = 0; offset < range.frames; offset += segmentFrames) {
11630
+ segments.push({
11631
+ firstFrame: range.firstFrame + offset,
11632
+ frames: Math.min(segmentFrames, range.frames - offset)
11633
+ });
11634
+ }
11635
+ }
11636
+ return segments;
11637
+ }
11638
+ async function runSegmentedExtraction(job) {
11639
+ const { decodeArgs, filterAndEncodeArgs, videoPath, startTime, fps, segments } = job;
11640
+ const segmentCount = segments.length;
11641
+ const failed = new AbortController();
11642
+ const signal = job.runOptions.signal ? AbortSignal.any([job.runOptions.signal, failed.signal]) : failed.signal;
11643
+ const results = [];
11644
+ let next = 0;
11645
+ const worker = async () => {
11646
+ while (next < segmentCount && !signal.aborted) {
11647
+ const index = next++;
11648
+ const { firstFrame, frames } = segments[index];
11649
+ const result = await runFfmpeg(
11650
+ [
11651
+ ...decodeArgs,
11652
+ "-noaccurate_seek",
11653
+ "-ss",
11654
+ String(startTime + firstFrame / fps),
11655
+ "-i",
11656
+ videoPath,
11657
+ // Read one frame past the segment and keep exactly its own frames.
11658
+ "-t",
11659
+ String((frames + 1) / fps),
11660
+ "-frames:v",
11661
+ String(frames),
11662
+ "-start_number",
11663
+ String(firstFrame + 1),
11664
+ ...filterAndEncodeArgs
11665
+ ],
11666
+ { ...job.runOptions, signal }
11667
+ );
11668
+ results[index] = result;
11669
+ if (!result.success) failed.abort();
11670
+ }
11671
+ };
11672
+ const workers = Math.min(segmentCount, Math.max(1, Math.floor(cpus().length / 2)));
11673
+ await Promise.all(Array.from({ length: workers }, worker));
11674
+ const ran = results.filter(Boolean);
11675
+ const failure = ran.find((r) => !r.success && r.terminationReason !== "abort") ?? ran.find((r) => !r.success);
11676
+ if (failure) return failure;
11677
+ if (ran.length < segmentCount) {
11678
+ return {
11679
+ success: false,
11680
+ exitCode: null,
11681
+ stderr: "",
11682
+ durationMs: 0,
11683
+ terminationReason: "abort"
11684
+ };
11685
+ }
11686
+ return ran[ran.length - 1];
11687
+ }
11468
11688
  async function extractVideoFramesRange(videoPath, videoId, startTime, duration, options, signal, config, outputDirOverride) {
11469
11689
  const ffmpegProcessTimeout = config?.ffmpegProcessTimeout ?? DEFAULT_CONFIG.ffmpegProcessTimeout;
11470
11690
  const { outputDir, quality = 95 } = options;
@@ -11504,6 +11724,9 @@ async function extractVideoFramesRange(videoPath, videoId, startTime, duration,
11504
11724
  const toneMappedToSdr = options.toneMapHdrToSdr === true && isHdr && await toneMapsWithZscale();
11505
11725
  const decodeWithVideoToolbox = isHdr && isMacOS && !toneMappedToSdr;
11506
11726
  const sampleCfrAtOutputFps = !options.finalFrameOnly && !metadata.isVFR;
11727
+ if (options.frameRanges && !sampleCfrAtOutputFps) {
11728
+ throw new Error("frameRanges extraction needs a constant-frame-rate source");
11729
+ }
11507
11730
  const args = [];
11508
11731
  if (decodeWithVideoToolbox) {
11509
11732
  args.push("-hwaccel", "videotoolbox");
@@ -11511,6 +11734,7 @@ async function extractVideoFramesRange(videoPath, videoId, startTime, duration,
11511
11734
  if (codecMayHaveAlpha(metadata.videoCodec)) {
11512
11735
  args.push("-c:v", decoderForCodec(metadata.videoCodec));
11513
11736
  }
11737
+ const decodeArgs = [...args];
11514
11738
  if (options.finalFrameOnly) {
11515
11739
  args.push("-i", videoPath, "-ss", String(startTime), "-frames:v", "1");
11516
11740
  } else {
@@ -11566,8 +11790,21 @@ async function extractVideoFramesRange(videoPath, videoId, startTime, duration,
11566
11790
  runOptions
11567
11791
  );
11568
11792
  } else {
11569
- if (vfFilters.length > 0) args.push("-vf", vfFilters.join(","));
11570
- processResult = await runFfmpeg([...args, ...encodeArgs], runOptions);
11793
+ const filterArgs = vfFilters.length > 0 ? ["-vf", vfFilters.join(",")] : [];
11794
+ const segmentFrames = Math.round(EXTRACTION_SEGMENT_SECONDS * fps);
11795
+ const totalFrames = extractionFrameCountForDuration(duration, normalizedFps, false);
11796
+ processResult = sampleCfrAtOutputFps && (options.frameRanges || totalFrames > segmentFrames) ? await runSegmentedExtraction({
11797
+ decodeArgs,
11798
+ filterAndEncodeArgs: [...filterArgs, ...encodeArgs],
11799
+ videoPath,
11800
+ startTime,
11801
+ fps,
11802
+ segments: splitFrameRanges(
11803
+ options.frameRanges ?? [{ firstFrame: 0, frames: totalFrames }],
11804
+ segmentFrames
11805
+ ),
11806
+ runOptions
11807
+ }) : await runFfmpeg([...args, ...filterArgs, ...encodeArgs], runOptions);
11571
11808
  }
11572
11809
  if (processResult.failureReason === "external_interruption") {
11573
11810
  throw new VideoSourceExtractionError(
@@ -11606,7 +11843,7 @@ async function extractVideoFramesRange(videoPath, videoId, startTime, duration,
11606
11843
  diagnostic
11607
11844
  );
11608
11845
  }
11609
- const framePaths = framePathsFromDirectory(videoOutputDir, format);
11846
+ const framePaths = framePathsFromDirectory(videoOutputDir, format, !!options.frameRanges);
11610
11847
  if (framePaths.size === 0 && duration > 0) {
11611
11848
  throw new VideoSourceExtractionError(
11612
11849
  "zero_output",
@@ -11615,7 +11852,7 @@ async function extractVideoFramesRange(videoPath, videoId, startTime, duration,
11615
11852
  `FFmpeg exited successfully but produced no frames (start=${startTime}, duration=${duration})`
11616
11853
  );
11617
11854
  }
11618
- if (framePaths.size > 0 && duration > 0 && !options.finalFrameOnly) {
11855
+ if (framePaths.size > 0 && duration > 0 && !options.finalFrameOnly && !options.frameRanges) {
11619
11856
  const expectedAtFps = extractionFrameCountForDuration(duration, normalizedFps, metadata.isVFR);
11620
11857
  if (expectedAtFps > 0 && framePaths.size <= expectedAtFps * 0.6) {
11621
11858
  const correctedDuration = startTime + framePaths.size / fps;
@@ -12298,6 +12535,57 @@ async function extractAllVideoFrames(videos, baseDir, options, signal, config, c
12298
12535
  }
12299
12536
  return rehydratePublishedCache(work, cacheTarget);
12300
12537
  }
12538
+ async function deferExtraction(work) {
12539
+ if (work.finalFrameOnly || work.metadata.isVFR || work.sdrToHdrTransfer !== void 0 || work.hdrToSdrTransformKey !== void 0) {
12540
+ return null;
12541
+ }
12542
+ const expectedFrames = extractionFrameCountForDuration(
12543
+ work.videoDuration,
12544
+ configuredFps,
12545
+ false
12546
+ );
12547
+ const tailFrames = Math.min(expectedFrames, Math.ceil(fps));
12548
+ if (expectedFrames <= tailFrames) return null;
12549
+ const probeDir = join10(options.outputDir, `${work.video.id}.tail-probe`);
12550
+ try {
12551
+ const tail = await extractVideoFramesRange(
12552
+ work.videoPath,
12553
+ work.video.id,
12554
+ work.extractionMediaStart,
12555
+ work.videoDuration,
12556
+ {
12557
+ ...scopedExtractionOptions(work),
12558
+ frameRanges: [{ firstFrame: expectedFrames - tailFrames, frames: tailFrames }]
12559
+ },
12560
+ signal,
12561
+ config,
12562
+ probeDir
12563
+ );
12564
+ const totalFrames = expectedFrames - tailFrames + tail.totalFrames;
12565
+ if (totalFrames <= expectedFrames * 0.6) return null;
12566
+ const outputDir = join10(options.outputDir, work.video.id);
12567
+ mkdirSync6(outputDir, { recursive: true });
12568
+ return {
12569
+ videoId: work.video.id,
12570
+ srcPath: work.videoPath,
12571
+ outputDir,
12572
+ framePattern: `${FRAME_FILENAME_PREFIX}%05d.${work.format}`,
12573
+ fps,
12574
+ totalFrames,
12575
+ metadata: work.metadata,
12576
+ framePaths: /* @__PURE__ */ new Map(),
12577
+ deferredRange: {
12578
+ startTime: work.extractionMediaStart,
12579
+ durationSeconds: work.videoDuration,
12580
+ format: work.format
12581
+ }
12582
+ };
12583
+ } catch {
12584
+ return null;
12585
+ } finally {
12586
+ rmSync4(probeDir, { recursive: true, force: true });
12587
+ }
12588
+ }
12301
12589
  async function executeDirectMiss(miss, maxTransientRetries = options.maxTransientRetries ?? 0) {
12302
12590
  try {
12303
12591
  return { result: await extractDirectMiss(miss, maxTransientRetries) };
@@ -12422,8 +12710,17 @@ async function extractAllVideoFrames(videos, baseDir, options, signal, config, c
12422
12710
  }
12423
12711
  }
12424
12712
  const uniqueOutcomes = /* @__PURE__ */ new Map();
12713
+ if (options.deferRangeExtraction) {
12714
+ const works = [...uniqueWorks.values()];
12715
+ const deferred = await Promise.all(works.map(deferExtraction));
12716
+ works.forEach((work, i) => {
12717
+ const result = deferred[i];
12718
+ if (result) uniqueOutcomes.set(work.dedupeKey, { result });
12719
+ });
12720
+ }
12425
12721
  const cacheMisses = [];
12426
12722
  for (const work of uniqueWorks.values()) {
12723
+ if (uniqueOutcomes.has(work.dedupeKey)) continue;
12427
12724
  const lookup = lookupCacheFor(work);
12428
12725
  if ("work" in lookup) {
12429
12726
  cacheMisses.push(lookup);
@@ -12479,7 +12776,7 @@ async function extractAllVideoFrames(videos, baseDir, options, signal, config, c
12479
12776
  errors.push(item.error);
12480
12777
  } else if ("result" in item) {
12481
12778
  extracted.push(item.result);
12482
- totalFramesExtracted += item.result.totalFrames;
12779
+ if (!item.result.deferredRange) totalFramesExtracted += item.result.totalFrames;
12483
12780
  }
12484
12781
  }
12485
12782
  const sweepDue = breakdown.cacheMisses > 0 || cacheRootDir !== void 0 && gcSweepDue(cacheRootDir, GC_STALENESS_MS);
@@ -14716,7 +15013,7 @@ async function processCompositionAudio(elements, baseDir, workDir, outputPath, t
14716
15013
  }
14717
15014
 
14718
15015
  // ../engine/src/services/parallelCoordinator.ts
14719
- import { cpus, freemem } from "os";
15016
+ import { cpus as cpus2, freemem } from "os";
14720
15017
  import { existsSync as existsSync11, mkdirSync as mkdirSync8, readdirSync as readdirSync6 } from "fs";
14721
15018
  import { copyFile, readFile as readFile2, rename } from "fs/promises";
14722
15019
  import { join as join15 } from "path";
@@ -14831,7 +15128,7 @@ var MIN_WORKERS = 1;
14831
15128
  var MAX_WORKER_DIAGNOSTIC_LINES = 8;
14832
15129
  var ABSOLUTE_MAX_WORKERS = 24;
14833
15130
  function defaultSafeMaxWorkers() {
14834
- return Math.max(6, Math.min(16, Math.floor(cpus().length / 8)));
15131
+ return Math.max(6, Math.min(16, Math.floor(cpus2().length / 8)));
14835
15132
  }
14836
15133
  var MIN_FRAMES_PER_WORKER = 30;
14837
15134
  function shouldDisableBrowserPoolForParallelWorker({
@@ -14876,7 +15173,7 @@ function formatWorkerFailure(result) {
14876
15173
  return `${base}; diagnostics: ${diagnostics}`;
14877
15174
  }
14878
15175
  function computeWorkerSizing(totalFrames, requested, config) {
14879
- const cpuCount = cpus().length;
15176
+ const cpuCount = cpus2().length;
14880
15177
  const totalMemoryMb = getSystemTotalMb();
14881
15178
  const heapLimitMb = Math.round(getHeapStatistics().heap_size_limit / (1024 * 1024));
14882
15179
  const heapBasedWorkers = Math.max(
@@ -15434,7 +15731,7 @@ async function mergeWorkerFrames(workDir, tasks, outputDir) {
15434
15731
  }
15435
15732
  function getSystemResources() {
15436
15733
  return {
15437
- cpuCores: cpus().length,
15734
+ cpuCores: cpus2().length,
15438
15735
  totalMemoryMB: getSystemTotalMb(),
15439
15736
  freeMemoryMB: Math.round(freemem() / (1024 * 1024)),
15440
15737
  recommendedWorkers: calculateOptimalWorkers(1e3)
@@ -15444,7 +15741,7 @@ function getSystemResources() {
15444
15741
  // ../engine/src/services/fileServer.ts
15445
15742
  import { Hono } from "hono";
15446
15743
  import { serve } from "@hono/node-server";
15447
- import { readFileSync as readFileSync7, openSync as openSync4, fstatSync, closeSync as closeSync4, statSync as statSync6, constants } from "fs";
15744
+ import { readFileSync as readFileSync7, openSync as openSync4, fstatSync, closeSync as closeSync4, statSync as statSync7, constants } from "fs";
15448
15745
  import { join as join16, extname as extname3 } from "path";
15449
15746
  var MIME_TYPES = {
15450
15747
  ".html": "text/html; charset=utf-8",
@@ -15481,7 +15778,7 @@ function readRegularFile(filePath) {
15481
15778
  } catch (error) {
15482
15779
  if (error instanceof Error && "code" in error && (error.code === "ENOENT" || error.code === "ENOTDIR"))
15483
15780
  return null;
15484
- if (!statSync6(filePath, { throwIfNoEntry: false })?.isFile()) return null;
15781
+ if (!statSync7(filePath, { throwIfNoEntry: false })?.isFile()) return null;
15485
15782
  throw error;
15486
15783
  }
15487
15784
  try {
@@ -15909,6 +16206,7 @@ export {
15909
16206
  resolveConfig,
15910
16207
  shouldClampToScreenshotForConcreteGpu,
15911
16208
  applyConcreteGpuScreenshotClamp,
16209
+ SDR_RGB_TO_BT709_FILTER,
15912
16210
  BrowserLeasePool,
15913
16211
  setHostHandlesSigint,
15914
16212
  resolveHeadlessShellPath,
@@ -15918,6 +16216,7 @@ export {
15918
16216
  releaseBrowser,
15919
16217
  drainBrowserPool,
15920
16218
  closeBrowserPool,
16219
+ usesSoftwareWebGpu,
15921
16220
  buildChromeArgs,
15922
16221
  compositionRequiresWebGpu,
15923
16222
  WebGpuUnavailableError,