voxflow 1.14.0 → 1.15.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +69 -2
- 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-preview.js +266 -0
- package/lib/commands/slice-render.js +282 -0
- package/lib/commands/slice-stage.js +264 -0
- package/lib/commands/slice.js +343 -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,266 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* `voxflow slice preview <deck.json>` — open a localhost HTML page that
|
|
5
|
+
* shows a midpoint-frame still of every card in the deck so users can
|
|
6
|
+
* see the cards before paying the full render cost (~30s for 20s video).
|
|
7
|
+
*
|
|
8
|
+
* Renders N PNG stills via @remotion/renderer.renderStill() at the
|
|
9
|
+
* midpoint frame of each card, caches them under /tmp/, then serves a
|
|
10
|
+
* minimal HTML page with the grid + auto-opens the browser. The server
|
|
11
|
+
* stays alive until Ctrl+C.
|
|
12
|
+
*
|
|
13
|
+
* Shares buildInputProps / resolveServeUrl / chromeBinaryExists with
|
|
14
|
+
* slice-render so behavior is consistent between preview and render.
|
|
15
|
+
*
|
|
16
|
+
* No `meta` export — reached via `slice.js` dispatch (`voxflow slice
|
|
17
|
+
* preview <path>`), so it stays out of the registry parity test.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
const fs = require('node:fs');
|
|
21
|
+
const http = require('node:http');
|
|
22
|
+
const path = require('node:path');
|
|
23
|
+
const crypto = require('node:crypto');
|
|
24
|
+
const os = require('node:os');
|
|
25
|
+
|
|
26
|
+
const open = require('open').default;
|
|
27
|
+
|
|
28
|
+
const { validatePaperSlideDeck, isV2LayoutTreeDeck } = require('../internal/deck-validator');
|
|
29
|
+
const {
|
|
30
|
+
buildInputProps,
|
|
31
|
+
resolveServeUrl,
|
|
32
|
+
chromeBinaryExists,
|
|
33
|
+
THEME_TO_DECK_ID,
|
|
34
|
+
DEFAULT_THEME,
|
|
35
|
+
} = require('./slice-render');
|
|
36
|
+
|
|
37
|
+
const FPS = 30;
|
|
38
|
+
const DEFAULT_PORT = 5555;
|
|
39
|
+
const PREVIEW_WIDTH = 540; // half of 1080 — stills don't need full-res for thumbnails
|
|
40
|
+
|
|
41
|
+
function deckHash(deck) {
|
|
42
|
+
// 8-char hash so the cache dir is short but unique per deck content.
|
|
43
|
+
// If the deck changes, we want fresh stills; if it doesn't, reuse cache.
|
|
44
|
+
return crypto.createHash('sha1').update(JSON.stringify(deck)).digest('hex').slice(0, 8);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function cardMidpointFrame(deck, cardIndex) {
|
|
48
|
+
// Composition plays cards sequentially. To get card K's midpoint
|
|
49
|
+
// frame we sum previous durations then add half of this one.
|
|
50
|
+
let acc = 0;
|
|
51
|
+
for (let i = 0; i < cardIndex; i++) {
|
|
52
|
+
acc += deck.cards[i].durationSec || 4;
|
|
53
|
+
}
|
|
54
|
+
const here = deck.cards[cardIndex].durationSec || 4;
|
|
55
|
+
return Math.floor((acc + here / 2) * FPS);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async function findFreePort(start) {
|
|
59
|
+
return new Promise((resolve) => {
|
|
60
|
+
const probe = http.createServer();
|
|
61
|
+
probe.unref();
|
|
62
|
+
probe.on('error', () => resolve(findFreePort(start + 1)));
|
|
63
|
+
probe.listen(start, () => {
|
|
64
|
+
const port = probe.address().port;
|
|
65
|
+
probe.close(() => resolve(port));
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function escapeHtml(s) {
|
|
71
|
+
return String(s ?? '').replace(/[&<>"']/g, (c) => ({
|
|
72
|
+
'&': '&', '<': '<', '>': '>', '"': '"', "'": ''',
|
|
73
|
+
}[c]));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function renderHtml(deck, port) {
|
|
77
|
+
const title = escapeHtml(deck.seriesTitle || deck.header || 'Preview');
|
|
78
|
+
const meta = `${deck.cards.length} cards · theme: ${escapeHtml(deck.theme)} · 1080×1920`;
|
|
79
|
+
const cards = deck.cards.map((c, i) => {
|
|
80
|
+
const label = c.kind === 'title'
|
|
81
|
+
? (Array.isArray(c.title) ? c.title.join(' / ') : '')
|
|
82
|
+
: (c.caption || '');
|
|
83
|
+
return `<figure class="card">
|
|
84
|
+
<img src="/still/${i}.png" alt="Card ${i + 1}" loading="lazy">
|
|
85
|
+
<figcaption><span class="idx">${i + 1}</span> <span class="kind">${escapeHtml(c.kind)}</span> ${escapeHtml(label)}</figcaption>
|
|
86
|
+
</figure>`;
|
|
87
|
+
}).join('\n ');
|
|
88
|
+
|
|
89
|
+
return `<!DOCTYPE html>
|
|
90
|
+
<html lang="zh">
|
|
91
|
+
<head>
|
|
92
|
+
<meta charset="utf-8">
|
|
93
|
+
<title>VoxFlow Slice · ${title}</title>
|
|
94
|
+
<style>
|
|
95
|
+
:root { color-scheme: light; }
|
|
96
|
+
body { font-family: ui-sans-serif, system-ui, -apple-system, "PingFang SC", sans-serif; margin: 0; padding: 40px 48px 64px; background: #faf6ee; color: #1b1815; }
|
|
97
|
+
h1 { font-size: 28px; font-weight: 500; letter-spacing: -0.01em; margin: 0 0 6px; }
|
|
98
|
+
.meta { color: #7a6f5f; font-size: 13px; margin-bottom: 28px; }
|
|
99
|
+
.grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(216px, 1fr)); gap: 20px 16px; }
|
|
100
|
+
.card { margin: 0; display: flex; flex-direction: column; gap: 8px; }
|
|
101
|
+
.card img { width: 100%; aspect-ratio: 9/16; background: #000; border-radius: 6px; box-shadow: 0 1px 2px rgba(0,0,0,0.08), 0 4px 14px rgba(60,40,20,0.07); display: block; }
|
|
102
|
+
.card figcaption { font-size: 12px; line-height: 1.45; color: #4d4339; display: flex; flex-wrap: wrap; align-items: baseline; gap: 6px; }
|
|
103
|
+
.idx { font-variant-numeric: tabular-nums; color: #b8a994; font-weight: 600; }
|
|
104
|
+
.kind { display: inline-block; padding: 1px 6px; border-radius: 3px; background: #efe7d6; color: #6e5f49; font-size: 10px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.04em; }
|
|
105
|
+
.cmd { font-family: ui-monospace, "SF Mono", monospace; background: #2b2520; color: #faf6ee; padding: 14px 18px; border-radius: 6px; font-size: 13px; margin-top: 32px; user-select: all; }
|
|
106
|
+
.cmd .hint { color: #9c8e75; font-size: 11px; display: block; margin-bottom: 6px; }
|
|
107
|
+
</style>
|
|
108
|
+
</head>
|
|
109
|
+
<body>
|
|
110
|
+
<h1>${title}</h1>
|
|
111
|
+
<div class="meta">${meta}</div>
|
|
112
|
+
<div class="grid">
|
|
113
|
+
${cards}
|
|
114
|
+
</div>
|
|
115
|
+
<div class="cmd">
|
|
116
|
+
<span class="hint">Satisfied? Render to mp4:</span>
|
|
117
|
+
voxflow slice render <your-deck.json> --output out.mp4
|
|
118
|
+
</div>
|
|
119
|
+
</body>
|
|
120
|
+
</html>`;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
async function preview(opts) {
|
|
124
|
+
const deckPath = path.resolve(opts.deckPath);
|
|
125
|
+
if (!fs.existsSync(deckPath)) {
|
|
126
|
+
throw new Error(`Deck file not found: ${deckPath}`);
|
|
127
|
+
}
|
|
128
|
+
const raw = fs.readFileSync(deckPath, 'utf8');
|
|
129
|
+
let deck;
|
|
130
|
+
try {
|
|
131
|
+
deck = JSON.parse(raw);
|
|
132
|
+
} catch (err) {
|
|
133
|
+
throw new Error(`Could not parse deck JSON: ${err.message}`);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (isV2LayoutTreeDeck(deck)) {
|
|
137
|
+
throw new Error(
|
|
138
|
+
'V2 LayoutTree decks are not yet supported by offline preview. ' +
|
|
139
|
+
'Use voxflow.studio/apps/slice for V2 decks or downgrade to V1 shape.'
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
validatePaperSlideDeck(deck);
|
|
143
|
+
|
|
144
|
+
const theme = deck.theme || DEFAULT_THEME;
|
|
145
|
+
const compId = THEME_TO_DECK_ID[theme];
|
|
146
|
+
if (!compId) {
|
|
147
|
+
throw new Error(
|
|
148
|
+
`Unknown theme "${theme}". Valid: ${Object.keys(THEME_TO_DECK_ID).join(', ')}`
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const serveUrl = resolveServeUrl();
|
|
153
|
+
const inputProps = buildInputProps(deck);
|
|
154
|
+
const cacheDir = path.join(os.tmpdir(), `voxflow-preview-${deckHash(deck)}`);
|
|
155
|
+
fs.mkdirSync(cacheDir, { recursive: true });
|
|
156
|
+
|
|
157
|
+
// Lazy require — mirrors slice-render.js — keeps `voxflow login` etc. fast
|
|
158
|
+
// for users who never trigger Remotion.
|
|
159
|
+
const { selectComposition, renderStill } = require('@remotion/renderer');
|
|
160
|
+
|
|
161
|
+
const coldStart = !chromeBinaryExists();
|
|
162
|
+
if (coldStart) {
|
|
163
|
+
console.log(' First-run: Remotion will download chrome-headless-shell (~90 MB).');
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
console.log(`[slice preview] theme: ${theme}`);
|
|
167
|
+
console.log(`[slice preview] composition: ${compId}`);
|
|
168
|
+
console.log(`[slice preview] cards: ${deck.cards.length}`);
|
|
169
|
+
|
|
170
|
+
const composition = await selectComposition({
|
|
171
|
+
serveUrl,
|
|
172
|
+
id: compId,
|
|
173
|
+
inputProps,
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
const t0 = Date.now();
|
|
177
|
+
const cachedCount = deck.cards.filter((_, i) =>
|
|
178
|
+
fs.existsSync(path.join(cacheDir, `card-${i}.png`))
|
|
179
|
+
).length;
|
|
180
|
+
if (cachedCount === deck.cards.length) {
|
|
181
|
+
console.log(`[slice preview] all ${cachedCount} stills cached — skipping render`);
|
|
182
|
+
} else {
|
|
183
|
+
for (let i = 0; i < deck.cards.length; i++) {
|
|
184
|
+
const outPath = path.join(cacheDir, `card-${i}.png`);
|
|
185
|
+
if (fs.existsSync(outPath)) continue;
|
|
186
|
+
const frame = cardMidpointFrame(deck, i);
|
|
187
|
+
process.stdout.write(`\r[slice preview] rendering still ${i + 1}/${deck.cards.length}...`);
|
|
188
|
+
await renderStill({
|
|
189
|
+
composition,
|
|
190
|
+
serveUrl,
|
|
191
|
+
inputProps,
|
|
192
|
+
frame,
|
|
193
|
+
output: outPath,
|
|
194
|
+
imageFormat: 'png',
|
|
195
|
+
scale: PREVIEW_WIDTH / 1080, // halve the resolution for snappier renders
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
const elapsed = ((Date.now() - t0) / 1000).toFixed(1);
|
|
199
|
+
process.stdout.write(`\r[slice preview] ${deck.cards.length} stills rendered in ${elapsed}s\n`);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const port = await findFreePort(DEFAULT_PORT);
|
|
203
|
+
const html = renderHtml(deck, port);
|
|
204
|
+
|
|
205
|
+
const server = http.createServer((req, res) => {
|
|
206
|
+
const urlPath = (req.url || '/').split('?')[0];
|
|
207
|
+
if (urlPath === '/' || urlPath === '/index.html') {
|
|
208
|
+
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
209
|
+
res.end(html);
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
const m = /^\/still\/(\d+)\.png$/.exec(urlPath);
|
|
213
|
+
if (m) {
|
|
214
|
+
const idx = Number(m[1]);
|
|
215
|
+
const file = path.join(cacheDir, `card-${idx}.png`);
|
|
216
|
+
if (!fs.existsSync(file)) {
|
|
217
|
+
res.writeHead(404);
|
|
218
|
+
res.end('still not found');
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
res.writeHead(200, { 'Content-Type': 'image/png', 'Cache-Control': 'public, max-age=600' });
|
|
222
|
+
fs.createReadStream(file).pipe(res);
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
res.writeHead(404);
|
|
226
|
+
res.end('Not found');
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
await new Promise((resolve) => server.listen(port, '127.0.0.1', resolve));
|
|
230
|
+
const url = `http://localhost:${port}`;
|
|
231
|
+
console.log(`[slice preview] http://localhost:${port} (Ctrl+C to stop)`);
|
|
232
|
+
|
|
233
|
+
try {
|
|
234
|
+
await open(url);
|
|
235
|
+
} catch {
|
|
236
|
+
// open failed — user can paste the URL manually
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
process.stdin.resume();
|
|
240
|
+
process.on('SIGINT', () => {
|
|
241
|
+
console.log('\n[slice preview] stopped');
|
|
242
|
+
server.close(() => process.exit(0));
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
async function handle(args) {
|
|
247
|
+
const { parseFlag } = require('../core/args');
|
|
248
|
+
const positional = args.find((a) => !a.startsWith('-'));
|
|
249
|
+
parseFlag(args, '--output', '-o'); // parsed-but-ignored for symmetry with `slice render`
|
|
250
|
+
if (!positional) {
|
|
251
|
+
console.error('Usage: voxflow slice preview <deck.json>');
|
|
252
|
+
process.exit(1);
|
|
253
|
+
}
|
|
254
|
+
try {
|
|
255
|
+
await preview({ deckPath: positional });
|
|
256
|
+
} catch (err) {
|
|
257
|
+
console.error(`\nslice preview failed: ${err.message}`);
|
|
258
|
+
if (process.env.VOXFLOW_DEBUG) console.error(err.stack);
|
|
259
|
+
process.exit(1);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
module.exports = {
|
|
264
|
+
preview,
|
|
265
|
+
handle,
|
|
266
|
+
};
|
|
@@ -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
|
+
};
|