voxflow 1.15.0 → 1.15.2
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/README.md +34 -0
- package/bin/voxflow.js +27 -0
- package/dist/index.js +1 -1
- package/dist/remotion-bundle/02a2fb2eb80bc7bf.woff2 +0 -0
- package/dist/remotion-bundle/052ca5351e5e06ba.woff2 +0 -0
- package/dist/remotion-bundle/05853dd28f4019cb.woff2 +0 -0
- package/dist/remotion-bundle/072ead3737f7c0d0.woff2 +0 -0
- package/dist/remotion-bundle/07d4248613c86a2e.woff2 +0 -0
- package/dist/remotion-bundle/0884a5c2d1d2d99b.woff2 +0 -0
- package/dist/remotion-bundle/0b0e185b2752095e.woff2 +0 -0
- package/dist/remotion-bundle/0e66c11bde067d91.woff2 +0 -0
- package/dist/remotion-bundle/0f7794cfba2c5d21.woff2 +0 -0
- package/dist/remotion-bundle/0fdbae5a4365783a.woff2 +0 -0
- package/dist/remotion-bundle/112.bundle.js +11 -0
- package/dist/remotion-bundle/112.bundle.js.map +1 -0
- package/dist/remotion-bundle/113.bundle.js +11 -0
- package/dist/remotion-bundle/113.bundle.js.map +1 -0
- package/dist/remotion-bundle/119cae0c4c16f7ed.woff2 +0 -0
- package/dist/remotion-bundle/14725f649fd1e78c.woff2 +0 -0
- package/dist/remotion-bundle/14abe9e3f95f7888.woff2 +0 -0
- package/dist/remotion-bundle/163.bundle.js +14678 -0
- package/dist/remotion-bundle/163.bundle.js.map +1 -0
- package/dist/remotion-bundle/1808c54072bf6d14.woff2 +0 -0
- package/dist/remotion-bundle/18948bec3e3012fe.woff2 +0 -0
- package/dist/remotion-bundle/1a661c60d0fc84fc.woff2 +0 -0
- package/dist/remotion-bundle/1af94941e1bc7e1e.woff2 +0 -0
- package/dist/remotion-bundle/1bee0219595f606c.woff2 +0 -0
- package/dist/remotion-bundle/1bfd5da7ce9d4ec4.woff2 +0 -0
- package/dist/remotion-bundle/1c158d56f1884f3f.woff2 +0 -0
- package/dist/remotion-bundle/1cf5e88e667610eb.woff2 +0 -0
- package/dist/remotion-bundle/1d431bd10f53c481.woff2 +0 -0
- package/dist/remotion-bundle/1d701a81a7670db2.woff2 +0 -0
- package/dist/remotion-bundle/1da0fecad4240f16.woff2 +0 -0
- package/dist/remotion-bundle/1ed14d3d0c5c63fe.woff2 +0 -0
- package/dist/remotion-bundle/1edfecf40e586f53.woff2 +0 -0
- package/dist/remotion-bundle/1f479711bc34b054.woff +0 -0
- package/dist/remotion-bundle/1f86e54a0ff5fcd1.woff2 +0 -0
- package/dist/remotion-bundle/2043ea87d9aabd11.woff2 +0 -0
- package/dist/remotion-bundle/20563c39ee8a0e40.woff2 +0 -0
- package/dist/remotion-bundle/20c231590fd12c44.woff2 +0 -0
- package/dist/remotion-bundle/20ce61713f754c07.woff2 +0 -0
- package/dist/remotion-bundle/21eb9306fce24bb1.woff2 +0 -0
- package/dist/remotion-bundle/244bf71c0cc851af.woff2 +0 -0
- package/dist/remotion-bundle/274d4cfc02bffbcb.woff2 +0 -0
- package/dist/remotion-bundle/275.bundle.js +21 -0
- package/dist/remotion-bundle/275.bundle.js.map +1 -0
- package/dist/remotion-bundle/2958f540b39513dc.woff2 +0 -0
- package/dist/remotion-bundle/2a168b98fd97722e.woff2 +0 -0
- package/dist/remotion-bundle/2d1f6373937ab55f.woff2 +0 -0
- package/dist/remotion-bundle/2d213ae47ff6daa9.woff2 +0 -0
- package/dist/remotion-bundle/2e4b1f04fcd05047.woff2 +0 -0
- package/dist/remotion-bundle/304170d98f4c4563.woff2 +0 -0
- package/dist/remotion-bundle/30d02e136e7a5642.woff2 +0 -0
- package/dist/remotion-bundle/3135562b52a714cd.woff2 +0 -0
- package/dist/remotion-bundle/313713af2c8144e9.woff2 +0 -0
- package/dist/remotion-bundle/325fa4108d2285b9.woff2 +0 -0
- package/dist/remotion-bundle/338e927ed3345e0c.woff2 +0 -0
- package/dist/remotion-bundle/35fc6b190365bc17.woff2 +0 -0
- package/dist/remotion-bundle/37a51f1122d4efc5.woff2 +0 -0
- package/dist/remotion-bundle/39a4d63e02736f5e.woff2 +0 -0
- package/dist/remotion-bundle/3a00e0d62dfc4171.woff2 +0 -0
- package/dist/remotion-bundle/3a6955e6561affe1.woff2 +0 -0
- package/dist/remotion-bundle/3c573945aef49b89.woff2 +0 -0
- package/dist/remotion-bundle/3cdbfbfa23b516a5.woff2 +0 -0
- package/dist/remotion-bundle/3e42f85a9e64ca8a.woff2 +0 -0
- package/dist/remotion-bundle/3e83eaf1ec859415.woff2 +0 -0
- package/dist/remotion-bundle/3f3c8c90de1250ee.woff2 +0 -0
- package/dist/remotion-bundle/434.bundle.js +205 -0
- package/dist/remotion-bundle/434.bundle.js.map +1 -0
- package/dist/remotion-bundle/44ffc6ca4d781692.woff2 +0 -0
- package/dist/remotion-bundle/4670d9c4580b09eb.woff2 +0 -0
- package/dist/remotion-bundle/479756881b302824.woff2 +0 -0
- package/dist/remotion-bundle/481b82134bfa9c82.woff2 +0 -0
- package/dist/remotion-bundle/48d27029626f4328.woff2 +0 -0
- package/dist/remotion-bundle/49b7b2a30329c511.woff2 +0 -0
- package/dist/remotion-bundle/4c8b25a1a9337045.woff2 +0 -0
- package/dist/remotion-bundle/4cba14788ca9259b.woff2 +0 -0
- package/dist/remotion-bundle/4cd6c589c004a6a7.woff2 +0 -0
- package/dist/remotion-bundle/4cd8d79c1021608d.woff2 +0 -0
- package/dist/remotion-bundle/4d8fa99b3f00f9f0.woff2 +0 -0
- package/dist/remotion-bundle/4e7805a643f86d53.woff2 +0 -0
- package/dist/remotion-bundle/4ff91be454542e3f.woff2 +0 -0
- package/dist/remotion-bundle/504cbcba1f63591b.woff2 +0 -0
- package/dist/remotion-bundle/5202d792e5791d6c.woff2 +0 -0
- package/dist/remotion-bundle/534db5ad4770cc1d.woff2 +0 -0
- package/dist/remotion-bundle/53b9568eb85f866b.woff2 +0 -0
- package/dist/remotion-bundle/543ad386ca171de9.woff2 +0 -0
- package/dist/remotion-bundle/54798e55bbf7976e.woff2 +0 -0
- package/dist/remotion-bundle/580.bundle.js +11 -0
- package/dist/remotion-bundle/580.bundle.js.map +1 -0
- package/dist/remotion-bundle/58d174d1193af6d1.woff2 +0 -0
- package/dist/remotion-bundle/591d29ff3ff53c80.woff2 +0 -0
- package/dist/remotion-bundle/5c28c4f4824383c6.woff2 +0 -0
- package/dist/remotion-bundle/5da9740d2ce894c8.woff2 +0 -0
- package/dist/remotion-bundle/6197735364642360.woff2 +0 -0
- package/dist/remotion-bundle/6265a4335724080f.woff2 +0 -0
- package/dist/remotion-bundle/633f5e4f6394daa7.woff2 +0 -0
- package/dist/remotion-bundle/637d95ace6a69c49.woff2 +0 -0
- package/dist/remotion-bundle/648e04a04dacff8f.woff2 +0 -0
- package/dist/remotion-bundle/64a6e83045a008b2.woff2 +0 -0
- package/dist/remotion-bundle/651.bundle.js +11 -0
- package/dist/remotion-bundle/651.bundle.js.map +1 -0
- package/dist/remotion-bundle/65e2a988c070facc.woff2 +0 -0
- package/dist/remotion-bundle/66a2f6ce5cc69105.woff2 +0 -0
- package/dist/remotion-bundle/690.bundle.js +3479 -0
- package/dist/remotion-bundle/690.bundle.js.map +1 -0
- package/dist/remotion-bundle/690ff55252ca715d.woff2 +0 -0
- package/dist/remotion-bundle/6a01a1cff49314fc.woff2 +0 -0
- package/dist/remotion-bundle/6cbc32670982986c.woff2 +0 -0
- package/dist/remotion-bundle/6d3cc42ae547f454.woff2 +0 -0
- package/dist/remotion-bundle/6d8f4cfa1ddc0830.woff2 +0 -0
- package/dist/remotion-bundle/6e4d7c6ae65e2dc3.woff2 +0 -0
- package/dist/remotion-bundle/6e86418bbcefb2e8.woff2 +0 -0
- package/dist/remotion-bundle/6ee02884b29cf7fb.woff2 +0 -0
- package/dist/remotion-bundle/6f436a74c9e3252c.woff2 +0 -0
- package/dist/remotion-bundle/78c8022f1657618b.woff2 +0 -0
- package/dist/remotion-bundle/7c5444169792bca4.woff2 +0 -0
- package/dist/remotion-bundle/7c86bddd9d997212.woff2 +0 -0
- package/dist/remotion-bundle/7e1284684767f584.woff2 +0 -0
- package/dist/remotion-bundle/7e81c17522d182b2.woff2 +0 -0
- package/dist/remotion-bundle/7eb87be198f7858c.woff2 +0 -0
- package/dist/remotion-bundle/8060c928f948aab5.woff2 +0 -0
- package/dist/remotion-bundle/80bc9dfbea2b35ae.woff2 +0 -0
- package/dist/remotion-bundle/811b83f69963bb48.woff2 +0 -0
- package/dist/remotion-bundle/813.bundle.js +117511 -0
- package/dist/remotion-bundle/813.bundle.js.map +1 -0
- package/dist/remotion-bundle/84df492e349f82e9.woff2 +0 -0
- package/dist/remotion-bundle/8501bfd73eb36f2b.woff2 +0 -0
- package/dist/remotion-bundle/854236a8376093fe.woff2 +0 -0
- package/dist/remotion-bundle/8571d74529082753.woff2 +0 -0
- package/dist/remotion-bundle/860bf44f8e6f4b5d.woff2 +0 -0
- package/dist/remotion-bundle/879.bundle.js +64 -0
- package/dist/remotion-bundle/879.bundle.js.map +1 -0
- package/dist/remotion-bundle/887dd482f848d56f.woff2 +0 -0
- package/dist/remotion-bundle/89b2132e85fbbb5a.woff2 +0 -0
- package/dist/remotion-bundle/8ba60d6c306010c2.woff2 +0 -0
- package/dist/remotion-bundle/8c7c4dadea897806.woff2 +0 -0
- package/dist/remotion-bundle/8c943f9999706f61.woff2 +0 -0
- package/dist/remotion-bundle/8f2a718c90575cc9.woff2 +0 -0
- package/dist/remotion-bundle/906b6edb3e1772c9.woff2 +0 -0
- package/dist/remotion-bundle/930ff9daccdf14eb.woff2 +0 -0
- package/dist/remotion-bundle/934db2f1c403c4d0.woff2 +0 -0
- package/dist/remotion-bundle/938.bundle.js +451 -0
- package/dist/remotion-bundle/938.bundle.js.map +1 -0
- package/dist/remotion-bundle/967.bundle.js +4462 -0
- package/dist/remotion-bundle/967.bundle.js.map +1 -0
- package/dist/remotion-bundle/9684a1093d3c02ce.woff2 +0 -0
- package/dist/remotion-bundle/973dcd0faa6116cc.woff2 +0 -0
- package/dist/remotion-bundle/9745400694e76cd8.woff2 +0 -0
- package/dist/remotion-bundle/999ef957bed3bdca.woff2 +0 -0
- package/dist/remotion-bundle/99a3d67c8b0f43e3.woff2 +0 -0
- package/dist/remotion-bundle/a0586c3e03127283.woff2 +0 -0
- package/dist/remotion-bundle/a0eb654fdae46269.woff2 +0 -0
- package/dist/remotion-bundle/a20e35d3b08f7994.woff2 +0 -0
- package/dist/remotion-bundle/a2dcaced7c8c25ab.woff2 +0 -0
- package/dist/remotion-bundle/a79255a972a2681a.woff2 +0 -0
- package/dist/remotion-bundle/a804b352cb9fec1a.woff2 +0 -0
- package/dist/remotion-bundle/aae7117164e1eabc.woff2 +0 -0
- package/dist/remotion-bundle/affd121385d0442d.woff2 +0 -0
- package/dist/remotion-bundle/b19a6083987ee0d7.woff2 +0 -0
- package/dist/remotion-bundle/b1b2bd04d8637981.woff2 +0 -0
- package/dist/remotion-bundle/b2c07f341486be87.woff2 +0 -0
- package/dist/remotion-bundle/b33d8f82e575c4ce.woff2 +0 -0
- package/dist/remotion-bundle/b366c0bed35ef491.woff2 +0 -0
- package/dist/remotion-bundle/b41e857ec1b85642.woff2 +0 -0
- package/dist/remotion-bundle/b420bb34ccf23e7f.woff2 +0 -0
- package/dist/remotion-bundle/b4f7bf4efb0c0ccf.woff2 +0 -0
- package/dist/remotion-bundle/b60fe5eca03cff93.woff2 +0 -0
- package/dist/remotion-bundle/b6bd31a336e64bce.woff2 +0 -0
- package/dist/remotion-bundle/b6d2befba3dfefeb.woff2 +0 -0
- package/dist/remotion-bundle/b75f39ab06c43bf4.woff2 +0 -0
- package/dist/remotion-bundle/b77880e8c413d4fd.woff2 +0 -0
- package/dist/remotion-bundle/b7e38ec441e4a77a.woff2 +0 -0
- package/dist/remotion-bundle/b83baa383ff0bf2b.woff2 +0 -0
- package/dist/remotion-bundle/b9ad7b6c0a11450a.woff2 +0 -0
- package/dist/remotion-bundle/baf84486e8ae3aaf.woff2 +0 -0
- package/dist/remotion-bundle/bc047b1f6869cffa.woff2 +0 -0
- package/dist/remotion-bundle/bf4f3ac6e93f33aa.woff2 +0 -0
- package/dist/remotion-bundle/bf6835ffec5897a2.woff2 +0 -0
- package/dist/remotion-bundle/bf8885f581eb1724.woff2 +0 -0
- package/dist/remotion-bundle/bundle.js +83376 -0
- package/dist/remotion-bundle/bundle.js.map +1 -0
- package/dist/remotion-bundle/c03f046bccd789d0.woff2 +0 -0
- package/dist/remotion-bundle/c0bb1f8962b73bc3.woff2 +0 -0
- package/dist/remotion-bundle/c1003f9a7db6e1cf.woff2 +0 -0
- package/dist/remotion-bundle/c15d83fb1e199515.woff2 +0 -0
- package/dist/remotion-bundle/c28e7e5d310f73ef.woff2 +0 -0
- package/dist/remotion-bundle/c2b840274db78aea.woff2 +0 -0
- package/dist/remotion-bundle/c3000e3299d4e45f.woff2 +0 -0
- package/dist/remotion-bundle/c83ce886e5288510.woff2 +0 -0
- package/dist/remotion-bundle/c87a5a64d4ac0918.woff2 +0 -0
- package/dist/remotion-bundle/c8a7e0d049e965fa.woff2 +0 -0
- package/dist/remotion-bundle/c949a35d3a3b1faf.woff2 +0 -0
- package/dist/remotion-bundle/c9618c9b9ac2bc78.woff2 +0 -0
- package/dist/remotion-bundle/ca3add3b84152d5b.woff2 +0 -0
- package/dist/remotion-bundle/cad9dd036408d707.woff2 +0 -0
- package/dist/remotion-bundle/cbb24916619df439.woff2 +0 -0
- package/dist/remotion-bundle/cc054f0b5514e177.woff2 +0 -0
- package/dist/remotion-bundle/ccc248ed9312bc71.woff2 +0 -0
- package/dist/remotion-bundle/cd9d623aa07af925.woff2 +0 -0
- package/dist/remotion-bundle/ce2ba7a321bd1247.woff2 +0 -0
- package/dist/remotion-bundle/cf72455f79a29b14.woff2 +0 -0
- package/dist/remotion-bundle/d267cbfefab452ac.woff2 +0 -0
- package/dist/remotion-bundle/d435cff46a64955f.woff +0 -0
- package/dist/remotion-bundle/d494d07f67e363f6.woff2 +0 -0
- package/dist/remotion-bundle/d7aa0cc1fa47bf38.woff2 +0 -0
- package/dist/remotion-bundle/d7c5ca93d885160a.woff2 +0 -0
- package/dist/remotion-bundle/d855d3e252db74e2.woff2 +0 -0
- package/dist/remotion-bundle/d8f13d47f02f82c2.woff2 +0 -0
- package/dist/remotion-bundle/d9567cce2ee11019.woff2 +0 -0
- package/dist/remotion-bundle/db8d4456fc75dd86.woff +0 -0
- package/dist/remotion-bundle/dc274628378c47ee.woff2 +0 -0
- package/dist/remotion-bundle/dc3e06947bb69903.woff2 +0 -0
- package/dist/remotion-bundle/dd67040ac3b6d523.woff2 +0 -0
- package/dist/remotion-bundle/e0b04bd488f953f4.woff2 +0 -0
- package/dist/remotion-bundle/e2a572ff95089370.woff2 +0 -0
- package/dist/remotion-bundle/e2e18a86b1c2b0cc.woff2 +0 -0
- package/dist/remotion-bundle/e3a78ee2fc9c6931.woff2 +0 -0
- package/dist/remotion-bundle/e654c9d547605a9f.woff2 +0 -0
- package/dist/remotion-bundle/e67a3a64c129927c.woff2 +0 -0
- package/dist/remotion-bundle/e6be28b4203cd6ce.woff2 +0 -0
- package/dist/remotion-bundle/e841907ad9b0a191.woff +0 -0
- package/dist/remotion-bundle/e889d1541c69fffa.woff2 +0 -0
- package/dist/remotion-bundle/e88ef8c76373a9e2.woff2 +0 -0
- package/dist/remotion-bundle/e9c72f4bc37defef.woff2 +0 -0
- package/dist/remotion-bundle/e9e35f863403a255.woff2 +0 -0
- package/dist/remotion-bundle/eb23b37b009375da.woff2 +0 -0
- package/dist/remotion-bundle/ee1342b741625721.woff2 +0 -0
- package/dist/remotion-bundle/f07da88543a57ec9.woff2 +0 -0
- package/dist/remotion-bundle/f522982115306f8a.woff2 +0 -0
- package/dist/remotion-bundle/f8449bd864e6d8bc.woff2 +0 -0
- package/dist/remotion-bundle/f906dd5bd95ff9ab.woff2 +0 -0
- package/dist/remotion-bundle/f9e9e9413e3c38bb.woff2 +0 -0
- package/dist/remotion-bundle/fa5a5b16280994a8.woff2 +0 -0
- package/dist/remotion-bundle/favicon.ico +0 -0
- package/dist/remotion-bundle/fb19c0517725599b.woff2 +0 -0
- package/dist/remotion-bundle/fcaf24232f684b9b.woff2 +0 -0
- package/dist/remotion-bundle/fe09e084a3eea8cf.woff2 +0 -0
- package/dist/remotion-bundle/ff38d5317df7345a.woff2 +0 -0
- package/dist/remotion-bundle/ffe7ea1ea08f455a.woff2 +0 -0
- package/dist/remotion-bundle/index.html +49 -0
- package/dist/remotion-bundle/public/paper-slide/female-kefu-xiaomei/communication/0.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/female-kefu-xiaomei/communication/1.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/female-kefu-xiaomei/communication/2.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/female-kefu-xiaomei/communication/3.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/female-kefu-xiaoxin/career/0.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/female-kefu-xiaoxin/career/1.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/female-kefu-xiaoxin/career/2.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/female-kefu-xiaoxin/career/3.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/female-kefu-xiaoyue/parenting/0.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/female-kefu-xiaoyue/parenting/1.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/female-kefu-xiaoyue/parenting/2.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/female-kefu-xiaoyue/parenting/3.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/male-kefu-xiaoxu/time-trap/0.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/male-kefu-xiaoxu/time-trap/1.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/male-kefu-xiaoxu/time-trap/2.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/male-kefu-xiaoxu/time-trap/3.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/v-female-A6b7WpG3/cognition/0.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/v-female-A6b7WpG3/cognition/1.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/v-female-A6b7WpG3/cognition/2.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/v-female-A6b7WpG3/cognition/3.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/v-female-A6b7WpG3/growth/0.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/v-female-A6b7WpG3/growth/1.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/v-female-A6b7WpG3/growth/2.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/v-female-A6b7WpG3/growth/3.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/v-female-A6b7WpG3/parenting/0.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/v-female-A6b7WpG3/parenting/1.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/v-female-A6b7WpG3/parenting/2.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/v-female-A6b7WpG3/parenting/3.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/v-female-A6b7WpG3/soothing/0.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/v-female-A6b7WpG3/soothing/1.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/v-female-A6b7WpG3/soothing/2.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/v-female-A6b7WpG3/soothing/3.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/v-female-R2s4N9qJ/cognition/0.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/v-female-R2s4N9qJ/cognition/1.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/v-female-R2s4N9qJ/cognition/2.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/v-female-R2s4N9qJ/cognition/3.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/v-male-Bk7vD3xP/decision/0.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/v-male-Bk7vD3xP/decision/1.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/v-male-Bk7vD3xP/decision/2.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/v-male-Bk7vD3xP/decision/3.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/v-male-W1tH9jVc/manager/0.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/v-male-W1tH9jVc/manager/1.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/v-male-W1tH9jVc/manager/2.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/v-male-W1tH9jVc/manager/3.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/v-male-W1tH9jVc/manager/4.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/v-male-s5NqE0rZ/founder/0.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/v-male-s5NqE0rZ/founder/1.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/v-male-s5NqE0rZ/founder/2.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/v-male-s5NqE0rZ/founder/3.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide-experiments/career-advice/0.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide-experiments/career-advice/1.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide-experiments/career-advice/2.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide-experiments/career-advice/3.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide-experiments/career-advice/4.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide-experiments/founder-lesson/0.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide-experiments/founder-lesson/1.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide-experiments/founder-lesson/2.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide-experiments/founder-lesson/3.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide-experiments/founder-lesson/4.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide-experiments/incident-review/0.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide-experiments/incident-review/1.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide-experiments/incident-review/2.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide-experiments/incident-review/3.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide-experiments/incident-review/4.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide-experiments/learning-loop/0.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide-experiments/learning-loop/1.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide-experiments/learning-loop/2.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide-experiments/learning-loop/3.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide-experiments/learning-loop/4.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide-experiments/meeting-closure/0.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide-experiments/meeting-closure/1.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide-experiments/meeting-closure/2.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide-experiments/meeting-closure/3.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide-experiments/product-update/0.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide-experiments/product-update/1.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide-experiments/product-update/2.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide-experiments/product-update/3.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide-experiments/research-reading/0.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide-experiments/research-reading/1.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide-experiments/research-reading/2.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide-experiments/research-reading/3.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide-experiments/sales-enablement/0.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide-experiments/sales-enablement/1.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide-experiments/sales-enablement/2.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide-experiments/sales-enablement/3.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide-experiments/sales-enablement/4.mp3 +0 -0
- package/dist/remotion-bundle/public/voiceover/ai-life/card-0.mp3 +0 -0
- package/dist/remotion-bundle/public/voiceover/ai-life/card-1.mp3 +0 -0
- package/dist/remotion-bundle/public/voiceover/ai-life/card-2.mp3 +0 -0
- package/dist/remotion-bundle/public/voiceover/ai-life/card-3.mp3 +0 -0
- package/dist/remotion-bundle/public/voiceover/ai-life/card-4.mp3 +0 -0
- package/dist/remotion-bundle/public/voiceover/ai-life/card-5.mp3 +0 -0
- package/dist/remotion-bundle/public/voiceover/coffee-science/card-0.mp3 +0 -0
- package/dist/remotion-bundle/public/voiceover/coffee-science/card-1.mp3 +0 -0
- package/dist/remotion-bundle/public/voiceover/coffee-science/card-2.mp3 +0 -0
- package/dist/remotion-bundle/public/voiceover/coffee-science/card-3.mp3 +0 -0
- package/dist/remotion-bundle/public/voiceover/coffee-science/card-4.mp3 +0 -0
- package/dist/remotion-bundle/public/voiceover/coffee-science/card-5.mp3 +0 -0
- package/dist/remotion-bundle/public/voiceover/coffee-science/card-6.mp3 +0 -0
- package/dist/remotion-bundle/public/voiceover/reading-secrets/card-0.mp3 +0 -0
- package/dist/remotion-bundle/public/voiceover/reading-secrets/card-1.mp3 +0 -0
- package/dist/remotion-bundle/public/voiceover/reading-secrets/card-2.mp3 +0 -0
- package/dist/remotion-bundle/public/voiceover/reading-secrets/card-3.mp3 +0 -0
- package/dist/remotion-bundle/public/voiceover/reading-secrets/card-4.mp3 +0 -0
- package/dist/remotion-bundle/public/voiceover/reading-secrets/card-5.mp3 +0 -0
- package/dist/remotion-bundle/public/voiceover/reading-secrets/card-6.mp3 +0 -0
- package/dist/remotion-bundle/public/voiceover/remote-work/card-0.mp3 +0 -0
- package/dist/remotion-bundle/public/voiceover/remote-work/card-1.mp3 +0 -0
- package/dist/remotion-bundle/public/voiceover/remote-work/card-2.mp3 +0 -0
- package/dist/remotion-bundle/public/voiceover/remote-work/card-3.mp3 +0 -0
- package/dist/remotion-bundle/public/voiceover/remote-work/card-4.mp3 +0 -0
- package/dist/remotion-bundle/public/voiceover/remote-work/card-5.mp3 +0 -0
- package/dist/remotion-bundle/source-map-helper.wasm +0 -0
- package/lib/cli.js +270 -0
- package/lib/commands/_registry.js +48 -0
- package/lib/commands/add.js +242 -0
- package/lib/commands/asr/azure-transcribe.js +336 -0
- package/lib/commands/asr/cloud-transcribe.js +384 -0
- package/lib/commands/asr/helpers.js +76 -0
- package/lib/commands/asr/index.js +236 -0
- package/lib/commands/asr/local-transcribe.js +125 -0
- package/lib/commands/asr-jobs.js +257 -0
- package/lib/commands/asr.js +11 -0
- package/lib/commands/auth-cmds.js +358 -0
- package/lib/commands/dub.js +542 -0
- package/lib/commands/explain.js +512 -0
- package/lib/commands/feedback.js +152 -0
- package/lib/commands/image.js +207 -0
- package/lib/commands/mcp-key.js +166 -0
- package/lib/commands/narrate.js +639 -0
- package/lib/commands/picstory-templates.js +276 -0
- package/lib/commands/picstory.js +547 -0
- package/lib/commands/podcast/dialogue.js +109 -0
- package/lib/commands/podcast/generate.js +127 -0
- package/lib/commands/podcast/index.js +561 -0
- package/lib/commands/podcast/synthesize.js +188 -0
- package/lib/commands/podcast.js +11 -0
- package/lib/commands/present.js +519 -0
- package/lib/commands/publish.js +415 -0
- package/lib/commands/skills.js +473 -0
- package/lib/commands/slice-render.js +282 -0
- package/lib/commands/slice-stage.js +264 -0
- package/lib/commands/slice.js +346 -0
- package/lib/commands/slides/constants.js +108 -0
- package/lib/commands/slides/html-renderer.js +338 -0
- package/lib/commands/slides/index.js +345 -0
- package/lib/commands/slides.js +11 -0
- package/lib/commands/story.js +302 -0
- package/lib/commands/summarize.js +532 -0
- package/lib/commands/synthesize.js +261 -0
- package/lib/commands/translate.js +593 -0
- package/lib/commands/upgrade.js +249 -0
- package/lib/commands/video-translate.js +577 -0
- package/lib/commands/voices.js +292 -0
- package/lib/core/agent-env.js +104 -0
- package/lib/core/args.js +107 -0
- package/lib/core/asr-client.js +448 -0
- package/lib/core/asr-jobs-client.js +126 -0
- package/lib/core/asr-jobs-store.js +105 -0
- package/lib/core/asr-r2-upload.js +181 -0
- package/lib/core/asr-upload.js +132 -0
- package/lib/core/audio-extract.js +150 -0
- package/lib/core/audio.js +219 -0
- package/lib/core/auth.js +880 -0
- package/lib/core/config.js +197 -0
- package/lib/core/feedback.js +64 -0
- package/lib/core/ffmpeg.js +476 -0
- package/lib/core/http.js +188 -0
- package/lib/core/image-client.js +55 -0
- package/lib/core/intent-params.js +11 -0
- package/lib/core/llm-client.js +76 -0
- package/lib/core/logger.js +208 -0
- package/lib/core/mic-recorder.js +182 -0
- package/lib/core/pause-markers.js +94 -0
- package/lib/core/podcast-pacing.js +118 -0
- package/lib/core/spinner.js +33 -0
- package/lib/core/srt.js +394 -0
- package/lib/core/telemetry.js +100 -0
- package/lib/core/timeline.js +92 -0
- package/lib/core/tts-synthesizer.js +70 -0
- package/lib/core/update-check.js +185 -0
- package/lib/core/url-download.js +148 -0
- package/lib/core/whisper-local.js +279 -0
- package/lib/internal/deck-validator.js +488 -0
- package/lib/internal/slice-themes.json +370 -0
- package/lib/stage-core/cloud-render.js +170 -0
- package/lib/stage-core/deck-format.js +133 -0
- package/lib/stage-core/edit-prompt.js +104 -0
- package/lib/stage-core/event-bus.js +31 -0
- package/lib/stage-core/port.js +46 -0
- package/lib/stage-core/server.js +352 -0
- package/lib/stage-core/snapshot-store.js +198 -0
- package/lib/stage-core/watcher.js +106 -0
- package/lib/stage-ui/slice/template.js +1672 -0
- package/package.json +9 -4
- package/skills/.claude-plugin/marketplace.json +22 -0
- package/skills/.claude-plugin/plugin.json +25 -0
- package/skills/LICENSE +21 -0
- package/skills/README.md +120 -0
- package/skills/hub/SKILL.md +317 -0
- package/skills/podcast/SKILL.md +146 -0
- package/skills/slice/SKILL.md +205 -0
- package/skills/slice/agents/openai.yaml +4 -0
- package/skills/slice/references/deck-schema.md +183 -0
- package/skills/slice/references/example-decks.md +108 -0
- package/skills/slice/references/themes.md +172 -0
- package/skills/transcribe/SKILL.md +473 -0
- package/skills/video/SKILL.md +261 -0
- package/skills/voxflow-slice/SKILL.md +271 -0
- package/skills/voxflow-slice/examples/article.md +13 -0
- package/skills/voxflow-slice/examples/expected-deck.json +39 -0
- package/skills/voxflow-slice/examples/validate.mjs +46 -0
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* `voxflow slice render <deck.json>` — offline local Remotion render.
|
|
5
|
+
*
|
|
6
|
+
* Reads a validator-shaped deck.json, transforms it into PaperSlideDeck
|
|
7
|
+
* composition input props, and renders directly to MP4 via the pre-bundled
|
|
8
|
+
* Remotion serveUrl at `cli/dist/remotion-bundle/`. No VoxFlow API call,
|
|
9
|
+
* no TTS, no quota — Phase 0 is silent video; Phase 1 will add local TTS.
|
|
10
|
+
*
|
|
11
|
+
* No `meta` export — reached only via `slice.js` dispatch (`voxflow slice
|
|
12
|
+
* render <path>`), so it stays out of the registry parity test (the same
|
|
13
|
+
* pattern slice-stage.js uses).
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const fs = require('node:fs');
|
|
17
|
+
const path = require('node:path');
|
|
18
|
+
|
|
19
|
+
const { validatePaperSlideDeck, isV2LayoutTreeDeck } = require('../internal/deck-validator');
|
|
20
|
+
const SLICE_THEME_REGISTRY = require('../internal/slice-themes.json');
|
|
21
|
+
|
|
22
|
+
const THEME_TO_DECK_ID = Object.fromEntries(
|
|
23
|
+
SLICE_THEME_REGISTRY.themes.map((t) => [t.id, t.remotion.deck])
|
|
24
|
+
);
|
|
25
|
+
const DEFAULT_THEME = SLICE_THEME_REGISTRY.default;
|
|
26
|
+
|
|
27
|
+
// Composition FPS is fixed at 30 across every theme's <Composition> in
|
|
28
|
+
// video-present/src/Root.tsx. Captured here as documentation; the actual
|
|
29
|
+
// frames-per-second comes back from selectComposition() at runtime.
|
|
30
|
+
const DEFAULT_CARD_SEC = 4;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Transform a validator-shaped deck (cards[].kind/title/caption/...) into
|
|
34
|
+
* PaperSlideDeckProps (cards[].slide + cards[].durationSec) so the same
|
|
35
|
+
* deck schema users author in Claude maps cleanly onto every Remotion deck
|
|
36
|
+
* composition without going through the cloud render-worker pipeline.
|
|
37
|
+
*
|
|
38
|
+
* Phase 0 is silent — every card gets DEFAULT_CARD_SEC. Phase 1 will
|
|
39
|
+
* splice per-card TTS in and replace this with audio-driven durations.
|
|
40
|
+
*/
|
|
41
|
+
function buildInputProps(deck) {
|
|
42
|
+
const numberBadge = null;
|
|
43
|
+
const cards = deck.cards.map((card) => {
|
|
44
|
+
const slide = {
|
|
45
|
+
kind: card.kind,
|
|
46
|
+
header: deck.header,
|
|
47
|
+
title: card.kind === 'title' ? card.title || [] : [],
|
|
48
|
+
caption: card.caption ?? null,
|
|
49
|
+
figureKeyword: card.figureKeyword ?? null,
|
|
50
|
+
seriesTitle: deck.seriesTitle,
|
|
51
|
+
seriesTagline: deck.seriesTagline,
|
|
52
|
+
voiceoverSrc: null,
|
|
53
|
+
numberBadge,
|
|
54
|
+
imageUrl: card.imageUrl,
|
|
55
|
+
};
|
|
56
|
+
if (deck.variantId) slide.variantId = deck.variantId;
|
|
57
|
+
return { slide, durationSec: card.durationSec || DEFAULT_CARD_SEC };
|
|
58
|
+
});
|
|
59
|
+
return { cards };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function humanSize(bytes) {
|
|
63
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
64
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
65
|
+
if (bytes < 1024 * 1024 * 1024) return `${(bytes / 1024 / 1024).toFixed(1)} MB`;
|
|
66
|
+
return `${(bytes / 1024 / 1024 / 1024).toFixed(2)} GB`;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function fmtSec(ms) {
|
|
70
|
+
const s = ms / 1000;
|
|
71
|
+
if (s < 60) return `${s.toFixed(1)}s`;
|
|
72
|
+
const m = Math.floor(s / 60);
|
|
73
|
+
const rem = (s - m * 60).toFixed(0);
|
|
74
|
+
return `${m}m${rem.padStart(2, '0')}s`;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function resolveServeUrl() {
|
|
78
|
+
// dist/remotion-bundle is generated by `npm run build:bundle` at CLI
|
|
79
|
+
// prepublish. Local dev: rebuild after a Remotion edit (`cd cli && npm
|
|
80
|
+
// run build:bundle`). Path is intentionally relative to this file so
|
|
81
|
+
// both the unbundled source layout and the ncc-bundled dist/ resolve
|
|
82
|
+
// to the same on-disk artifact (dist/remotion-bundle is shipped flat
|
|
83
|
+
// in the npm tarball).
|
|
84
|
+
// Two layouts to support:
|
|
85
|
+
// - Source/dev (`node bin/voxflow.js`): __dirname = cli/lib/commands/
|
|
86
|
+
// → `../../dist/remotion-bundle` = cli/dist/remotion-bundle
|
|
87
|
+
// - ncc-published (`voxflow ...` from npm tarball): __dirname = <pkg>/dist/
|
|
88
|
+
// → `remotion-bundle` (sibling of bundled index.js)
|
|
89
|
+
const candidates = [
|
|
90
|
+
path.resolve(__dirname, '../../dist/remotion-bundle'),
|
|
91
|
+
path.resolve(__dirname, 'remotion-bundle'),
|
|
92
|
+
];
|
|
93
|
+
for (const c of candidates) {
|
|
94
|
+
if (fs.existsSync(path.join(c, 'index.html'))) return c;
|
|
95
|
+
}
|
|
96
|
+
throw new Error(
|
|
97
|
+
`Pre-bundled Remotion serveUrl not found at ${candidates.join(' or ')}.\n` +
|
|
98
|
+
' Run `cd cli && npm run build:bundle` from the monorepo first.'
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Heuristic: Remotion downloads chrome-headless-shell into node_modules/.remotion
|
|
104
|
+
* the FIRST time the renderer is invoked. After that it's cached.
|
|
105
|
+
*
|
|
106
|
+
* We probe for the binary so we can give the user a clear "first-run wait"
|
|
107
|
+
* message instead of leaving them staring at a silent terminal for a minute.
|
|
108
|
+
*/
|
|
109
|
+
function chromeBinaryExists() {
|
|
110
|
+
// Remotion stores the downloaded chrome-headless-shell next to whatever
|
|
111
|
+
// node_modules/ contains @remotion/renderer at runtime. In a flat
|
|
112
|
+
// consumer install that's `<pkg>/node_modules/.remotion/`; in a hoisted
|
|
113
|
+
// monorepo it's the hoist root. We probe both CWD-based and renderer-
|
|
114
|
+
// resolved ancestors so a warm install is detected regardless of layout.
|
|
115
|
+
const candidates = new Set();
|
|
116
|
+
const addAncestors = (start) => {
|
|
117
|
+
let dir = start;
|
|
118
|
+
for (let i = 0; i < 12; i++) {
|
|
119
|
+
candidates.add(path.join(dir, 'node_modules', '.remotion', 'chrome-headless-shell'));
|
|
120
|
+
candidates.add(path.join(dir, '.remotion', 'chrome-headless-shell'));
|
|
121
|
+
const parent = path.dirname(dir);
|
|
122
|
+
if (parent === dir) break;
|
|
123
|
+
dir = parent;
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
addAncestors(process.cwd());
|
|
127
|
+
addAncestors(__dirname);
|
|
128
|
+
try {
|
|
129
|
+
const rendererPkgPath = require.resolve('@remotion/renderer/package.json');
|
|
130
|
+
addAncestors(path.dirname(rendererPkgPath));
|
|
131
|
+
} catch {
|
|
132
|
+
// renderer not installed — definitely cold
|
|
133
|
+
}
|
|
134
|
+
for (const cand of candidates) {
|
|
135
|
+
if (fs.existsSync(cand)) return true;
|
|
136
|
+
}
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
async function render(opts) {
|
|
141
|
+
const deckPath = path.resolve(opts.deckPath);
|
|
142
|
+
if (!fs.existsSync(deckPath)) {
|
|
143
|
+
throw new Error(`Deck file not found: ${deckPath}`);
|
|
144
|
+
}
|
|
145
|
+
const raw = fs.readFileSync(deckPath, 'utf8');
|
|
146
|
+
let deck;
|
|
147
|
+
try {
|
|
148
|
+
deck = JSON.parse(raw);
|
|
149
|
+
} catch (err) {
|
|
150
|
+
throw new Error(`Could not parse deck JSON: ${err.message}`);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// V2 LayoutTree decks have `cards[].children[]` — different shape than
|
|
154
|
+
// the V1 PaperSlide deck this renderer expects. V1 validator doesn't
|
|
155
|
+
// catch V2 input (V2 cards happen to satisfy V1's kind/narration checks)
|
|
156
|
+
// so without an explicit guard, render() would silently produce a deck
|
|
157
|
+
// with undefined caption/figureKeyword on every card and Remotion would
|
|
158
|
+
// happily render blank slides. Fail loud instead.
|
|
159
|
+
if (isV2LayoutTreeDeck(deck)) {
|
|
160
|
+
throw new Error(
|
|
161
|
+
'V2 LayoutTree decks are not yet supported by offline render. ' +
|
|
162
|
+
'Use voxflow.studio/apps/slice for V2 decks or downgrade to V1 shape.'
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Run the same validator the production cloud route uses — so an offline
|
|
167
|
+
// render fails fast on the same shape errors the backend would reject.
|
|
168
|
+
validatePaperSlideDeck(deck);
|
|
169
|
+
|
|
170
|
+
const theme = deck.theme || DEFAULT_THEME;
|
|
171
|
+
const deckId = THEME_TO_DECK_ID[theme];
|
|
172
|
+
if (!deckId) {
|
|
173
|
+
throw new Error(
|
|
174
|
+
`Unknown theme "${theme}". Valid: ${Object.keys(THEME_TO_DECK_ID).join(', ')}`
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const outputPath = path.resolve(
|
|
179
|
+
opts.output || path.join(process.cwd(), 'out.mp4')
|
|
180
|
+
);
|
|
181
|
+
const outputDir = path.dirname(outputPath);
|
|
182
|
+
if (!fs.existsSync(outputDir)) fs.mkdirSync(outputDir, { recursive: true });
|
|
183
|
+
|
|
184
|
+
const serveUrl = resolveServeUrl();
|
|
185
|
+
const inputProps = buildInputProps(deck);
|
|
186
|
+
|
|
187
|
+
// Lazy require so users who never run `slice render` don't pay the
|
|
188
|
+
// remotion install cost at CLI startup (renderer pulls in puppeteer-
|
|
189
|
+
// core + chromium glue, ~30 MB on disk).
|
|
190
|
+
const { selectComposition, renderMedia } = require('@remotion/renderer');
|
|
191
|
+
|
|
192
|
+
const coldStart = !chromeBinaryExists();
|
|
193
|
+
if (coldStart) {
|
|
194
|
+
console.log(' First-run: Remotion will download chrome-headless-shell (~90 MB).');
|
|
195
|
+
console.log(' Allow ~30-60 s for the one-time download, then ~30 s for the render itself.');
|
|
196
|
+
}
|
|
197
|
+
console.log(`[slice render] theme: ${theme}`);
|
|
198
|
+
console.log(`[slice render] composition: ${deckId}`);
|
|
199
|
+
console.log(`[slice render] cards: ${deck.cards.length}`);
|
|
200
|
+
console.log(`[slice render] output: ${outputPath}`);
|
|
201
|
+
|
|
202
|
+
const t0 = Date.now();
|
|
203
|
+
|
|
204
|
+
const composition = await selectComposition({
|
|
205
|
+
serveUrl,
|
|
206
|
+
id: deckId,
|
|
207
|
+
inputProps,
|
|
208
|
+
});
|
|
209
|
+
const totalFrames = composition.durationInFrames;
|
|
210
|
+
const videoSec = totalFrames / composition.fps;
|
|
211
|
+
console.log(
|
|
212
|
+
`[slice render] duration: ${videoSec.toFixed(1)}s ` +
|
|
213
|
+
`(${totalFrames} frames @ ${composition.fps} fps, ` +
|
|
214
|
+
`${composition.width}x${composition.height})`
|
|
215
|
+
);
|
|
216
|
+
|
|
217
|
+
let lastFrame = 0;
|
|
218
|
+
let lastLog = 0;
|
|
219
|
+
await renderMedia({
|
|
220
|
+
composition,
|
|
221
|
+
serveUrl,
|
|
222
|
+
codec: 'h264',
|
|
223
|
+
outputLocation: outputPath,
|
|
224
|
+
inputProps,
|
|
225
|
+
imageFormat: 'jpeg',
|
|
226
|
+
jpegQuality: 85,
|
|
227
|
+
concurrency: 4,
|
|
228
|
+
x264Preset: 'ultrafast',
|
|
229
|
+
chromiumOptions: { gl: 'swiftshader' },
|
|
230
|
+
offthreadVideoCacheSizeInBytes: 512 * 1024 * 1024,
|
|
231
|
+
onProgress: ({ progress, renderedFrames }) => {
|
|
232
|
+
lastFrame = renderedFrames;
|
|
233
|
+
const now = Date.now();
|
|
234
|
+
// Throttle to ~2 lines/sec so the terminal stays scrollable in
|
|
235
|
+
// dev and so AI agents reading stdout aren't flooded.
|
|
236
|
+
if (now - lastLog < 500 && progress < 1) return;
|
|
237
|
+
lastLog = now;
|
|
238
|
+
const elapsed = now - t0;
|
|
239
|
+
const pct = Math.round(progress * 100);
|
|
240
|
+
process.stdout.write(
|
|
241
|
+
`\r[slice render] frame ${renderedFrames}/${totalFrames} ` +
|
|
242
|
+
`(${pct}%) — elapsed ${fmtSec(elapsed)} `
|
|
243
|
+
);
|
|
244
|
+
},
|
|
245
|
+
});
|
|
246
|
+
const totalMs = Date.now() - t0;
|
|
247
|
+
process.stdout.write('\n');
|
|
248
|
+
|
|
249
|
+
const stat = fs.statSync(outputPath);
|
|
250
|
+
console.log(`[slice render] done in ${fmtSec(totalMs)} — ${humanSize(stat.size)}`);
|
|
251
|
+
console.log(`[slice render] saved to ${outputPath}`);
|
|
252
|
+
return { outputPath, totalMs, frames: lastFrame, size: stat.size };
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
async function handle(args) {
|
|
256
|
+
const { parseFlag } = require('../core/args');
|
|
257
|
+
const output = parseFlag(args, '--output', '-o');
|
|
258
|
+
const positional = args.find(
|
|
259
|
+
(a) => !a.startsWith('-') && !a.startsWith('--')
|
|
260
|
+
);
|
|
261
|
+
if (!positional) {
|
|
262
|
+
console.error('Usage: voxflow slice render <deck.json> [--output out.mp4]');
|
|
263
|
+
process.exit(1);
|
|
264
|
+
}
|
|
265
|
+
try {
|
|
266
|
+
await render({ deckPath: positional, output });
|
|
267
|
+
} catch (err) {
|
|
268
|
+
console.error(`\nslice render failed: ${err.message}`);
|
|
269
|
+
if (process.env.VOXFLOW_DEBUG) console.error(err.stack);
|
|
270
|
+
process.exit(1);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
module.exports = {
|
|
275
|
+
render,
|
|
276
|
+
handle,
|
|
277
|
+
buildInputProps,
|
|
278
|
+
resolveServeUrl,
|
|
279
|
+
chromeBinaryExists,
|
|
280
|
+
THEME_TO_DECK_ID,
|
|
281
|
+
DEFAULT_THEME,
|
|
282
|
+
};
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* `voxflow slice stage <deck.json>` — boot a local Stage server that watches a
|
|
5
|
+
* deck JSON file and hot-reloads a localhost preview page on every change.
|
|
6
|
+
*
|
|
7
|
+
* No `meta` export — this is reached only via `slice.js` dispatch (first
|
|
8
|
+
* positional arg `stage`), so it stays out of the registry parity test.
|
|
9
|
+
*
|
|
10
|
+
* Phase 1 scope: server + watcher + minimal deck-JSON UI.
|
|
11
|
+
* Phase 1.3+ will add: Remotion `renderStill` thumbnails, exports, publish.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const fs = require('fs');
|
|
15
|
+
const path = require('path');
|
|
16
|
+
|
|
17
|
+
const { startStageServer } = require('../stage-core/server');
|
|
18
|
+
const { watchDeckFile } = require('../stage-core/watcher');
|
|
19
|
+
const { createEventBus } = require('../stage-core/event-bus');
|
|
20
|
+
const { createSnapshotStore } = require('../stage-core/snapshot-store');
|
|
21
|
+
const { createCloudRenderClient } = require('../stage-core/cloud-render');
|
|
22
|
+
const { renderSliceStageHtml } = require('../stage-ui/slice/template');
|
|
23
|
+
const { emit: emitTelemetry } = require('../core/telemetry');
|
|
24
|
+
|
|
25
|
+
// Sourced from the canonical registry at repo root. Previously this list
|
|
26
|
+
// silently fell out of sync (lagged at 6 themes while the rest of the repo
|
|
27
|
+
// had 13) — the JSON import makes that impossible.
|
|
28
|
+
const SLICE_THEME_REGISTRY = require('../../../slice-themes.json');
|
|
29
|
+
const VALID_THEMES = SLICE_THEME_REGISTRY.themes.map((t) => t.id);
|
|
30
|
+
|
|
31
|
+
function loadInitialDeck(absPath) {
|
|
32
|
+
try {
|
|
33
|
+
const raw = fs.readFileSync(absPath, 'utf8');
|
|
34
|
+
return { deck: JSON.parse(raw), error: null };
|
|
35
|
+
} catch (err) {
|
|
36
|
+
return { deck: null, error: err.code === 'ENOENT'
|
|
37
|
+
? `File not found: ${absPath}. Run \`voxflow slice <article>\` first, or pass --output to write a deck.json.`
|
|
38
|
+
: `Could not read deck: ${err.message}` };
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Programmatic entry — used by tests + the CLI handler. Returns a handle that
|
|
44
|
+
* exposes the listening URL and a stop() that tears everything down.
|
|
45
|
+
*
|
|
46
|
+
* @param {object} opts
|
|
47
|
+
* @param {string} opts.deckPath
|
|
48
|
+
* @param {string} [opts.theme] Lock to a single theme (validated)
|
|
49
|
+
* @param {number} [opts.port=5180]
|
|
50
|
+
* @param {number} [opts.maxPortTries=10]
|
|
51
|
+
* @returns {Promise<{url:string, port:number, stop:() => Promise<void>}>}
|
|
52
|
+
*/
|
|
53
|
+
async function startSliceStage(opts) {
|
|
54
|
+
const deckPath = path.resolve(opts.deckPath);
|
|
55
|
+
const theme = opts.theme;
|
|
56
|
+
if (theme && !VALID_THEMES.includes(theme)) {
|
|
57
|
+
throw new Error(
|
|
58
|
+
`Unknown theme "${theme}". Valid: ${VALID_THEMES.join(', ')}`
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const bus = createEventBus();
|
|
63
|
+
let snapshot = loadInitialDeck(deckPath);
|
|
64
|
+
snapshot.sourcePath = deckPath;
|
|
65
|
+
|
|
66
|
+
// Per-deck history. Initial parseable load = baseline snapshot (v0). Every
|
|
67
|
+
// subsequent watcher event with parseable content gets snapshotted; the
|
|
68
|
+
// store dedups by content hash so atomic-write double events stay one
|
|
69
|
+
// version. A `restore` flag rides on the deck event so the UI knows to
|
|
70
|
+
// skip the just-changed diff highlight (otherwise restoring an old deck
|
|
71
|
+
// would flash every card as "changed").
|
|
72
|
+
const snapshotStore = createSnapshotStore(deckPath);
|
|
73
|
+
let pendingRestoreId = null;
|
|
74
|
+
if (snapshot.deck) snapshotStore.snapshotIfChanged(snapshot.deck);
|
|
75
|
+
|
|
76
|
+
const watcher = watchDeckFile({
|
|
77
|
+
filePath: deckPath,
|
|
78
|
+
onChange(deck, err) {
|
|
79
|
+
if (err && !deck) {
|
|
80
|
+
snapshot = { deck: null, error: err.message, sourcePath: deckPath };
|
|
81
|
+
bus.publish({ type: 'error', sourcePath: deckPath, error: err.message, ts: Date.now() });
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
snapshot = { deck, error: null, sourcePath: deckPath };
|
|
85
|
+
snapshotStore.snapshotIfChanged(deck);
|
|
86
|
+
const restoredFromId = pendingRestoreId;
|
|
87
|
+
pendingRestoreId = null;
|
|
88
|
+
bus.publish({
|
|
89
|
+
type: 'deck',
|
|
90
|
+
sourcePath: deckPath,
|
|
91
|
+
deck,
|
|
92
|
+
theme: theme || null,
|
|
93
|
+
ts: Date.now(),
|
|
94
|
+
restoredFrom: restoredFromId,
|
|
95
|
+
});
|
|
96
|
+
},
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
// Server-side restore writer: writes the chosen snapshot bytes back to the
|
|
100
|
+
// live deck file, sets `pendingRestoreId` so the next watcher event tags
|
|
101
|
+
// the deck event with `restoredFrom`. Server.js passes through to this.
|
|
102
|
+
const snapshotsBridge = {
|
|
103
|
+
list: snapshotStore.list,
|
|
104
|
+
load: snapshotStore.load,
|
|
105
|
+
getStorePath: snapshotStore.getStorePath,
|
|
106
|
+
restore(id) {
|
|
107
|
+
const deck = snapshotStore.load(id);
|
|
108
|
+
if (!deck) return null;
|
|
109
|
+
pendingRestoreId = id;
|
|
110
|
+
try {
|
|
111
|
+
fs.writeFileSync(deckPath, JSON.stringify(deck, null, 2) + '\n', 'utf8');
|
|
112
|
+
} catch (err) {
|
|
113
|
+
pendingRestoreId = null;
|
|
114
|
+
throw err;
|
|
115
|
+
}
|
|
116
|
+
return { deck };
|
|
117
|
+
},
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
const preferredPort = Number.isInteger(opts.port) && opts.port > 0 ? opts.port : 5180;
|
|
121
|
+
const maxPortTries = Number.isInteger(opts.maxPortTries) && opts.maxPortTries > 0
|
|
122
|
+
? opts.maxPortTries : 10;
|
|
123
|
+
|
|
124
|
+
// We have to start the server before knowing the final port (port-conflict
|
|
125
|
+
// retry happens inside startStageServer). So we render the HTML lazily,
|
|
126
|
+
// re-rendering each request with the actual port baked in.
|
|
127
|
+
let actualPort = preferredPort;
|
|
128
|
+
const uiHtml = () => renderSliceStageHtml({ sourcePath: deckPath, port: actualPort });
|
|
129
|
+
|
|
130
|
+
// startStageServer takes a string for uiHtml; we pass a stub that will be
|
|
131
|
+
// replaced once we know the port. Cheap workaround: render once with
|
|
132
|
+
// `preferredPort`, then patch after we get the real port back.
|
|
133
|
+
const initialHtml = renderSliceStageHtml({ sourcePath: deckPath, port: preferredPort });
|
|
134
|
+
|
|
135
|
+
// Cloud render bridge — proxies POST /api/render-mp4 (and friends) to the
|
|
136
|
+
// backend cloud-render endpoint. Token comes from the same CLI cache the
|
|
137
|
+
// user logged into; the page never sees the JWT.
|
|
138
|
+
const cloudRender = opts.cloudRender || createCloudRenderClient();
|
|
139
|
+
|
|
140
|
+
const handle = await startStageServer({
|
|
141
|
+
uiHtml: initialHtml,
|
|
142
|
+
getDeckSnapshot: () => snapshot,
|
|
143
|
+
subscribe: bus.subscribe,
|
|
144
|
+
snapshots: snapshotsBridge,
|
|
145
|
+
cloudRender,
|
|
146
|
+
preferredPort,
|
|
147
|
+
maxPortTries,
|
|
148
|
+
});
|
|
149
|
+
actualPort = handle.port;
|
|
150
|
+
|
|
151
|
+
// Single anonymous launch event — feeds the "should Stage get an MCP?"
|
|
152
|
+
// decision. Fire-and-forget; opt-out via VOXFLOW_TELEMETRY=0 / DO_NOT_TRACK.
|
|
153
|
+
emitTelemetry('cli.slice.stage.launched', { theme: theme || null });
|
|
154
|
+
|
|
155
|
+
// If the port shifted (5180 → 5181 due to conflict), re-render the HTML so
|
|
156
|
+
// the footer hint shows the truthy URL. We swap by replacing the listener
|
|
157
|
+
// on the server: simpler than re-injecting, just update the closure used
|
|
158
|
+
// by startStageServer's GET / branch via a tiny patch.
|
|
159
|
+
if (actualPort !== preferredPort) {
|
|
160
|
+
// The HTML is captured by closure inside startStageServer; rather than
|
|
161
|
+
// wiring a setter, we do nothing — the page only references the port in
|
|
162
|
+
// a footer hint, and the live SSE connection works regardless. This is
|
|
163
|
+
// acceptable for MVP; revisit if/when port mismatch becomes user-visible.
|
|
164
|
+
void uiHtml;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return {
|
|
168
|
+
url: handle.url,
|
|
169
|
+
port: handle.port,
|
|
170
|
+
sourcePath: deckPath,
|
|
171
|
+
async stop() {
|
|
172
|
+
watcher.stop();
|
|
173
|
+
await handle.close();
|
|
174
|
+
},
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// ─── CLI Handler ────────────────────────────────────────────────────────────
|
|
179
|
+
|
|
180
|
+
async function handle(args) {
|
|
181
|
+
const { parseFlag } = require('../core/args');
|
|
182
|
+
|
|
183
|
+
const portFlag = parseFlag(args, '--port');
|
|
184
|
+
const themeFlag = parseFlag(args, '--theme');
|
|
185
|
+
const noOpen = args.includes('--no-open');
|
|
186
|
+
|
|
187
|
+
// First positional arg that isn't a flag value
|
|
188
|
+
const valuedFlags = new Set(['--port', '--theme']);
|
|
189
|
+
let deckPath = null;
|
|
190
|
+
for (let i = 0; i < args.length; i++) {
|
|
191
|
+
if (args[i].startsWith('-')) {
|
|
192
|
+
if (valuedFlags.has(args[i])) i++;
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
deckPath = args[i];
|
|
196
|
+
break;
|
|
197
|
+
}
|
|
198
|
+
if (!deckPath) {
|
|
199
|
+
console.error('Error: provide a deck JSON file');
|
|
200
|
+
console.error('Usage: voxflow slice stage <deck.json> [--port <n>] [--theme <id>] [--no-open]');
|
|
201
|
+
process.exit(1);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
let port;
|
|
205
|
+
if (portFlag) {
|
|
206
|
+
const parsed = Number.parseInt(portFlag, 10);
|
|
207
|
+
if (!Number.isInteger(parsed) || parsed <= 0 || parsed > 65535) {
|
|
208
|
+
console.error(`Error: --port "${portFlag}" is not a valid TCP port`);
|
|
209
|
+
process.exit(1);
|
|
210
|
+
}
|
|
211
|
+
port = parsed;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
let stage;
|
|
215
|
+
try {
|
|
216
|
+
stage = await startSliceStage({
|
|
217
|
+
deckPath,
|
|
218
|
+
theme: themeFlag || undefined,
|
|
219
|
+
port,
|
|
220
|
+
});
|
|
221
|
+
} catch (err) {
|
|
222
|
+
console.error(`\x1b[31mError:\x1b[0m ${err.message}`);
|
|
223
|
+
process.exit(1);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
console.log('');
|
|
227
|
+
console.log('\x1b[36m=== VoxFlow Stage — Slice ===\x1b[0m');
|
|
228
|
+
console.log(`Watching: ${stage.sourcePath}`);
|
|
229
|
+
console.log(`Preview: ${stage.url}`);
|
|
230
|
+
if (themeFlag) console.log(`Theme: ${themeFlag}`);
|
|
231
|
+
console.log('');
|
|
232
|
+
console.log('Edit the deck JSON to hot-reload. Ctrl+C to stop.');
|
|
233
|
+
console.log('');
|
|
234
|
+
|
|
235
|
+
if (!noOpen && !process.env.CI) {
|
|
236
|
+
try {
|
|
237
|
+
const open = (await import('open')).default;
|
|
238
|
+
await open(stage.url);
|
|
239
|
+
} catch {
|
|
240
|
+
// Non-fatal — user can open the URL manually.
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Keep the process alive until SIGINT.
|
|
245
|
+
let stopping = false;
|
|
246
|
+
const stopAndExit = async (signal) => {
|
|
247
|
+
if (stopping) return;
|
|
248
|
+
stopping = true;
|
|
249
|
+
console.log(`\n\x1b[33m${signal} received — stopping Stage…\x1b[0m`);
|
|
250
|
+
try { await stage.stop(); } catch { /* best-effort */ }
|
|
251
|
+
process.exit(0);
|
|
252
|
+
};
|
|
253
|
+
process.on('SIGINT', () => stopAndExit('SIGINT'));
|
|
254
|
+
process.on('SIGTERM', () => stopAndExit('SIGTERM'));
|
|
255
|
+
|
|
256
|
+
// Park.
|
|
257
|
+
return new Promise(() => { /* never resolves; SIGINT exits */ });
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
module.exports = {
|
|
261
|
+
handle,
|
|
262
|
+
startSliceStage,
|
|
263
|
+
VALID_THEMES,
|
|
264
|
+
};
|