framefields 2.1.0 → 2.1.1

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 (372) hide show
  1. package/dist/audio/index.d.mts +4 -4
  2. package/dist/audio/index.mjs +11 -11
  3. package/dist/{audio-C2fLC3Tx.d.mts → audio-CfBpzxn9.d.mts} +3 -3
  4. package/dist/{audio-C2fLC3Tx.d.mts.map → audio-CfBpzxn9.d.mts.map} +1 -1
  5. package/dist/{audio-D_4SeMRK.mjs → audio-GGuQ_JI0.mjs} +3 -3
  6. package/dist/{audio-D_4SeMRK.mjs.map → audio-GGuQ_JI0.mjs.map} +1 -1
  7. package/dist/{chunk-GMkBE123.mjs → chunk-GBe2k3y8.mjs} +1 -4
  8. package/dist/{dist-DlzqyRar.mjs → dist-Bb8BoMiw.mjs} +3 -10
  9. package/dist/dist-Bb8BoMiw.mjs.map +1 -0
  10. package/dist/{dist-Bun_TUvZ.mjs → dist-DkuDAIrG.mjs} +2 -2
  11. package/dist/{dist-Bun_TUvZ.mjs.map → dist-DkuDAIrG.mjs.map} +1 -1
  12. package/dist/effects/index.d.mts +4 -4
  13. package/dist/effects/index.mjs +35 -35
  14. package/dist/{effects-FTlu0dri.mjs → effects-D6wFg2FR.mjs} +2 -2
  15. package/dist/{effects-FTlu0dri.mjs.map → effects-D6wFg2FR.mjs.map} +1 -1
  16. package/dist/fonts/index.d.mts +1 -1
  17. package/dist/fonts/index.mjs +3 -3
  18. package/dist/{fonts-UM5xaBOx.mjs → fonts-CZMYtbP7.mjs} +2 -2
  19. package/dist/{fonts-UM5xaBOx.mjs.map → fonts-CZMYtbP7.mjs.map} +1 -1
  20. package/dist/{gpu-RkpiFGOA.mjs → gpu-DG3gI88-.mjs} +2 -2
  21. package/dist/{gpu-RkpiFGOA.mjs.map → gpu-DG3gI88-.mjs.map} +1 -1
  22. package/dist/{index-DsOqONsj.d.mts → index--0vVZDPM.d.mts} +3 -3
  23. package/dist/{index-DsOqONsj.d.mts.map → index--0vVZDPM.d.mts.map} +1 -1
  24. package/dist/{index-l03NV8JS.d.mts → index-BcA8rep0.d.mts} +1 -1
  25. package/dist/{index-l03NV8JS.d.mts.map → index-BcA8rep0.d.mts.map} +1 -1
  26. package/dist/{index-Db8Z3AtX.d.mts → index-BzQc812z.d.mts} +3 -3
  27. package/dist/{index-Db8Z3AtX.d.mts.map → index-BzQc812z.d.mts.map} +1 -1
  28. package/dist/{index-D67p-rHX.d.mts → index-Cpkzy5XR.d.mts} +1 -1
  29. package/dist/{index-D67p-rHX.d.mts.map → index-Cpkzy5XR.d.mts.map} +1 -1
  30. package/dist/{index-CzQseCtg.d.mts → index-D__HdInU.d.mts} +4 -4
  31. package/dist/{index-CzQseCtg.d.mts.map → index-D__HdInU.d.mts.map} +1 -1
  32. package/dist/{index-CHQ39_Oe.d.mts → index-DpWia6iH.d.mts} +4 -11
  33. package/dist/index-DpWia6iH.d.mts.map +1 -0
  34. package/dist/{index-DPicEt8H.d.mts → index-Fciq8bcC.d.mts} +2 -2
  35. package/dist/{index-DPicEt8H.d.mts.map → index-Fciq8bcC.d.mts.map} +1 -1
  36. package/dist/index.d.mts +10 -10
  37. package/dist/index.d.mts.map +1 -1
  38. package/dist/index.mjs +50 -51
  39. package/dist/index.mjs.map +1 -1
  40. package/dist/preview/index.d.mts +2 -2
  41. package/dist/preview/index.mjs +11 -13
  42. package/dist/{preview-DwClLMlU.mjs → preview--rOXbBxl.mjs} +2 -3
  43. package/dist/preview--rOXbBxl.mjs.map +1 -0
  44. package/dist/preview-engine/audio.mjs +1 -1
  45. package/dist/preview-engine/{chunk-JXNG2ACR.mjs → chunk-2MZXSWVT.mjs} +1 -1
  46. package/dist/preview-engine/{chunk-Q22PJXFJ.mjs → chunk-3UPI4EPV.mjs} +1 -1
  47. package/dist/preview-engine/{chunk-S2CTIJSI.mjs → chunk-7EPDVSCL.mjs} +1 -1
  48. package/dist/preview-engine/{chunk-AY7UX37L.mjs → chunk-AXQZX3TC.mjs} +1 -1
  49. package/dist/preview-engine/{chunk-T2FE5VOW.mjs → chunk-E3HC4GRQ.mjs} +1 -1
  50. package/dist/preview-engine/{chunk-DIGIQ5MJ.mjs → chunk-FPRXK2WD.mjs} +1 -1
  51. package/dist/preview-engine/{chunk-EVXWDOCL.mjs → chunk-K773AOF3.mjs} +1 -1
  52. package/dist/preview-engine/{chunk-IR5DNNWR.mjs → chunk-L3CHEZMZ.mjs} +1 -1
  53. package/dist/preview-engine/{chunk-PSRQA3QW.mjs → chunk-LBRVQ4GE.mjs} +1 -1
  54. package/dist/preview-engine/{chunk-U4JIFQRR.mjs → chunk-NEE3KQTX.mjs} +1 -1
  55. package/dist/preview-engine/{chunk-L6BH5J4P.mjs → chunk-O2RNIQXT.mjs} +1 -1
  56. package/dist/preview-engine/{chunk-MLY5M7Y5.mjs → chunk-PFA427ZC.mjs} +1 -1
  57. package/dist/preview-engine/chunk-PNB3ERR3.mjs +1 -0
  58. package/dist/preview-engine/{chunk-7G3XIOP7.mjs → chunk-PR2BUYCL.mjs} +1 -1
  59. package/dist/preview-engine/{chunk-TS3JI3BH.mjs → chunk-Q2DPNVIA.mjs} +1 -1
  60. package/dist/preview-engine/{chunk-ZDQF3OTC.mjs → chunk-QY7MHWZW.mjs} +1 -1
  61. package/dist/preview-engine/{chunk-44CQSOYE.mjs → chunk-VO2R3DWV.mjs} +1 -1
  62. package/dist/preview-engine/{chunk-HE4R7EZM.mjs → chunk-WSIURGP2.mjs} +1 -1
  63. package/dist/preview-engine/{chunk-OG62XGCT.mjs → chunk-ZH5MZQTZ.mjs} +1 -1
  64. package/dist/preview-engine/{chunk-EYWPOCZW.mjs → chunk-ZNL4W4DK.mjs} +1 -1
  65. package/dist/preview-engine/effects.mjs +1 -1
  66. package/dist/preview-engine/framefields.mjs +2 -2
  67. package/dist/preview-engine/player.mjs +1 -1
  68. package/dist/preview-engine/renderers-2H7YEUTD.mjs +1 -1
  69. package/dist/preview-engine/renderers-2P6KXWEZ.mjs +1 -1
  70. package/dist/preview-engine/renderers-2SVWAB42.mjs +1 -1
  71. package/dist/preview-engine/renderers-3T7Q4SPP.mjs +1 -0
  72. package/dist/preview-engine/renderers-4DV4REGM.mjs +1 -0
  73. package/dist/preview-engine/renderers-57WDL6XN.mjs +1 -1
  74. package/dist/preview-engine/renderers-6WH7NZCC.mjs +1 -1
  75. package/dist/preview-engine/{renderers-A6HM64UZ.mjs → renderers-7JTNJE7M.mjs} +1 -1
  76. package/dist/preview-engine/renderers-7VJN5L3Z.mjs +1 -1
  77. package/dist/preview-engine/renderers-CQXQHOW2.mjs +1 -0
  78. package/dist/preview-engine/{renderers-RLHTNLRE.mjs → renderers-D2EJULOJ.mjs} +1 -1
  79. package/dist/preview-engine/renderers-DXHJAMZR.mjs +1 -1
  80. package/dist/preview-engine/renderers-FWGMNPYB.mjs +1 -1
  81. package/dist/preview-engine/renderers-HXFRTIJD.mjs +1 -1
  82. package/dist/preview-engine/renderers-IWQ3V6AJ.mjs +1 -1
  83. package/dist/preview-engine/renderers-IXGM55XK.mjs +1 -0
  84. package/dist/preview-engine/renderers-KVHUVWSN.mjs +1 -0
  85. package/dist/preview-engine/renderers-KZWGSKRF.mjs +1 -1
  86. package/dist/preview-engine/{renderers-CRRLV7IO.mjs → renderers-LVDUZZ75.mjs} +1 -1
  87. package/dist/preview-engine/renderers-M7R2ZJUP.mjs +1 -0
  88. package/dist/preview-engine/renderers-NWHS7N2L.mjs +1 -1
  89. package/dist/preview-engine/{renderers-6OGFUSH6.mjs → renderers-O2DZPBOJ.mjs} +1 -1
  90. package/dist/preview-engine/renderers-OT7D3BUA.mjs +1 -1
  91. package/dist/preview-engine/{renderers-2VN3AH45.mjs → renderers-P4S7GL64.mjs} +1 -1
  92. package/dist/preview-engine/renderers-PMOO7DLX.mjs +1 -0
  93. package/dist/preview-engine/renderers-Q4OFCMXI.mjs +1 -1
  94. package/dist/preview-engine/renderers-QCCPCH73.mjs +1 -0
  95. package/dist/preview-engine/renderers-RJO6H4FC.mjs +1 -1
  96. package/dist/preview-engine/renderers-RRXFJRGI.mjs +1 -0
  97. package/dist/preview-engine/renderers-T4JMJXWL.mjs +1 -1
  98. package/dist/preview-engine/renderers-TAWT3ZSS.mjs +1 -1
  99. package/dist/preview-engine/{renderers-D6T6IPER.mjs → renderers-TFZST6WW.mjs} +1 -1
  100. package/dist/preview-engine/renderers-UHTO6BQA.mjs +1 -1
  101. package/dist/preview-engine/{renderers-U6KY34TP.mjs → renderers-UPKB5OLS.mjs} +1 -1
  102. package/dist/preview-engine/renderers-VLMQR3DW.mjs +1 -0
  103. package/dist/preview-engine/renderers-WVTTRCVI.mjs +1 -0
  104. package/dist/preview-engine/renderers-X5PUIPU5.mjs +1 -0
  105. package/dist/preview-engine/{renderers-BSKZXITT.mjs → renderers-XCMVZ2XG.mjs} +1 -1
  106. package/dist/preview-engine/renderers-XHTMRXC5.mjs +1 -1
  107. package/dist/preview-engine/renderers-YDCDY3BR.mjs +1 -0
  108. package/dist/preview-engine/renderers-YECY2YFT.mjs +1 -1
  109. package/dist/preview-engine/renderers-YJNQ4B7B.mjs +1 -0
  110. package/dist/preview-engine/renderers-YMN6QXK7.mjs +1 -1
  111. package/dist/preview-engine/{renderers-H46TS3IG.mjs → renderers-YZEVPHPQ.mjs} +1 -1
  112. package/dist/preview-engine/renderers-Z7YQUFAY.mjs +1 -0
  113. package/dist/preview-engine/renderers-ZANPE3OK.mjs +1 -1
  114. package/dist/preview-engine/renderers-ZJTRANIX.mjs +1 -1
  115. package/dist/preview-engine/signals.mjs +1 -1
  116. package/dist/{program-BqFVw5Mu.mjs → program-Cc5AsBHz.mjs} +2 -2
  117. package/dist/{program-BqFVw5Mu.mjs.map → program-Cc5AsBHz.mjs.map} +1 -1
  118. package/dist/renderer/index.d.mts +2 -2
  119. package/dist/renderer/index.mjs +3 -4
  120. package/dist/renderer-1VSi7Vum.mjs +5 -0
  121. package/dist/renderer-4BlpUHQT.mjs +5 -0
  122. package/dist/{renderer-BLcu9RDt.mjs → renderer-9L0p8IT3.mjs} +3 -3
  123. package/dist/renderer-AzSX2-mZ.mjs +5 -0
  124. package/dist/renderer-B-cxK24t.mjs +5 -0
  125. package/dist/{renderer-CGldH-Zx.mjs → renderer-B1Zn91xt.mjs} +3 -3
  126. package/dist/{renderer-CGldH-Zx.mjs.map → renderer-B1Zn91xt.mjs.map} +1 -1
  127. package/dist/renderer-B5d5oM_D.mjs +5 -0
  128. package/dist/renderer-B8BdNjaN.mjs +5 -0
  129. package/dist/{renderer-BYqfkwLf.mjs → renderer-BI5rvAyo.mjs} +3 -3
  130. package/dist/{renderer-BYqfkwLf.mjs.map → renderer-BI5rvAyo.mjs.map} +1 -1
  131. package/dist/renderer-BKEfeg-4.mjs +5 -0
  132. package/dist/{renderer-BvpSIFkw.mjs → renderer-BKf1qDwL.mjs} +4 -4
  133. package/dist/{renderer-BvpSIFkw.mjs.map → renderer-BKf1qDwL.mjs.map} +1 -1
  134. package/dist/renderer-BLLOkeFB.mjs +5 -0
  135. package/dist/renderer-BTvov1NH.mjs +5 -0
  136. package/dist/renderer-BVun05IK.mjs +5 -0
  137. package/dist/{renderer-DZrQffIP.mjs → renderer-BWVsBB3t.mjs} +2 -2
  138. package/dist/{renderer-DZrQffIP.mjs.map → renderer-BWVsBB3t.mjs.map} +1 -1
  139. package/dist/{renderer-Bs4tz_pw.mjs → renderer-BXQQxeNC.mjs} +3 -3
  140. package/dist/{renderer-Bs4tz_pw.mjs.map → renderer-BXQQxeNC.mjs.map} +1 -1
  141. package/dist/renderer-B_3h5nhy.mjs +5 -0
  142. package/dist/renderer-BaZjoFYK.mjs +5 -0
  143. package/dist/{renderer-DOEsFT7Q.mjs → renderer-BaaOR3Ao.mjs} +2 -2
  144. package/dist/{renderer-DOEsFT7Q.mjs.map → renderer-BaaOR3Ao.mjs.map} +1 -1
  145. package/dist/{renderer-DRsPCkxr.mjs → renderer-Bb8T5eRX.mjs} +2 -2
  146. package/dist/{renderer-DRsPCkxr.mjs.map → renderer-Bb8T5eRX.mjs.map} +1 -1
  147. package/dist/{renderer-CRIM_XRK.mjs → renderer-Bc6J0iVG.mjs} +2 -2
  148. package/dist/{renderer-CRIM_XRK.mjs.map → renderer-Bc6J0iVG.mjs.map} +1 -1
  149. package/dist/renderer-BcApaCVe.mjs +5 -0
  150. package/dist/{renderer-DAtBnNGC.mjs → renderer-Bg7647VN.mjs} +4 -4
  151. package/dist/{renderer-DAtBnNGC.mjs.map → renderer-Bg7647VN.mjs.map} +1 -1
  152. package/dist/{renderer-CLyuu_19.mjs → renderer-BhKIZDsm.mjs} +2 -2
  153. package/dist/{renderer-CLyuu_19.mjs.map → renderer-BhKIZDsm.mjs.map} +1 -1
  154. package/dist/{renderer-BqsV75Ml.mjs → renderer-BhhSyHKF.mjs} +3 -3
  155. package/dist/{renderer-BvG1GF1c.mjs → renderer-Br9BOkQs.mjs} +2 -2
  156. package/dist/{renderer-BvG1GF1c.mjs.map → renderer-Br9BOkQs.mjs.map} +1 -1
  157. package/dist/{renderer-CTGe8SqQ.d.mts → renderer-Bs_f_App.d.mts} +2 -2
  158. package/dist/{renderer-CTGe8SqQ.d.mts.map → renderer-Bs_f_App.d.mts.map} +1 -1
  159. package/dist/{renderer-DG1NFsSV.mjs → renderer-BygIYH8s.mjs} +2 -2
  160. package/dist/{renderer-DG1NFsSV.mjs.map → renderer-BygIYH8s.mjs.map} +1 -1
  161. package/dist/{renderer-BGr_cBKV.mjs → renderer-C--Dqe-3.mjs} +3 -3
  162. package/dist/{renderer-BGr_cBKV.mjs.map → renderer-C--Dqe-3.mjs.map} +1 -1
  163. package/dist/renderer-C14mKYCw.mjs +5 -0
  164. package/dist/{renderer-2Nf9ypST.mjs → renderer-C27RfwXN.mjs} +3 -3
  165. package/dist/{renderer-2Nf9ypST.mjs.map → renderer-C27RfwXN.mjs.map} +1 -1
  166. package/dist/{renderer-CgyOylPO.mjs → renderer-C5caYAA3.mjs} +2 -2
  167. package/dist/{renderer-CgyOylPO.mjs.map → renderer-C5caYAA3.mjs.map} +1 -1
  168. package/dist/{renderer-Cwhux4VX.mjs → renderer-C5zpaQed.mjs} +2 -2
  169. package/dist/{renderer-Cwhux4VX.mjs.map → renderer-C5zpaQed.mjs.map} +1 -1
  170. package/dist/{renderer-Bq_dO0LG.mjs → renderer-C7UZLOYk.mjs} +2 -2
  171. package/dist/{renderer-Bq_dO0LG.mjs.map → renderer-C7UZLOYk.mjs.map} +1 -1
  172. package/dist/{renderer-D0nWLogn.mjs → renderer-C92mwhZz.mjs} +2 -2
  173. package/dist/{renderer-D0nWLogn.mjs.map → renderer-C92mwhZz.mjs.map} +1 -1
  174. package/dist/{renderer-v0vuUUUI.mjs → renderer-CAJe9eLS.mjs} +2 -2
  175. package/dist/{renderer-v0vuUUUI.mjs.map → renderer-CAJe9eLS.mjs.map} +1 -1
  176. package/dist/renderer-CFqhAXkH.mjs +5 -0
  177. package/dist/renderer-CK-spD4j.mjs +5 -0
  178. package/dist/renderer-CLLzQTIk.mjs +5 -0
  179. package/dist/{renderer-C3RfT8Gv.mjs → renderer-CMsjnZ0G.mjs} +3 -3
  180. package/dist/{renderer-C3RfT8Gv.mjs.map → renderer-CMsjnZ0G.mjs.map} +1 -1
  181. package/dist/renderer-COwa6xlF.mjs +5 -0
  182. package/dist/renderer-CQPuuoVf.mjs +6 -0
  183. package/dist/renderer-CRd-cDqZ.mjs +5 -0
  184. package/dist/renderer-CTVnQAUm.mjs +5 -0
  185. package/dist/renderer-CbiEBSGx.mjs +5 -0
  186. package/dist/renderer-Cl7PruQ1.mjs +5 -0
  187. package/dist/renderer-CmuGGUDR.mjs +5 -0
  188. package/dist/{renderer-D8C-pamu.mjs → renderer-CoGP4Mvf.mjs} +2 -2
  189. package/dist/{renderer-D8C-pamu.mjs.map → renderer-CoGP4Mvf.mjs.map} +1 -1
  190. package/dist/renderer-CoNRrfqx.mjs +5 -0
  191. package/dist/{renderer-2uN2cd0y.mjs → renderer-CrAqu3Rg.mjs} +2 -2
  192. package/dist/{renderer-2uN2cd0y.mjs.map → renderer-CrAqu3Rg.mjs.map} +1 -1
  193. package/dist/renderer-CvIGa0mE.mjs +5 -0
  194. package/dist/renderer-CwMDPv0p.mjs +5 -0
  195. package/dist/{renderer-Dpj68gTn.mjs → renderer-CydWqyf0.mjs} +2 -2
  196. package/dist/{renderer-Dpj68gTn.mjs.map → renderer-CydWqyf0.mjs.map} +1 -1
  197. package/dist/{renderer-B4oS8ZFO.mjs → renderer-CzBQgGD2.mjs} +4 -4
  198. package/dist/{renderer-B4oS8ZFO.mjs.map → renderer-CzBQgGD2.mjs.map} +1 -1
  199. package/dist/{renderer-Dmv_Aj6x.mjs → renderer-D0aZNkQE.mjs} +3 -3
  200. package/dist/{renderer-Dmv_Aj6x.mjs.map → renderer-D0aZNkQE.mjs.map} +1 -1
  201. package/dist/{renderer-BwAqwTQq.mjs → renderer-D2BRlqof.mjs} +2 -2
  202. package/dist/{renderer-BwAqwTQq.mjs.map → renderer-D2BRlqof.mjs.map} +1 -1
  203. package/dist/{renderer-D4sYVV-I.mjs → renderer-D2FFvxsq.mjs} +3 -3
  204. package/dist/{renderer-D4sYVV-I.mjs.map → renderer-D2FFvxsq.mjs.map} +1 -1
  205. package/dist/{renderer-DPYB6ZcB.mjs → renderer-D8-zNjyz.mjs} +2 -2
  206. package/dist/{renderer-DPYB6ZcB.mjs.map → renderer-D8-zNjyz.mjs.map} +1 -1
  207. package/dist/{renderer-DhEyjn6Q.mjs → renderer-DDqwds9R.mjs} +2 -2
  208. package/dist/{renderer-DhEyjn6Q.mjs.map → renderer-DDqwds9R.mjs.map} +1 -1
  209. package/dist/{renderer-CpKNqciR.mjs → renderer-DGtBOdst.mjs} +3 -3
  210. package/dist/{renderer-BwVfhrEx.mjs → renderer-DKqG9Lek.mjs} +2 -2
  211. package/dist/{renderer-BwVfhrEx.mjs.map → renderer-DKqG9Lek.mjs.map} +1 -1
  212. package/dist/renderer-DOHbhGPu.mjs +5 -0
  213. package/dist/renderer-DOmA6IKc.mjs +5 -0
  214. package/dist/{renderer-BgnJUx24.mjs → renderer-DUhXH5Tz.mjs} +3 -3
  215. package/dist/{renderer-BgnJUx24.mjs.map → renderer-DUhXH5Tz.mjs.map} +1 -1
  216. package/dist/{renderer-CH9Qtpy7.mjs → renderer-DVnVnS3c.mjs} +2 -2
  217. package/dist/{renderer-CH9Qtpy7.mjs.map → renderer-DVnVnS3c.mjs.map} +1 -1
  218. package/dist/{renderer-DZLJnnU2.mjs → renderer-DWEvQ-hQ.mjs} +3 -3
  219. package/dist/{renderer-DZLJnnU2.mjs.map → renderer-DWEvQ-hQ.mjs.map} +1 -1
  220. package/dist/renderer-DX2qEJTL.mjs +2173 -0
  221. package/dist/renderer-DX2qEJTL.mjs.map +1 -0
  222. package/dist/{renderer-CEGbqT2m.mjs → renderer-DX8aBDqR.mjs} +2 -2
  223. package/dist/{renderer-CEGbqT2m.mjs.map → renderer-DX8aBDqR.mjs.map} +1 -1
  224. package/dist/{renderer-BkN20ZKO.mjs → renderer-DYIPWN_B.mjs} +4 -4
  225. package/dist/{renderer-BkN20ZKO.mjs.map → renderer-DYIPWN_B.mjs.map} +1 -1
  226. package/dist/{renderer-Q63y7HP_.mjs → renderer-DZD2G76E.mjs} +2 -2
  227. package/dist/{renderer-Q63y7HP_.mjs.map → renderer-DZD2G76E.mjs.map} +1 -1
  228. package/dist/{renderer-B12_76qk.mjs → renderer-Da06_9FZ.mjs} +3 -3
  229. package/dist/{renderer-B12_76qk.mjs.map → renderer-Da06_9FZ.mjs.map} +1 -1
  230. package/dist/{renderer-DHAErBtJ.mjs → renderer-DkyXqrQ6.mjs} +2 -2
  231. package/dist/{renderer-DHAErBtJ.mjs.map → renderer-DkyXqrQ6.mjs.map} +1 -1
  232. package/dist/renderer-DmCWc4JL.mjs +5 -0
  233. package/dist/{renderer-DKtP7-fr.mjs → renderer-Dno-fujw.mjs} +2 -2
  234. package/dist/{renderer-DKtP7-fr.mjs.map → renderer-Dno-fujw.mjs.map} +1 -1
  235. package/dist/{renderer-BO2lYmtE.mjs → renderer-DoYdfDEA.mjs} +4 -4
  236. package/dist/{renderer-BO2lYmtE.mjs.map → renderer-DoYdfDEA.mjs.map} +1 -1
  237. package/dist/{renderer-Cjq1s3br.mjs → renderer-DqjITuT-.mjs} +2 -2
  238. package/dist/{renderer-Cjq1s3br.mjs.map → renderer-DqjITuT-.mjs.map} +1 -1
  239. package/dist/{renderer-BjTq3a0L.mjs → renderer-Dy-DBU0X.mjs} +3 -3
  240. package/dist/{renderer-BjTq3a0L.mjs.map → renderer-Dy-DBU0X.mjs.map} +1 -1
  241. package/dist/{renderer-to9DwqXK.mjs → renderer-DyzNM721.mjs} +2 -2
  242. package/dist/{renderer-to9DwqXK.mjs.map → renderer-DyzNM721.mjs.map} +1 -1
  243. package/dist/{renderer-CjT5Zy-J.mjs → renderer-DzrM1KMN.mjs} +3 -3
  244. package/dist/{renderer-CjT5Zy-J.mjs.map → renderer-DzrM1KMN.mjs.map} +1 -1
  245. package/dist/{renderer-D5z3WDkm.mjs → renderer-FHyZn0em.mjs} +2 -2
  246. package/dist/{renderer-D5z3WDkm.mjs.map → renderer-FHyZn0em.mjs.map} +1 -1
  247. package/dist/{renderer-Bsaokil3.mjs → renderer-NXdm6HLb.mjs} +3 -3
  248. package/dist/{renderer-Bsaokil3.mjs.map → renderer-NXdm6HLb.mjs.map} +1 -1
  249. package/dist/renderer-OLaYikwi.mjs +5 -0
  250. package/dist/renderer-Q75RC5Uz.mjs +5 -0
  251. package/dist/renderer-T0LJ4Po3.mjs +5 -0
  252. package/dist/renderer-VuXt1hfq.mjs +5 -0
  253. package/dist/{renderer-DzhRNdBS.mjs → renderer-_1xGBbxn.mjs} +6 -6
  254. package/dist/{renderer-DzhRNdBS.mjs.map → renderer-_1xGBbxn.mjs.map} +1 -1
  255. package/dist/{renderer-C3cYDT3m.mjs → renderer-c4AorKyw.mjs} +3 -3
  256. package/dist/renderer-hY_2WLt3.mjs +5 -0
  257. package/dist/{renderer-BpZaHFRc.mjs → renderer-qsg-JMuN.mjs} +4 -4
  258. package/dist/{renderer-BpZaHFRc.mjs.map → renderer-qsg-JMuN.mjs.map} +1 -1
  259. package/dist/{renderer-D1lynYuy.mjs → renderer-timEC0Gl.mjs} +4 -4
  260. package/dist/{renderer-D1lynYuy.mjs.map → renderer-timEC0Gl.mjs.map} +1 -1
  261. package/dist/renderer-y4EqE06a.mjs +5 -0
  262. package/dist/{renderers-CP8CFWDs-DlFq8jmz.mjs → renderers-CP8CFWDs-CMYe4kvZ.mjs} +2 -2
  263. package/dist/{renderers-CP8CFWDs-DlFq8jmz.mjs.map → renderers-CP8CFWDs-CMYe4kvZ.mjs.map} +1 -1
  264. package/dist/signals/index.d.mts +6 -6
  265. package/dist/signals/index.mjs +6 -6
  266. package/dist/{signals-BDkO0xcZ.mjs → signals-DGRs6ddH.mjs} +2 -2
  267. package/dist/{signals-BDkO0xcZ.mjs.map → signals-DGRs6ddH.mjs.map} +1 -1
  268. package/package.json +55 -55
  269. package/dist/client-dVxUnmHz.mjs +0 -66
  270. package/dist/client-dVxUnmHz.mjs.map +0 -1
  271. package/dist/dist-DlzqyRar.mjs.map +0 -1
  272. package/dist/dist-cjs-91nR6B7Z.mjs +0 -48
  273. package/dist/dist-cjs-91nR6B7Z.mjs.map +0 -1
  274. package/dist/dist-cjs-BAIenEMC.mjs +0 -9069
  275. package/dist/dist-cjs-BAIenEMC.mjs.map +0 -1
  276. package/dist/dist-cjs-BEQhh76X.mjs +0 -153
  277. package/dist/dist-cjs-BEQhh76X.mjs.map +0 -1
  278. package/dist/dist-cjs-BYPq7pfw.mjs +0 -43
  279. package/dist/dist-cjs-BYPq7pfw.mjs.map +0 -1
  280. package/dist/dist-cjs-BocDqSCX.mjs +0 -66
  281. package/dist/dist-cjs-BocDqSCX.mjs.map +0 -1
  282. package/dist/dist-cjs-CWiNggTl.mjs +0 -323
  283. package/dist/dist-cjs-CWiNggTl.mjs.map +0 -1
  284. package/dist/dist-cjs-CwabutT6.mjs +0 -1091
  285. package/dist/dist-cjs-CwabutT6.mjs.map +0 -1
  286. package/dist/dist-cjs-D-EE4Ahy.mjs +0 -51
  287. package/dist/dist-cjs-D-EE4Ahy.mjs.map +0 -1
  288. package/dist/dist-cjs-DNtDUWfk.mjs +0 -137
  289. package/dist/dist-cjs-DNtDUWfk.mjs.map +0 -1
  290. package/dist/dist-cjs-DbZhsgS0.mjs +0 -98
  291. package/dist/dist-cjs-DbZhsgS0.mjs.map +0 -1
  292. package/dist/dist-cjs-Dh7REKcN.mjs +0 -85
  293. package/dist/dist-cjs-Dh7REKcN.mjs.map +0 -1
  294. package/dist/dist-cjs-Dobx0Nz-.mjs +0 -181
  295. package/dist/dist-cjs-Dobx0Nz-.mjs.map +0 -1
  296. package/dist/dist-cjs-Du0UpNof.mjs +0 -437
  297. package/dist/dist-cjs-Du0UpNof.mjs.map +0 -1
  298. package/dist/dist-cjs-DyHiUfsr.mjs +0 -6
  299. package/dist/dist-cjs-HHMfMF5a.mjs +0 -209
  300. package/dist/dist-cjs-HHMfMF5a.mjs.map +0 -1
  301. package/dist/dist-cjs-UJYQzLNT.mjs +0 -64
  302. package/dist/dist-cjs-UJYQzLNT.mjs.map +0 -1
  303. package/dist/dist-cjs-UKxm5cFR.mjs +0 -5015
  304. package/dist/dist-cjs-UKxm5cFR.mjs.map +0 -1
  305. package/dist/event-streams-DnRe8uwT.mjs +0 -204
  306. package/dist/event-streams-DnRe8uwT.mjs.map +0 -1
  307. package/dist/index-CHQ39_Oe.d.mts.map +0 -1
  308. package/dist/package-CudzAF-n.mjs +0 -6
  309. package/dist/package-CudzAF-n.mjs.map +0 -1
  310. package/dist/preview-DwClLMlU.mjs.map +0 -1
  311. package/dist/preview-engine/chunk-QBEUICZD.mjs +0 -1
  312. package/dist/preview-engine/renderers-2ILG23ED.mjs +0 -1
  313. package/dist/preview-engine/renderers-B7DSGUMI.mjs +0 -1
  314. package/dist/preview-engine/renderers-BFNJ7VCI.mjs +0 -1
  315. package/dist/preview-engine/renderers-CGYIDAVV.mjs +0 -1
  316. package/dist/preview-engine/renderers-ECA3P4ON.mjs +0 -1
  317. package/dist/preview-engine/renderers-JHFT27YU.mjs +0 -1
  318. package/dist/preview-engine/renderers-MON4QFRF.mjs +0 -1
  319. package/dist/preview-engine/renderers-NQXMYNSY.mjs +0 -1
  320. package/dist/preview-engine/renderers-ORAIXUL3.mjs +0 -1
  321. package/dist/preview-engine/renderers-QVZVEYP5.mjs +0 -1
  322. package/dist/preview-engine/renderers-RKDPX5JU.mjs +0 -1
  323. package/dist/preview-engine/renderers-SBFF7JP5.mjs +0 -1
  324. package/dist/preview-engine/renderers-UNK6I4JJ.mjs +0 -1
  325. package/dist/preview-engine/renderers-W773SBPV.mjs +0 -1
  326. package/dist/preview-engine/renderers-ZHTX73PR.mjs +0 -1
  327. package/dist/preview-engine/server-utils-2FAIV45N.mjs +0 -1
  328. package/dist/renderer-31jAuNzF.mjs +0 -5
  329. package/dist/renderer-36EI7Rsz.mjs +0 -5
  330. package/dist/renderer-3io-cjTt.mjs +0 -5
  331. package/dist/renderer-9_zWTHbg.mjs +0 -5
  332. package/dist/renderer-B6KnbfdA.mjs +0 -5
  333. package/dist/renderer-B7Y2bmRc.mjs +0 -5
  334. package/dist/renderer-BA6miaaI.mjs +0 -5
  335. package/dist/renderer-BHi-Q6Z_.mjs +0 -5
  336. package/dist/renderer-BOlN9HeG.mjs +0 -5
  337. package/dist/renderer-BTOW-ceB.mjs +0 -5
  338. package/dist/renderer-BiQ0PYOp.mjs +0 -5
  339. package/dist/renderer-Bla7GDyV.mjs +0 -5
  340. package/dist/renderer-BoYyLVKy.mjs +0 -5
  341. package/dist/renderer-BqOgb7bM.mjs +0 -5
  342. package/dist/renderer-BqeM8yTJ.mjs +0 -5
  343. package/dist/renderer-BzpRe50i.mjs +0 -5
  344. package/dist/renderer-C0eAPwpr.mjs +0 -5
  345. package/dist/renderer-CGn8V63v.mjs +0 -5
  346. package/dist/renderer-CM5Syo9r.mjs +0 -5
  347. package/dist/renderer-CWCEW6Wv.mjs +0 -5
  348. package/dist/renderer-Cl70zxkP.mjs +0 -5
  349. package/dist/renderer-ClJjoVpB.mjs +0 -6
  350. package/dist/renderer-DIDSmoFA.mjs +0 -23900
  351. package/dist/renderer-DIDSmoFA.mjs.map +0 -1
  352. package/dist/renderer-DLnF-V_h.mjs +0 -5
  353. package/dist/renderer-DN1rjmcL.mjs +0 -5
  354. package/dist/renderer-DXTw9Oqb.mjs +0 -5
  355. package/dist/renderer-DbAVSBNI.mjs +0 -5
  356. package/dist/renderer-Dg8rD--8.mjs +0 -5
  357. package/dist/renderer-DnFxegGu.mjs +0 -5
  358. package/dist/renderer-DnfmelHn.mjs +0 -5
  359. package/dist/renderer-DuOjzggd.mjs +0 -5
  360. package/dist/renderer-DvYql0Cl.mjs +0 -5
  361. package/dist/renderer-LfUdjgjF.mjs +0 -5
  362. package/dist/renderer-SecCv3U8.mjs +0 -5
  363. package/dist/renderer-oZyoB4CT.mjs +0 -5
  364. package/dist/renderer-rzci_kTy.mjs +0 -5
  365. package/dist/renderer-vLaZuZbX.mjs +0 -5
  366. package/dist/signin-SVSXQw9i.mjs +0 -865
  367. package/dist/signin-SVSXQw9i.mjs.map +0 -1
  368. package/dist/sso-oidc-B3Sagl43.mjs +0 -1051
  369. package/dist/sso-oidc-B3Sagl43.mjs.map +0 -1
  370. package/dist/sts-CYeWec9i.mjs +0 -6
  371. package/dist/sts-ClVtt-bV.mjs +0 -1395
  372. package/dist/sts-ClVtt-bV.mjs.map +0 -1
