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.
- package/NOTICE.md +5 -1
- package/dist/audio/index.d.mts +3 -3
- package/dist/audio/index.mjs +10 -10
- package/dist/{audio-DFwRbID_.mjs → audio-6TfPlfl4.mjs} +3 -3
- package/dist/{audio-DFwRbID_.mjs.map → audio-6TfPlfl4.mjs.map} +1 -1
- package/dist/{audio-CdCuGK5d.d.mts → audio-7oqTUi4G.d.mts} +2 -2
- package/dist/{audio-CdCuGK5d.d.mts.map → audio-7oqTUi4G.d.mts.map} +1 -1
- package/dist/cli/check-worker.mjs +44 -44
- package/dist/cli.mjs +1 -1
- package/dist/cloud/index.mjs +1 -1
- package/dist/{cloud-Cj1TIme8.mjs → cloud-jnk1hPYf.mjs} +2 -2
- package/dist/{cloud-Cj1TIme8.mjs.map → cloud-jnk1hPYf.mjs.map} +1 -1
- package/dist/components/index.d.mts +11 -11
- package/dist/components/index.mjs +43 -43
- package/dist/{dist-Kbf-JTry.mjs → dist-BtI6ukB5.mjs} +2 -2
- package/dist/{dist-Kbf-JTry.mjs.map → dist-BtI6ukB5.mjs.map} +1 -1
- package/dist/{dist-B8Nby2CU.mjs → dist-COOpgG9G.mjs} +4 -3
- package/dist/{dist-B8Nby2CU.mjs.map → dist-COOpgG9G.mjs.map} +1 -1
- package/dist/{dist--Uu5bQ_6.mjs → dist-XqlFM2a8.mjs} +13733 -5323
- package/dist/dist-XqlFM2a8.mjs.map +1 -0
- package/dist/{dist-CiTgmfUw.mjs → dist-f1zkl69f.mjs} +4 -4
- package/dist/dist-f1zkl69f.mjs.map +1 -0
- package/dist/{dist-B_rSWeOz.mjs → dist-umQOWz0a.mjs} +1 -1
- package/dist/effects/index.d.mts +4 -4
- package/dist/effects/index.mjs +35 -35
- package/dist/{effects-WMgv8ZaD.mjs → effects-DlRMovU5.mjs} +2 -2
- package/dist/{effects-WMgv8ZaD.mjs.map → effects-DlRMovU5.mjs.map} +1 -1
- package/dist/fonts/index.d.mts +1 -1
- package/dist/fonts/index.mjs +2 -2
- package/dist/{fonts-OW3yAfO5.mjs → fonts-BWYcXjyK.mjs} +3 -3
- package/dist/fonts-BWYcXjyK.mjs.map +1 -0
- package/dist/{gpu-R4T2Tndu.mjs → gpu-CH1PdM4T.mjs} +2 -2
- package/dist/{gpu-R4T2Tndu.mjs.map → gpu-CH1PdM4T.mjs.map} +1 -1
- package/dist/{index-CEKxvGY-.d.mts → index-BHTycml5.d.mts} +2 -2
- package/dist/{index-CEKxvGY-.d.mts.map → index-BHTycml5.d.mts.map} +1 -1
- package/dist/{index-DoP6EiHj.d.mts → index-BcxxxwhV.d.mts} +4 -4
- package/dist/{index-DoP6EiHj.d.mts.map → index-BcxxxwhV.d.mts.map} +1 -1
- package/dist/index-C-V6CAhP.d.mts.map +1 -1
- package/dist/{index-D1FqV1eu.d.mts → index-CMhkkc56.d.mts} +43 -11
- package/dist/index-CMhkkc56.d.mts.map +1 -0
- package/dist/{index-B0Vq8Bwf.d.mts → index-CYVpYGM-.d.mts} +2 -2
- package/dist/{index-B0Vq8Bwf.d.mts.map → index-CYVpYGM-.d.mts.map} +1 -1
- package/dist/{index-Bv7gxGK1.d.mts → index-Cq8jgdNY.d.mts} +90 -3
- package/dist/index-Cq8jgdNY.d.mts.map +1 -0
- package/dist/{index-9xte4JgL.d.mts → index-Dg5BAtDq.d.mts} +1446 -541
- package/dist/index-Dg5BAtDq.d.mts.map +1 -0
- package/dist/{index-C7-p9zwU.d.mts → index-DhL7ckvs.d.mts} +5 -5
- package/dist/index-DhL7ckvs.d.mts.map +1 -0
- package/dist/{index-GA9_Z8Fn.d.mts → index-RILu3ULT.d.mts} +2 -2
- package/dist/{index-GA9_Z8Fn.d.mts.map → index-RILu3ULT.d.mts.map} +1 -1
- package/dist/{index-C5sgRLlG.d.mts → index-TCtdSXd7.d.mts} +3 -1
- package/dist/index-TCtdSXd7.d.mts.map +1 -0
- package/dist/{index-CRaydvLi.d.mts → index-s2qQ8ifZ.d.mts} +886 -51
- package/dist/index-s2qQ8ifZ.d.mts.map +1 -0
- package/dist/index.d.mts +13 -13
- package/dist/index.mjs +52 -52
- package/dist/preview/index.d.mts +4 -4
- package/dist/preview/index.mjs +11 -11
- package/dist/preview-Dm912wly.mjs +1037 -0
- package/dist/preview-Dm912wly.mjs.map +1 -0
- package/dist/preview-engine/audio.mjs +1 -1
- package/dist/preview-engine/{chunk-64L4GUOF.mjs → chunk-4F3DG6YB.mjs} +2 -2
- package/dist/preview-engine/chunk-A2OTOW3B.mjs +4694 -0
- package/dist/preview-engine/{chunk-GNDE3OXQ.mjs → chunk-BR56TQBP.mjs} +1 -1
- package/dist/preview-engine/{chunk-ADLIF544.mjs → chunk-H2XKXXAU.mjs} +1 -1
- package/dist/preview-engine/chunk-NDUODAY2.mjs +3 -0
- package/dist/preview-engine/{chunk-FZ4RD7HV.mjs → chunk-QAQJSWUT.mjs} +1 -1
- package/dist/preview-engine/chunk-SWRGQTVC.mjs +138 -0
- package/dist/preview-engine/{chunk-52X7SURQ.mjs → chunk-TEA3GVBJ.mjs} +1 -1
- package/dist/preview-engine/chunk-VZUVB3BP.mjs +644 -0
- package/dist/preview-engine/{chunk-UBY2UYEL.mjs → chunk-XSHNQJ7L.mjs} +1 -1
- package/dist/preview-engine/components.mjs +1 -1
- package/dist/preview-engine/effects.mjs +1 -1
- package/dist/preview-engine/fonts.mjs +1 -1
- package/dist/preview-engine/framefields.mjs +1 -1
- package/dist/preview-engine/player.mjs +3 -3
- package/dist/preview-engine/{renderers-DU7KPFVV.mjs → renderers-6C7NWWDX.mjs} +1 -1
- package/dist/preview-engine/renderers-HHVGNMSJ.mjs +206 -0
- package/dist/preview-engine/{src-YXNX4MLK.mjs → src-RCNEKIRP.mjs} +1 -1
- package/dist/preview-page/index.html +19 -0
- package/dist/preview-page/main.css +1 -0
- package/dist/preview-page/main.js +326 -0
- package/dist/{program-tFKCWU2j.mjs → program-CMigyfso.mjs} +829 -52
- package/dist/program-CMigyfso.mjs.map +1 -0
- package/dist/renderer/index.d.mts +2 -2
- package/dist/renderer/index.mjs +2 -2
- package/dist/{renderer-DkOuQi84.mjs → renderer-0GMEL_Zi.mjs} +3 -3
- package/dist/{renderer-DkOuQi84.mjs.map → renderer-0GMEL_Zi.mjs.map} +1 -1
- package/dist/{renderer-BGp0An5A.mjs → renderer-3Wv2ZBJG.mjs} +3 -3
- package/dist/{renderer-BGp0An5A.mjs.map → renderer-3Wv2ZBJG.mjs.map} +1 -1
- package/dist/{renderer-BVIXKfbC.mjs → renderer-4nXkaAWu.mjs} +1178 -199
- package/dist/renderer-4nXkaAWu.mjs.map +1 -0
- package/dist/{renderer-Cw6_NL2i.mjs → renderer-6SnfLXeg.mjs} +3 -3
- package/dist/{renderer-Cw6_NL2i.mjs.map → renderer-6SnfLXeg.mjs.map} +1 -1
- package/dist/{renderer-BVU22lBS.mjs → renderer-8gHw3O0P.mjs} +2 -2
- package/dist/{renderer-BVU22lBS.mjs.map → renderer-8gHw3O0P.mjs.map} +1 -1
- package/dist/{renderer-BjDddtw9.mjs → renderer-AOBglZHC.mjs} +2 -2
- package/dist/{renderer-D-KHHWQD.mjs → renderer-B0bSARK3.mjs} +3 -3
- package/dist/{renderer-D-KHHWQD.mjs.map → renderer-B0bSARK3.mjs.map} +1 -1
- package/dist/{renderer-CCxk-RJR.mjs → renderer-B2_tuRfl.mjs} +2 -2
- package/dist/{renderer-CCxk-RJR.mjs.map → renderer-B2_tuRfl.mjs.map} +1 -1
- package/dist/{renderer-k5d7JSbs.mjs → renderer-B82FhkEr.mjs} +3 -3
- package/dist/{renderer-k5d7JSbs.mjs.map → renderer-B82FhkEr.mjs.map} +1 -1
- package/dist/{renderer-I0Hh9cLK.mjs → renderer-B9lzA0PY.mjs} +3 -3
- package/dist/{renderer-I0Hh9cLK.mjs.map → renderer-B9lzA0PY.mjs.map} +1 -1
- package/dist/{renderer-Dwwnx7Dk.mjs → renderer-BBSIWSry.mjs} +2 -2
- package/dist/{renderer-Dwwnx7Dk.mjs.map → renderer-BBSIWSry.mjs.map} +1 -1
- package/dist/{renderer-_pC-jB9O.mjs → renderer-BEKDMxbi.mjs} +2 -2
- package/dist/{renderer-_pC-jB9O.mjs.map → renderer-BEKDMxbi.mjs.map} +1 -1
- package/dist/renderer-BERGAxqA.mjs +5 -0
- package/dist/{renderer-raxwsiGI.mjs → renderer-BF960HY8.mjs} +3 -3
- package/dist/{renderer-raxwsiGI.mjs.map → renderer-BF960HY8.mjs.map} +1 -1
- package/dist/{renderer-Dr0tXA4k.mjs → renderer-BFwyVxCu.mjs} +3 -3
- package/dist/{renderer-Dr0tXA4k.mjs.map → renderer-BFwyVxCu.mjs.map} +1 -1
- package/dist/{renderer-DEjo6dw_.mjs → renderer-BNiwcI3-.mjs} +2 -2
- package/dist/{renderer-DEjo6dw_.mjs.map → renderer-BNiwcI3-.mjs.map} +1 -1
- package/dist/{renderer-DIEeHiQA.mjs → renderer-BPbVb2Q4.mjs} +2 -2
- package/dist/{renderer-DIEeHiQA.mjs.map → renderer-BPbVb2Q4.mjs.map} +1 -1
- package/dist/renderer-BQ_YkBMt.mjs +6 -0
- package/dist/{renderer-BBMtmxks.mjs → renderer-BUWxlA8T.mjs} +3 -3
- package/dist/{renderer-BBMtmxks.mjs.map → renderer-BUWxlA8T.mjs.map} +1 -1
- package/dist/renderer-BW0Tjsv9.mjs +5 -0
- package/dist/{renderer-Dgj46uEU.mjs → renderer-BX8liu-e.mjs} +2 -2
- package/dist/{renderer-ZnrEfkYh.mjs → renderer-BXIwDAr_.mjs} +2 -2
- package/dist/{renderer-ZnrEfkYh.mjs.map → renderer-BXIwDAr_.mjs.map} +1 -1
- package/dist/{renderer-DA8YEo_d.mjs → renderer-B_Hy1W-g.mjs} +3 -3
- package/dist/{renderer-DA8YEo_d.mjs.map → renderer-B_Hy1W-g.mjs.map} +1 -1
- package/dist/{renderer-iMiYStcW.mjs → renderer-Bb6cvSgh.mjs} +2 -2
- package/dist/{renderer-iMiYStcW.mjs.map → renderer-Bb6cvSgh.mjs.map} +1 -1
- package/dist/{renderer-BacY4nh8.mjs → renderer-BdppwWC9.mjs} +2 -2
- package/dist/{renderer-BacY4nh8.mjs.map → renderer-BdppwWC9.mjs.map} +1 -1
- package/dist/{renderer-CrxwW6VQ.mjs → renderer-Bhc24HVe.mjs} +3 -3
- package/dist/{renderer-CrxwW6VQ.mjs.map → renderer-Bhc24HVe.mjs.map} +1 -1
- package/dist/{renderer-BH7UCMPQ.mjs → renderer-BiNA2wF0.mjs} +2 -2
- package/dist/{renderer-BH7UCMPQ.mjs.map → renderer-BiNA2wF0.mjs.map} +1 -1
- package/dist/{renderer-DVPgWHZw.mjs → renderer-BmoBgTp5.mjs} +3 -3
- package/dist/{renderer-DVPgWHZw.mjs.map → renderer-BmoBgTp5.mjs.map} +1 -1
- package/dist/{renderer-C20efMI0.mjs → renderer-BmqFmCWS.mjs} +3 -3
- package/dist/{renderer-C20efMI0.mjs.map → renderer-BmqFmCWS.mjs.map} +1 -1
- package/dist/{renderer-B0yVUOPP.mjs → renderer-BnVwAObV.mjs} +2 -2
- package/dist/{renderer-B0yVUOPP.mjs.map → renderer-BnVwAObV.mjs.map} +1 -1
- package/dist/{renderer-CP8wLzTU.mjs → renderer-BxttZHme.mjs} +2 -2
- package/dist/{renderer-CP8wLzTU.mjs.map → renderer-BxttZHme.mjs.map} +1 -1
- package/dist/renderer-BzyPdUD1.mjs +5 -0
- package/dist/renderer-C0hM-mtY.mjs +5 -0
- package/dist/renderer-C6cyUTvk.mjs +5 -0
- package/dist/{renderer-BOSZAdgF.mjs → renderer-C8E4XsSg.mjs} +2 -2
- package/dist/{renderer-BOSZAdgF.mjs.map → renderer-C8E4XsSg.mjs.map} +1 -1
- package/dist/renderer-C8zY3pzk.mjs +5 -0
- package/dist/{renderer-DWq9gYRw.mjs → renderer-C9U-LY50.mjs} +3 -3
- package/dist/{renderer-DWq9gYRw.mjs.map → renderer-C9U-LY50.mjs.map} +1 -1
- package/dist/{renderer-Dj2H5JPR.mjs → renderer-CB2JVu5x.mjs} +3 -3
- package/dist/{renderer-Dj2H5JPR.mjs.map → renderer-CB2JVu5x.mjs.map} +1 -1
- package/dist/{renderer-Bcu3UrI-.mjs → renderer-CC4YBJ8Z.mjs} +3 -3
- package/dist/{renderer-Bcu3UrI-.mjs.map → renderer-CC4YBJ8Z.mjs.map} +1 -1
- package/dist/renderer-CEkSOyKu.mjs +5 -0
- package/dist/{renderer-DzLrvCB2.mjs → renderer-CIycc5O-.mjs} +2 -2
- package/dist/{renderer-DzLrvCB2.mjs.map → renderer-CIycc5O-.mjs.map} +1 -1
- package/dist/renderer-CJYtfa4x.mjs +5 -0
- package/dist/{renderer-BeNl_6EG.mjs → renderer-CJraQlV-.mjs} +2 -2
- package/dist/{renderer-BeNl_6EG.mjs.map → renderer-CJraQlV-.mjs.map} +1 -1
- package/dist/renderer-CKMtHGZh.mjs +5 -0
- package/dist/renderer-CLzicoiy.mjs +5 -0
- package/dist/renderer-CM2A4BHd.mjs +5 -0
- package/dist/{renderer-DtMVitEF.mjs → renderer-COWGWtob.mjs} +2 -2
- package/dist/{renderer-DtMVitEF.mjs.map → renderer-COWGWtob.mjs.map} +1 -1
- package/dist/{renderer-CT3zkfbf.mjs → renderer-COxG2XGf.mjs} +2 -2
- package/dist/{renderer-CT3zkfbf.mjs.map → renderer-COxG2XGf.mjs.map} +1 -1
- package/dist/{renderer-DNxWOz8Q.mjs → renderer-CQDCIsQm.mjs} +2 -2
- package/dist/{renderer-DNxWOz8Q.mjs.map → renderer-CQDCIsQm.mjs.map} +1 -1
- package/dist/{renderer-C5AoaymD.mjs → renderer-CRUeag8i.mjs} +3 -3
- package/dist/{renderer-C5AoaymD.mjs.map → renderer-CRUeag8i.mjs.map} +1 -1
- package/dist/{renderer-ngmYIIob.mjs → renderer-CWVput06.mjs} +3 -3
- package/dist/{renderer-ngmYIIob.mjs.map → renderer-CWVput06.mjs.map} +1 -1
- package/dist/renderer-CZlHjcxr.mjs +5 -0
- package/dist/{renderer-0rveCDm-.mjs → renderer-CbbnBLOB.mjs} +3 -3
- package/dist/{renderer-0rveCDm-.mjs.map → renderer-CbbnBLOB.mjs.map} +1 -1
- package/dist/renderer-CbimvSU8.mjs +5 -0
- package/dist/renderer-Ccvc63yd.mjs +5 -0
- package/dist/renderer-Cdp-SGfq.mjs +5 -0
- package/dist/renderer-CeTcSs3e.mjs +5 -0
- package/dist/{renderer-Brmbl12Y.mjs → renderer-CfLZein2.mjs} +3 -3
- package/dist/{renderer-Brmbl12Y.mjs.map → renderer-CfLZein2.mjs.map} +1 -1
- package/dist/{renderer-BJ8ea92W.mjs → renderer-Ch-q1O-q.mjs} +3 -3
- package/dist/{renderer-BJ8ea92W.mjs.map → renderer-Ch-q1O-q.mjs.map} +1 -1
- package/dist/{renderer-CP108ABB.mjs → renderer-Ch5JvJWl.mjs} +2 -2
- package/dist/{renderer-CP108ABB.mjs.map → renderer-Ch5JvJWl.mjs.map} +1 -1
- package/dist/{renderer-47y12Z37.mjs → renderer-ChtEzq-R.mjs} +2 -2
- package/dist/{renderer-47y12Z37.mjs.map → renderer-ChtEzq-R.mjs.map} +1 -1
- package/dist/{renderer-DhyMfG1v.mjs → renderer-Cj9nTC5l.mjs} +3 -3
- package/dist/{renderer-DhyMfG1v.mjs.map → renderer-Cj9nTC5l.mjs.map} +1 -1
- package/dist/{renderer-BDzn1AKF.mjs → renderer-CqT8HsSS.mjs} +2 -2
- package/dist/{renderer-BDzn1AKF.mjs.map → renderer-CqT8HsSS.mjs.map} +1 -1
- package/dist/{renderer-B7G1KohJ.mjs → renderer-CuQjMhdV.mjs} +2 -2
- package/dist/renderer-CvNuu52U.mjs +5 -0
- package/dist/renderer-CwciC0oI.mjs +5 -0
- package/dist/renderer-CysLUbIr.mjs +5 -0
- package/dist/{renderer-Cm0tsbQH.mjs → renderer-Cz30EVws.mjs} +2 -2
- package/dist/{renderer-Cm0tsbQH.mjs.map → renderer-Cz30EVws.mjs.map} +1 -1
- package/dist/renderer-D3A22Vxb.mjs +5 -0
- package/dist/renderer-D5SY9EmA.mjs +5 -0
- package/dist/renderer-D8h3ZvaE.mjs +5 -0
- package/dist/renderer-DE-B6N9n.mjs +5 -0
- package/dist/renderer-DETZTI7t.mjs +5 -0
- package/dist/{renderer-nHS3Sius.mjs → renderer-DHoX-5U_.mjs} +3 -3
- package/dist/{renderer-nHS3Sius.mjs.map → renderer-DHoX-5U_.mjs.map} +1 -1
- package/dist/{renderer-BWSRryIA.mjs → renderer-DOkm0Ycd.mjs} +2 -2
- package/dist/{renderer-BWSRryIA.mjs.map → renderer-DOkm0Ycd.mjs.map} +1 -1
- package/dist/{renderer-BHYJlK6H.d.mts → renderer-DU3f6ZrS.d.mts} +2 -2
- package/dist/{renderer-BHYJlK6H.d.mts.map → renderer-DU3f6ZrS.d.mts.map} +1 -1
- package/dist/{renderer-BpVOk9ux.mjs → renderer-DUzv-shq.mjs} +2 -2
- package/dist/{renderer-BpVOk9ux.mjs.map → renderer-DUzv-shq.mjs.map} +1 -1
- package/dist/renderer-DWnDj0IM.mjs +5 -0
- package/dist/renderer-DcGUIbQF.mjs +5 -0
- package/dist/renderer-Dfq6FoNO.mjs +5 -0
- package/dist/renderer-DhasXW6S.mjs +3 -0
- package/dist/{renderer-NRMlYkX6.mjs → renderer-DhnOWnEA.mjs} +3 -3
- package/dist/{renderer-NRMlYkX6.mjs.map → renderer-DhnOWnEA.mjs.map} +1 -1
- package/dist/{renderer-COgwYqev.mjs → renderer-Dhy1XHzz.mjs} +3 -3
- package/dist/{renderer-COgwYqev.mjs.map → renderer-Dhy1XHzz.mjs.map} +1 -1
- package/dist/renderer-DjCvpsV_.mjs +5 -0
- package/dist/{renderer-BfZNIiSv.mjs → renderer-DjJshz9Y.mjs} +2 -2
- package/dist/{renderer-BfZNIiSv.mjs.map → renderer-DjJshz9Y.mjs.map} +1 -1
- package/dist/{renderer-CKo7VR8x.mjs → renderer-DmTUIdus.mjs} +3 -3
- package/dist/{renderer-CKo7VR8x.mjs.map → renderer-DmTUIdus.mjs.map} +1 -1
- package/dist/{renderer-Baw6Utxi.mjs → renderer-Dq7AioJa.mjs} +2 -2
- package/dist/{renderer-Baw6Utxi.mjs.map → renderer-Dq7AioJa.mjs.map} +1 -1
- package/dist/renderer-DrAaM6JD.mjs +5 -0
- package/dist/{renderer-D-GWBoVT.mjs → renderer-Dt4sqNEu.mjs} +2 -2
- package/dist/{renderer-D-GWBoVT.mjs.map → renderer-Dt4sqNEu.mjs.map} +1 -1
- package/dist/{renderer-FYOL5Za6.mjs → renderer-Dtcs4idV.mjs} +3 -3
- package/dist/{renderer-FYOL5Za6.mjs.map → renderer-Dtcs4idV.mjs.map} +1 -1
- package/dist/renderer-Dxlv8LYm.mjs +5 -0
- package/dist/{renderer-D-RhYvVl.mjs → renderer-Gj8SrE2W.mjs} +2 -2
- package/dist/{renderer-D-RhYvVl.mjs.map → renderer-Gj8SrE2W.mjs.map} +1 -1
- package/dist/{renderer-D_j1WKMD.mjs → renderer-KfnXDJS0.mjs} +3 -3
- package/dist/{renderer-D_j1WKMD.mjs.map → renderer-KfnXDJS0.mjs.map} +1 -1
- package/dist/{renderer-Bpm145BW.mjs → renderer-MXVlg4p5.mjs} +3 -3
- package/dist/{renderer-Bpm145BW.mjs.map → renderer-MXVlg4p5.mjs.map} +1 -1
- package/dist/{renderer-Nnmi8Gt6.mjs → renderer-N2b6QZ1E.mjs} +3 -3
- package/dist/{renderer-Nnmi8Gt6.mjs.map → renderer-N2b6QZ1E.mjs.map} +1 -1
- package/dist/renderer-Pb43OO2t.mjs +5 -0
- package/dist/{renderer-Ca1KjNCO.mjs → renderer-Q7gDzm8c.mjs} +2 -2
- package/dist/{renderer-Ca1KjNCO.mjs.map → renderer-Q7gDzm8c.mjs.map} +1 -1
- package/dist/{renderer-D2wNFNRf.mjs → renderer-RCiANtRV.mjs} +2 -2
- package/dist/{renderer-D2wNFNRf.mjs.map → renderer-RCiANtRV.mjs.map} +1 -1
- package/dist/{renderer-Cqb7PGrI.mjs → renderer-Sig9nZcI.mjs} +2 -2
- package/dist/renderer-VQbm-cGr.mjs +5 -0
- package/dist/{renderer-W1bVNVR6.mjs → renderer-fabBgwQX.mjs} +3 -3
- package/dist/{renderer-W1bVNVR6.mjs.map → renderer-fabBgwQX.mjs.map} +1 -1
- package/dist/{renderer-zRq0j6Xy.mjs → renderer-g-0PCNoo.mjs} +3 -3
- package/dist/{renderer-zRq0j6Xy.mjs.map → renderer-g-0PCNoo.mjs.map} +1 -1
- package/dist/{renderer-2PjR3w-q.mjs → renderer-gmHKIbKI.mjs} +3 -3
- package/dist/{renderer-2PjR3w-q.mjs.map → renderer-gmHKIbKI.mjs.map} +1 -1
- package/dist/{renderer-03mYJLgX.mjs → renderer-kVZwiRoK.mjs} +3 -3
- package/dist/{renderer-03mYJLgX.mjs.map → renderer-kVZwiRoK.mjs.map} +1 -1
- package/dist/renderer-oPMypDM1.mjs +5 -0
- package/dist/{renderer-DcAKDAnS.mjs → renderer-p7tgxP-A.mjs} +3 -3
- package/dist/{renderer-DcAKDAnS.mjs.map → renderer-p7tgxP-A.mjs.map} +1 -1
- package/dist/{renderer-jUKdkmG5.mjs → renderer-tvYj306A.mjs} +81 -78
- package/dist/renderer-tvYj306A.mjs.map +1 -0
- package/dist/renderer-xvWGpVyp.mjs +5 -0
- package/dist/renderer-zafZbD6Q.mjs +5 -0
- package/dist/{renderers-CP8CFWDs-CshljORp.mjs → renderers-CP8CFWDs-DLjtjrwx.mjs} +2 -2
- package/dist/{renderers-CP8CFWDs-CshljORp.mjs.map → renderers-CP8CFWDs-DLjtjrwx.mjs.map} +1 -1
- package/dist/sam/index.d.mts +2 -2
- package/dist/sam/index.mjs +1 -1
- package/dist/signals/index.d.mts +4 -4
- package/dist/signals/index.mjs +4 -4
- package/dist/{src-BU6vm9sP-DdTHmCr4.mjs → src-B23p_eh7-Cj6-lvV6.mjs} +293 -6
- package/dist/src-B23p_eh7-Cj6-lvV6.mjs.map +1 -0
- package/dist/{src-Dy4gnc6w.mjs → src-CoHAjviq.mjs} +1349 -177
- package/dist/src-CoHAjviq.mjs.map +1 -0
- package/dist/storage/index.mjs +1 -1
- package/package.json +63 -60
- package/dist/dist--Uu5bQ_6.mjs.map +0 -1
- package/dist/dist-CiTgmfUw.mjs.map +0 -1
- package/dist/fonts-OW3yAfO5.mjs.map +0 -1
- package/dist/index-9xte4JgL.d.mts.map +0 -1
- package/dist/index-Bv7gxGK1.d.mts.map +0 -1
- package/dist/index-C5sgRLlG.d.mts.map +0 -1
- package/dist/index-C7-p9zwU.d.mts.map +0 -1
- package/dist/index-CRaydvLi.d.mts.map +0 -1
- package/dist/index-D1FqV1eu.d.mts.map +0 -1
- package/dist/preview-D7BGmE1E.mjs +0 -2446
- package/dist/preview-D7BGmE1E.mjs.map +0 -1
- package/dist/preview-engine/chunk-5DZTPUMH.mjs +0 -2568
- package/dist/preview-engine/chunk-NCQHOGZK.mjs +0 -3
- package/dist/preview-engine/chunk-PH3UUX6A.mjs +0 -138
- package/dist/preview-engine/chunk-UPZURPJY.mjs +0 -2227
- package/dist/preview-engine/renderers-DLJOA3BM.mjs +0 -228
- package/dist/program-tFKCWU2j.mjs.map +0 -1
- package/dist/renderer-6_bzbbXt.mjs +0 -5
- package/dist/renderer-8M-9G6F_.mjs +0 -5
- package/dist/renderer-B5OzKvF8.mjs +0 -5
- package/dist/renderer-BBCZ2qbE.mjs +0 -5
- package/dist/renderer-BChbxCux.mjs +0 -5
- package/dist/renderer-BGeMPr8C.mjs +0 -5
- package/dist/renderer-BPQ8Pkjb.mjs +0 -5
- package/dist/renderer-BTRpA3HD.mjs +0 -5
- package/dist/renderer-BVIXKfbC.mjs.map +0 -1
- package/dist/renderer-BVsECSn-.mjs +0 -5
- package/dist/renderer-Bgos68gc.mjs +0 -6
- package/dist/renderer-BjHOvoqO.mjs +0 -5
- package/dist/renderer-BjXMPPlV.mjs +0 -5
- package/dist/renderer-Byobxqkv.mjs +0 -5
- package/dist/renderer-CKrsg3EG.mjs +0 -5
- package/dist/renderer-Cj875NuH.mjs +0 -5
- package/dist/renderer-CnmzJoMv.mjs +0 -5
- package/dist/renderer-CtklDaMg.mjs +0 -5
- package/dist/renderer-D1tK3in_.mjs +0 -5
- package/dist/renderer-D2yc9P61.mjs +0 -5
- package/dist/renderer-D4v-JvSr.mjs +0 -5
- package/dist/renderer-D5yp74NL.mjs +0 -3
- package/dist/renderer-D7WRWDwB.mjs +0 -5
- package/dist/renderer-DGvA0tdB.mjs +0 -5
- package/dist/renderer-DO7v5Sdm.mjs +0 -5
- package/dist/renderer-DRVvt7-V.mjs +0 -5
- package/dist/renderer-DYMCd3jL.mjs +0 -5
- package/dist/renderer-DmHKw-MM.mjs +0 -5
- package/dist/renderer-DoDsuZ6G.mjs +0 -5
- package/dist/renderer-EpgclS70.mjs +0 -5
- package/dist/renderer-GroKiYyK.mjs +0 -5
- package/dist/renderer-MQ5K_xIS.mjs +0 -5
- package/dist/renderer-U43SflML.mjs +0 -5
- package/dist/renderer-UqOmBgCv.mjs +0 -5
- package/dist/renderer-XLULgJM0.mjs +0 -5
- package/dist/renderer-jUKdkmG5.mjs.map +0 -1
- package/dist/renderer-lNjxY1GG.mjs +0 -5
- package/dist/renderer-ukufL7pl.mjs +0 -5
- package/dist/renderer-wb0NCKdF.mjs +0 -5
- package/dist/src-BU6vm9sP-DdTHmCr4.mjs.map +0 -1
- 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
|