framefields 2.14.0 → 2.16.0

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 (333) hide show
  1. package/NOTICE.md +5 -1
  2. package/dist/audio/index.d.mts +3 -3
  3. package/dist/audio/index.mjs +10 -10
  4. package/dist/{audio-DFwRbID_.mjs → audio-6TfPlfl4.mjs} +3 -3
  5. package/dist/{audio-DFwRbID_.mjs.map → audio-6TfPlfl4.mjs.map} +1 -1
  6. package/dist/{audio-CdCuGK5d.d.mts → audio-7oqTUi4G.d.mts} +2 -2
  7. package/dist/{audio-CdCuGK5d.d.mts.map → audio-7oqTUi4G.d.mts.map} +1 -1
  8. package/dist/cli/check-worker.mjs +44 -44
  9. package/dist/cli.mjs +1 -1
  10. package/dist/cloud/index.mjs +1 -1
  11. package/dist/{cloud-Cj1TIme8.mjs → cloud-jnk1hPYf.mjs} +2 -2
  12. package/dist/{cloud-Cj1TIme8.mjs.map → cloud-jnk1hPYf.mjs.map} +1 -1
  13. package/dist/components/index.d.mts +11 -11
  14. package/dist/components/index.mjs +43 -43
  15. package/dist/{dist-Kbf-JTry.mjs → dist-BtI6ukB5.mjs} +2 -2
  16. package/dist/{dist-Kbf-JTry.mjs.map → dist-BtI6ukB5.mjs.map} +1 -1
  17. package/dist/{dist-B8Nby2CU.mjs → dist-COOpgG9G.mjs} +4 -3
  18. package/dist/{dist-B8Nby2CU.mjs.map → dist-COOpgG9G.mjs.map} +1 -1
  19. package/dist/{dist--Uu5bQ_6.mjs → dist-XqlFM2a8.mjs} +13733 -5323
  20. package/dist/dist-XqlFM2a8.mjs.map +1 -0
  21. package/dist/{dist-CiTgmfUw.mjs → dist-f1zkl69f.mjs} +4 -4
  22. package/dist/dist-f1zkl69f.mjs.map +1 -0
  23. package/dist/{dist-B_rSWeOz.mjs → dist-umQOWz0a.mjs} +1 -1
  24. package/dist/effects/index.d.mts +4 -4
  25. package/dist/effects/index.mjs +35 -35
  26. package/dist/{effects-WMgv8ZaD.mjs → effects-DlRMovU5.mjs} +2 -2
  27. package/dist/{effects-WMgv8ZaD.mjs.map → effects-DlRMovU5.mjs.map} +1 -1
  28. package/dist/fonts/index.d.mts +1 -1
  29. package/dist/fonts/index.mjs +2 -2
  30. package/dist/{fonts-OW3yAfO5.mjs → fonts-BWYcXjyK.mjs} +3 -3
  31. package/dist/fonts-BWYcXjyK.mjs.map +1 -0
  32. package/dist/{gpu-R4T2Tndu.mjs → gpu-CH1PdM4T.mjs} +2 -2
  33. package/dist/{gpu-R4T2Tndu.mjs.map → gpu-CH1PdM4T.mjs.map} +1 -1
  34. package/dist/{index-CEKxvGY-.d.mts → index-BHTycml5.d.mts} +2 -2
  35. package/dist/{index-CEKxvGY-.d.mts.map → index-BHTycml5.d.mts.map} +1 -1
  36. package/dist/{index-DoP6EiHj.d.mts → index-BcxxxwhV.d.mts} +4 -4
  37. package/dist/{index-DoP6EiHj.d.mts.map → index-BcxxxwhV.d.mts.map} +1 -1
  38. package/dist/index-C-V6CAhP.d.mts.map +1 -1
  39. package/dist/{index-D1FqV1eu.d.mts → index-CMhkkc56.d.mts} +43 -11
  40. package/dist/index-CMhkkc56.d.mts.map +1 -0
  41. package/dist/{index-B0Vq8Bwf.d.mts → index-CYVpYGM-.d.mts} +2 -2
  42. package/dist/{index-B0Vq8Bwf.d.mts.map → index-CYVpYGM-.d.mts.map} +1 -1
  43. package/dist/{index-Bv7gxGK1.d.mts → index-Cq8jgdNY.d.mts} +90 -3
  44. package/dist/index-Cq8jgdNY.d.mts.map +1 -0
  45. package/dist/{index-9xte4JgL.d.mts → index-Dg5BAtDq.d.mts} +1446 -541
  46. package/dist/index-Dg5BAtDq.d.mts.map +1 -0
  47. package/dist/{index-C7-p9zwU.d.mts → index-DhL7ckvs.d.mts} +5 -5
  48. package/dist/index-DhL7ckvs.d.mts.map +1 -0
  49. package/dist/{index-GA9_Z8Fn.d.mts → index-RILu3ULT.d.mts} +2 -2
  50. package/dist/{index-GA9_Z8Fn.d.mts.map → index-RILu3ULT.d.mts.map} +1 -1
  51. package/dist/{index-C5sgRLlG.d.mts → index-TCtdSXd7.d.mts} +3 -1
  52. package/dist/index-TCtdSXd7.d.mts.map +1 -0
  53. package/dist/{index-CRaydvLi.d.mts → index-s2qQ8ifZ.d.mts} +886 -51
  54. package/dist/index-s2qQ8ifZ.d.mts.map +1 -0
  55. package/dist/index.d.mts +13 -13
  56. package/dist/index.mjs +52 -52
  57. package/dist/preview/index.d.mts +4 -4
  58. package/dist/preview/index.mjs +11 -11
  59. package/dist/preview-Dm912wly.mjs +1037 -0
  60. package/dist/preview-Dm912wly.mjs.map +1 -0
  61. package/dist/preview-engine/audio.mjs +1 -1
  62. package/dist/preview-engine/{chunk-64L4GUOF.mjs → chunk-4F3DG6YB.mjs} +2 -2
  63. package/dist/preview-engine/chunk-A2OTOW3B.mjs +4694 -0
  64. package/dist/preview-engine/{chunk-GNDE3OXQ.mjs → chunk-BR56TQBP.mjs} +1 -1
  65. package/dist/preview-engine/{chunk-ADLIF544.mjs → chunk-H2XKXXAU.mjs} +1 -1
  66. package/dist/preview-engine/chunk-NDUODAY2.mjs +3 -0
  67. package/dist/preview-engine/{chunk-FZ4RD7HV.mjs → chunk-QAQJSWUT.mjs} +1 -1
  68. package/dist/preview-engine/chunk-SWRGQTVC.mjs +138 -0
  69. package/dist/preview-engine/{chunk-52X7SURQ.mjs → chunk-TEA3GVBJ.mjs} +1 -1
  70. package/dist/preview-engine/chunk-VZUVB3BP.mjs +644 -0
  71. package/dist/preview-engine/{chunk-UBY2UYEL.mjs → chunk-XSHNQJ7L.mjs} +1 -1
  72. package/dist/preview-engine/components.mjs +1 -1
  73. package/dist/preview-engine/effects.mjs +1 -1
  74. package/dist/preview-engine/fonts.mjs +1 -1
  75. package/dist/preview-engine/framefields.mjs +1 -1
  76. package/dist/preview-engine/player.mjs +3 -3
  77. package/dist/preview-engine/{renderers-DU7KPFVV.mjs → renderers-6C7NWWDX.mjs} +1 -1
  78. package/dist/preview-engine/renderers-HHVGNMSJ.mjs +206 -0
  79. package/dist/preview-engine/{src-YXNX4MLK.mjs → src-RCNEKIRP.mjs} +1 -1
  80. package/dist/preview-page/index.html +19 -0
  81. package/dist/preview-page/main.css +1 -0
  82. package/dist/preview-page/main.js +326 -0
  83. package/dist/{program-tFKCWU2j.mjs → program-CMigyfso.mjs} +829 -52
  84. package/dist/program-CMigyfso.mjs.map +1 -0
  85. package/dist/renderer/index.d.mts +2 -2
  86. package/dist/renderer/index.mjs +2 -2
  87. package/dist/{renderer-DkOuQi84.mjs → renderer-0GMEL_Zi.mjs} +3 -3
  88. package/dist/{renderer-DkOuQi84.mjs.map → renderer-0GMEL_Zi.mjs.map} +1 -1
  89. package/dist/{renderer-BGp0An5A.mjs → renderer-3Wv2ZBJG.mjs} +3 -3
  90. package/dist/{renderer-BGp0An5A.mjs.map → renderer-3Wv2ZBJG.mjs.map} +1 -1
  91. package/dist/{renderer-BVIXKfbC.mjs → renderer-4nXkaAWu.mjs} +1178 -199
  92. package/dist/renderer-4nXkaAWu.mjs.map +1 -0
  93. package/dist/{renderer-Cw6_NL2i.mjs → renderer-6SnfLXeg.mjs} +3 -3
  94. package/dist/{renderer-Cw6_NL2i.mjs.map → renderer-6SnfLXeg.mjs.map} +1 -1
  95. package/dist/{renderer-BVU22lBS.mjs → renderer-8gHw3O0P.mjs} +2 -2
  96. package/dist/{renderer-BVU22lBS.mjs.map → renderer-8gHw3O0P.mjs.map} +1 -1
  97. package/dist/{renderer-BjDddtw9.mjs → renderer-AOBglZHC.mjs} +2 -2
  98. package/dist/{renderer-D-KHHWQD.mjs → renderer-B0bSARK3.mjs} +3 -3
  99. package/dist/{renderer-D-KHHWQD.mjs.map → renderer-B0bSARK3.mjs.map} +1 -1
  100. package/dist/{renderer-CCxk-RJR.mjs → renderer-B2_tuRfl.mjs} +2 -2
  101. package/dist/{renderer-CCxk-RJR.mjs.map → renderer-B2_tuRfl.mjs.map} +1 -1
  102. package/dist/{renderer-k5d7JSbs.mjs → renderer-B82FhkEr.mjs} +3 -3
  103. package/dist/{renderer-k5d7JSbs.mjs.map → renderer-B82FhkEr.mjs.map} +1 -1
  104. package/dist/{renderer-I0Hh9cLK.mjs → renderer-B9lzA0PY.mjs} +3 -3
  105. package/dist/{renderer-I0Hh9cLK.mjs.map → renderer-B9lzA0PY.mjs.map} +1 -1
  106. package/dist/{renderer-Dwwnx7Dk.mjs → renderer-BBSIWSry.mjs} +2 -2
  107. package/dist/{renderer-Dwwnx7Dk.mjs.map → renderer-BBSIWSry.mjs.map} +1 -1
  108. package/dist/{renderer-_pC-jB9O.mjs → renderer-BEKDMxbi.mjs} +2 -2
  109. package/dist/{renderer-_pC-jB9O.mjs.map → renderer-BEKDMxbi.mjs.map} +1 -1
  110. package/dist/renderer-BERGAxqA.mjs +5 -0
  111. package/dist/{renderer-raxwsiGI.mjs → renderer-BF960HY8.mjs} +3 -3
  112. package/dist/{renderer-raxwsiGI.mjs.map → renderer-BF960HY8.mjs.map} +1 -1
  113. package/dist/{renderer-Dr0tXA4k.mjs → renderer-BFwyVxCu.mjs} +3 -3
  114. package/dist/{renderer-Dr0tXA4k.mjs.map → renderer-BFwyVxCu.mjs.map} +1 -1
  115. package/dist/{renderer-DEjo6dw_.mjs → renderer-BNiwcI3-.mjs} +2 -2
  116. package/dist/{renderer-DEjo6dw_.mjs.map → renderer-BNiwcI3-.mjs.map} +1 -1
  117. package/dist/{renderer-DIEeHiQA.mjs → renderer-BPbVb2Q4.mjs} +2 -2
  118. package/dist/{renderer-DIEeHiQA.mjs.map → renderer-BPbVb2Q4.mjs.map} +1 -1
  119. package/dist/renderer-BQ_YkBMt.mjs +6 -0
  120. package/dist/{renderer-BBMtmxks.mjs → renderer-BUWxlA8T.mjs} +3 -3
  121. package/dist/{renderer-BBMtmxks.mjs.map → renderer-BUWxlA8T.mjs.map} +1 -1
  122. package/dist/renderer-BW0Tjsv9.mjs +5 -0
  123. package/dist/{renderer-Dgj46uEU.mjs → renderer-BX8liu-e.mjs} +2 -2
  124. package/dist/{renderer-ZnrEfkYh.mjs → renderer-BXIwDAr_.mjs} +2 -2
  125. package/dist/{renderer-ZnrEfkYh.mjs.map → renderer-BXIwDAr_.mjs.map} +1 -1
  126. package/dist/{renderer-DA8YEo_d.mjs → renderer-B_Hy1W-g.mjs} +3 -3
  127. package/dist/{renderer-DA8YEo_d.mjs.map → renderer-B_Hy1W-g.mjs.map} +1 -1
  128. package/dist/{renderer-iMiYStcW.mjs → renderer-Bb6cvSgh.mjs} +2 -2
  129. package/dist/{renderer-iMiYStcW.mjs.map → renderer-Bb6cvSgh.mjs.map} +1 -1
  130. package/dist/{renderer-BacY4nh8.mjs → renderer-BdppwWC9.mjs} +2 -2
  131. package/dist/{renderer-BacY4nh8.mjs.map → renderer-BdppwWC9.mjs.map} +1 -1
  132. package/dist/{renderer-CrxwW6VQ.mjs → renderer-Bhc24HVe.mjs} +3 -3
  133. package/dist/{renderer-CrxwW6VQ.mjs.map → renderer-Bhc24HVe.mjs.map} +1 -1
  134. package/dist/{renderer-BH7UCMPQ.mjs → renderer-BiNA2wF0.mjs} +2 -2
  135. package/dist/{renderer-BH7UCMPQ.mjs.map → renderer-BiNA2wF0.mjs.map} +1 -1
  136. package/dist/{renderer-DVPgWHZw.mjs → renderer-BmoBgTp5.mjs} +3 -3
  137. package/dist/{renderer-DVPgWHZw.mjs.map → renderer-BmoBgTp5.mjs.map} +1 -1
  138. package/dist/{renderer-C20efMI0.mjs → renderer-BmqFmCWS.mjs} +3 -3
  139. package/dist/{renderer-C20efMI0.mjs.map → renderer-BmqFmCWS.mjs.map} +1 -1
  140. package/dist/{renderer-B0yVUOPP.mjs → renderer-BnVwAObV.mjs} +2 -2
  141. package/dist/{renderer-B0yVUOPP.mjs.map → renderer-BnVwAObV.mjs.map} +1 -1
  142. package/dist/{renderer-CP8wLzTU.mjs → renderer-BxttZHme.mjs} +2 -2
  143. package/dist/{renderer-CP8wLzTU.mjs.map → renderer-BxttZHme.mjs.map} +1 -1
  144. package/dist/renderer-BzyPdUD1.mjs +5 -0
  145. package/dist/renderer-C0hM-mtY.mjs +5 -0
  146. package/dist/renderer-C6cyUTvk.mjs +5 -0
  147. package/dist/{renderer-BOSZAdgF.mjs → renderer-C8E4XsSg.mjs} +2 -2
  148. package/dist/{renderer-BOSZAdgF.mjs.map → renderer-C8E4XsSg.mjs.map} +1 -1
  149. package/dist/renderer-C8zY3pzk.mjs +5 -0
  150. package/dist/{renderer-DWq9gYRw.mjs → renderer-C9U-LY50.mjs} +3 -3
  151. package/dist/{renderer-DWq9gYRw.mjs.map → renderer-C9U-LY50.mjs.map} +1 -1
  152. package/dist/{renderer-Dj2H5JPR.mjs → renderer-CB2JVu5x.mjs} +3 -3
  153. package/dist/{renderer-Dj2H5JPR.mjs.map → renderer-CB2JVu5x.mjs.map} +1 -1
  154. package/dist/{renderer-Bcu3UrI-.mjs → renderer-CC4YBJ8Z.mjs} +3 -3
  155. package/dist/{renderer-Bcu3UrI-.mjs.map → renderer-CC4YBJ8Z.mjs.map} +1 -1
  156. package/dist/renderer-CEkSOyKu.mjs +5 -0
  157. package/dist/{renderer-DzLrvCB2.mjs → renderer-CIycc5O-.mjs} +2 -2
  158. package/dist/{renderer-DzLrvCB2.mjs.map → renderer-CIycc5O-.mjs.map} +1 -1
  159. package/dist/renderer-CJYtfa4x.mjs +5 -0
  160. package/dist/{renderer-BeNl_6EG.mjs → renderer-CJraQlV-.mjs} +2 -2
  161. package/dist/{renderer-BeNl_6EG.mjs.map → renderer-CJraQlV-.mjs.map} +1 -1
  162. package/dist/renderer-CKMtHGZh.mjs +5 -0
  163. package/dist/renderer-CLzicoiy.mjs +5 -0
  164. package/dist/renderer-CM2A4BHd.mjs +5 -0
  165. package/dist/{renderer-DtMVitEF.mjs → renderer-COWGWtob.mjs} +2 -2
  166. package/dist/{renderer-DtMVitEF.mjs.map → renderer-COWGWtob.mjs.map} +1 -1
  167. package/dist/{renderer-CT3zkfbf.mjs → renderer-COxG2XGf.mjs} +2 -2
  168. package/dist/{renderer-CT3zkfbf.mjs.map → renderer-COxG2XGf.mjs.map} +1 -1
  169. package/dist/{renderer-DNxWOz8Q.mjs → renderer-CQDCIsQm.mjs} +2 -2
  170. package/dist/{renderer-DNxWOz8Q.mjs.map → renderer-CQDCIsQm.mjs.map} +1 -1
  171. package/dist/{renderer-C5AoaymD.mjs → renderer-CRUeag8i.mjs} +3 -3
  172. package/dist/{renderer-C5AoaymD.mjs.map → renderer-CRUeag8i.mjs.map} +1 -1
  173. package/dist/{renderer-ngmYIIob.mjs → renderer-CWVput06.mjs} +3 -3
  174. package/dist/{renderer-ngmYIIob.mjs.map → renderer-CWVput06.mjs.map} +1 -1
  175. package/dist/renderer-CZlHjcxr.mjs +5 -0
  176. package/dist/{renderer-0rveCDm-.mjs → renderer-CbbnBLOB.mjs} +3 -3
  177. package/dist/{renderer-0rveCDm-.mjs.map → renderer-CbbnBLOB.mjs.map} +1 -1
  178. package/dist/renderer-CbimvSU8.mjs +5 -0
  179. package/dist/renderer-Ccvc63yd.mjs +5 -0
  180. package/dist/renderer-Cdp-SGfq.mjs +5 -0
  181. package/dist/renderer-CeTcSs3e.mjs +5 -0
  182. package/dist/{renderer-Brmbl12Y.mjs → renderer-CfLZein2.mjs} +3 -3
  183. package/dist/{renderer-Brmbl12Y.mjs.map → renderer-CfLZein2.mjs.map} +1 -1
  184. package/dist/{renderer-BJ8ea92W.mjs → renderer-Ch-q1O-q.mjs} +3 -3
  185. package/dist/{renderer-BJ8ea92W.mjs.map → renderer-Ch-q1O-q.mjs.map} +1 -1
  186. package/dist/{renderer-CP108ABB.mjs → renderer-Ch5JvJWl.mjs} +2 -2
  187. package/dist/{renderer-CP108ABB.mjs.map → renderer-Ch5JvJWl.mjs.map} +1 -1
  188. package/dist/{renderer-47y12Z37.mjs → renderer-ChtEzq-R.mjs} +2 -2
  189. package/dist/{renderer-47y12Z37.mjs.map → renderer-ChtEzq-R.mjs.map} +1 -1
  190. package/dist/{renderer-DhyMfG1v.mjs → renderer-Cj9nTC5l.mjs} +3 -3
  191. package/dist/{renderer-DhyMfG1v.mjs.map → renderer-Cj9nTC5l.mjs.map} +1 -1
  192. package/dist/{renderer-BDzn1AKF.mjs → renderer-CqT8HsSS.mjs} +2 -2
  193. package/dist/{renderer-BDzn1AKF.mjs.map → renderer-CqT8HsSS.mjs.map} +1 -1
  194. package/dist/{renderer-B7G1KohJ.mjs → renderer-CuQjMhdV.mjs} +2 -2
  195. package/dist/renderer-CvNuu52U.mjs +5 -0
  196. package/dist/renderer-CwciC0oI.mjs +5 -0
  197. package/dist/renderer-CysLUbIr.mjs +5 -0
  198. package/dist/{renderer-Cm0tsbQH.mjs → renderer-Cz30EVws.mjs} +2 -2
  199. package/dist/{renderer-Cm0tsbQH.mjs.map → renderer-Cz30EVws.mjs.map} +1 -1
  200. package/dist/renderer-D3A22Vxb.mjs +5 -0
  201. package/dist/renderer-D5SY9EmA.mjs +5 -0
  202. package/dist/renderer-D8h3ZvaE.mjs +5 -0
  203. package/dist/renderer-DE-B6N9n.mjs +5 -0
  204. package/dist/renderer-DETZTI7t.mjs +5 -0
  205. package/dist/{renderer-nHS3Sius.mjs → renderer-DHoX-5U_.mjs} +3 -3
  206. package/dist/{renderer-nHS3Sius.mjs.map → renderer-DHoX-5U_.mjs.map} +1 -1
  207. package/dist/{renderer-BWSRryIA.mjs → renderer-DOkm0Ycd.mjs} +2 -2
  208. package/dist/{renderer-BWSRryIA.mjs.map → renderer-DOkm0Ycd.mjs.map} +1 -1
  209. package/dist/{renderer-BHYJlK6H.d.mts → renderer-DU3f6ZrS.d.mts} +2 -2
  210. package/dist/{renderer-BHYJlK6H.d.mts.map → renderer-DU3f6ZrS.d.mts.map} +1 -1
  211. package/dist/{renderer-BpVOk9ux.mjs → renderer-DUzv-shq.mjs} +2 -2
  212. package/dist/{renderer-BpVOk9ux.mjs.map → renderer-DUzv-shq.mjs.map} +1 -1
  213. package/dist/renderer-DWnDj0IM.mjs +5 -0
  214. package/dist/renderer-DcGUIbQF.mjs +5 -0
  215. package/dist/renderer-Dfq6FoNO.mjs +5 -0
  216. package/dist/renderer-DhasXW6S.mjs +3 -0
  217. package/dist/{renderer-NRMlYkX6.mjs → renderer-DhnOWnEA.mjs} +3 -3
  218. package/dist/{renderer-NRMlYkX6.mjs.map → renderer-DhnOWnEA.mjs.map} +1 -1
  219. package/dist/{renderer-COgwYqev.mjs → renderer-Dhy1XHzz.mjs} +3 -3
  220. package/dist/{renderer-COgwYqev.mjs.map → renderer-Dhy1XHzz.mjs.map} +1 -1
  221. package/dist/renderer-DjCvpsV_.mjs +5 -0
  222. package/dist/{renderer-BfZNIiSv.mjs → renderer-DjJshz9Y.mjs} +2 -2
  223. package/dist/{renderer-BfZNIiSv.mjs.map → renderer-DjJshz9Y.mjs.map} +1 -1
  224. package/dist/{renderer-CKo7VR8x.mjs → renderer-DmTUIdus.mjs} +3 -3
  225. package/dist/{renderer-CKo7VR8x.mjs.map → renderer-DmTUIdus.mjs.map} +1 -1
  226. package/dist/{renderer-Baw6Utxi.mjs → renderer-Dq7AioJa.mjs} +2 -2
  227. package/dist/{renderer-Baw6Utxi.mjs.map → renderer-Dq7AioJa.mjs.map} +1 -1
  228. package/dist/renderer-DrAaM6JD.mjs +5 -0
  229. package/dist/{renderer-D-GWBoVT.mjs → renderer-Dt4sqNEu.mjs} +2 -2
  230. package/dist/{renderer-D-GWBoVT.mjs.map → renderer-Dt4sqNEu.mjs.map} +1 -1
  231. package/dist/{renderer-FYOL5Za6.mjs → renderer-Dtcs4idV.mjs} +3 -3
  232. package/dist/{renderer-FYOL5Za6.mjs.map → renderer-Dtcs4idV.mjs.map} +1 -1
  233. package/dist/renderer-Dxlv8LYm.mjs +5 -0
  234. package/dist/{renderer-D-RhYvVl.mjs → renderer-Gj8SrE2W.mjs} +2 -2
  235. package/dist/{renderer-D-RhYvVl.mjs.map → renderer-Gj8SrE2W.mjs.map} +1 -1
  236. package/dist/{renderer-D_j1WKMD.mjs → renderer-KfnXDJS0.mjs} +3 -3
  237. package/dist/{renderer-D_j1WKMD.mjs.map → renderer-KfnXDJS0.mjs.map} +1 -1
  238. package/dist/{renderer-Bpm145BW.mjs → renderer-MXVlg4p5.mjs} +3 -3
  239. package/dist/{renderer-Bpm145BW.mjs.map → renderer-MXVlg4p5.mjs.map} +1 -1
  240. package/dist/{renderer-Nnmi8Gt6.mjs → renderer-N2b6QZ1E.mjs} +3 -3
  241. package/dist/{renderer-Nnmi8Gt6.mjs.map → renderer-N2b6QZ1E.mjs.map} +1 -1
  242. package/dist/renderer-Pb43OO2t.mjs +5 -0
  243. package/dist/{renderer-Ca1KjNCO.mjs → renderer-Q7gDzm8c.mjs} +2 -2
  244. package/dist/{renderer-Ca1KjNCO.mjs.map → renderer-Q7gDzm8c.mjs.map} +1 -1
  245. package/dist/{renderer-D2wNFNRf.mjs → renderer-RCiANtRV.mjs} +2 -2
  246. package/dist/{renderer-D2wNFNRf.mjs.map → renderer-RCiANtRV.mjs.map} +1 -1
  247. package/dist/{renderer-Cqb7PGrI.mjs → renderer-Sig9nZcI.mjs} +2 -2
  248. package/dist/renderer-VQbm-cGr.mjs +5 -0
  249. package/dist/{renderer-W1bVNVR6.mjs → renderer-fabBgwQX.mjs} +3 -3
  250. package/dist/{renderer-W1bVNVR6.mjs.map → renderer-fabBgwQX.mjs.map} +1 -1
  251. package/dist/{renderer-zRq0j6Xy.mjs → renderer-g-0PCNoo.mjs} +3 -3
  252. package/dist/{renderer-zRq0j6Xy.mjs.map → renderer-g-0PCNoo.mjs.map} +1 -1
  253. package/dist/{renderer-2PjR3w-q.mjs → renderer-gmHKIbKI.mjs} +3 -3
  254. package/dist/{renderer-2PjR3w-q.mjs.map → renderer-gmHKIbKI.mjs.map} +1 -1
  255. package/dist/{renderer-03mYJLgX.mjs → renderer-kVZwiRoK.mjs} +3 -3
  256. package/dist/{renderer-03mYJLgX.mjs.map → renderer-kVZwiRoK.mjs.map} +1 -1
  257. package/dist/renderer-oPMypDM1.mjs +5 -0
  258. package/dist/{renderer-DcAKDAnS.mjs → renderer-p7tgxP-A.mjs} +3 -3
  259. package/dist/{renderer-DcAKDAnS.mjs.map → renderer-p7tgxP-A.mjs.map} +1 -1
  260. package/dist/{renderer-jUKdkmG5.mjs → renderer-tvYj306A.mjs} +81 -78
  261. package/dist/renderer-tvYj306A.mjs.map +1 -0
  262. package/dist/renderer-xvWGpVyp.mjs +5 -0
  263. package/dist/renderer-zafZbD6Q.mjs +5 -0
  264. package/dist/{renderers-CP8CFWDs-CshljORp.mjs → renderers-CP8CFWDs-DLjtjrwx.mjs} +2 -2
  265. package/dist/{renderers-CP8CFWDs-CshljORp.mjs.map → renderers-CP8CFWDs-DLjtjrwx.mjs.map} +1 -1
  266. package/dist/sam/index.d.mts +2 -2
  267. package/dist/sam/index.mjs +1 -1
  268. package/dist/signals/index.d.mts +4 -4
  269. package/dist/signals/index.mjs +4 -4
  270. package/dist/{src-BU6vm9sP-DdTHmCr4.mjs → src-B23p_eh7-Cj6-lvV6.mjs} +293 -6
  271. package/dist/src-B23p_eh7-Cj6-lvV6.mjs.map +1 -0
  272. package/dist/{src-Dy4gnc6w.mjs → src-CoHAjviq.mjs} +1349 -177
  273. package/dist/src-CoHAjviq.mjs.map +1 -0
  274. package/dist/storage/index.mjs +1 -1
  275. package/package.json +63 -60
  276. package/dist/dist--Uu5bQ_6.mjs.map +0 -1
  277. package/dist/dist-CiTgmfUw.mjs.map +0 -1
  278. package/dist/fonts-OW3yAfO5.mjs.map +0 -1
  279. package/dist/index-9xte4JgL.d.mts.map +0 -1
  280. package/dist/index-Bv7gxGK1.d.mts.map +0 -1
  281. package/dist/index-C5sgRLlG.d.mts.map +0 -1
  282. package/dist/index-C7-p9zwU.d.mts.map +0 -1
  283. package/dist/index-CRaydvLi.d.mts.map +0 -1
  284. package/dist/index-D1FqV1eu.d.mts.map +0 -1
  285. package/dist/preview-D7BGmE1E.mjs +0 -2446
  286. package/dist/preview-D7BGmE1E.mjs.map +0 -1
  287. package/dist/preview-engine/chunk-5DZTPUMH.mjs +0 -2568
  288. package/dist/preview-engine/chunk-NCQHOGZK.mjs +0 -3
  289. package/dist/preview-engine/chunk-PH3UUX6A.mjs +0 -138
  290. package/dist/preview-engine/chunk-UPZURPJY.mjs +0 -2227
  291. package/dist/preview-engine/renderers-DLJOA3BM.mjs +0 -228
  292. package/dist/program-tFKCWU2j.mjs.map +0 -1
  293. package/dist/renderer-6_bzbbXt.mjs +0 -5
  294. package/dist/renderer-8M-9G6F_.mjs +0 -5
  295. package/dist/renderer-B5OzKvF8.mjs +0 -5
  296. package/dist/renderer-BBCZ2qbE.mjs +0 -5
  297. package/dist/renderer-BChbxCux.mjs +0 -5
  298. package/dist/renderer-BGeMPr8C.mjs +0 -5
  299. package/dist/renderer-BPQ8Pkjb.mjs +0 -5
  300. package/dist/renderer-BTRpA3HD.mjs +0 -5
  301. package/dist/renderer-BVIXKfbC.mjs.map +0 -1
  302. package/dist/renderer-BVsECSn-.mjs +0 -5
  303. package/dist/renderer-Bgos68gc.mjs +0 -6
  304. package/dist/renderer-BjHOvoqO.mjs +0 -5
  305. package/dist/renderer-BjXMPPlV.mjs +0 -5
  306. package/dist/renderer-Byobxqkv.mjs +0 -5
  307. package/dist/renderer-CKrsg3EG.mjs +0 -5
  308. package/dist/renderer-Cj875NuH.mjs +0 -5
  309. package/dist/renderer-CnmzJoMv.mjs +0 -5
  310. package/dist/renderer-CtklDaMg.mjs +0 -5
  311. package/dist/renderer-D1tK3in_.mjs +0 -5
  312. package/dist/renderer-D2yc9P61.mjs +0 -5
  313. package/dist/renderer-D4v-JvSr.mjs +0 -5
  314. package/dist/renderer-D5yp74NL.mjs +0 -3
  315. package/dist/renderer-D7WRWDwB.mjs +0 -5
  316. package/dist/renderer-DGvA0tdB.mjs +0 -5
  317. package/dist/renderer-DO7v5Sdm.mjs +0 -5
  318. package/dist/renderer-DRVvt7-V.mjs +0 -5
  319. package/dist/renderer-DYMCd3jL.mjs +0 -5
  320. package/dist/renderer-DmHKw-MM.mjs +0 -5
  321. package/dist/renderer-DoDsuZ6G.mjs +0 -5
  322. package/dist/renderer-EpgclS70.mjs +0 -5
  323. package/dist/renderer-GroKiYyK.mjs +0 -5
  324. package/dist/renderer-MQ5K_xIS.mjs +0 -5
  325. package/dist/renderer-U43SflML.mjs +0 -5
  326. package/dist/renderer-UqOmBgCv.mjs +0 -5
  327. package/dist/renderer-XLULgJM0.mjs +0 -5
  328. package/dist/renderer-jUKdkmG5.mjs.map +0 -1
  329. package/dist/renderer-lNjxY1GG.mjs +0 -5
  330. package/dist/renderer-ukufL7pl.mjs +0 -5
  331. package/dist/renderer-wb0NCKdF.mjs +0 -5
  332. package/dist/src-BU6vm9sP-DdTHmCr4.mjs.map +0 -1
  333. package/dist/src-Dy4gnc6w.mjs.map +0 -1