@@ -0,0 +1,2173 @@
1
+ import { Ot as getMediaType, in as takeRenderDiagnostics, on as updateClockSignals } from "./dist-fjKpBYkc.mjs";
2
+ import { i as registerWebGPURenderer, t as audioRegistry } from "./renderer-BSitV56h.mjs";
3
+ import { $ as textureCache, D as ensureDevice, M as getRenderer2D, N as initHeadlessWebGPU, P as inputStore, V as mediaDecoderCache, X as shaderStore, _ as clearAllVideoCache, g as WebGPUAudioProcessor, p as SlugFontCache, s as NodeSurfaceProvider, z as lutStore } from "./dist-DkuDAIrG.mjs";
4
+ import { i as mixAudioTracks, m as preloadCompositionFonts, r as drawCompositionTree, t as compositionStateStore } from "./dist-Bb8BoMiw.mjs";
5
+ import { AudioSample, AudioSampleSource, FilePathTarget, Mp3OutputFormat, Mp4OutputFormat, Output, QUALITY_HIGH, QUALITY_LOW, QUALITY_VERY_HIGH, QUALITY_VERY_LOW, VideoSample, VideoSampleSource, WebMOutputFormat } from "mediabunny";
6
+ import fs from "node:fs";
7
+ import path from "node:path";
8
+ import { Canvas, Image, loadImage } from "skia-canvas";
9
+ import fs$1 from "node:fs/promises";
10
+ import { randomUUID } from "node:crypto";
11
+ import os from "node:os";
12
+ import pino from "pino";
13
+ import * as webcodecs from "@napi-rs/webcodecs";
14
+ import { VideoFrame } from "@napi-rs/webcodecs";
15
+ import { fileURLToPath, pathToFileURL } from "node:url";
16
+ import { registerMediabunnyServer } from "@mediabunny/server";
17
+ import { exec, spawn } from "node:child_process";
18
+ import sharp from "sharp";
19
+
20
+ //#region ../renderer/dist/bootstrap-mediabunny-CxYBXeAy.mjs
21
+ const rendererLogger = pino({
22
+ level: typeof process !== "undefined" && process.env?.LOG_LEVEL || "info",
23
+ timestamp: pino.stdTimeFunctions.isoTime,
24
+ base: { component: "renderer" }
25
+ });
26
+ /**
27
+ * Preloads only the fonts (and emoji) a VirtualMediaData tree's text uses, so
28
+ * typography renders correctly on Node.js without loading its media. Shared
29
+ * with the browser preview; emoji need a font set with
30
+ * `FontManager.registerEmojiFont()`.
31
+ */
32
+ async function preloadFonts(media, device = null) {
33
+ if (!media) return;
34
+ await preloadCompositionFonts(media, device, (err, what) => rendererLogger.error(err, `[HeadlessMediaRenderer] Failed to preload ${what}`));
35
+ }
36
+ /**
37
+ * Pipelined Asynchronous DMA Staging Ring for WebGPU.
38
+ *
39
+ * Implements double-buffered (or N-buffered) GPU staging buffers to overlap
40
+ * GPU render passes with asynchronous DMA host transfers and WebCodecs hardware encoding.
41
+ * Eliminates GPU stalls by allowing Frame i+1 to execute on the GPU while Frame i
42
+ * is asynchronously mapped and consumed by the hardware video encoder.
43
+ */
44
+ var DmaStagingRing = class {
45
+ width;
46
+ height;
47
+ capacity;
48
+ bytesPerRow;
49
+ unpaddedBytesPerRow;
50
+ bufferSize;
51
+ format;
52
+ device;
53
+ slots;
54
+ isDestroyed = false;
55
+ constructor(device, options) {
56
+ this.device = device;
57
+ this.width = options.width;
58
+ this.height = options.height;
59
+ this.capacity = Math.max(2, options.capacity ?? 2);
60
+ this.format = options.format ?? "rgba8unorm";
61
+ this.unpaddedBytesPerRow = this.width * 4;
62
+ const rowAlignment = 256;
63
+ this.bytesPerRow = Math.ceil(this.unpaddedBytesPerRow / rowAlignment) * rowAlignment;
64
+ this.bufferSize = this.bytesPerRow * this.height;
65
+ this.slots = [];
66
+ for (let i = 0; i < this.capacity; i++) {
67
+ const buffer = device.createBuffer({
68
+ size: this.bufferSize,
69
+ usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST
70
+ });
71
+ this.slots.push({
72
+ index: i,
73
+ buffer,
74
+ status: "idle",
75
+ inFlightFrame: null,
76
+ mapPromise: null
77
+ });
78
+ }
79
+ }
80
+ /**
81
+ * Prepares the staging buffer slot for writing by ensuring any prior mapping
82
+ * from a previous cycle has been completed and unmapped.
83
+ */
84
+ async prepareSlot(frameIndex) {
85
+ this.checkDestroyed();
86
+ const slot = this.slots[frameIndex % this.capacity];
87
+ if (!slot) return;
88
+ if (slot.status === "mapping" && slot.mapPromise) await slot.mapPromise;
89
+ if (slot.status === "mapped") {
90
+ slot.buffer.unmap();
91
+ slot.status = "idle";
92
+ slot.inFlightFrame = null;
93
+ slot.mapPromise = null;
94
+ }
95
+ }
96
+ /**
97
+ * Records a GPU-to-buffer DMA copy command into the provided command encoder.
98
+ */
99
+ stageTexture(encoder, texture, frameIndex) {
100
+ this.checkDestroyed();
101
+ const slot = this.slots[frameIndex % this.capacity];
102
+ if (!slot) throw new Error(`Invalid slot index for frame ${frameIndex}`);
103
+ if (slot.status === "mapped" || slot.status === "mapping") throw new Error(`Staging buffer slot ${slot.index} is busy (status: ${slot.status}) for frame ${frameIndex}. Call prepareSlot() before staging.`);
104
+ encoder.copyTextureToBuffer({ texture }, {
105
+ buffer: slot.buffer,
106
+ bytesPerRow: this.bytesPerRow
107
+ }, [this.width, this.height]);
108
+ slot.status = "rendering";
109
+ slot.inFlightFrame = frameIndex;
110
+ }
111
+ /**
112
+ * Initiates asynchronous DMA mapping on the slot for the specified frame.
113
+ * Must be called immediately after submitting the command encoder to the device queue.
114
+ */
115
+ scheduleMap(frameIndex) {
116
+ this.checkDestroyed();
117
+ const slot = this.slots[frameIndex % this.capacity];
118
+ if (!slot) throw new Error(`Invalid slot index for frame ${frameIndex}`);
119
+ slot.status = "mapping";
120
+ const mapPromise = slot.buffer.mapAsync(GPUMapMode.READ).then(() => {
121
+ slot.status = "mapped";
122
+ });
123
+ slot.mapPromise = mapPromise;
124
+ return mapPromise;
125
+ }
126
+ /**
127
+ * Awaits asynchronous mapping and extracts pixels into a tightly packed Uint8Array.
128
+ * The slot remains in the "mapped" state until releaseFrame() is called.
129
+ */
130
+ async readFramePixels(frameIndex) {
131
+ this.checkDestroyed();
132
+ const slot = this.slots[frameIndex % this.capacity];
133
+ if (!slot) throw new Error(`Invalid slot index for frame ${frameIndex}`);
134
+ if (slot.mapPromise) await slot.mapPromise;
135
+ if (slot.status !== "mapped") throw new Error(`Staging slot ${slot.index} is not mapped (status: ${slot.status}) for frame ${frameIndex}`);
136
+ const mappedRange = slot.buffer.getMappedRange();
137
+ const sourceView = new Uint8Array(mappedRange);
138
+ const output = new Uint8Array(this.unpaddedBytesPerRow * this.height);
139
+ if (this.bytesPerRow === this.unpaddedBytesPerRow) output.set(sourceView);
140
+ else for (let y = 0; y < this.height; y++) {
141
+ const srcOffset = y * this.bytesPerRow;
142
+ const dstOffset = y * this.unpaddedBytesPerRow;
143
+ output.set(sourceView.subarray(srcOffset, srcOffset + this.unpaddedBytesPerRow), dstOffset);
144
+ }
145
+ return output;
146
+ }
147
+ /**
148
+ * Awaits asynchronous mapping, extracts pixels, and immediately unmaps the staging buffer,
149
+ * returning the slot to "idle" state so it can be reused immediately.
150
+ */
151
+ async readAndReleaseFrame(frameIndex) {
152
+ const pixels = await this.readFramePixels(frameIndex);
153
+ this.releaseFrame(frameIndex);
154
+ return pixels;
155
+ }
156
+ /**
157
+ * Directly reads the mapped staging buffer into a WebCodecs VideoFrame,
158
+ * bypassing redundant intermediate JS allocations when row pitches align,
159
+ * and immediately unmaps the buffer.
160
+ */
161
+ async readFrameToVideoFrame(frameIndex, timestampUs, colorSpace, inspect) {
162
+ this.checkDestroyed();
163
+ const slot = this.slots[frameIndex % this.capacity];
164
+ if (!slot) throw new Error(`Invalid slot index for frame ${frameIndex}`);
165
+ if (slot.mapPromise) await slot.mapPromise;
166
+ if (slot.status !== "mapped") throw new Error(`Staging slot ${slot.index} is not mapped (status: ${slot.status}) for frame ${frameIndex}`);
167
+ const mappedRange = slot.buffer.getMappedRange();
168
+ const sourceView = new Uint8Array(mappedRange);
169
+ let pixelData;
170
+ if (this.bytesPerRow === this.unpaddedBytesPerRow) pixelData = sourceView;
171
+ else {
172
+ pixelData = new Uint8Array(this.unpaddedBytesPerRow * this.height);
173
+ for (let y = 0; y < this.height; y++) {
174
+ const srcOffset = y * this.bytesPerRow;
175
+ const dstOffset = y * this.unpaddedBytesPerRow;
176
+ pixelData.set(sourceView.subarray(srcOffset, srcOffset + this.unpaddedBytesPerRow), dstOffset);
177
+ }
178
+ }
179
+ inspect?.(pixelData);
180
+ const vf = new VideoFrame(pixelData, {
181
+ format: "RGBA",
182
+ codedWidth: this.width,
183
+ codedHeight: this.height,
184
+ timestamp: timestampUs,
185
+ colorSpace: {
186
+ primaries: colorSpace?.primaries ?? "bt709",
187
+ transfer: colorSpace?.transfer ?? "bt709",
188
+ matrix: colorSpace?.matrix ?? "bt709",
189
+ fullRange: colorSpace?.fullRange ?? false
190
+ }
191
+ });
192
+ this.releaseFrame(frameIndex);
193
+ return vf;
194
+ }
195
+ /**
196
+ * Unmaps the staging buffer for the specified frame and marks the slot idle.
197
+ */
198
+ releaseFrame(frameIndex) {
199
+ const slot = this.slots[frameIndex % this.capacity];
200
+ if (!slot) return;
201
+ if (slot.status === "mapped") {
202
+ try {
203
+ slot.buffer.unmap();
204
+ } catch (_) {}
205
+ slot.status = "idle";
206
+ slot.inFlightFrame = null;
207
+ slot.mapPromise = null;
208
+ }
209
+ }
210
+ /**
211
+ * Returns current status info for each slot in the ring.
212
+ */
213
+ getSlotInfos() {
214
+ return this.slots.map((slot) => ({
215
+ index: slot.index,
216
+ status: slot.status,
217
+ inFlightFrame: slot.inFlightFrame
218
+ }));
219
+ }
220
+ /**
221
+ * Destroys all staging buffers and releases resources.
222
+ */
223
+ destroy() {
224
+ if (this.isDestroyed) return;
225
+ this.isDestroyed = true;
226
+ for (const slot of this.slots) {
227
+ if (slot.status === "mapped") try {
228
+ slot.buffer.unmap();
229
+ } catch (_) {}
230
+ slot.status = "idle";
231
+ slot.inFlightFrame = null;
232
+ slot.mapPromise = null;
233
+ }
234
+ this.slots.length = 0;
235
+ }
236
+ checkDestroyed() {
237
+ if (this.isDestroyed) throw new Error("DmaStagingRing has been destroyed");
238
+ }
239
+ };
240
+ const BUILTIN_NODE_RENDERERS = [
241
+ {
242
+ packageName: "@framefields/node-lut",
243
+ ops: ["ApplyLUT"],
244
+ load: () => import("./renderer-CFqhAXkH.mjs")
245
+ },
246
+ {
247
+ packageName: "@framefields/node-audio-compressor",
248
+ ops: ["AudioCompressor"],
249
+ load: () => import("./renderer-CbiEBSGx.mjs")
250
+ },
251
+ {
252
+ packageName: "@framefields/node-audio-delay",
253
+ ops: ["AudioDelay"],
254
+ load: () => import("./renderer-hY_2WLt3.mjs")
255
+ },
256
+ {
257
+ packageName: "@framefields/node-audio-fade",
258
+ ops: ["AudioFade"],
259
+ load: () => import("./renderer-BKEfeg-4.mjs")
260
+ },
261
+ {
262
+ packageName: "@framefields/node-audio-noise-gate",
263
+ ops: ["AudioNoiseGate"],
264
+ load: () => import("./renderer-VuXt1hfq.mjs")
265
+ },
266
+ {
267
+ packageName: "@framefields/node-audio-parametric-eq",
268
+ ops: ["AudioParametricEq"],
269
+ load: () => import("./renderer-CTVnQAUm.mjs")
270
+ },
271
+ {
272
+ packageName: "@framefields/node-audio-reverb",
273
+ ops: ["AudioReverb"],
274
+ load: () => import("./renderer-B_3h5nhy.mjs")
275
+ },
276
+ {
277
+ packageName: "@framefields/node-audio-signal-extractor",
278
+ ops: ["AudioSignalExtractor"],
279
+ load: () => import("./renderer-CQPuuoVf.mjs")
280
+ },
281
+ {
282
+ packageName: "@framefields/node-blur",
283
+ ops: ["Blur"],
284
+ load: () => import("./renderer-T0LJ4Po3.mjs")
285
+ },
286
+ {
287
+ packageName: "@framefields/node-camera-parallax-3d",
288
+ ops: ["CameraParallax3D"],
289
+ load: () => import("./renderer-BKf1qDwL.mjs")
290
+ },
291
+ {
292
+ packageName: "@framefields/node-canvas-generator",
293
+ ops: ["CanvasGenerator"],
294
+ load: () => import("./renderer-DYIPWN_B.mjs")
295
+ },
296
+ {
297
+ packageName: "@framefields/node-channel-merger",
298
+ ops: ["ChannelMerger"],
299
+ load: () => import("./renderer-DoYdfDEA.mjs")
300
+ },
301
+ {
302
+ packageName: "@framefields/node-channel-splitter",
303
+ ops: ["ChannelSplitter"],
304
+ load: () => import("./renderer-Bg7647VN.mjs")
305
+ },
306
+ {
307
+ packageName: "@framefields/node-color-balance",
308
+ ops: ["ColorBalance"],
309
+ load: () => import("./renderer-9L0p8IT3.mjs")
310
+ },
311
+ {
312
+ packageName: "@framefields/node-colorkey",
313
+ ops: ["ColorKey"],
314
+ load: () => import("./renderer-B-cxK24t.mjs")
315
+ },
316
+ {
317
+ packageName: "@framefields/node-compositor",
318
+ ops: ["Compositor"],
319
+ load: () => import("./renderer-_1xGBbxn.mjs")
320
+ },
321
+ {
322
+ packageName: "@framefields/node-corner-pin",
323
+ ops: ["CornerPin"],
324
+ load: () => import("./renderer-DOmA6IKc.mjs")
325
+ },
326
+ {
327
+ packageName: "@framefields/node-crop",
328
+ ops: ["Crop"],
329
+ load: () => import("./renderer-C14mKYCw.mjs")
330
+ },
331
+ {
332
+ packageName: "@framefields/node-curves",
333
+ ops: ["Curves"],
334
+ load: () => import("./renderer-B8BdNjaN.mjs")
335
+ },
336
+ {
337
+ packageName: "@framefields/node-deflicker",
338
+ ops: ["TemporalDeflicker", "Deflicker"],
339
+ load: () => import("./renderer-CvIGa0mE.mjs")
340
+ },
341
+ {
342
+ packageName: "@framefields/node-displacement-map",
343
+ ops: ["DisplacementMap"],
344
+ load: () => import("./renderer-COwa6xlF.mjs")
345
+ },
346
+ {
347
+ packageName: "@framefields/node-extract-frame",
348
+ ops: ["ExtractFrame"],
349
+ load: () => import("./renderer-qsg-JMuN.mjs")
350
+ },
351
+ {
352
+ packageName: "@framefields/node-extract-lut",
353
+ ops: ["ExtractLUT"],
354
+ load: () => import("./renderer-Dy-DBU0X.mjs")
355
+ },
356
+ {
357
+ packageName: "@framefields/node-film-grain",
358
+ ops: ["FilmGrain"],
359
+ load: () => import("./renderer-CK-spD4j.mjs")
360
+ },
361
+ {
362
+ packageName: "@framefields/node-flip",
363
+ ops: ["Flip"],
364
+ load: () => import("./renderer-1VSi7Vum.mjs")
365
+ },
366
+ {
367
+ packageName: "@framefields/node-gradient-map",
368
+ ops: ["GradientMap"],
369
+ load: () => import("./renderer-BhhSyHKF.mjs")
370
+ },
371
+ {
372
+ packageName: "@framefields/node-halftone-screen",
373
+ ops: ["HalftoneScreen"],
374
+ load: () => import("./renderer-Q75RC5Uz.mjs")
375
+ },
376
+ {
377
+ packageName: "@framefields/node-high-pass",
378
+ ops: ["HighPass"],
379
+ load: () => import("./renderer-DmCWc4JL.mjs")
380
+ },
381
+ {
382
+ packageName: "@framefields/node-kenburns",
383
+ ops: ["KenBurns"],
384
+ load: () => import("./renderer-4BlpUHQT.mjs")
385
+ },
386
+ {
387
+ packageName: "@framefields/node-layer-style",
388
+ ops: ["LayerStyle"],
389
+ load: () => import("./renderer-CRd-cDqZ.mjs")
390
+ },
391
+ {
392
+ packageName: "@framefields/node-levels",
393
+ ops: ["Levels"],
394
+ load: () => import("./renderer-AzSX2-mZ.mjs")
395
+ },
396
+ {
397
+ packageName: "@framefields/node-liquify",
398
+ ops: ["Liquify"],
399
+ load: () => import("./renderer-BaZjoFYK.mjs")
400
+ },
401
+ {
402
+ packageName: "@framefields/node-mask-math",
403
+ ops: ["MaskMath"],
404
+ load: () => import("./renderer-CLLzQTIk.mjs")
405
+ },
406
+ {
407
+ packageName: "@framefields/node-mesh-warp",
408
+ ops: ["MeshWarp"],
409
+ load: () => import("./renderer-BLLOkeFB.mjs")
410
+ },
411
+ {
412
+ packageName: "@framefields/node-modulate",
413
+ ops: ["Modulate"],
414
+ load: () => import("./renderer-BcApaCVe.mjs")
415
+ },
416
+ {
417
+ packageName: "@framefields/node-noise-generator",
418
+ ops: ["NoiseGenerator"],
419
+ load: () => import("./renderer-timEC0Gl.mjs")
420
+ },
421
+ {
422
+ packageName: "@framefields/node-paint",
423
+ ops: ["Paint"],
424
+ load: () => import("./renderer-DOHbhGPu.mjs")
425
+ },
426
+ {
427
+ packageName: "@framefields/node-patch-heal",
428
+ ops: ["PatchHeal"],
429
+ load: () => import("./renderer-c4AorKyw.mjs")
430
+ },
431
+ {
432
+ packageName: "@framefields/node-refine-edge",
433
+ ops: ["RefineEdge"],
434
+ load: () => import("./renderer-CmuGGUDR.mjs")
435
+ },
436
+ {
437
+ packageName: "@framefields/node-refraction-caustics",
438
+ ops: ["RefractionCaustics3D"],
439
+ load: () => import("./renderer-DGtBOdst.mjs")
440
+ },
441
+ {
442
+ packageName: "@framefields/node-relight-3d",
443
+ ops: ["Relight3D"],
444
+ load: () => import("./renderer-y4EqE06a.mjs")
445
+ },
446
+ {
447
+ packageName: "@framefields/node-resizer-scaler",
448
+ ops: ["ResizerScaler"],
449
+ load: () => import("./renderer-CzBQgGD2.mjs")
450
+ },
451
+ {
452
+ packageName: "@framefields/node-rotate",
453
+ ops: ["Rotate"],
454
+ load: () => import("./renderer-Cl7PruQ1.mjs")
455
+ },
456
+ {
457
+ packageName: "@framefields/node-selective-color",
458
+ ops: ["SelectiveColor"],
459
+ load: () => import("./renderer-B5d5oM_D.mjs")
460
+ },
461
+ {
462
+ packageName: "@framefields/node-shadows-highlights",
463
+ ops: ["ShadowsHighlights"],
464
+ load: () => import("./renderer-CoNRrfqx.mjs")
465
+ },
466
+ {
467
+ packageName: "@framefields/node-shape-generator",
468
+ ops: ["ShapeGenerator"],
469
+ load: () => import("./renderer-Da06_9FZ.mjs")
470
+ },
471
+ {
472
+ packageName: "@framefields/node-signal-gate",
473
+ ops: ["SignalGate"],
474
+ load: () => import("./renderer-DuuOlYIG.mjs")
475
+ },
476
+ {
477
+ packageName: "@framefields/node-stereo-panning",
478
+ ops: ["StereoPanning"],
479
+ load: () => import("./renderer-CwMDPv0p.mjs")
480
+ },
481
+ {
482
+ packageName: "@framefields/node-tile-offset",
483
+ ops: ["TileOffset"],
484
+ load: () => import("./renderer-BVun05IK.mjs")
485
+ },
486
+ {
487
+ packageName: "@framefields/node-unsharp-mask",
488
+ ops: ["UnsharpMask"],
489
+ load: () => import("./renderer-OLaYikwi.mjs")
490
+ },
491
+ {
492
+ packageName: "@framefields/node-vignette",
493
+ ops: ["Vignette"],
494
+ load: () => import("./renderer-BTvov1NH.mjs")
495
+ },
496
+ {
497
+ packageName: "@framefields/node-vision",
498
+ ops: ["Vision"],
499
+ load: () => import("./renderer-GSFP7v2z.mjs")
500
+ }
501
+ ];
502
+ /**
503
+ * The `framefields` block of a node package's `package.json`.
504
+ *
505
+ * This is the single declaration of a node's op: renderer discovery, the
506
+ * effects generator (`scripts/generate-effects.ts`) and the skill catalog all
507
+ * read it from here. There is deliberately no fallback that derives an op from
508
+ * the package name. See `spec/sync-node.md` §5.1.
509
+ */
510
+ const NODE_KINDS = [
511
+ "effect",
512
+ "audio",
513
+ "source",
514
+ "signal",
515
+ "utility"
516
+ ];
517
+ const IDENTIFIER = /^[A-Za-z][A-Za-z0-9]*$/;
518
+ var NodeManifestError = class extends Error {
519
+ constructor(problems) {
520
+ super(`Invalid node manifest(s):\n${problems.map((p) => ` - ${p}`).join("\n")}`);
521
+ this.problems = problems;
522
+ this.name = "NodeManifestError";
523
+ }
524
+ };
525
+ function isRecord(value) {
526
+ return typeof value === "object" && value !== null && !Array.isArray(value);
527
+ }
528
+ /**
529
+ * Validates one parsed `package.json`. Returns the manifest, or the list of
530
+ * problems that make it unusable. Never throws.
531
+ */
532
+ function parseNodeManifest(pkg, dir, nodePath = dir) {
533
+ const problems = [];
534
+ if (!isRecord(pkg)) return { problems: [`${dir}: package.json is not an object`] };
535
+ const block = pkg.framefields;
536
+ if (!isRecord(block)) return { problems: [`${dir}: package.json has no "framefields" block (expected { "type": "<Op>", "kind": "<kind>" })`] };
537
+ const { type, kind, aliases, schema, className, factory, custom } = block;
538
+ if (typeof type !== "string" || !IDENTIFIER.test(type)) problems.push(`${dir}: framefields.type must be an op identifier`);
539
+ if (typeof kind !== "string" || !NODE_KINDS.includes(kind)) problems.push(`${dir}: framefields.kind must be one of ${NODE_KINDS.join(", ")}`);
540
+ if (aliases !== void 0 && !(Array.isArray(aliases) && aliases.every((a) => typeof a === "string" && IDENTIFIER.test(a)))) problems.push(`${dir}: framefields.aliases must be an array of op identifiers`);
541
+ for (const [key, value] of Object.entries({
542
+ schema,
543
+ className,
544
+ factory
545
+ })) if (value !== void 0 && (typeof value !== "string" || !IDENTIFIER.test(value))) problems.push(`${dir}: framefields.${key} must be an identifier`);
546
+ if (custom !== void 0 && typeof custom !== "boolean") problems.push(`${dir}: framefields.custom must be a boolean`);
547
+ if (problems.length > 0) return { problems };
548
+ const exportsField = isRecord(pkg.exports) ? pkg.exports["./renderer"] : null;
549
+ const legacy = isRecord(pkg.gatewai) ? pkg.gatewai : {};
550
+ return {
551
+ problems,
552
+ manifest: {
553
+ dir,
554
+ path: nodePath,
555
+ packageName: typeof pkg.name === "string" ? pkg.name : dir,
556
+ type,
557
+ kind,
558
+ aliases: aliases ?? [],
559
+ schema,
560
+ className,
561
+ factory,
562
+ custom: custom === true,
563
+ enabled: legacy.enabled !== false,
564
+ rendererExports: isRecord(exportsField) ? exportsField : void 0
565
+ }
566
+ };
567
+ }
568
+ /**
569
+ * Reads every `nodes/node-*` package and returns its manifest, sorted by
570
+ * directory name. Throws a single {@link NodeManifestError} listing every
571
+ * problem: a missing or malformed block, or an op/alias claimed twice.
572
+ */
573
+ function loadNodeManifests(nodesDir) {
574
+ const problems = [];
575
+ const manifests = [];
576
+ const dirs = fs.readdirSync(nodesDir).filter((d) => d.startsWith("node-") && fs.existsSync(path.join(nodesDir, d, "package.json"))).sort();
577
+ for (const dir of dirs) {
578
+ const nodePath = path.join(nodesDir, dir);
579
+ let pkg;
580
+ try {
581
+ pkg = JSON.parse(fs.readFileSync(path.join(nodePath, "package.json"), "utf-8"));
582
+ } catch (err) {
583
+ problems.push(`${dir}: package.json is not valid JSON (${String(err)})`);
584
+ continue;
585
+ }
586
+ const parsed = parseNodeManifest(pkg, dir, nodePath);
587
+ problems.push(...parsed.problems);
588
+ if (parsed.manifest) manifests.push(parsed.manifest);
589
+ }
590
+ const owners = /* @__PURE__ */ new Map();
591
+ for (const manifest of manifests) for (const op of [manifest.type, ...manifest.aliases]) {
592
+ const owner = owners.get(op);
593
+ if (owner) problems.push(`${manifest.dir}: op "${op}" is already declared by ${owner}`);
594
+ else owners.set(op, manifest.dir);
595
+ }
596
+ if (problems.length > 0) throw new NodeManifestError(problems);
597
+ return manifests;
598
+ }
599
+ let __filename = "";
600
+ let __dirname = "";
601
+ if (typeof process !== "undefined" && !!process.versions?.node) {
602
+ __filename = fileURLToPath(import.meta.url);
603
+ __dirname = path.dirname(__filename);
604
+ }
605
+ /**
606
+ * Picks the renderer entry file: development source in dev/vitest, otherwise
607
+ * the bundled dist, falling back to source when dist has not been built.
608
+ */
609
+ function resolveRendererEntry(nodePath, rendererExports) {
610
+ const exists = (rel) => !!rel && fs.existsSync(path.join(nodePath, rel));
611
+ if ((process.env.NODE_ENV !== "production" || Boolean(process.env.VITEST)) && exists(rendererExports.development)) return rendererExports.development;
612
+ const bundled = rendererExports.import || rendererExports.default;
613
+ if (exists(bundled)) return bundled;
614
+ if (exists(rendererExports.development)) return rendererExports.development;
615
+ }
616
+ async function registerNode(manifest) {
617
+ if (!manifest.rendererExports) return;
618
+ const entry = resolveRendererEntry(manifest.path, manifest.rendererExports);
619
+ if (!entry) return;
620
+ const mod = await import(pathToFileURL(path.join(manifest.path, entry)).href);
621
+ registerPlugin([manifest.type, ...manifest.aliases], mod?.default);
622
+ }
623
+ function registerPlugin(ops, rendererPlugin) {
624
+ if (!rendererPlugin) return;
625
+ if (rendererPlugin.WebGPURenderer) for (const op of ops) registerWebGPURenderer(op, rendererPlugin.WebGPURenderer);
626
+ if (rendererPlugin.audioProcessor) for (const op of ops) audioRegistry.register(op, rendererPlugin.audioProcessor);
627
+ }
628
+ /**
629
+ * Registers the renderers bundled with this package. They are imported by
630
+ * package name, so they resolve from `node_modules` (or the published bundle)
631
+ * and never depend on a `nodes/` directory on disk.
632
+ */
633
+ async function registerBuiltinNodeRenderers() {
634
+ const failed = [];
635
+ await Promise.all(BUILTIN_NODE_RENDERERS.map(async ({ packageName, ops, load }) => {
636
+ try {
637
+ registerPlugin(ops, (await load()).default);
638
+ } catch (err) {
639
+ failed.push(packageName);
640
+ console.error(`[HeadlessWebGPURenderer] Failed to load renderer ${packageName}:`, err);
641
+ }
642
+ }));
643
+ if (failed.length === BUILTIN_NODE_RENDERERS.length) throw new Error("[HeadlessWebGPURenderer] No built-in node renderers could be loaded.");
644
+ }
645
+ /**
646
+ * Registers the built-in node renderers, then any extra nodes found in the
647
+ * directory named by `FRAMEFIELDS_NODES_DIR` (local node development). A node in
648
+ * that directory overrides the built-in renderer for the same op.
649
+ */
650
+ async function discoverAndRegisterNodeRenderers() {
651
+ if (typeof process === "undefined" || !process.versions?.node) return;
652
+ await registerBuiltinNodeRenderers();
653
+ const extraDir = process.env.FRAMEFIELDS_NODES_DIR;
654
+ if (!extraDir) return;
655
+ const nodesDir = path.resolve(extraDir);
656
+ if (!fs.existsSync(nodesDir)) {
657
+ console.warn(`[HeadlessWebGPURenderer] FRAMEFIELDS_NODES_DIR does not exist: ${nodesDir}`);
658
+ return;
659
+ }
660
+ rendererLogger.debug(`[HeadlessWebGPURenderer] Discovering node renderers in: ${nodesDir}`);
661
+ const manifests = loadNodeManifests(nodesDir);
662
+ for (const manifest of manifests) {
663
+ if (!manifest.enabled) continue;
664
+ try {
665
+ await registerNode(manifest);
666
+ } catch (err) {
667
+ console.error(`[HeadlessWebGPURenderer] Failed to discover renderer for node ${manifest.dir}:`, err);
668
+ }
669
+ }
670
+ }
671
+ const isAMD = () => {
672
+ if (process.platform !== "linux") return false;
673
+ try {
674
+ if (fs.existsSync("/sys/module/amdgpu") || fs.existsSync("/dev/kfd")) return true;
675
+ const drmDir = "/sys/class/drm";
676
+ if (fs.existsSync(drmDir)) {
677
+ const files = fs.readdirSync(drmDir);
678
+ for (const file of files) {
679
+ const vendorPath = `${drmDir}/${file}/device/vendor`;
680
+ if (fs.existsSync(vendorPath)) {
681
+ if (fs.readFileSync(vendorPath, "utf8").trim() === "0x1002") return true;
682
+ }
683
+ }
684
+ }
685
+ } catch {}
686
+ return false;
687
+ };
688
+ const bootstrapMediabunny = () => {
689
+ const getHardwareContextOption = () => {
690
+ if (isAMD()) return { hardwareContext: null };
691
+ return {};
692
+ };
693
+ registerMediabunnyServer(getHardwareContextOption());
694
+ for (const key of Object.keys(webcodecs)) if (typeof globalThis[key] === "undefined") globalThis[key] = webcodecs[key];
695
+ };
696
+
697
+ //#endregion
698
+ //#region ../renderer/dist/headless-webgpu-renderer-D4Uc6ba-.mjs
699
+ const DEFAULTS = {
700
+ blackPixelThreshold: .1,
701
+ blackFrameRatio: .98,
702
+ minBlackSec: .5,
703
+ minFrozenSec: 2,
704
+ silenceDbfs: -60,
705
+ minSilenceSec: 2,
706
+ lufsTolerance: 1
707
+ };
708
+ const SAMPLE_STEP = 4;
709
+ const FROZEN_DIFF = .25;
710
+ /** Measures frames handed to it in order, as RGBA bytes. */
711
+ var FrameInspector = class {
712
+ blackLuma;
713
+ blackRatio;
714
+ black = [];
715
+ sameAsPrevious = [];
716
+ lumaSum = 0;
717
+ previous = null;
718
+ constructor(options = {}) {
719
+ this.blackLuma = (options.blackPixelThreshold ?? DEFAULTS.blackPixelThreshold) * 255;
720
+ this.blackRatio = options.blackFrameRatio ?? DEFAULTS.blackFrameRatio;
721
+ }
722
+ get frameCount() {
723
+ return this.black.length;
724
+ }
725
+ inspect(rgba, width, height) {
726
+ const cols = Math.ceil(width / SAMPLE_STEP);
727
+ const rows = Math.ceil(height / SAMPLE_STEP);
728
+ const luma = new Uint8Array(cols * rows);
729
+ let blackCount = 0;
730
+ let sum = 0;
731
+ let k = 0;
732
+ for (let y = 0; y < height; y += SAMPLE_STEP) {
733
+ let p = y * width * 4;
734
+ for (let x = 0; x < width; x += SAMPLE_STEP, p += SAMPLE_STEP * 4) {
735
+ const l = .2126 * rgba[p] + .7152 * rgba[p + 1] + .0722 * rgba[p + 2];
736
+ luma[k++] = l;
737
+ sum += l;
738
+ if (l <= this.blackLuma) blackCount++;
739
+ }
740
+ }
741
+ const n = luma.length;
742
+ this.lumaSum += sum / n / 255;
743
+ this.black.push(blackCount / n >= this.blackRatio);
744
+ let same = false;
745
+ if (this.previous && this.previous.length === n) {
746
+ let diff = 0;
747
+ for (let i = 0; i < n; i++) diff += Math.abs(luma[i] - this.previous[i]);
748
+ same = diff / n < FROZEN_DIFF;
749
+ }
750
+ this.sameAsPrevious.push(same);
751
+ this.previous = luma;
752
+ }
753
+ stats(expectedFrames, encodedFrames, fps, width, height, options = {}) {
754
+ const minBlack = Math.max(1, Math.round((options.minBlackSec ?? DEFAULTS.minBlackSec) * fps));
755
+ const minFrozen = Math.max(2, Math.round((options.minFrozenSec ?? DEFAULTS.minFrozenSec) * fps));
756
+ const blackSegments = runs(this.black, minBlack).map(([a, b]) => ({
757
+ startSec: a / fps,
758
+ endSec: b / fps
759
+ }));
760
+ const frozenSegments = runs(this.sameAsPrevious, minFrozen - 1).map(([a, b]) => ({
761
+ startSec: (a - 1) / fps,
762
+ endSec: b / fps
763
+ }));
764
+ return {
765
+ expectedFrames,
766
+ encodedFrames,
767
+ fps,
768
+ width,
769
+ height,
770
+ meanLuma: this.frameCount > 0 ? this.lumaSum / this.frameCount : 0,
771
+ blackSegments,
772
+ frozenSegments
773
+ };
774
+ }
775
+ };
776
+ /** [start, end) index ranges where flags hold for at least minLength in a row. */
777
+ function runs(flags, minLength) {
778
+ const out = [];
779
+ let start = -1;
780
+ for (let i = 0; i <= flags.length; i++) if (i < flags.length && flags[i]) {
781
+ if (start < 0) start = i;
782
+ } else if (start >= 0) {
783
+ if (i - start >= minLength) out.push([start, i]);
784
+ start = -1;
785
+ }
786
+ return out;
787
+ }
788
+ const toDb = (power) => power > 0 ? 10 * Math.log10(power) : -Infinity;
789
+ /** Measures a mixed audio track (one Float32Array per channel). */
790
+ function analyzeAudio(channels, sampleRate, options = {}) {
791
+ const length = channels[0]?.length ?? 0;
792
+ let peak = 0;
793
+ let sumSquares = 0;
794
+ let clipped = 0;
795
+ for (const ch of channels) for (let i = 0; i < length; i++) {
796
+ const v = Math.abs(ch[i]);
797
+ if (v > peak) peak = v;
798
+ if (v >= .9999) clipped++;
799
+ sumSquares += v * v;
800
+ }
801
+ const total = length * Math.max(1, channels.length);
802
+ const silenceDbfs = options.silenceDbfs ?? DEFAULTS.silenceDbfs;
803
+ const window = Math.max(1, Math.round(sampleRate * .1));
804
+ const silent = [];
805
+ for (let start = 0; start < length; start += window) {
806
+ const end = Math.min(length, start + window);
807
+ let s = 0;
808
+ for (const ch of channels) for (let i = start; i < end; i++) s += ch[i] * ch[i];
809
+ silent.push(toDb(s / ((end - start) * channels.length)) < silenceDbfs);
810
+ }
811
+ const silentSegments = runs(silent, Math.max(1, Math.round((options.minSilenceSec ?? DEFAULTS.minSilenceSec) / .1))).map(([a, b]) => ({
812
+ startSec: a * window / sampleRate,
813
+ endSec: Math.min(length, b * window) / sampleRate
814
+ }));
815
+ const peakDbfs = 20 * Math.log10(Math.max(peak, 1e-12));
816
+ return {
817
+ sampleRate,
818
+ channels: channels.length,
819
+ durationSec: length / sampleRate,
820
+ peakDbfs,
821
+ rmsDbfs: toDb(sumSquares / Math.max(1, total)),
822
+ integratedLufs: peakDbfs < silenceDbfs ? null : integratedLoudness(channels, sampleRate),
823
+ clippedSamples: clipped,
824
+ silentSegments
825
+ };
826
+ }
827
+ /** BS.1770 K-weighting stage 1: +4 dB high shelf around 1.5 kHz (the head). */
828
+ function highShelf(sampleRate) {
829
+ const A = 10 ** (4 / 40);
830
+ const w0 = 2 * Math.PI * 1500 / sampleRate;
831
+ const cos = Math.cos(w0);
832
+ const alpha = Math.sin(w0) / (2 / Math.SQRT2);
833
+ const sq = 2 * Math.sqrt(A) * alpha;
834
+ const a0 = A + 1 - (A - 1) * cos + sq;
835
+ return {
836
+ b0: A * (A + 1 + (A - 1) * cos + sq) / a0,
837
+ b1: -2 * A * (A - 1 + (A + 1) * cos) / a0,
838
+ b2: A * (A + 1 + (A - 1) * cos - sq) / a0,
839
+ a1: 2 * (A - 1 - (A + 1) * cos) / a0,
840
+ a2: (A + 1 - (A - 1) * cos - sq) / a0
841
+ };
842
+ }
843
+ /** BS.1770 K-weighting stage 2: 38 Hz high pass, Q 0.5 (the RLB curve). */
844
+ function highPass(sampleRate) {
845
+ const w0 = 2 * Math.PI * 38 / sampleRate;
846
+ const cos = Math.cos(w0);
847
+ const alpha = Math.sin(w0) / (2 * .5);
848
+ const a0 = 1 + alpha;
849
+ return {
850
+ b0: (1 + cos) / 2 / a0,
851
+ b1: -(1 + cos) / a0,
852
+ b2: (1 + cos) / 2 / a0,
853
+ a1: -2 * cos / a0,
854
+ a2: (1 - alpha) / a0
855
+ };
856
+ }
857
+ function filter(input, f) {
858
+ const out = new Float32Array(input.length);
859
+ let x1 = 0;
860
+ let x2 = 0;
861
+ let y1 = 0;
862
+ let y2 = 0;
863
+ for (let i = 0; i < input.length; i++) {
864
+ const x = input[i];
865
+ const y = f.b0 * x + f.b1 * x1 + f.b2 * x2 - f.a1 * y1 - f.a2 * y2;
866
+ out[i] = y;
867
+ x2 = x1;
868
+ x1 = x;
869
+ y2 = y1;
870
+ y1 = y;
871
+ }
872
+ return out;
873
+ }
874
+ /**
875
+ * ITU-R BS.1770-4 integrated loudness (LUFS): K-weighted mean square in
876
+ * 400 ms blocks with 75% overlap, gated at -70 LUFS and then at 10 LU below
877
+ * the mean of what passed. Channels weigh 1 (stereo and mono layouts).
878
+ */
879
+ function integratedLoudness(channels, sampleRate) {
880
+ const shelf = highShelf(sampleRate);
881
+ const hp = highPass(sampleRate);
882
+ const weighted = channels.map((ch) => filter(filter(ch, shelf), hp));
883
+ const length = weighted[0]?.length ?? 0;
884
+ const block = Math.round(.4 * sampleRate);
885
+ const hop = Math.round(.1 * sampleRate);
886
+ if (length < block) return null;
887
+ const blockPower = [];
888
+ for (let start = 0; start + block <= length; start += hop) {
889
+ let power = 0;
890
+ for (const ch of weighted) {
891
+ let s = 0;
892
+ for (let i = start; i < start + block; i++) s += ch[i] * ch[i];
893
+ power += s / block;
894
+ }
895
+ blockPower.push(power);
896
+ }
897
+ const lufs = (power) => -.691 + toDb(power);
898
+ const mean = (xs) => xs.reduce((a, b) => a + b, 0) / xs.length;
899
+ const absolute = blockPower.filter((p) => lufs(p) > -70);
900
+ if (absolute.length === 0) return null;
901
+ const relativeGate = lufs(mean(absolute)) - 10;
902
+ const gated = absolute.filter((p) => lufs(p) > relativeGate);
903
+ return gated.length > 0 ? lufs(mean(gated)) : null;
904
+ }
905
+ /** Turn measurements and document diagnostics into a report with issues. */
906
+ function buildQaReport(parts) {
907
+ const { video, audio, options = {} } = parts;
908
+ const issues = [];
909
+ const at = (s) => `${s.startSec.toFixed(2)}–${s.endSec.toFixed(2)} s`;
910
+ if (video) {
911
+ if (video.encodedFrames !== video.expectedFrames) issues.push({
912
+ severity: "error",
913
+ code: "frames_missing",
914
+ message: `${video.encodedFrames} of ${video.expectedFrames} frames were encoded`
915
+ });
916
+ for (const s of video.blackSegments) issues.push({
917
+ severity: "warning",
918
+ code: "black_frames",
919
+ message: `Black frames at ${at(s)}`,
920
+ ...s
921
+ });
922
+ for (const s of video.frozenSegments) issues.push({
923
+ severity: "info",
924
+ code: "frozen_video",
925
+ message: `Picture does not change at ${at(s)}`,
926
+ ...s
927
+ });
928
+ }
929
+ if (audio) if (audio.integratedLufs === null) issues.push({
930
+ severity: "info",
931
+ code: "audio_silent",
932
+ message: "The audio track is silent"
933
+ });
934
+ else {
935
+ if (audio.clippedSamples > 0) issues.push({
936
+ severity: "warning",
937
+ code: "audio_clipping",
938
+ message: `${audio.clippedSamples} audio sample(s) at or beyond full scale (peak ${audio.peakDbfs.toFixed(1)} dBFS)`
939
+ });
940
+ for (const s of audio.silentSegments) issues.push({
941
+ severity: "info",
942
+ code: "audio_silence",
943
+ message: `Silence at ${at(s)}`,
944
+ ...s
945
+ });
946
+ if (options.targetLufs !== void 0) {
947
+ const off = audio.integratedLufs - options.targetLufs;
948
+ const tolerance = options.lufsTolerance ?? DEFAULTS.lufsTolerance;
949
+ if (Math.abs(off) > tolerance) issues.push({
950
+ severity: "warning",
951
+ code: "loudness_off_target",
952
+ message: `Integrated loudness ${audio.integratedLufs.toFixed(1)} LUFS is ${Math.abs(off).toFixed(1)} LU ${off > 0 ? "above" : "below"} the ${options.targetLufs} LUFS target`
953
+ });
954
+ }
955
+ }
956
+ for (const d of parts.diagnostics ?? []) issues.push({
957
+ severity: d.severity,
958
+ code: d.code,
959
+ message: d.message,
960
+ ...d.layerId !== void 0 && { layerId: d.layerId }
961
+ });
962
+ return {
963
+ passed: !issues.some((i) => i.severity !== "info"),
964
+ ...video && { video },
965
+ ...audio && { audio },
966
+ issues
967
+ };
968
+ }
969
+ /** A short human- and agent-readable summary of a report. */
970
+ function formatQaReport(report) {
971
+ const lines = [`QA ${report.passed ? "passed" : "FAILED"}`];
972
+ const v = report.video;
973
+ if (v) lines.push(` video: ${v.encodedFrames}/${v.expectedFrames} frames, ${v.width}x${v.height} @ ${v.fps} fps, mean luma ${v.meanLuma.toFixed(2)}`);
974
+ const a = report.audio;
975
+ if (a) {
976
+ const lufs = a.integratedLufs === null ? "silent" : `${a.integratedLufs.toFixed(1)} LUFS`;
977
+ lines.push(` audio: ${a.durationSec.toFixed(2)} s, ${lufs}, peak ${a.peakDbfs.toFixed(1)} dBFS, rms ${a.rmsDbfs.toFixed(1)} dBFS`);
978
+ }
979
+ for (const i of report.issues) lines.push(` ${i.severity.padEnd(7)} ${i.code}: ${i.message}`);
980
+ return lines.join("\n");
981
+ }
982
+ /**
983
+ * High-performance hardware video encoding pipeline integrating WebGPU textures with WebCodecs.
984
+ *
985
+ * Provides:
986
+ * 1. Direct hardware acceleration when running with WebCodecs / OffscreenCanvas.
987
+ * 2. Pipelined asynchronous double-buffered DMA staging ring for headless Node.js (Dawn native),
988
+ * allowing GPU Frame i+1 rendering to overlap concurrently with Frame i hardware encoding.
989
+ */
990
+ var ZeroCopyWebCodecsPipeline = class {
991
+ width;
992
+ height;
993
+ fps;
994
+ ringCapacity;
995
+ device;
996
+ videoSource;
997
+ stagingRing;
998
+ colorSpace;
999
+ directCanvasSource;
1000
+ onFrame;
1001
+ inFlightFrames = [];
1002
+ cycleStartTimes = /* @__PURE__ */ new Map();
1003
+ totalFramesEnqueued = 0;
1004
+ totalFramesEncoded = 0;
1005
+ totalGpuSubmitTimeMs = 0;
1006
+ totalEncodeTimeMs = 0;
1007
+ totalCycleTimeMs = 0;
1008
+ maxCycleTimeMs = 0;
1009
+ minCycleTimeMs = Number.POSITIVE_INFINITY;
1010
+ pipelineStartTime = 0;
1011
+ constructor(device, options) {
1012
+ this.device = device;
1013
+ this.width = options.width;
1014
+ this.height = options.height;
1015
+ this.fps = options.fps;
1016
+ this.videoSource = options.videoSource;
1017
+ this.ringCapacity = Math.max(2, options.ringCapacity ?? 2);
1018
+ this.colorSpace = options.colorSpace;
1019
+ this.directCanvasSource = options.directCanvasSource;
1020
+ this.onFrame = options.onFrame;
1021
+ this.stagingRing = new DmaStagingRing(device, {
1022
+ width: this.width,
1023
+ height: this.height,
1024
+ capacity: this.ringCapacity
1025
+ });
1026
+ this.pipelineStartTime = performance.now();
1027
+ }
1028
+ /**
1029
+ * Returns true if direct zero-copy from an OffscreenCanvas or texture is active.
1030
+ */
1031
+ canDirectZeroCopy() {
1032
+ return this.directCanvasSource !== void 0 && this.directCanvasSource !== null;
1033
+ }
1034
+ /**
1035
+ * Enqueues a rendered GPU texture frame into the pipeline.
1036
+ *
1037
+ * Uses double-buffered DMA staging: if the staging ring has reached capacity,
1038
+ * the oldest in-flight frame is asynchronously consumed and added to the video encoder
1039
+ * before staging the new frame, guaranteeing constant bounded VRAM usage and zero GPU stalls.
1040
+ */
1041
+ async enqueueFrame(frameIndex, targetTexture, encoder) {
1042
+ const cycleStartTime = performance.now();
1043
+ if (this.inFlightFrames.length >= this.ringCapacity) {
1044
+ const oldestFrame = this.inFlightFrames.shift();
1045
+ if (oldestFrame !== void 0) await this.consumeAndEncode(oldestFrame);
1046
+ }
1047
+ if (this.canDirectZeroCopy()) {
1048
+ const gpuStart = performance.now();
1049
+ this.device.queue.submit([encoder.finish()]);
1050
+ this.totalGpuSubmitTimeMs += performance.now() - gpuStart;
1051
+ this.cycleStartTimes.set(frameIndex, cycleStartTime);
1052
+ this.totalFramesEnqueued++;
1053
+ await this.consumeAndEncode(frameIndex);
1054
+ } else {
1055
+ await this.stagingRing.prepareSlot(frameIndex);
1056
+ this.stagingRing.stageTexture(encoder, targetTexture, frameIndex);
1057
+ const gpuStart = performance.now();
1058
+ this.device.queue.submit([encoder.finish()]);
1059
+ this.totalGpuSubmitTimeMs += performance.now() - gpuStart;
1060
+ this.stagingRing.scheduleMap(frameIndex);
1061
+ this.inFlightFrames.push(frameIndex);
1062
+ this.cycleStartTimes.set(frameIndex, cycleStartTime);
1063
+ this.totalFramesEnqueued++;
1064
+ }
1065
+ }
1066
+ /**
1067
+ * Drains all remaining in-flight frames in the pipeline, ensuring every enqueued
1068
+ * frame has been encoded and added to the video sample source.
1069
+ */
1070
+ async drain() {
1071
+ while (this.inFlightFrames.length > 0) {
1072
+ const frameIndex = this.inFlightFrames.shift();
1073
+ if (frameIndex !== void 0) await this.consumeAndEncode(frameIndex);
1074
+ }
1075
+ }
1076
+ /**
1077
+ * Returns runtime throughput, latency, and pipeline statistics.
1078
+ */
1079
+ getStats() {
1080
+ const totalElapsedMs = performance.now() - this.pipelineStartTime;
1081
+ const throughputFps = totalElapsedMs > 0 ? this.totalFramesEncoded / totalElapsedMs * 1e3 : 0;
1082
+ const avgSubmit = this.totalFramesEnqueued > 0 ? this.totalGpuSubmitTimeMs / this.totalFramesEnqueued : 0;
1083
+ const avgEncode = this.totalFramesEncoded > 0 ? this.totalEncodeTimeMs / this.totalFramesEncoded : 0;
1084
+ const avgCycle = this.totalFramesEncoded > 0 ? this.totalCycleTimeMs / this.totalFramesEncoded : 0;
1085
+ return {
1086
+ totalFramesEnqueued: this.totalFramesEnqueued,
1087
+ totalFramesEncoded: this.totalFramesEncoded,
1088
+ averageGpuSubmitTimeMs: avgSubmit,
1089
+ averageEncodeTimeMs: avgEncode,
1090
+ averageCycleTimeMs: avgCycle,
1091
+ maxCycleTimeMs: this.maxCycleTimeMs,
1092
+ minCycleTimeMs: Number.isFinite(this.minCycleTimeMs) ? this.minCycleTimeMs : 0,
1093
+ throughputFps,
1094
+ ringCapacity: this.ringCapacity,
1095
+ isDirectZeroCopy: this.canDirectZeroCopy()
1096
+ };
1097
+ }
1098
+ /**
1099
+ * Destroys the pipeline and releases all staging buffers.
1100
+ */
1101
+ destroy() {
1102
+ this.inFlightFrames.length = 0;
1103
+ this.cycleStartTimes.clear();
1104
+ this.stagingRing.destroy();
1105
+ }
1106
+ async consumeAndEncode(frameIndex) {
1107
+ const encodeStart = performance.now();
1108
+ const timestampUs = frameIndex / this.fps * 1e6;
1109
+ if (this.canDirectZeroCopy()) {
1110
+ const sample = new VideoSample(this.directCanvasSource, {
1111
+ duration: 1 / this.fps,
1112
+ timestamp: frameIndex / this.fps
1113
+ });
1114
+ try {
1115
+ await this.videoSource.add(sample);
1116
+ } finally {
1117
+ sample.close();
1118
+ }
1119
+ } else {
1120
+ const onFrame = this.onFrame;
1121
+ const vf = await this.stagingRing.readFrameToVideoFrame(frameIndex, timestampUs, this.colorSpace, onFrame && ((rgba) => onFrame(rgba, frameIndex)));
1122
+ try {
1123
+ const sample = new VideoSample(vf, {
1124
+ duration: 1 / this.fps,
1125
+ timestamp: frameIndex / this.fps
1126
+ });
1127
+ try {
1128
+ await this.videoSource.add(sample);
1129
+ } finally {
1130
+ sample.close();
1131
+ }
1132
+ } finally {
1133
+ vf.close();
1134
+ }
1135
+ }
1136
+ const now = performance.now();
1137
+ this.totalEncodeTimeMs += now - encodeStart;
1138
+ this.totalFramesEncoded++;
1139
+ const cycleStart = this.cycleStartTimes.get(frameIndex);
1140
+ if (cycleStart !== void 0) {
1141
+ const cycleDuration = now - cycleStart;
1142
+ this.cycleStartTimes.delete(frameIndex);
1143
+ this.totalCycleTimeMs += cycleDuration;
1144
+ if (cycleDuration > this.maxCycleTimeMs) this.maxCycleTimeMs = cycleDuration;
1145
+ if (cycleDuration < this.minCycleTimeMs) this.minCycleTimeMs = cycleDuration;
1146
+ }
1147
+ }
1148
+ };
1149
+ async function convertMp4ToGif(inputMp4Path, outputGifPath) {
1150
+ return new Promise((resolve$1, reject) => {
1151
+ const ffmpeg = spawn("ffmpeg", [
1152
+ "-i",
1153
+ inputMp4Path,
1154
+ "-vf",
1155
+ "split[s0][s1];[s0]palettegen[p];[s1][p]paletteuse",
1156
+ "-y",
1157
+ outputGifPath
1158
+ ]);
1159
+ let errorOutput = "";
1160
+ ffmpeg.stderr.on("data", (data) => {
1161
+ errorOutput += data.toString();
1162
+ });
1163
+ ffmpeg.on("close", async (code) => {
1164
+ if (code === 0) resolve$1();
1165
+ else {
1166
+ rendererLogger.error({
1167
+ code,
1168
+ err: errorOutput
1169
+ }, "[convertMp4ToGif] ffmpeg conversion failed");
1170
+ reject(/* @__PURE__ */ new Error(`ffmpeg exited with code ${code}: ${errorOutput}`));
1171
+ }
1172
+ });
1173
+ ffmpeg.on("error", (err) => {
1174
+ rendererLogger.error({ err }, "[convertMp4ToGif] ffmpeg spawn error");
1175
+ reject(err);
1176
+ });
1177
+ });
1178
+ }
1179
+ bootstrapMediabunny();
1180
+ const globalObj = globalThis;
1181
+ if (typeof globalThis !== "undefined") {
1182
+ globalObj.__IS_HEADLESS_RENDERER__ = true;
1183
+ globalObj.__GATEWAI_DELAYS__ ??= /* @__PURE__ */ new Set();
1184
+ }
1185
+ if (typeof globalThis.requestAnimationFrame === "undefined") {
1186
+ globalObj.requestAnimationFrame = (cb) => setTimeout(cb, 0);
1187
+ globalObj.cancelAnimationFrame = (id) => clearTimeout(id);
1188
+ }
1189
+ const dummyDomElement = {
1190
+ clientWidth: 1920,
1191
+ clientHeight: 1080,
1192
+ getBoundingClientRect: () => ({
1193
+ x: 0,
1194
+ y: 0,
1195
+ left: 0,
1196
+ top: 0,
1197
+ right: 1920,
1198
+ bottom: 1080,
1199
+ width: 1920,
1200
+ height: 1080
1201
+ }),
1202
+ style: {},
1203
+ appendChild: () => {},
1204
+ removeChild: () => {},
1205
+ addEventListener: () => {},
1206
+ removeEventListener: () => {},
1207
+ getAttribute: () => null
1208
+ };
1209
+ if (typeof globalThis.OffscreenCanvas === "undefined") globalObj.OffscreenCanvas = Canvas;
1210
+ if (typeof globalThis.HTMLCanvasElement === "undefined") globalObj.HTMLCanvasElement = Canvas;
1211
+ if (typeof globalThis.Image === "undefined") globalObj.Image = Image;
1212
+ if (typeof globalThis.window === "undefined") globalObj.window = globalThis;
1213
+ if (!globalThis.location) globalObj.location = new URL("http://localhost");
1214
+ if (typeof globalThis.IntersectionObserver === "undefined") globalObj.IntersectionObserver = class IntersectionObserver {
1215
+ observe() {}
1216
+ unobserve() {}
1217
+ disconnect() {}
1218
+ };
1219
+ if (typeof globalThis.document === "undefined") globalObj.document = {
1220
+ createElement: (tag) => {
1221
+ if (tag === "canvas") return new Canvas(1, 1);
1222
+ return { ...dummyDomElement };
1223
+ },
1224
+ getElementsByTagName: () => [],
1225
+ documentElement: dummyDomElement,
1226
+ body: dummyDomElement
1227
+ };
1228
+ else {
1229
+ const docObj = globalThis.document;
1230
+ if (typeof docObj.getElementsByTagName === "undefined") docObj.getElementsByTagName = () => [];
1231
+ docObj.documentElement ??= dummyDomElement;
1232
+ docObj.body ??= dummyDomElement;
1233
+ }
1234
+ const skiaProto = Canvas.prototype;
1235
+ if (typeof skiaProto.getBoundingClientRect === "undefined") skiaProto.getBoundingClientRect = function() {
1236
+ const width = this.width || 512;
1237
+ const height = this.height || 512;
1238
+ return {
1239
+ x: 0,
1240
+ y: 0,
1241
+ left: 0,
1242
+ top: 0,
1243
+ right: width,
1244
+ bottom: height,
1245
+ width,
1246
+ height
1247
+ };
1248
+ };
1249
+ if (!("style" in skiaProto)) Object.defineProperty(skiaProto, "style", {
1250
+ get() {
1251
+ this._style ??= {};
1252
+ return this._style;
1253
+ },
1254
+ configurable: true
1255
+ });
1256
+ if (!("clientWidth" in skiaProto)) Object.defineProperty(skiaProto, "clientWidth", {
1257
+ get() {
1258
+ return this.width || 512;
1259
+ },
1260
+ configurable: true
1261
+ });
1262
+ if (!("clientHeight" in skiaProto)) Object.defineProperty(skiaProto, "clientHeight", {
1263
+ get() {
1264
+ return this.height || 512;
1265
+ },
1266
+ configurable: true
1267
+ });
1268
+ if (!("parentElement" in skiaProto)) Object.defineProperty(skiaProto, "parentElement", {
1269
+ get() {
1270
+ return dummyDomElement;
1271
+ },
1272
+ configurable: true
1273
+ });
1274
+ if (typeof skiaProto.addEventListener === "undefined") skiaProto.addEventListener = () => {};
1275
+ if (typeof skiaProto.removeEventListener === "undefined") skiaProto.removeEventListener = () => {};
1276
+ if (typeof skiaProto.getAttribute === "undefined") skiaProto.getAttribute = () => null;
1277
+ const CPU_COUNT = os.cpus().length;
1278
+ const TOTAL_WORKER_BUDGET = Math.max(1, CPU_COUNT - 3);
1279
+ const MAX_CONCURRENT_RENDERS = Math.max(1, Math.min(3, Math.floor(TOTAL_WORKER_BUDGET / 3)));
1280
+ const QUEUE_TIMEOUT_MS = 120 * 6e4;
1281
+ var RenderSemaphore = class {
1282
+ _running = 0;
1283
+ _max;
1284
+ _queue = [];
1285
+ constructor(max) {
1286
+ this._max = max;
1287
+ }
1288
+ acquire(timeoutMs = QUEUE_TIMEOUT_MS) {
1289
+ if (this._running < this._max) {
1290
+ this._running++;
1291
+ return Promise.resolve();
1292
+ }
1293
+ return new Promise((resolve$1, reject) => {
1294
+ const timer = setTimeout(() => {
1295
+ const idx = this._queue.findIndex((e) => e.resolve === resolve$1);
1296
+ if (idx !== -1) this._queue.splice(idx, 1);
1297
+ reject(/* @__PURE__ */ new Error(`Render queue timeout after ${timeoutMs}ms (${this._queue.length} queued, ${this._running}/${this._max} active)`));
1298
+ }, timeoutMs);
1299
+ this._queue.push({
1300
+ resolve: resolve$1,
1301
+ reject,
1302
+ timer
1303
+ });
1304
+ });
1305
+ }
1306
+ release() {
1307
+ if (this._queue.length > 0) {
1308
+ const next = this._queue.shift();
1309
+ clearTimeout(next.timer);
1310
+ next.resolve();
1311
+ } else this._running--;
1312
+ }
1313
+ get stats() {
1314
+ return {
1315
+ active: this._running,
1316
+ queued: this._queue.length,
1317
+ max: this._max
1318
+ };
1319
+ }
1320
+ };
1321
+ const renderSemaphore = new RenderSemaphore(MAX_CONCURRENT_RENDERS);
1322
+ function toEvenDimension(n) {
1323
+ const v = Math.round(n);
1324
+ return v % 2 === 0 ? v : v + 1;
1325
+ }
1326
+ function resolveQuality(quality) {
1327
+ switch (quality) {
1328
+ case "very_low": return QUALITY_VERY_LOW;
1329
+ case "low": return QUALITY_LOW;
1330
+ case "high": return QUALITY_HIGH;
1331
+ case "very_high": return QUALITY_VERY_HIGH;
1332
+ default: return QUALITY_VERY_HIGH;
1333
+ }
1334
+ }
1335
+ let cachedVramUsage = "N/A";
1336
+ let vramProbeInFlight = false;
1337
+ function refreshVramUsage() {
1338
+ if (vramProbeInFlight) return;
1339
+ vramProbeInFlight = true;
1340
+ exec("nvidia-smi --query-gpu=memory.used,memory.total --format=csv,noheader,nounits", {
1341
+ encoding: "utf-8",
1342
+ timeout: 2e3
1343
+ }, (err, stdout) => {
1344
+ vramProbeInFlight = false;
1345
+ if (err || !stdout) return;
1346
+ const [used, total] = stdout.trim().split(",").map((s) => s.trim());
1347
+ if (used && total) cachedVramUsage = `${used}MB / ${total}MB`;
1348
+ });
1349
+ }
1350
+ function getVramUsage() {
1351
+ return cachedVramUsage;
1352
+ }
1353
+ const DEFAULT_RING_CAPACITY = 2;
1354
+ const MIN_RING_CAPACITY = 2;
1355
+ const MAX_RING_CAPACITY = 8;
1356
+ function resolveRingCapacity(explicit) {
1357
+ const fromEnv = Number(process.env.FRAMEFIELDS_RING_CAPACITY);
1358
+ const requested = explicit ?? (Number.isFinite(fromEnv) && fromEnv > 0 ? fromEnv : NaN);
1359
+ if (!Number.isFinite(requested) || requested <= 0) return DEFAULT_RING_CAPACITY;
1360
+ return Math.max(MIN_RING_CAPACITY, Math.min(MAX_RING_CAPACITY, Math.floor(requested)));
1361
+ }
1362
+ var NodeSurfacePool = class {
1363
+ idle = [];
1364
+ maxIdle = 2;
1365
+ acquire(device, width, height) {
1366
+ const index = this.idle.findIndex((entry) => entry.device === device && entry.surface.width === width && entry.surface.height === height);
1367
+ if (index >= 0) {
1368
+ const [entry] = this.idle.splice(index, 1);
1369
+ if (entry) return entry.surface;
1370
+ }
1371
+ for (let i = this.idle.length - 1; i >= 0; i--) {
1372
+ const entry = this.idle[i];
1373
+ if (entry && entry.device !== device) {
1374
+ this.idle.splice(i, 1);
1375
+ try {
1376
+ entry.surface.destroy();
1377
+ } catch {}
1378
+ }
1379
+ }
1380
+ return new NodeSurfaceProvider(device, width, height);
1381
+ }
1382
+ release(device, surface) {
1383
+ if (this.idle.length >= this.maxIdle) {
1384
+ try {
1385
+ surface.destroy();
1386
+ } catch {}
1387
+ return;
1388
+ }
1389
+ this.idle.push({
1390
+ device,
1391
+ surface
1392
+ });
1393
+ }
1394
+ destroy() {
1395
+ for (const entry of this.idle.splice(0)) try {
1396
+ entry.surface.destroy();
1397
+ } catch {}
1398
+ }
1399
+ };
1400
+ const nodeSurfacePool = new NodeSurfacePool();
1401
+ function formatLutAsCube(lut) {
1402
+ const lines = [
1403
+ "# Created by Gatewai",
1404
+ lut.type === "3D" ? `LUT_3D_SIZE ${lut.size}` : `LUT_1D_SIZE ${lut.size}`,
1405
+ ""
1406
+ ];
1407
+ for (const p of lut.points) lines.push(`${p[0].toFixed(6)} ${p[1].toFixed(6)} ${p[2].toFixed(6)}`);
1408
+ return lines.join("\n");
1409
+ }
1410
+ var HeadlessWebGPURenderer = class HeadlessWebGPURenderer$1 {
1411
+ static isInitialized = false;
1412
+ static initPromise = null;
1413
+ static initFailureCount = 0;
1414
+ static MAX_INIT_FAILURES = 3;
1415
+ static nextRetryAt = 0;
1416
+ static initialize() {
1417
+ if (HeadlessWebGPURenderer$1.isInitialized) return Promise.resolve();
1418
+ if (HeadlessWebGPURenderer$1.initFailureCount >= HeadlessWebGPURenderer$1.MAX_INIT_FAILURES) return Promise.reject(/* @__PURE__ */ new Error("GPU initialization permanently failed — circuit breaker open"));
1419
+ if (Date.now() < HeadlessWebGPURenderer$1.nextRetryAt) return Promise.reject(/* @__PURE__ */ new Error("GPU initialization in backoff period"));
1420
+ if (HeadlessWebGPURenderer$1.initPromise) return HeadlessWebGPURenderer$1.initPromise;
1421
+ HeadlessWebGPURenderer$1.initPromise = (async () => {
1422
+ try {
1423
+ rendererLogger.debug("[HeadlessWebGPURenderer] Initializing headless WebGPU…");
1424
+ await initHeadlessWebGPU();
1425
+ rendererLogger.debug("[HeadlessWebGPURenderer] Scanning node renderers…");
1426
+ await discoverAndRegisterNodeRenderers();
1427
+ HeadlessWebGPURenderer$1.isInitialized = true;
1428
+ HeadlessWebGPURenderer$1.initPromise = null;
1429
+ HeadlessWebGPURenderer$1.initFailureCount = 0;
1430
+ HeadlessWebGPURenderer$1.nextRetryAt = 0;
1431
+ rendererLogger.debug("[HeadlessWebGPURenderer] Initialization complete.");
1432
+ } catch (error) {
1433
+ HeadlessWebGPURenderer$1.initFailureCount++;
1434
+ HeadlessWebGPURenderer$1.nextRetryAt = Date.now() + 5e3 * 2 ** HeadlessWebGPURenderer$1.initFailureCount;
1435
+ HeadlessWebGPURenderer$1.initPromise = null;
1436
+ rendererLogger.error({
1437
+ err: error instanceof Error ? error.message : String(error),
1438
+ failureCount: HeadlessWebGPURenderer$1.initFailureCount,
1439
+ nextRetryAt: new Date(HeadlessWebGPURenderer$1.nextRetryAt).toISOString()
1440
+ }, "[HeadlessWebGPURenderer] Initialization failed");
1441
+ throw error;
1442
+ }
1443
+ })();
1444
+ return HeadlessWebGPURenderer$1.initPromise;
1445
+ }
1446
+ async renderImage(virtualMedia, frame = 0, fps = 24, options = {}) {
1447
+ await HeadlessWebGPURenderer$1.initialize();
1448
+ const width = toEvenDimension(virtualMedia.metadata.width ?? 0);
1449
+ const height = toEvenDimension(virtualMedia.metadata.height ?? 0);
1450
+ if (!width || !height) throw new Error(`Invalid dimensions: ${width}x${height}`);
1451
+ const renderId = options.renderId ?? `img-${randomUUID()}`;
1452
+ rendererLogger.debug({
1453
+ renderId,
1454
+ frame,
1455
+ ...renderSemaphore.stats
1456
+ }, "[HeadlessWebGPURenderer] Acquiring slot for image render");
1457
+ await renderSemaphore.acquire();
1458
+ let surface;
1459
+ let renderer;
1460
+ const device = await ensureDevice();
1461
+ try {
1462
+ await preloadFonts(virtualMedia, device);
1463
+ surface = nodeSurfacePool.acquire(device, width, height);
1464
+ renderer = getRenderer2D(device, surface.colorFormat);
1465
+ const ctx = {
1466
+ device,
1467
+ renderer,
1468
+ surface
1469
+ };
1470
+ updateClockSignals(frame, fps, virtualMedia.metadata.durationMs ?? void 0);
1471
+ if (frame === 0 || lutStore.hasPending(device) || SlugFontCache.hasPending()) {
1472
+ const preEncoder = device.createCommandEncoder();
1473
+ const dummyTex = renderer.getTemporaryTexture(width, height);
1474
+ const dummyView = dummyTex.createView();
1475
+ renderer.beginFrame(preEncoder, dummyView, {
1476
+ r: 0,
1477
+ g: 0,
1478
+ b: 0,
1479
+ a: 0
1480
+ }, width, height, "clear").end();
1481
+ await drawCompositionTree(ctx, preEncoder, dummyView, dummyTex, width, height, virtualMedia, {
1482
+ frame,
1483
+ compositionFrame: frame,
1484
+ fps,
1485
+ isHeadless: true,
1486
+ renderId,
1487
+ virtualMedia,
1488
+ containerWidth: width,
1489
+ containerHeight: height,
1490
+ isVideoMode: virtualMedia.operation?.dataType === "Video"
1491
+ });
1492
+ device.queue.submit([preEncoder.finish()]);
1493
+ await lutStore.awaitAllPending(device);
1494
+ }
1495
+ compositionStateStore.setState(renderId, frame, fps, true);
1496
+ const encoder = device.createCommandEncoder();
1497
+ const targetView = surface.getCurrentTextureView();
1498
+ const targetTexture = surface.getCurrentTexture();
1499
+ renderer.beginFrame(encoder, targetView, {
1500
+ r: 0,
1501
+ g: 0,
1502
+ b: 0,
1503
+ a: 0
1504
+ }, width, height, "clear").end();
1505
+ await drawCompositionTree(ctx, encoder, targetView, targetTexture, width, height, virtualMedia, {
1506
+ frame,
1507
+ compositionFrame: frame,
1508
+ fps,
1509
+ isHeadless: true,
1510
+ renderId,
1511
+ virtualMedia,
1512
+ containerWidth: width,
1513
+ containerHeight: height,
1514
+ isVideoMode: virtualMedia.operation?.dataType === "Video"
1515
+ });
1516
+ device.queue.submit([encoder.finish()]);
1517
+ const pixels = await surface.readPixels();
1518
+ const pixelsArr = new Uint8Array(pixels.buffer, pixels.byteOffset, pixels.byteLength);
1519
+ if (options.format === "rgba") return Buffer.from(pixelsArr);
1520
+ return await sharp(Buffer.from(pixelsArr), { raw: {
1521
+ width,
1522
+ height,
1523
+ channels: 4
1524
+ } }).png().toBuffer();
1525
+ } finally {
1526
+ try {
1527
+ clearAllVideoCache();
1528
+ } catch {}
1529
+ try {
1530
+ shaderStore.clear(renderId);
1531
+ } catch {}
1532
+ if (surface) nodeSurfacePool.release(device, surface);
1533
+ try {
1534
+ textureCache.destroy();
1535
+ } catch {}
1536
+ renderSemaphore.release();
1537
+ rendererLogger.debug({
1538
+ renderId,
1539
+ ...renderSemaphore.stats
1540
+ }, "[HeadlessWebGPURenderer] Image render slot released");
1541
+ }
1542
+ }
1543
+ /**
1544
+ * Renders a single frame to a PNG buffer at a specified timestamp in milliseconds or frame index.
1545
+ */
1546
+ async renderFrame(source, options = {}) {
1547
+ const vm = typeof source === "object" && source !== null && "toVirtualMedia" in source ? source.toVirtualMedia() : source;
1548
+ const fps = options.fps ?? (vm.metadata?.fps || 30);
1549
+ const frame = options.atMs !== void 0 ? Math.round(options.atMs / 1e3 * fps) : options.frame ?? 0;
1550
+ return this.renderImage(vm, frame, fps, {
1551
+ renderId: options.renderId,
1552
+ format: options.format
1553
+ });
1554
+ }
1555
+ /**
1556
+ * Mixes the composition's full audio timeline (clips, effects, synthesized
1557
+ * tracks) to stereo PCM, exactly as `renderVideo` muxes it.
1558
+ */
1559
+ async renderAudio(virtualMedia, options = {}) {
1560
+ await HeadlessWebGPURenderer$1.initialize();
1561
+ const fps = options.fps ?? (virtualMedia.metadata?.fps || 30);
1562
+ await renderSemaphore.acquire();
1563
+ try {
1564
+ const device = await ensureDevice();
1565
+ return await mixAudioTracks(virtualMedia, fps, options.sampleRate ?? 48e3, device, `aud-${randomUUID()}`);
1566
+ } finally {
1567
+ renderSemaphore.release();
1568
+ }
1569
+ }
1570
+ async renderLut(virtualMedia, frame = 0, fps = 24) {
1571
+ await HeadlessWebGPURenderer$1.initialize();
1572
+ const width = 32;
1573
+ const height = 32;
1574
+ const renderId = `lut-${randomUUID()}`;
1575
+ rendererLogger.debug({
1576
+ renderId,
1577
+ frame,
1578
+ ...renderSemaphore.stats
1579
+ }, "[HeadlessWebGPURenderer] Acquiring slot for LUT render");
1580
+ await renderSemaphore.acquire();
1581
+ let surface;
1582
+ let renderer;
1583
+ const device = await ensureDevice();
1584
+ let lutData = null;
1585
+ const lutSub = lutStore.onChange((key) => {
1586
+ const raw = lutStore.getRawData(key);
1587
+ if (raw) lutData = raw;
1588
+ });
1589
+ try {
1590
+ await preloadFonts(virtualMedia, device);
1591
+ surface = new NodeSurfaceProvider(device, width, height);
1592
+ renderer = getRenderer2D(device, surface.colorFormat);
1593
+ const ctx = {
1594
+ device,
1595
+ renderer,
1596
+ surface
1597
+ };
1598
+ if (frame === 0 || lutStore.hasPending(device) || SlugFontCache.hasPending()) {
1599
+ const preEncoder = device.createCommandEncoder();
1600
+ const dummyTex = renderer.getTemporaryTexture(width, height);
1601
+ const dummyView = dummyTex.createView();
1602
+ renderer.beginFrame(preEncoder, dummyView, {
1603
+ r: 0,
1604
+ g: 0,
1605
+ b: 0,
1606
+ a: 0
1607
+ }, width, height, "clear").end();
1608
+ await drawCompositionTree(ctx, preEncoder, dummyView, dummyTex, width, height, virtualMedia, {
1609
+ frame,
1610
+ compositionFrame: frame,
1611
+ fps,
1612
+ isHeadless: true,
1613
+ renderId,
1614
+ virtualMedia,
1615
+ containerWidth: width,
1616
+ containerHeight: height,
1617
+ isVideoMode: virtualMedia.operation?.dataType === "Video"
1618
+ });
1619
+ device.queue.submit([preEncoder.finish()]);
1620
+ await lutStore.awaitAllPending(device);
1621
+ }
1622
+ compositionStateStore.setState(renderId, frame, fps, true);
1623
+ const encoder = device.createCommandEncoder();
1624
+ const targetView = surface.getCurrentTextureView();
1625
+ const targetTexture = surface.getCurrentTexture();
1626
+ renderer.beginFrame(encoder, targetView, {
1627
+ r: 0,
1628
+ g: 0,
1629
+ b: 0,
1630
+ a: 0
1631
+ }, width, height, "clear").end();
1632
+ await drawCompositionTree(ctx, encoder, targetView, targetTexture, width, height, virtualMedia, {
1633
+ frame,
1634
+ compositionFrame: frame,
1635
+ fps,
1636
+ isHeadless: true,
1637
+ renderId,
1638
+ virtualMedia,
1639
+ containerWidth: width,
1640
+ containerHeight: height,
1641
+ isVideoMode: virtualMedia.operation?.dataType === "Video"
1642
+ });
1643
+ device.queue.submit([encoder.finish()]);
1644
+ await lutStore.awaitAllPending(device);
1645
+ if (!lutData) lutData = lutStore.getAnyRawData();
1646
+ if (!lutData) throw new Error("No LUT data generated during rendering");
1647
+ const cubeContent = formatLutAsCube(lutData);
1648
+ return Buffer.from(cubeContent, "utf-8");
1649
+ } finally {
1650
+ try {
1651
+ lutSub();
1652
+ } catch {}
1653
+ try {
1654
+ mediaDecoderCache.destroy();
1655
+ clearAllVideoCache();
1656
+ } catch {}
1657
+ try {
1658
+ shaderStore.clear(renderId);
1659
+ } catch {}
1660
+ try {
1661
+ surface?.destroy();
1662
+ } catch {}
1663
+ try {
1664
+ textureCache.destroy();
1665
+ } catch {}
1666
+ renderSemaphore.release();
1667
+ rendererLogger.debug({
1668
+ renderId,
1669
+ ...renderSemaphore.stats
1670
+ }, "[HeadlessWebGPURenderer] LUT render slot released");
1671
+ }
1672
+ }
1673
+ async resolveMediaDuration(virtualMedia, fps) {
1674
+ let maxDurationMs = 0;
1675
+ const queue = [virtualMedia];
1676
+ const sources = /* @__PURE__ */ new Set();
1677
+ while (queue.length > 0) {
1678
+ const current = queue.shift();
1679
+ if (current.metadata?.durationMs && current.metadata.durationMs > maxDurationMs) maxDurationMs = current.metadata.durationMs;
1680
+ const op = current.operation;
1681
+ if (op) {
1682
+ if (typeof op.source === "string") sources.add(op.source);
1683
+ if (typeof op.url === "string") sources.add(op.url);
1684
+ if (typeof op.inputHandleId === "string") sources.add(op.inputHandleId);
1685
+ if (Array.isArray(op.layout)) for (const node of op.layout) {
1686
+ if (node.inputHandleId) sources.add(node.inputHandleId);
1687
+ if (node.src) sources.add(node.src);
1688
+ if (typeof node.durationFrames === "number") {
1689
+ const durMs = ((typeof node.startFrame === "number" ? node.startFrame : 0) + node.durationFrames) / fps * 1e3;
1690
+ if (durMs > maxDurationMs) maxDurationMs = durMs;
1691
+ }
1692
+ }
1693
+ }
1694
+ if (current.children) queue.push(...current.children);
1695
+ }
1696
+ for (const src of sources) try {
1697
+ const sec = await (await inputStore.acquire(src)).computeDuration();
1698
+ if (Number.isFinite(sec) && sec > 0) {
1699
+ const ms = Math.round(sec * 1e3);
1700
+ if (ms > maxDurationMs) maxDurationMs = ms;
1701
+ }
1702
+ } catch (_) {}
1703
+ return maxDurationMs > 0 ? maxDurationMs : 1e3;
1704
+ }
1705
+ async renderVideo(virtualMedia, options) {
1706
+ await HeadlessWebGPURenderer$1.initialize();
1707
+ const codecOption = options?.codec ?? "h264";
1708
+ const isMp3 = codecOption === "mp3";
1709
+ const isWebM = codecOption === "vp8" || codecOption === "vp9" || codecOption === "opus";
1710
+ const mediaType = getMediaType(virtualMedia);
1711
+ const isLut = mediaType === "LUT" || virtualMedia.operation?.dataType === "LUT";
1712
+ const isAudioOnly = isMp3 || codecOption === "aac" || codecOption === "opus" || mediaType === "Audio";
1713
+ const fps = virtualMedia.metadata.fps ?? (isAudioOnly || isLut ? 24 : void 0);
1714
+ if (!fps) {
1715
+ const dataType = mediaType ?? virtualMedia.dataType ?? "Unknown";
1716
+ const op = virtualMedia.operation?.op ?? virtualMedia.op ?? "Unknown";
1717
+ throw new Error(`FPS is missing from virtualMedia metadata (dataType: "${dataType}", operation: "${op}")`);
1718
+ }
1719
+ const width = toEvenDimension(virtualMedia.metadata.width ?? (isAudioOnly ? 1280 : 0));
1720
+ const height = toEvenDimension(virtualMedia.metadata.height ?? (isAudioOnly ? 720 : 0));
1721
+ if (!width || !height) throw new Error(`Invalid dimensions: ${width}x${height}`);
1722
+ let durationMs = virtualMedia.metadata.durationMs;
1723
+ if (!durationMs || durationMs <= 0) {
1724
+ durationMs = await this.resolveMediaDuration(virtualMedia, fps);
1725
+ virtualMedia.metadata.durationMs = durationMs;
1726
+ }
1727
+ const totalFrames = Math.max(1, Math.round(durationMs / 1e3 * fps));
1728
+ const renderId = `vid-${randomUUID()}`;
1729
+ const qaOptions = options?.qa === true ? {} : options?.qa ? options.qa : void 0;
1730
+ const inspector = qaOptions ? new FrameInspector(qaOptions) : void 0;
1731
+ let videoQa;
1732
+ let audioQa;
1733
+ const videoCodec = codecOption === "vp9" ? "vp9" : codecOption === "vp8" ? "vp8" : "avc";
1734
+ const audioCodec = options?.audioCodec ?? (isMp3 ? "mp3" : isWebM ? "opus" : "aac");
1735
+ const quality = resolveQuality(options?.quality);
1736
+ rendererLogger.debug({
1737
+ renderId,
1738
+ totalFrames,
1739
+ fps,
1740
+ width,
1741
+ height,
1742
+ codec: codecOption,
1743
+ ...renderSemaphore.stats
1744
+ }, "[HeadlessWebGPURenderer] Acquiring slot for video render (inline)");
1745
+ await renderSemaphore.acquire();
1746
+ let lastCpuUsage = process.cpuUsage();
1747
+ let lastCpuTime = process.hrtime.bigint();
1748
+ const getCpuUsage = () => {
1749
+ const curCpuUsage = process.cpuUsage();
1750
+ const curCpuTime = process.hrtime.bigint();
1751
+ const userDiff = curCpuUsage.user - lastCpuUsage.user;
1752
+ const sysDiff = curCpuUsage.system - lastCpuUsage.system;
1753
+ const timeDiff = Number(curCpuTime - lastCpuTime) / 1e3;
1754
+ lastCpuUsage = curCpuUsage;
1755
+ lastCpuTime = curCpuTime;
1756
+ if (timeDiff === 0) return "0.0%";
1757
+ const cpus = os.cpus().length || 1;
1758
+ return `${((userDiff + sysDiff) / timeDiff * 100 / cpus).toFixed(1)}%`;
1759
+ };
1760
+ const getRamUsage = () => {
1761
+ const mem = process.memoryUsage();
1762
+ const freeMem = os.freemem();
1763
+ const totalMem = os.totalmem();
1764
+ const usedMem = totalMem - freeMem;
1765
+ const heapUsedMb = Math.round(mem.heapUsed / 1024 / 1024);
1766
+ const heapTotalMb = Math.round(mem.heapTotal / 1024 / 1024);
1767
+ return `Process: ${Math.round(mem.rss / 1024 / 1024)}MB RSS (${heapUsedMb}MB/${heapTotalMb}MB Heap), System: ${Math.round(usedMem / 1024 / 1024)}MB/${Math.round(totalMem / 1024 / 1024)}MB`;
1768
+ };
1769
+ let statsInterval;
1770
+ if (process.env.NODE_ENV === "production") statsInterval = setInterval(() => {
1771
+ refreshVramUsage();
1772
+ rendererLogger.info({
1773
+ renderId,
1774
+ cpu: getCpuUsage(),
1775
+ ram: getRamUsage(),
1776
+ vram: getVramUsage(),
1777
+ renderQueue: renderSemaphore.stats
1778
+ }, "[HeadlessWebGPURenderer] Performance Stats");
1779
+ }, 5e3);
1780
+ const tempDir = os.tmpdir();
1781
+ const extension = isMp3 ? "mp3" : isWebM ? "webm" : "mp4";
1782
+ const tempFilePath = path.join(tempDir, `render-${renderId}.${extension}`);
1783
+ const tempGifPath = path.join(tempDir, `render-${renderId}.gif`);
1784
+ const target = new FilePathTarget(tempFilePath);
1785
+ const output = new Output({
1786
+ format: isMp3 ? new Mp3OutputFormat() : isWebM ? new WebMOutputFormat() : new Mp4OutputFormat(),
1787
+ target
1788
+ });
1789
+ let videoSource;
1790
+ if (!isAudioOnly) {
1791
+ videoSource = new VideoSampleSource({
1792
+ codec: videoCodec,
1793
+ bitrate: quality,
1794
+ latencyMode: "realtime",
1795
+ hardwareAcceleration: isAMD() ? "prefer-software" : "prefer-hardware"
1796
+ });
1797
+ output.addVideoTrack(videoSource);
1798
+ }
1799
+ const audioSource = new AudioSampleSource({
1800
+ codec: audioCodec,
1801
+ bitrate: quality
1802
+ });
1803
+ output.addAudioTrack(audioSource);
1804
+ let outputStarted = false;
1805
+ let outputFinalized = false;
1806
+ const device = await ensureDevice();
1807
+ let surface;
1808
+ let renderer;
1809
+ let pipeline;
1810
+ try {
1811
+ await output.start();
1812
+ outputStarted = true;
1813
+ await preloadFonts(virtualMedia, device);
1814
+ if (!isAudioOnly && videoSource) {
1815
+ surface = new NodeSurfaceProvider(device, width, height);
1816
+ renderer = getRenderer2D(device, surface.colorFormat);
1817
+ pipeline = new ZeroCopyWebCodecsPipeline(device, {
1818
+ width,
1819
+ height,
1820
+ fps,
1821
+ videoSource,
1822
+ ringCapacity: resolveRingCapacity(options?.ringCapacity),
1823
+ onFrame: inspector ? (rgba) => inspector.inspect(rgba, width, height) : void 0
1824
+ });
1825
+ const ctx = surface && renderer ? {
1826
+ device,
1827
+ renderer,
1828
+ surface
1829
+ } : null;
1830
+ const prePass = async (frame) => {
1831
+ if (!ctx || !renderer) return;
1832
+ const preEncoder = device.createCommandEncoder();
1833
+ const dummyTex = renderer.getTemporaryTexture(width, height);
1834
+ const dummyView = dummyTex.createView();
1835
+ renderer.beginFrame(preEncoder, dummyView, {
1836
+ r: 0,
1837
+ g: 0,
1838
+ b: 0,
1839
+ a: 0
1840
+ }, width, height, "clear").end();
1841
+ await drawCompositionTree(ctx, preEncoder, dummyView, dummyTex, width, height, virtualMedia, {
1842
+ frame,
1843
+ compositionFrame: frame,
1844
+ fps,
1845
+ isHeadless: true,
1846
+ renderId,
1847
+ virtualMedia,
1848
+ containerWidth: width,
1849
+ containerHeight: height,
1850
+ isVideoMode: virtualMedia.operation?.dataType === "Video"
1851
+ });
1852
+ device.queue.submit([preEncoder.finish()]);
1853
+ await lutStore.awaitAllPending(device);
1854
+ };
1855
+ await prePass(0);
1856
+ for (let i = 0; i < totalFrames; i++) {
1857
+ if (i % 10 === 0) rendererLogger.info({
1858
+ renderId,
1859
+ frame: i,
1860
+ totalFrames
1861
+ }, `[HeadlessWebGPURenderer] Rendering frame ${i}/${totalFrames}`);
1862
+ if (ctx && surface && renderer && pipeline) {
1863
+ if (i > 0 && (lutStore.hasPending(device) || SlugFontCache.hasPending())) {
1864
+ updateClockSignals(i, fps, durationMs);
1865
+ await prePass(i);
1866
+ }
1867
+ compositionStateStore.setState(renderId, i, fps, true);
1868
+ updateClockSignals(i, fps, durationMs);
1869
+ const encoder = device.createCommandEncoder();
1870
+ const targetView = surface.getCurrentTextureView();
1871
+ const targetTexture = surface.getCurrentTexture();
1872
+ renderer.beginFrame(encoder, targetView, {
1873
+ r: 0,
1874
+ g: 0,
1875
+ b: 0,
1876
+ a: 0
1877
+ }, width, height, "clear").end();
1878
+ await drawCompositionTree(ctx, encoder, targetView, targetTexture, width, height, virtualMedia, {
1879
+ frame: i,
1880
+ compositionFrame: i,
1881
+ fps,
1882
+ isHeadless: true,
1883
+ renderId,
1884
+ virtualMedia,
1885
+ containerWidth: width,
1886
+ containerHeight: height,
1887
+ isVideoMode: virtualMedia.operation?.dataType === "Video"
1888
+ });
1889
+ await pipeline.enqueueFrame(i, targetTexture, encoder);
1890
+ } else throw new Error("Invalid render context state");
1891
+ }
1892
+ if (pipeline) {
1893
+ await pipeline.drain();
1894
+ const pipelineStats = pipeline.getStats();
1895
+ if (inspector && qaOptions) videoQa = inspector.stats(totalFrames, pipelineStats.totalFramesEncoded, fps, width, height, qaOptions);
1896
+ rendererLogger.debug({
1897
+ renderId,
1898
+ totalFrames,
1899
+ ...pipelineStats
1900
+ }, `[HeadlessWebGPURenderer] All frames rendered and encoded (avg cycle: ${pipelineStats.averageCycleTimeMs.toFixed(2)}ms, throughput: ${pipelineStats.throughputFps.toFixed(1)} fps)`);
1901
+ }
1902
+ }
1903
+ const { channels, sampleRate } = await mixAudioTracks(virtualMedia, fps, 48e3, device, renderId);
1904
+ const numChannels = channels.length;
1905
+ if (numChannels === 0) throw new Error("mixAudioTracks returned no channels");
1906
+ if (qaOptions) audioQa = analyzeAudio(channels, sampleRate, qaOptions);
1907
+ const totalSamples = channels[0].length;
1908
+ const chunkSize = Math.round(.1 * sampleRate);
1909
+ for (let offset = 0; offset < totalSamples; offset += chunkSize) {
1910
+ const currentChunk = Math.min(chunkSize, totalSamples - offset);
1911
+ const interleaved = new Float32Array(currentChunk * numChannels);
1912
+ for (let i = 0; i < currentChunk; i++) for (let ch = 0; ch < numChannels; ch++) interleaved[i * numChannels + ch] = channels[ch]?.[offset + i] ?? 0;
1913
+ const sample = new AudioSample({
1914
+ data: interleaved,
1915
+ format: "f32",
1916
+ numberOfChannels: numChannels,
1917
+ sampleRate,
1918
+ timestamp: offset / sampleRate
1919
+ });
1920
+ try {
1921
+ await audioSource.add(sample);
1922
+ } finally {
1923
+ sample.close();
1924
+ }
1925
+ }
1926
+ await output.finalize();
1927
+ outputFinalized = true;
1928
+ const qa = qaOptions ? buildQaReport({
1929
+ video: videoQa,
1930
+ audio: audioQa,
1931
+ diagnostics: takeRenderDiagnostics(renderId),
1932
+ options: qaOptions
1933
+ }) : void 0;
1934
+ const cleanup = async () => {
1935
+ try {
1936
+ await fs$1.unlink(tempFilePath).catch(() => {});
1937
+ if (codecOption === "gif") await fs$1.unlink(tempGifPath).catch(() => {});
1938
+ } catch {}
1939
+ };
1940
+ if (codecOption === "gif") {
1941
+ rendererLogger.debug("[HeadlessWebGPURenderer] Converting rendered MP4 to high-quality GIF...");
1942
+ await convertMp4ToGif(tempFilePath, tempGifPath);
1943
+ return {
1944
+ filePath: tempGifPath,
1945
+ cleanup,
1946
+ ...qa && { qa }
1947
+ };
1948
+ }
1949
+ return {
1950
+ filePath: tempFilePath,
1951
+ cleanup,
1952
+ ...qa && { qa }
1953
+ };
1954
+ } catch (error) {
1955
+ if (outputStarted && !outputFinalized) try {
1956
+ await output.finalize();
1957
+ } catch {}
1958
+ try {
1959
+ await fs$1.unlink(tempFilePath).catch(() => {});
1960
+ if (codecOption === "gif") await fs$1.unlink(tempGifPath).catch(() => {});
1961
+ } catch {}
1962
+ throw error;
1963
+ } finally {
1964
+ if (statsInterval) clearInterval(statsInterval);
1965
+ try {
1966
+ renderer?.destroy();
1967
+ } catch {}
1968
+ try {
1969
+ pipeline?.destroy();
1970
+ } catch {}
1971
+ try {
1972
+ surface?.destroy();
1973
+ } catch {}
1974
+ try {
1975
+ mediaDecoderCache.destroy();
1976
+ } catch {}
1977
+ try {
1978
+ clearAllVideoCache();
1979
+ } catch {}
1980
+ try {
1981
+ textureCache.destroy();
1982
+ } catch {}
1983
+ try {
1984
+ WebGPUAudioProcessor.clearCache(renderId);
1985
+ } catch {}
1986
+ try {
1987
+ shaderStore.clear(renderId);
1988
+ } catch {}
1989
+ takeRenderDiagnostics(renderId);
1990
+ renderSemaphore.release();
1991
+ rendererLogger.debug({
1992
+ renderId,
1993
+ ...renderSemaphore.stats
1994
+ }, "[HeadlessWebGPURenderer] Video render slot released");
1995
+ }
1996
+ }
1997
+ };
1998
+ const HeadlessMediaRenderer = HeadlessWebGPURenderer;
1999
+
2000
+ //#endregion
2001
+ //#region src/renderer/framegrid.ts
2002
+ /**
2003
+ * Resolves and validates the list of sample points from the provided options.
2004
+ */
2005
+ function resolveSamplePoints(options, defaultFps = 24, durationMs) {
2006
+ const fps = options.fps ?? defaultFps;
2007
+ if (fps <= 0) throw new Error(`[FrameGrid] Invalid fps: ${fps}. Must be > 0.`);
2008
+ const samples = [];
2009
+ if (Array.isArray(options.frames) && options.frames.length > 0) for (const f of options.frames) {
2010
+ if (typeof f !== "number" || f < 0 || !Number.isFinite(f)) throw new Error(`[FrameGrid] Invalid frame index in frames list: ${f}`);
2011
+ samples.push({
2012
+ frame: Math.round(f),
2013
+ timeMs: Math.round(f / fps * 1e3)
2014
+ });
2015
+ }
2016
+ else if (Array.isArray(options.timesMs) && options.timesMs.length > 0) for (const ms of options.timesMs) {
2017
+ if (typeof ms !== "number" || ms < 0 || !Number.isFinite(ms)) throw new Error(`[FrameGrid] Invalid timestamp in timesMs list: ${ms}`);
2018
+ samples.push({
2019
+ frame: Math.round(ms / 1e3 * fps),
2020
+ timeMs: Math.round(ms)
2021
+ });
2022
+ }
2023
+ else if (options.startMs !== void 0 || options.endMs !== void 0 || options.stepMs !== void 0) {
2024
+ const startMs = options.startMs ?? 0;
2025
+ const endMs = options.endMs ?? durationMs ?? startMs + 1e3;
2026
+ const stepMs = options.stepMs ?? Math.round(5 / fps * 1e3);
2027
+ if (startMs < 0 || endMs < startMs) throw new Error(`[FrameGrid] Invalid millisecond range: startMs=${startMs}, endMs=${endMs}. startMs must be <= endMs.`);
2028
+ if (stepMs <= 0) throw new Error(`[FrameGrid] Invalid stepMs: ${stepMs}. Must be > 0.`);
2029
+ for (let ms = startMs; ms <= endMs; ms += stepMs) samples.push({
2030
+ frame: Math.round(ms / 1e3 * fps),
2031
+ timeMs: Math.round(ms)
2032
+ });
2033
+ } else {
2034
+ const startFrame = options.startFrame ?? 0;
2035
+ const endFrame = options.endFrame ?? (durationMs ? Math.min(startFrame + 30, Math.floor(durationMs / 1e3 * fps)) : startFrame + 30);
2036
+ const stepFrames = options.stepFrames ?? 5;
2037
+ if (startFrame < 0 || endFrame < startFrame) throw new Error(`[FrameGrid] Invalid frame range: startFrame=${startFrame}, endFrame=${endFrame}. startFrame must be <= endFrame.`);
2038
+ if (stepFrames <= 0) throw new Error(`[FrameGrid] Invalid stepFrames: ${stepFrames}. Must be > 0.`);
2039
+ for (let f = startFrame; f <= endFrame; f += stepFrames) samples.push({
2040
+ frame: Math.round(f),
2041
+ timeMs: Math.round(f / fps * 1e3)
2042
+ });
2043
+ }
2044
+ const maxFramesLimit = Math.min(options.maxFrames ?? 64, 120);
2045
+ if (samples.length > maxFramesLimit) throw new Error(`[FrameGrid] Requested ${samples.length} frames exceeds the safety threshold of ${maxFramesLimit}. Increase stepFrames/stepMs or narrow the start/end range to prevent oversized images.`);
2046
+ if (samples.length === 0) throw new Error("[FrameGrid] Zero frames were resolved for sampling.");
2047
+ return {
2048
+ samples,
2049
+ fps
2050
+ };
2051
+ }
2052
+ /**
2053
+ * Computes grid layout dimensions, ensuring resulting canvas does not exceed size limits.
2054
+ */
2055
+ function computeGridLayout(numSamples, aspectRatio, options) {
2056
+ const gap = options.gap ?? 12;
2057
+ const padding = options.padding ?? 16;
2058
+ const headerHeight = options.showHeader ?? true ? 54 : 0;
2059
+ const maxGridWidth = options.maxGridWidth ?? 4096;
2060
+ const maxGridHeight = options.maxGridHeight ?? 4096;
2061
+ let columns = options.columns;
2062
+ if (!columns || columns <= 0) if (numSamples <= 4) columns = numSamples;
2063
+ else if (numSamples <= 9) columns = 3;
2064
+ else if (numSamples <= 16) columns = 4;
2065
+ else if (numSamples <= 25) columns = 5;
2066
+ else columns = 6;
2067
+ columns = Math.min(columns, numSamples);
2068
+ const rows = Math.ceil(numSamples / columns);
2069
+ let cellWidth = Math.max(120, Math.min(options.cellWidth ?? 320, 960));
2070
+ let cellHeight = options.cellHeight ?? Math.round(cellWidth / (aspectRatio > 0 ? aspectRatio : 16 / 9));
2071
+ let totalWidth = columns * cellWidth + (columns - 1) * gap + padding * 2;
2072
+ let totalHeight = rows * cellHeight + (rows - 1) * gap + padding * 2 + headerHeight;
2073
+ if (totalWidth > maxGridWidth || totalHeight > maxGridHeight) {
2074
+ const scaleFactor = Math.min((maxGridWidth - (columns - 1) * gap - padding * 2) / (columns * cellWidth), (maxGridHeight - (rows - 1) * gap - padding * 2 - headerHeight) / (rows * cellHeight));
2075
+ if (scaleFactor < .25) throw new Error(`[FrameGrid] Cannot fit ${numSamples} cells (${columns}x${rows}) into max dimensions [${maxGridWidth}x${maxGridHeight}]. Reduce cell count or decrease cellWidth.`);
2076
+ cellWidth = Math.max(100, Math.floor(cellWidth * scaleFactor));
2077
+ cellHeight = Math.round(cellWidth / (aspectRatio > 0 ? aspectRatio : 16 / 9));
2078
+ totalWidth = columns * cellWidth + (columns - 1) * gap + padding * 2;
2079
+ totalHeight = rows * cellHeight + (rows - 1) * gap + padding * 2 + headerHeight;
2080
+ }
2081
+ return {
2082
+ columns,
2083
+ rows,
2084
+ cellWidth,
2085
+ cellHeight,
2086
+ totalWidth,
2087
+ totalHeight,
2088
+ headerHeight,
2089
+ gap,
2090
+ padding
2091
+ };
2092
+ }
2093
+ /**
2094
+ * Renders a sequence of frames from a Composition or Media pipeline into a unified image grid (contact sheet).
2095
+ * Ideal for visual inspection of kinetic motion, transitions, easing, and layout stability.
2096
+ */
2097
+ async function renderFrameGrid(target, options = {}) {
2098
+ const defaultFps = target.fps ?? 24;
2099
+ const durationMs = target.durationMs;
2100
+ const { samples, fps } = resolveSamplePoints(options, defaultFps, durationMs);
2101
+ const compWidth = target.width ?? 1920;
2102
+ const compHeight = target.height ?? 1080;
2103
+ const aspectRatio = compWidth > 0 && compHeight > 0 ? compWidth / compHeight : 16 / 9;
2104
+ const layout = computeGridLayout(samples.length, aspectRatio, options);
2105
+ const renderer = options.renderer ?? new HeadlessMediaRenderer();
2106
+ const frameBuffers = [];
2107
+ for (const sample of samples) {
2108
+ const buf = await target.renderFrame({
2109
+ frame: sample.frame,
2110
+ atMs: sample.timeMs,
2111
+ fps,
2112
+ renderer
2113
+ });
2114
+ frameBuffers.push(buf);
2115
+ }
2116
+ const canvas = new Canvas(layout.totalWidth, layout.totalHeight);
2117
+ const ctx = canvas.getContext("2d");
2118
+ ctx.fillStyle = options.backgroundColor ?? "#090a0f";
2119
+ ctx.fillRect(0, 0, layout.totalWidth, layout.totalHeight);
2120
+ if (options.showHeader !== false && layout.headerHeight > 0) {
2121
+ const title = options.headerText ?? `FrameGrid Conformance Review | Frames ${samples[0].frame}–${samples[samples.length - 1].frame} (Count: ${samples.length})`;
2122
+ const subtitle = `${compWidth}×${compHeight} @ ${fps} FPS | Cell: ${layout.cellWidth}×${layout.cellHeight}px | Step: ${options.stepFrames ? `${options.stepFrames}f` : options.stepMs ? `${options.stepMs}ms` : "Custom"}`;
2123
+ ctx.fillStyle = "#f8fafc";
2124
+ ctx.font = "bold 15px sans-serif";
2125
+ ctx.fillText(title, layout.padding, layout.padding + 18);
2126
+ ctx.fillStyle = "#94a3b8";
2127
+ ctx.font = "12px sans-serif";
2128
+ ctx.fillText(subtitle, layout.padding, layout.padding + 36);
2129
+ ctx.strokeStyle = "#1e2433";
2130
+ ctx.lineWidth = 1;
2131
+ ctx.beginPath();
2132
+ ctx.moveTo(layout.padding, layout.padding + 46);
2133
+ ctx.lineTo(layout.totalWidth - layout.padding, layout.padding + 46);
2134
+ ctx.stroke();
2135
+ }
2136
+ const showLabels = options.showLabels ?? true;
2137
+ for (let i = 0; i < samples.length; i++) {
2138
+ const sample = samples[i];
2139
+ const col = i % layout.columns;
2140
+ const row = Math.floor(i / layout.columns);
2141
+ const x = layout.padding + col * (layout.cellWidth + layout.gap);
2142
+ const y = layout.padding + layout.headerHeight + row * (layout.cellHeight + layout.gap);
2143
+ const img = await loadImage(frameBuffers[i]);
2144
+ ctx.drawImage(img, x, y, layout.cellWidth, layout.cellHeight);
2145
+ ctx.strokeStyle = "#262b3d";
2146
+ ctx.lineWidth = 1;
2147
+ ctx.strokeRect(x, y, layout.cellWidth, layout.cellHeight);
2148
+ if (showLabels) {
2149
+ const label = `F:${sample.frame} • ${sample.timeMs}ms`;
2150
+ ctx.font = "bold 11px sans-serif";
2151
+ const badgeW = ctx.measureText(label).width + 14;
2152
+ const badgeH = 20;
2153
+ const badgeX = x + 6;
2154
+ const badgeY = y + 6;
2155
+ ctx.fillStyle = "#090a0fee";
2156
+ ctx.beginPath();
2157
+ ctx.roundRect(badgeX, badgeY, badgeW, badgeH, 4);
2158
+ ctx.fill();
2159
+ ctx.strokeStyle = "#6366f155";
2160
+ ctx.lineWidth = 1;
2161
+ ctx.stroke();
2162
+ ctx.fillStyle = "#e2e8f0";
2163
+ ctx.fillText(label, badgeX + 7, badgeY + 14);
2164
+ }
2165
+ }
2166
+ const gridBuffer = await canvas.toBuffer("png");
2167
+ if (options.outputPath) await fs$1.writeFile(options.outputPath, gridBuffer);
2168
+ return gridBuffer;
2169
+ }
2170
+
2171
+ //#endregion
2172
+ export { HeadlessWebGPURenderer as a, HeadlessMediaRenderer as i, renderFrameGrid as n, formatQaReport as o, resolveSamplePoints as r, renderSemaphore as s, computeGridLayout as t };
2173
+ //# sourceMappingURL=renderer-DX2qEJTL.mjs.map