@@ -0,0 +1,1037 @@
1
+ import { q as inputStore } from "./dist-XqlFM2a8.mjs";
2
+ import { t as encodeStereoWav } from "./audio-DS9Jg2ls.mjs";
3
+ import { s as HeadlessMediaRenderer } from "./renderer-tvYj306A.mjs";
4
+ import { h as openAssetLocator } from "./dist-f1zkl69f.mjs";
5
+ import { createRequire, isBuiltin } from "node:module";
6
+ import fs from "node:fs";
7
+ import path from "node:path";
8
+ import { createHash, randomUUID } from "node:crypto";
9
+ import os from "node:os";
10
+ import { fileURLToPath, pathToFileURL } from "node:url";
11
+ import { spawn } from "node:child_process";
12
+ import http from "node:http";
13
+
14
+ //#region src/preview/bundle.ts
15
+ /**
16
+ * The preview page runs the project's composition code and renders it with
17
+ * WebGPU. Two bundles make that work:
18
+ *
19
+ * - The engine (`buildEngine`): framefields and its browser player, built once
20
+ * for the browser. Published packages ship it in `dist/preview-engine`;
21
+ * running from source, it is built on first use.
22
+ * - The project (`bundleProject`): only the user's own files, built on each
23
+ * preview. Its `framefields` imports point at the engine's files, so the
24
+ * composition and the player share one engine (signals, clock, caches).
25
+ *
26
+ * The page around them (`buildPage`) is its own small bundle: the Lit UI in
27
+ * `ui/`, shipped prebuilt in `dist/preview-page`, or built on first use.
28
+ *
29
+ * Node APIs that compositions use at build time are shimmed: `path`, `url`,
30
+ * `os`, `import.meta.url` (each file keeps its own `file://` URL), and `fs`
31
+ * reads, which fetch the file from the preview server. Absolute file paths
32
+ * double as URLs there, so assets referenced by path load unchanged.
33
+ * Node-only packages become stubs that throw when used.
34
+ */
35
+ const HERE = path.dirname(fileURLToPath(import.meta.url));
36
+ const META_URL = [
37
+ "import",
38
+ "meta",
39
+ "url"
40
+ ].join(".");
41
+ const require = createRequire(import.meta.url);
42
+ /** URL prefix the server serves the engine's files under. */
43
+ const ENGINE_URL = "/@framefields/engine";
44
+ /** URL prefix the server serves the page's script and styles under. */
45
+ const PAGE_URL = "/@framefields/ui";
46
+ /** Public entry points a composition may import, and their sources. */
47
+ const ENTRIES = {
48
+ framefields: "index.ts",
49
+ "framefields/effects": "effects/index.ts",
50
+ "framefields/audio": "audio/index.ts",
51
+ "framefields/signals": "signals/index.ts",
52
+ "framefields/fonts": "fonts/index.ts",
53
+ "framefields/components": "components/index.ts"
54
+ };
55
+ /** Node built-ins with a browser version (shim file names); others are stubbed. */
56
+ const SHIMS = {
57
+ path: "path",
58
+ "path/posix": "path",
59
+ url: "url",
60
+ os: "os",
61
+ fs: "fs",
62
+ "fs/promises": "fs-promises",
63
+ module: "module"
64
+ };
65
+ /** Packages that only work in Node; the browser never needs them to draw. */
66
+ const NODE_ONLY = new Set([
67
+ "sharp",
68
+ "skia-canvas",
69
+ "node-av",
70
+ "onnxruntime-node",
71
+ "@resvg/resvg-js",
72
+ "@napi-rs/webcodecs",
73
+ "@mediabunny/server",
74
+ "@mediabunny/mp3-encoder",
75
+ "webgpu",
76
+ "pino",
77
+ "pino-pretty",
78
+ "dotenv",
79
+ "esbuild",
80
+ "@framefields/renderer",
81
+ "framefields/renderer",
82
+ "framefields/preview"
83
+ ]);
84
+ const STUB = `
85
+ const stub = () => new Proxy(function () {}, {
86
+ get: (_t, k) => (k === "__esModule" ? true : k === Symbol.toPrimitive ? () => "" : stub()),
87
+ apply: () => stub(),
88
+ construct: () => stub(),
89
+ });
90
+ module.exports = stub();
91
+ `;
92
+ /**
93
+ * Builds framefields for the browser into `outDir`: one module per public entry,
94
+ * the player and the shims, with shared code split into chunks (node renderers
95
+ * load only when a composition uses them).
96
+ */
97
+ async function buildEngine(outDir) {
98
+ const { build } = await import("esbuild");
99
+ const src = path.resolve(HERE, "..");
100
+ const shims = path.join(HERE, "shims");
101
+ const entryPoints = { player: path.join(HERE, "player.ts") };
102
+ for (const [spec, file] of Object.entries(ENTRIES)) entryPoints[engineName(spec)] = path.join(src, file);
103
+ for (const name of new Set(Object.values(SHIMS))) entryPoints[`shims/${name}`] = path.join(shims, `${name}.ts`);
104
+ await fs.promises.rm(outDir, {
105
+ recursive: true,
106
+ force: true
107
+ });
108
+ await build({
109
+ ...BROWSER,
110
+ entryPoints,
111
+ outdir: outDir,
112
+ outExtension: { ".js": ".mjs" },
113
+ splitting: true,
114
+ minify: true,
115
+ conditions: [
116
+ "development",
117
+ "browser",
118
+ "import"
119
+ ],
120
+ define: {
121
+ "process.env.NODE_ENV": "\"production\"",
122
+ global: "globalThis"
123
+ },
124
+ inject: [path.join(shims, "process.ts")],
125
+ plugins: [{
126
+ name: "framefields-engine",
127
+ setup(b) {
128
+ b.onResolve({ filter: /^framefields-preview:renderers$/ }, () => ({ path: builtinRenderersModule() }));
129
+ resolveNode(b, (name) => ({ path: path.join(shims, `${name}.ts`) }));
130
+ }
131
+ }]
132
+ });
133
+ }
134
+ /** Bundles the module that builds the composition, and what it imports. */
135
+ async function bundleProject(options) {
136
+ const { build } = await import("esbuild");
137
+ const entry = path.resolve(options.entry);
138
+ const fromEngine = (file) => ({
139
+ path: `${ENGINE_URL}/${file}.mjs`,
140
+ external: true
141
+ });
142
+ const settings = {
143
+ modelsDir: options.modelsDir,
144
+ ortBase: options.ortBase
145
+ };
146
+ const result = await build({
147
+ ...BROWSER,
148
+ entryPoints: ["framefields-preview:entry"],
149
+ write: false,
150
+ metafile: true,
151
+ sourcemap: "inline",
152
+ plugins: [{
153
+ name: "framefields-project",
154
+ setup(b) {
155
+ b.onResolve({ filter: /^framefields-preview:entry$/ }, () => ({
156
+ path: "entry",
157
+ namespace: "framefields-preview"
158
+ }));
159
+ b.onLoad({
160
+ filter: /.*/,
161
+ namespace: "framefields-preview"
162
+ }, () => ({
163
+ contents: [
164
+ `import { startAudioMixer, startPlayer } from ${JSON.stringify(`${ENGINE_URL}/player.mjs`)};`,
165
+ `import * as mod from ${JSON.stringify(entry)};`,
166
+ `if (typeof document === "undefined") startAudioMixer(mod, ${JSON.stringify(options.exportName)});`,
167
+ `else startPlayer(mod, ${JSON.stringify(options.exportName)}, ${JSON.stringify(settings)});`
168
+ ].join("\n"),
169
+ resolveDir: path.dirname(entry),
170
+ loader: "js"
171
+ }));
172
+ b.onResolve({ filter: /^\/@framefields\/engine\// }, (args) => ({
173
+ path: args.path,
174
+ external: true
175
+ }));
176
+ b.onResolve({ filter: /^framefields(\/|$)/ }, (args) => ENTRIES[args.path] ? fromEngine(engineName(args.path)) : void 0);
177
+ resolveNode(b, (name) => fromEngine(`shims/${name}`));
178
+ rewriteMetaUrl(b);
179
+ }
180
+ }]
181
+ });
182
+ const inputs = Object.keys(result.metafile?.inputs ?? {}).filter((p) => !p.includes(":") && !p.includes("node_modules")).map((p) => path.resolve(p));
183
+ return {
184
+ code: result.outputFiles[0].text,
185
+ inputs
186
+ };
187
+ }
188
+ /**
189
+ * Builds the preview page into `outDir`: `index.html`, and `main.js` and
190
+ * `main.css` from the UI's sources (Lit included; nothing else of the page
191
+ * reaches a project's code). `dev` keeps it readable, with a source map.
192
+ */
193
+ async function buildPage(outDir, { dev = false } = {}) {
194
+ const { build } = await import("esbuild");
195
+ const ui = path.join(HERE, "ui");
196
+ await fs.promises.rm(outDir, {
197
+ recursive: true,
198
+ force: true
199
+ });
200
+ await build({
201
+ ...BROWSER,
202
+ entryPoints: { main: path.join(ui, "main.ts") },
203
+ outdir: outDir,
204
+ minify: !dev,
205
+ sourcemap: dev ? "inline" : false,
206
+ conditions: ["browser", "import"],
207
+ plugins: [{
208
+ name: "framefields-page",
209
+ setup(b) {
210
+ b.onResolve({ filter: /^(node:|@framefields\/)/ }, (args) => ({ errors: [{ text: `the preview page may not import ${args.path} (from ${args.importer})` }] }));
211
+ }
212
+ }]
213
+ });
214
+ await fs.promises.copyFile(path.join(ui, "index.html"), path.join(outDir, "index.html"));
215
+ }
216
+ /**
217
+ * A prebuilt folder shipped beside a compiled bundle (`dist/<name>`), if
218
+ * there is one. The bundle lands either at the package's dist root or beside
219
+ * dist/preview, depending on how the bundler splits it, so both are checked.
220
+ */
221
+ function prebuiltDir(here, name, marker) {
222
+ return [here, path.join(here, "..")].map((base) => path.join(base, name)).find((dir) => fs.existsSync(path.join(dir, marker)));
223
+ }
224
+ /** The prebuilt engine shipped beside a compiled bundle, if there is one. */
225
+ function prebuiltEngineDir(here) {
226
+ return prebuiltDir(here, "preview-engine", "player.mjs");
227
+ }
228
+ const builds = /* @__PURE__ */ new Map();
229
+ /**
230
+ * `name` prebuilt in the package's dist or, running from source (where HERE
231
+ * is src/preview, so the lookup misses), built once per process into a
232
+ * temporary folder, so edits show without a package build.
233
+ */
234
+ function prebuiltOrBuild(prebuilt, name, build) {
235
+ if (prebuilt) return Promise.resolve(prebuilt);
236
+ let pending = builds.get(name);
237
+ if (!pending) {
238
+ const dir = path.join(os.tmpdir(), `framefields-${name}-${process.pid}`);
239
+ pending = build(dir).then(() => dir);
240
+ builds.set(name, pending);
241
+ pending.catch(() => builds.delete(name));
242
+ }
243
+ return pending;
244
+ }
245
+ /** The browser engine to serve. */
246
+ function engineDir() {
247
+ return prebuiltOrBuild(prebuiltEngineDir(HERE), "preview-engine", buildEngine);
248
+ }
249
+ /** The preview page to serve. */
250
+ function pageDir() {
251
+ return prebuiltOrBuild(prebuiltDir(HERE, "preview-page", "main.js"), "preview-page", (dir) => buildPage(dir, { dev: true }));
252
+ }
253
+ /** Where vision models are cached, as `@framefields/vision` resolves it. */
254
+ function visionModelsDir() {
255
+ return process.env.FRAMEFIELDS_MODELS_DIR ? path.resolve(process.env.FRAMEFIELDS_MODELS_DIR) : path.join(os.homedir(), ".cache", "framefields", "models");
256
+ }
257
+ const ORT_WEB_VERSION = "1.30.0";
258
+ /**
259
+ * onnxruntime-web, for vision effects: the project's own install when it has
260
+ * one (served from disk), otherwise a pinned CDN build. Loaded only when a
261
+ * vision effect runs, so framefields doesn't ship its WebAssembly.
262
+ */
263
+ function onnxRuntimeWeb(fromDir) {
264
+ for (const from of [fromDir, HERE]) try {
265
+ const main = createRequire(path.join(from, "noop.js")).resolve("onnxruntime-web");
266
+ const dir = path.dirname(main);
267
+ return {
268
+ base: `/@fs${dir}/`,
269
+ dir
270
+ };
271
+ } catch {}
272
+ return { base: `https://cdn.jsdelivr.net/npm/onnxruntime-web@${ORT_WEB_VERSION}/dist/` };
273
+ }
274
+ const BROWSER = {
275
+ bundle: true,
276
+ format: "esm",
277
+ platform: "browser",
278
+ target: "es2022",
279
+ mainFields: [
280
+ "browser",
281
+ "module",
282
+ "main"
283
+ ],
284
+ logLevel: "silent"
285
+ };
286
+ function engineName(spec) {
287
+ return spec === "framefields" ? "framefields" : spec.slice(12);
288
+ }
289
+ /** Node built-ins to shims, the rest of Node to stubs. */
290
+ function resolveNode(b, shim) {
291
+ b.onResolve({ filter: /^(node:)?[a-z_/]+$/ }, (args) => {
292
+ const name = args.path.replace(/^node:/, "");
293
+ if (SHIMS[name]) return shim(SHIMS[name]);
294
+ if (args.path.startsWith("node:") || isBuiltin(name)) return {
295
+ path: name,
296
+ namespace: "framefields-stub"
297
+ };
298
+ });
299
+ b.onResolve({ filter: /.*/ }, (args) => {
300
+ const pkg = packageName(args.path);
301
+ if (pkg && (NODE_ONLY.has(pkg) || NODE_ONLY.has(args.path))) return {
302
+ path: args.path,
303
+ namespace: "framefields-stub"
304
+ };
305
+ });
306
+ b.onLoad({
307
+ filter: /.*/,
308
+ namespace: "framefields-stub"
309
+ }, () => ({
310
+ contents: STUB,
311
+ loader: "js"
312
+ }));
313
+ }
314
+ /** Each project file keeps its own URL, as `fileURLToPath(import.meta.url)` expects. */
315
+ function rewriteMetaUrl(b) {
316
+ b.onLoad({ filter: /\.[cm]?[jt]sx?$/ }, async (args) => {
317
+ if (args.path.includes(`${path.sep}node_modules${path.sep}`)) return void 0;
318
+ const source = await fs.promises.readFile(args.path, "utf8");
319
+ if (!source.includes(META_URL)) return void 0;
320
+ return {
321
+ contents: `const __framefieldsFileUrl = ${JSON.stringify(pathToFileURL(args.path).href)};\n${source.replaceAll(META_URL, "__framefieldsFileUrl")}`,
322
+ loader: loaderFor(args.path)
323
+ };
324
+ });
325
+ }
326
+ /** The generated list of node renderers (needed only to build the engine). */
327
+ function builtinRenderersModule() {
328
+ const dist = require.resolve("@framefields/renderer");
329
+ const file = path.join(path.dirname(dist), "..", "src", "generated", "node-renderers.ts");
330
+ if (!fs.existsSync(file)) throw new Error(`[framefields] node renderer list not found at ${file}`);
331
+ return file;
332
+ }
333
+ function packageName(spec) {
334
+ if (spec.startsWith(".") || spec.startsWith("/")) return void 0;
335
+ const parts = spec.split("/");
336
+ return spec.startsWith("@") ? parts.slice(0, 2).join("/") : parts[0];
337
+ }
338
+ function loaderFor(file) {
339
+ if (file.endsWith(".tsx")) return "tsx";
340
+ if (file.endsWith(".jsx")) return "jsx";
341
+ return /\.[cm]?ts$/.test(file) ? "ts" : "js";
342
+ }
343
+
344
+ //#endregion
345
+ //#region src/preview/notes.ts
346
+ /**
347
+ * Notes the user pins on the preview: a moment, a spot (or an area) on the
348
+ * frame, and what should change there. They are kept as files so whoever
349
+ * edits the film (a person, or an agent) can read them: `notes.json`, plus the
350
+ * frame with the spot marked as `note-<id>.jpg`. The folder ignores itself in
351
+ * git. `notes.json` is read on every change, so it can be edited by hand
352
+ * (marking a note done, say) while the preview runs.
353
+ */
354
+ var NoteStore = class {
355
+ file;
356
+ constructor(dir) {
357
+ this.dir = dir;
358
+ this.file = path.join(dir, "notes.json");
359
+ }
360
+ async list() {
361
+ try {
362
+ const notes = JSON.parse(await fs.promises.readFile(this.file, "utf8"));
363
+ return Array.isArray(notes) ? notes : [];
364
+ } catch {
365
+ return [];
366
+ }
367
+ }
368
+ async add(input) {
369
+ const notes = await this.list();
370
+ const id = notes.reduce((max, n) => Math.max(max, n.id), 0) + 1;
371
+ const note = {
372
+ id,
373
+ frame: Math.max(0, Math.round(Number(input.frame) || 0)),
374
+ time: round(Number(input.time) || 0, 3),
375
+ x: round(clamp01(input.x), 4),
376
+ y: round(clamp01(input.y), 4),
377
+ ...input.w !== void 0 && input.h !== void 0 && {
378
+ w: round(clamp01(input.w), 4),
379
+ h: round(clamp01(input.h), 4)
380
+ },
381
+ text: String(input.text ?? "").trim(),
382
+ done: false,
383
+ created: (/* @__PURE__ */ new Date()).toISOString()
384
+ };
385
+ await this.ensureDir();
386
+ const jpeg = /^data:image\/jpeg;base64,(.+)$/.exec(input.image ?? "");
387
+ if (jpeg) {
388
+ note.image = path.join(this.dir, `note-${id}.jpg`);
389
+ await fs.promises.writeFile(note.image, Buffer.from(jpeg[1], "base64"));
390
+ }
391
+ await this.save([...notes, note]);
392
+ return note;
393
+ }
394
+ async update(id, patch) {
395
+ const notes = await this.list();
396
+ const note = notes.find((n) => n.id === id);
397
+ if (!note) return void 0;
398
+ if (typeof patch.done === "boolean") note.done = patch.done;
399
+ if (typeof patch.text === "string" && patch.text.trim()) note.text = patch.text.trim();
400
+ await this.save(notes);
401
+ return note;
402
+ }
403
+ async remove(id) {
404
+ const notes = await this.list();
405
+ const note = notes.find((n) => n.id === id);
406
+ if (!note) return void 0;
407
+ if (note.image) await fs.promises.rm(note.image, { force: true });
408
+ await this.save(notes.filter((n) => n !== note));
409
+ return note;
410
+ }
411
+ async save(notes) {
412
+ await this.ensureDir();
413
+ const tmp = `${this.file}.${process.pid}.tmp`;
414
+ await fs.promises.writeFile(tmp, `${JSON.stringify(notes, null, " ")}\n`);
415
+ await fs.promises.rename(tmp, this.file);
416
+ }
417
+ async ensureDir() {
418
+ await fs.promises.mkdir(this.dir, { recursive: true });
419
+ const ignore = path.join(path.dirname(this.dir), ".gitignore");
420
+ if (!fs.existsSync(ignore)) await fs.promises.writeFile(ignore, "*\n");
421
+ }
422
+ };
423
+ /** One line for a terminal (or an agent reading one). */
424
+ function describeNote(note, fps) {
425
+ const pct = (v) => `${Math.round(v * 100)}%`;
426
+ const where = note.w !== void 0 && note.h !== void 0 ? `area ${pct(note.x)},${pct(note.y)} to ${pct(note.x + note.w)},${pct(note.y + note.h)}` : `spot ${pct(note.x)},${pct(note.y)}`;
427
+ return `note ${note.id} at ${timecode(note.frame, fps)} (frame ${note.frame}), ${where}: ${note.text}`;
428
+ }
429
+ function timecode(frame, fps) {
430
+ const s = frame / fps;
431
+ const m = Math.floor(s / 60);
432
+ return `${m}:${(s - m * 60).toFixed(2).padStart(5, "0")}`;
433
+ }
434
+ function clamp01(v) {
435
+ const n = Number(v);
436
+ return Number.isFinite(n) ? Math.min(1, Math.max(0, n)) : 0;
437
+ }
438
+ function round(v, digits) {
439
+ return Number(v.toFixed(digits));
440
+ }
441
+
442
+ //#endregion
443
+ //#region src/preview/peaks.ts
444
+ /** Waveform peaks of a stereo mix: the loudest sample of either channel per bin, 0–1. */
445
+ function computePeaks(left, right, bins) {
446
+ const peaks = new Array(bins).fill(0);
447
+ if (left.length === 0) return peaks;
448
+ const per = left.length / bins;
449
+ for (let b = 0; b < bins; b++) {
450
+ const start = Math.floor(b * per);
451
+ const end = Math.min(left.length, Math.floor((b + 1) * per));
452
+ let max = 0;
453
+ for (let i = start; i < end; i++) {
454
+ const v = Math.max(Math.abs(left[i] ?? 0), Math.abs(right[i] ?? 0));
455
+ if (v > max) max = v;
456
+ }
457
+ peaks[b] = Math.min(1, max);
458
+ }
459
+ return peaks;
460
+ }
461
+ /** Bins the page's waveform draws. */
462
+ const PEAK_BINS = 1200;
463
+ /** A mix counts as silent below this peak. */
464
+ const SILENT_PEAK = .001;
465
+
466
+ //#endregion
467
+ //#region src/preview/embed-protocol.ts
468
+ /**
469
+ * The preview page's message channel with an app that embeds it in a frame
470
+ * (protocol 1). The page talks only when embedded, with
471
+ * `window.parent.postMessage(message, "*")`, and takes commands only from
472
+ * `window.parent`. Every message has a `type` starting `framefields-preview:`.
473
+ *
474
+ * Page → parent: `ready` once the first frame shows; `state` on play, pause,
475
+ * each finished seek, and up to 4 times a second while playing; `build`
476
+ * around a rebuild (and `error` when a build or a frame fails); `reply` to
477
+ * each command. Parent → page: `command`, answered by a `reply` with its `id`.
478
+ *
479
+ * The page also seeks when its address fragment changes (`#t=12.5`,
480
+ * `#frame=375`), without a reload; `seekByHash` in `ready` says so.
481
+ */
482
+ const PREVIEW_PROTOCOL = 1;
483
+ const COMMANDS = new Set([
484
+ "seek",
485
+ "seekFrame",
486
+ "play",
487
+ "pause",
488
+ "state"
489
+ ]);
490
+ /** The command in a message from the parent, or null when it isn't a well-formed one. */
491
+ function readPreviewCommand(data) {
492
+ if (typeof data !== "object" || data === null) return null;
493
+ const m = data;
494
+ if (m.type !== "framefields-preview:command" || typeof m.id !== "string") return null;
495
+ if (typeof m.command !== "string" || !COMMANDS.has(m.command)) return null;
496
+ if (m.command === "seek" && !Number.isFinite(m.time)) return null;
497
+ if (m.command === "seekFrame" && !Number.isFinite(m.frame)) return null;
498
+ return m;
499
+ }
500
+
501
+ //#endregion
502
+ //#region src/preview/index.ts
503
+ /** Requests carrying this header come from another preview process, not a page. */
504
+ const TAKEOVER_HEADER = "x-framefields-preview";
505
+ const PLAYER_PATH = "/@framefields/player.js";
506
+ /**
507
+ * Serves a localhost player for a composition. The page loads the
508
+ * composition's own code, bundled with the engine, and renders its frames
509
+ * with WebGPU in the browser, like the editor's player; nothing is streamed.
510
+ * The server provides the bundle, the project's files, and the soundtrack,
511
+ * which it mixes with the export engine.
512
+ *
513
+ * The server runs until its tab closes or `close()`. Starting another preview
514
+ * on the same port (the default for a project) stops this one, and open tabs
515
+ * reload into it.
516
+ */
517
+ async function startPreview(source, options = {}) {
518
+ const entry = source.entry instanceof URL || String(source.entry).startsWith("file:") ? fileURLToPath(source.entry) : path.resolve(String(source.entry));
519
+ const exportName = source.export ?? "default";
520
+ const modelsDir = visionModelsDir();
521
+ const ort = onnxRuntimeWeb(path.dirname(entry));
522
+ const roots = [
523
+ ...options.root === void 0 ? [repoRoot(path.dirname(entry))] : Array.isArray(options.root) ? options.root : [options.root],
524
+ modelsDir,
525
+ ort.dir
526
+ ].filter((r) => !!r).map((r) => path.resolve(r));
527
+ const sseClients = /* @__PURE__ */ new Set();
528
+ const idleCloseMs = options.idleCloseMs ?? 5e3;
529
+ let idleTimer;
530
+ const title = options.title ?? "framefields preview";
531
+ const session = randomUUID();
532
+ const log = options.log === false ? () => {} : options.log ?? ((line) => console.error(line));
533
+ const say = (line) => log(`[framefields preview] ${line}`);
534
+ const reported = /* @__PURE__ */ new Set();
535
+ let pageOpened = false;
536
+ const notes = options.notesDir === false ? void 0 : new NoteStore(path.resolve(options.notesDir ?? path.join(process.cwd(), ".framefields", "preview-notes")));
537
+ let noteQueue = Promise.resolve();
538
+ const assets = await openAssetLocator(path.dirname(entry)).catch((err) => {
539
+ say(`pinned assets are off: ${err.message}`);
540
+ });
541
+ if (assets) inputStore.setMissingMediaResolver((file) => assets.url(file));
542
+ const unreadable = /* @__PURE__ */ new Set();
543
+ const target = await loadTarget(entry, exportName);
544
+ const meta = await resolveMeta(target, title, session, notes?.dir ?? null);
545
+ let bundle;
546
+ const playerBundle = () => {
547
+ if (!bundle) {
548
+ const built = bundleProject({
549
+ entry,
550
+ exportName,
551
+ modelsDir,
552
+ ortBase: ort.base
553
+ }).then((b) => b.code);
554
+ bundle = built;
555
+ built.catch((err) => {
556
+ say(`could not build ${path.relative(process.cwd(), entry)}:\n${errorText(err)}`);
557
+ if (bundle === built) bundle = void 0;
558
+ });
559
+ }
560
+ return bundle;
561
+ };
562
+ const engine = engineDir();
563
+ engine.catch(() => {});
564
+ const page = pageDir();
565
+ page.catch(() => {});
566
+ let audio = { state: options.audio === false ? "none" : "browser" };
567
+ let audioWav;
568
+ function broadcast(event, data) {
569
+ for (const client of sseClients) client.write(`event: ${event}\ndata: ${JSON.stringify(data)}\n\n`);
570
+ }
571
+ function setAudio(next) {
572
+ audio = next;
573
+ broadcast("audio", audio);
574
+ }
575
+ function startAudio() {
576
+ if (audio.state !== "mixing") return;
577
+ if (!target.toVirtualMediaAsync && !target.toVirtualMedia) {
578
+ setAudio({ state: "none" });
579
+ return;
580
+ }
581
+ const renderer = options.renderer ?? new HeadlessMediaRenderer();
582
+ (async () => {
583
+ const vm = target.toVirtualMediaAsync ? await target.toVirtualMediaAsync() : target.toVirtualMedia();
584
+ await target.prepareMediaTime?.();
585
+ return renderer.renderAudio(vm, { fps: meta.fps });
586
+ })().then(({ channels, sampleRate }) => {
587
+ const left = channels[0] ?? new Float32Array(0);
588
+ const right = channels[1] ?? left;
589
+ const peaks = computePeaks(left, right, PEAK_BINS);
590
+ if (left.length === 0 || Math.max(...peaks) < SILENT_PEAK) {
591
+ setAudio({ state: "none" });
592
+ return;
593
+ }
594
+ audioWav = encodeStereoWav([left, right], { sampleRate });
595
+ setAudio({
596
+ state: "ready",
597
+ durationSec: left.length / sampleRate,
598
+ peaks: peaks.map((p) => Math.round(p * 1e3) / 1e3)
599
+ });
600
+ }, (err) => {
601
+ setAudio({
602
+ state: "error",
603
+ error: err instanceof Error ? err.message : String(err)
604
+ });
605
+ });
606
+ }
607
+ let resolveClosed;
608
+ const closed = new Promise((r) => {
609
+ resolveClosed = r;
610
+ });
611
+ let closing;
612
+ function close(reason = "closed") {
613
+ closing ??= new Promise((resolve$1) => {
614
+ clearTimeout(idleTimer);
615
+ if (reason === "idle") say("stopped: its tab was closed");
616
+ if (reason === "replaced") say("stopped: a newer preview took over");
617
+ for (const client of sseClients) client.end();
618
+ sseClients.clear();
619
+ if (assets) inputStore.setMissingMediaResolver();
620
+ server.close(() => {
621
+ resolve$1();
622
+ resolveClosed(reason);
623
+ });
624
+ server.closeAllConnections();
625
+ });
626
+ return closing;
627
+ }
628
+ const server = http.createServer(async (req, res) => {
629
+ const url$1 = new URL(req.url ?? "/", "http://localhost");
630
+ res.setHeader("cross-origin-opener-policy", "same-origin");
631
+ res.setHeader("cross-origin-embedder-policy", "credentialless");
632
+ try {
633
+ if (req.method === "GET" && url$1.pathname === "/") {
634
+ await sendFile(req, res, path.join(await page, "index.html"), true);
635
+ return;
636
+ }
637
+ if (req.method === "GET" && url$1.pathname === "/meta") {
638
+ sendJson(res, 200, {
639
+ ...meta,
640
+ audio
641
+ });
642
+ return;
643
+ }
644
+ for (const [prefix, dir] of [[ENGINE_URL, engine], [PAGE_URL, page]]) {
645
+ if (req.method !== "GET" || !url$1.pathname.startsWith(`${prefix}/`)) continue;
646
+ const root = await dir;
647
+ const file = path.resolve(root, `.${url$1.pathname.slice(prefix.length)}`);
648
+ if (isInside(root, file) && await isFile(file)) await sendFile(req, res, file, prefix === PAGE_URL);
649
+ else sendJson(res, 404, { error: "not found" });
650
+ return;
651
+ }
652
+ if (req.method === "GET" && url$1.pathname === PLAYER_PATH) {
653
+ let code;
654
+ try {
655
+ await engine;
656
+ code = await playerBundle();
657
+ } catch (err) {
658
+ const message = err instanceof Error ? err.message : String(err);
659
+ code = `throw new Error(${JSON.stringify(`Could not build the preview:\n${message}`)});`;
660
+ }
661
+ res.writeHead(200, {
662
+ "content-type": "text/javascript; charset=utf-8",
663
+ "cache-control": "no-store"
664
+ });
665
+ res.end(code);
666
+ return;
667
+ }
668
+ if (req.method === "GET" && url$1.pathname === "/@framefields/stat") {
669
+ const file = path.resolve(url$1.searchParams.get("path") ?? "");
670
+ let size = -1;
671
+ if (roots.some((r) => isInside(r, file))) size = await fs.promises.stat(file).then((st) => st.isFile() ? st.size : -1, () => assets?.entry(file)?.size ?? -1);
672
+ sendJson(res, 200, {
673
+ exists: size >= 0,
674
+ size: Math.max(0, size)
675
+ });
676
+ return;
677
+ }
678
+ if (req.method === "GET" && url$1.pathname === "/events") {
679
+ res.writeHead(200, {
680
+ "content-type": "text/event-stream",
681
+ "cache-control": "no-store",
682
+ connection: "keep-alive"
683
+ });
684
+ res.write("retry: 1000\n");
685
+ res.write(`event: hello\ndata: ${JSON.stringify({ session: meta.session })}\n\n`);
686
+ sseClients.add(res);
687
+ clearTimeout(idleTimer);
688
+ if (!pageOpened) {
689
+ pageOpened = true;
690
+ say(idleCloseMs === false ? "page opened" : `page opened; the server stops ${Number((idleCloseMs / 1e3).toFixed(1))} s after its last tab closes`);
691
+ }
692
+ onClient?.();
693
+ req.on("close", () => {
694
+ sseClients.delete(res);
695
+ if (sseClients.size === 0 && idleCloseMs !== false && !closing) idleTimer = setTimeout(() => {
696
+ if (sseClients.size === 0) close("idle");
697
+ }, idleCloseMs);
698
+ });
699
+ return;
700
+ }
701
+ if (req.method === "POST" && url$1.pathname === "/takeover" && req.headers[TAKEOVER_HEADER] === "1") {
702
+ sendJson(res, 200, { ok: true });
703
+ setImmediate(() => void close("replaced"));
704
+ return;
705
+ }
706
+ if (req.method === "POST" && url$1.pathname === "/@framefields/log" && req.headers["content-type"] === "application/json") {
707
+ const body = await readBody(req, 16 * 1024);
708
+ const { message, level } = JSON.parse(body);
709
+ const text = String(message ?? "").trim();
710
+ if (text && !reported.has(text) && reported.size < 200) {
711
+ reported.add(text);
712
+ say(`${level === "warning" ? "warning" : "error"} in the page: ${text}`);
713
+ }
714
+ res.writeHead(204).end();
715
+ return;
716
+ }
717
+ if (notes && url$1.pathname === "/@framefields/notes") {
718
+ if (req.method === "GET") {
719
+ sendJson(res, 200, await notes.list());
720
+ return;
721
+ }
722
+ if (req.method === "POST" && req.headers["content-type"] === "application/json") {
723
+ const body = JSON.parse(await readBody(req, 16 * 1024 * 1024));
724
+ const change = noteQueue.then(() => changeNote(notes, body, meta.fps, say));
725
+ noteQueue = change.catch(() => {});
726
+ const note = await change;
727
+ broadcast("notes", await notes.list());
728
+ sendJson(res, note ? 200 : 404, note ?? { error: "no such note" });
729
+ return;
730
+ }
731
+ }
732
+ if (req.method === "POST" && url$1.pathname === "/audio/mix") {
733
+ if (audio.state === "browser" || audio.state === "error") {
734
+ audio = { state: "mixing" };
735
+ startAudio();
736
+ }
737
+ sendJson(res, 202, audio);
738
+ return;
739
+ }
740
+ if (req.method === "GET" && url$1.pathname === "/audio") {
741
+ if (audio.state !== "ready" || !audioWav) {
742
+ sendJson(res, 404, { error: `audio is ${audio.state}` });
743
+ return;
744
+ }
745
+ res.writeHead(200, {
746
+ "content-type": "audio/wav",
747
+ "content-length": audioWav.length,
748
+ "cache-control": "no-store"
749
+ });
750
+ res.end(audioWav);
751
+ return;
752
+ }
753
+ const fontMatch = /^\/assets\/fonts\/([^/]+)$/.exec(url$1.pathname);
754
+ if (fontMatch && (req.method === "GET" || req.method === "HEAD")) {
755
+ const file = path.join(process.cwd(), "assets", "fonts", decodeURIComponent(fontMatch[1]));
756
+ if (await isFile(file)) {
757
+ await sendFile(req, res, file);
758
+ return;
759
+ }
760
+ }
761
+ if (req.method === "GET" || req.method === "HEAD") {
762
+ const file = path.resolve(decodeURIComponent(url$1.pathname.replace(/^\/@fs(?=\/)/, "")));
763
+ if (roots.some((r) => isInside(r, file)) && await isFile(file)) {
764
+ await sendFile(req, res, file);
765
+ return;
766
+ }
767
+ if (assets && await redirectToBucket(res, assets, file, unreadable, say)) return;
768
+ }
769
+ sendJson(res, 404, { error: "not found" });
770
+ } catch (err) {
771
+ if (!res.headersSent) sendJson(res, 500, { error: err instanceof Error ? err.message : String(err) });
772
+ else res.end();
773
+ }
774
+ });
775
+ let onClient;
776
+ const host = options.host ?? "127.0.0.1";
777
+ const port = options.port ?? stablePort();
778
+ const listen = (p) => new Promise((resolve$1, reject) => {
779
+ const fail = (err) => {
780
+ server.off("listening", ok);
781
+ reject(err);
782
+ };
783
+ const ok = () => {
784
+ server.off("error", fail);
785
+ resolve$1();
786
+ };
787
+ server.once("error", fail);
788
+ server.once("listening", ok);
789
+ server.listen(p, host);
790
+ });
791
+ try {
792
+ await listen(port);
793
+ } catch (err) {
794
+ if (err.code !== "EADDRINUSE") throw err;
795
+ if (await requestTakeover(host, port)) await listen(port).catch(() => listen(0));
796
+ else if (options.port === void 0) await listen(0);
797
+ else throw err;
798
+ }
799
+ const { port: actualPort } = server.address();
800
+ const url = `http://${host.includes(":") ? `[${host}]` : host}:${actualPort}/`;
801
+ playerBundle().catch(() => {});
802
+ if (options.open === true) {
803
+ const reconnected = await new Promise((resolve$1) => {
804
+ const timer = setTimeout(() => resolve$1(false), 2e3);
805
+ onClient = () => {
806
+ clearTimeout(timer);
807
+ resolve$1(true);
808
+ };
809
+ if (sseClients.size > 0) onClient();
810
+ });
811
+ onClient = void 0;
812
+ if (!reconnected) openBrowser(url);
813
+ }
814
+ return {
815
+ url,
816
+ closed,
817
+ close: () => close()
818
+ };
819
+ }
820
+ async function loadTarget(entry, exportName) {
821
+ const exported = (await import(pathToFileURL(entry).href))[exportName];
822
+ if (exported === void 0) throw new Error(`[framefields] ${entry} has no export named "${exportName}" to preview.`);
823
+ return typeof exported === "function" ? await exported() : exported;
824
+ }
825
+ /**
826
+ * The project holding `dir`: the nearest `.git`, else the nearest
827
+ * `package.json`, else `dir` itself. A repo wins over a nested package, so a
828
+ * monorepo's shared assets stay reachable.
829
+ */
830
+ function repoRoot(dir) {
831
+ let project;
832
+ for (let d = dir;; d = path.dirname(d)) {
833
+ if (fs.existsSync(path.join(d, ".git"))) return d;
834
+ if (!project && fs.existsSync(path.join(d, "package.json"))) project = d;
835
+ if (path.dirname(d) === d) return project ?? dir;
836
+ }
837
+ }
838
+ /** Applies one change from the page and says what it was. */
839
+ async function changeNote(notes, body, fps, say) {
840
+ if (body.op === "add" && body.note && String(body.note.text).trim()) {
841
+ const note = await notes.add(body.note);
842
+ const files = [note.image && `frame with the spot marked: ${note.image}`, `all notes: ${notes.file}`];
843
+ say(`${describeNote(note, fps)}\n ${files.filter(Boolean).join(" · ")}`);
844
+ return note;
845
+ }
846
+ const id = Number(body.id);
847
+ if (body.op === "update") {
848
+ const note = await notes.update(id, body);
849
+ if (note && typeof body.done === "boolean") say(`note ${id} marked ${note.done ? "done" : "open"}`);
850
+ return note;
851
+ }
852
+ if (body.op === "remove") {
853
+ const note = await notes.remove(id);
854
+ if (note) say(`note ${id} deleted`);
855
+ return note;
856
+ }
857
+ }
858
+ function errorText(err) {
859
+ return err instanceof Error ? err.message : String(err);
860
+ }
861
+ function readBody(req, limit) {
862
+ return new Promise((resolve$1, reject) => {
863
+ let body = "";
864
+ req.setEncoding("utf8");
865
+ req.on("data", (chunk) => {
866
+ body += chunk;
867
+ if (body.length > limit) {
868
+ reject(/* @__PURE__ */ new Error("request body too large"));
869
+ req.destroy();
870
+ }
871
+ });
872
+ req.on("end", () => resolve$1(body));
873
+ req.on("error", reject);
874
+ });
875
+ }
876
+ function isInside(root, file) {
877
+ const rel = path.relative(root, file);
878
+ return !!rel && !rel.startsWith("..") && !path.isAbsolute(rel);
879
+ }
880
+ async function isFile(file) {
881
+ try {
882
+ return (await fs.promises.stat(file)).isFile();
883
+ } catch {
884
+ return false;
885
+ }
886
+ }
887
+ const CONTENT_TYPES = {
888
+ ".png": "image/png",
889
+ ".jpg": "image/jpeg",
890
+ ".jpeg": "image/jpeg",
891
+ ".webp": "image/webp",
892
+ ".gif": "image/gif",
893
+ ".svg": "image/svg+xml",
894
+ ".mp4": "video/mp4",
895
+ ".mov": "video/quicktime",
896
+ ".webm": "video/webm",
897
+ ".mp3": "audio/mpeg",
898
+ ".wav": "audio/wav",
899
+ ".m4a": "audio/mp4",
900
+ ".ogg": "audio/ogg",
901
+ ".ttf": "font/ttf",
902
+ ".otf": "font/otf",
903
+ ".woff": "font/woff",
904
+ ".woff2": "font/woff2",
905
+ ".json": "application/json",
906
+ ".html": "text/html; charset=utf-8",
907
+ ".css": "text/css; charset=utf-8",
908
+ ".map": "application/json",
909
+ ".js": "text/javascript; charset=utf-8",
910
+ ".mjs": "text/javascript; charset=utf-8",
911
+ ".wasm": "application/wasm",
912
+ ".onnx": "application/octet-stream",
913
+ ".srt": "text/plain; charset=utf-8",
914
+ ".txt": "text/plain; charset=utf-8"
915
+ };
916
+ /**
917
+ * Sends the page to the bucket for a pinned asset that isn't on disk. The
918
+ * bucket must allow this page's origin (CORS): the engine reads media with
919
+ * `fetch`. True when it answered.
920
+ */
921
+ async function redirectToBucket(res, assets, file, unreadable, say) {
922
+ if (!assets.entry(file)) return false;
923
+ const url = await assets.url(file);
924
+ if (url) {
925
+ res.writeHead(302, {
926
+ location: url,
927
+ "cache-control": "no-store"
928
+ });
929
+ res.end();
930
+ return true;
931
+ }
932
+ const rel = path.relative(assets.root, file);
933
+ if (!unreadable.has(rel)) {
934
+ unreadable.add(rel);
935
+ say(`${rel} is pinned on a private remote and there are no keys here: set its FRAMEFIELDS_STORAGE_* keys, give the remote a publicUrl, or \`npx framefields assets pull\``);
936
+ }
937
+ return false;
938
+ }
939
+ /** Serves a file, with byte ranges so video can seek without loading it whole. */
940
+ async function sendFile(req, res, file, noStore = false) {
941
+ const { size } = await fs.promises.stat(file);
942
+ const type = CONTENT_TYPES[path.extname(file).toLowerCase()] ?? "application/octet-stream";
943
+ const range = /^bytes=(\d*)-(\d*)$/.exec(req.headers.range ?? "");
944
+ let start = 0;
945
+ let end = size - 1;
946
+ if (range && (range[1] || range[2])) {
947
+ if (range[1]) {
948
+ start = Number(range[1]);
949
+ if (range[2]) end = Math.min(end, Number(range[2]));
950
+ } else start = Math.max(0, size - Number(range[2]));
951
+ if (start > end || start >= size) {
952
+ res.writeHead(416, { "content-range": `bytes */${size}` });
953
+ res.end();
954
+ return;
955
+ }
956
+ }
957
+ res.writeHead(range ? 206 : 200, {
958
+ "content-type": type,
959
+ "content-length": end - start + 1,
960
+ "accept-ranges": "bytes",
961
+ "cache-control": noStore ? "no-store" : "no-cache",
962
+ ...range && { "content-range": `bytes ${start}-${end}/${size}` }
963
+ });
964
+ if (req.method === "HEAD") {
965
+ res.end();
966
+ return;
967
+ }
968
+ fs.createReadStream(file, {
969
+ start,
970
+ end
971
+ }).pipe(res);
972
+ }
973
+ async function resolveMeta(target, title, session, notesDir) {
974
+ const fps = target.fps ?? 24;
975
+ const durationMs = target.durationMs ?? await target.computeDuration?.() ?? 5e3;
976
+ return {
977
+ session,
978
+ notesDir,
979
+ title,
980
+ fps,
981
+ frameCount: Math.max(1, Math.round(durationMs / 1e3 * fps)),
982
+ width: target.width ?? 1920,
983
+ height: target.height ?? 1080
984
+ };
985
+ }
986
+ /** Same project, same port: 41000–41899, from the working directory. */
987
+ function stablePort() {
988
+ return 41e3 + createHash("sha1").update(process.cwd()).digest().readUInt16BE(0) % 900;
989
+ }
990
+ /** Asks a preview already on `port` to shut down; false if something else is there. */
991
+ async function requestTakeover(host, port) {
992
+ const ok = await new Promise((resolve$1) => {
993
+ const req = http.request({
994
+ host,
995
+ port,
996
+ method: "POST",
997
+ path: "/takeover",
998
+ agent: false,
999
+ headers: { [TAKEOVER_HEADER]: "1" },
1000
+ timeout: 1500
1001
+ }, (res) => {
1002
+ res.resume();
1003
+ resolve$1(res.statusCode === 200);
1004
+ });
1005
+ req.on("timeout", () => req.destroy());
1006
+ req.on("error", () => resolve$1(false));
1007
+ req.end();
1008
+ });
1009
+ if (ok) await new Promise((r) => setTimeout(r, 150));
1010
+ return ok;
1011
+ }
1012
+ /** Max absolute amplitude of both channels per bin, 0..1. */
1013
+ function sendJson(res, status, body) {
1014
+ res.writeHead(status, {
1015
+ "content-type": "application/json",
1016
+ "cache-control": "no-store"
1017
+ });
1018
+ res.end(JSON.stringify(body));
1019
+ }
1020
+ function openBrowser(url) {
1021
+ const [cmd, args] = process.platform === "darwin" ? ["open", [url]] : process.platform === "win32" ? ["cmd", [
1022
+ "/c",
1023
+ "start",
1024
+ "",
1025
+ url
1026
+ ]] : ["xdg-open", [url]];
1027
+ try {
1028
+ spawn(cmd, args, {
1029
+ stdio: "ignore",
1030
+ detached: true
1031
+ }).on("error", () => {}).unref();
1032
+ } catch {}
1033
+ }
1034
+
1035
+ //#endregion
1036
+ export { PREVIEW_PROTOCOL as n, readPreviewCommand as r, startPreview as t };
1037
+ //# sourceMappingURL=preview-Dm912wly.mjs.map