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,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Podcast pacing helpers — per-segment silence + pace presets.
|
|
3
|
+
*
|
|
4
|
+
* Mirrors `backend/config/intent-tts-params.js`. The backend stamps each
|
|
5
|
+
* dialogue turn's `pauseAfterMs` (base) into the script JSON; here we apply
|
|
6
|
+
* the user's pace preset and a speaker-change boost when the JSON is missing
|
|
7
|
+
* those fields (older payloads, hand-edited scripts).
|
|
8
|
+
*
|
|
9
|
+
* Keep these constants in sync with the backend file.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
'use strict';
|
|
13
|
+
|
|
14
|
+
const INTENT_PAUSE_AFTER_MS = Object.freeze({
|
|
15
|
+
opening: 600,
|
|
16
|
+
explain: 450,
|
|
17
|
+
question: 750,
|
|
18
|
+
react: 350,
|
|
19
|
+
debate: 400,
|
|
20
|
+
summary: 600,
|
|
21
|
+
summarize: 600,
|
|
22
|
+
transition: 1000,
|
|
23
|
+
joke: 500,
|
|
24
|
+
closing: 1500,
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
const DEFAULT_PAUSE_AFTER_MS = 500;
|
|
28
|
+
const SPEAKER_CHANGE_BOOST_MS = 100;
|
|
29
|
+
|
|
30
|
+
// Silence multiplier for inter-turn gaps (client-side splice).
|
|
31
|
+
const PACE_MULTIPLIERS = Object.freeze({ tight: 0.7, natural: 1.0, relaxed: 1.3 });
|
|
32
|
+
|
|
33
|
+
// Speed multiplier for the TRTC `Speed` API param. Reciprocals of
|
|
34
|
+
// PACE_MULTIPLIERS so within-turn pauses (TRTC scales them by Speed) and
|
|
35
|
+
// inter-turn silences (client-side, scaled by PACE_MULTIPLIERS) move
|
|
36
|
+
// together. Final Speed is clamped to [0.5, 2.0] (TRTC's API range).
|
|
37
|
+
const PACE_SPEED_MULTIPLIERS = Object.freeze({ tight: 1.43, natural: 1.0, relaxed: 0.77 });
|
|
38
|
+
const TRTC_SPEED_MIN = 0.5;
|
|
39
|
+
const TRTC_SPEED_MAX = 2.0;
|
|
40
|
+
|
|
41
|
+
const VALID_PACE = Object.freeze(['tight', 'natural', 'relaxed']);
|
|
42
|
+
|
|
43
|
+
function resolvePaceMultiplier(pace) {
|
|
44
|
+
if (typeof pace !== 'string') return PACE_MULTIPLIERS.natural;
|
|
45
|
+
return PACE_MULTIPLIERS[pace] ?? PACE_MULTIPLIERS.natural;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function resolvePaceSpeedMultiplier(pace) {
|
|
49
|
+
if (typeof pace !== 'string') return PACE_SPEED_MULTIPLIERS.natural;
|
|
50
|
+
return PACE_SPEED_MULTIPLIERS[pace] ?? PACE_SPEED_MULTIPLIERS.natural;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function composeTtsSpeed({ voiceSpeed = 1.0, intentSpeed = 1.0, pace = 'natural' } = {}) {
|
|
54
|
+
const paceMult = resolvePaceSpeedMultiplier(pace);
|
|
55
|
+
const raw = voiceSpeed * intentSpeed * paceMult;
|
|
56
|
+
return Math.min(TRTC_SPEED_MAX, Math.max(TRTC_SPEED_MIN, raw));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function intentBaseMs(intent) {
|
|
60
|
+
if (!intent || typeof intent !== 'string') return DEFAULT_PAUSE_AFTER_MS;
|
|
61
|
+
return INTENT_PAUSE_AFTER_MS[intent] ?? DEFAULT_PAUSE_AFTER_MS;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Resolve the per-segment silence array (in ms) for `buildWav({ gapsMs })`.
|
|
66
|
+
*
|
|
67
|
+
* - If a segment has a numeric `pauseAfterMs` (stamped by backend), use it
|
|
68
|
+
* - Otherwise compute from `intent` + speaker-change boost
|
|
69
|
+
* - Apply pace multiplier last
|
|
70
|
+
* - Last segment's gap is ignored by buildWav (no trailing silence)
|
|
71
|
+
*
|
|
72
|
+
* @param {{intent?:string|null,speaker?:string|null,pauseAfterMs?:number|null}[]} segments
|
|
73
|
+
* @param {object} [options]
|
|
74
|
+
* @param {string} [options.pace='natural']
|
|
75
|
+
* @param {number} [options.fallbackSec] — when neither pauseAfterMs nor intent
|
|
76
|
+
* yields a value AND a uniform legacy override is in play, use this seconds value
|
|
77
|
+
* @returns {number[]} gapsMs aligned to segments[]
|
|
78
|
+
*/
|
|
79
|
+
function computeGapsMs(segments, options = {}) {
|
|
80
|
+
const multiplier = resolvePaceMultiplier(options.pace);
|
|
81
|
+
const fallbackMs = Number.isFinite(options.fallbackSec) ? options.fallbackSec * 1000 : null;
|
|
82
|
+
|
|
83
|
+
return segments.map((seg, i) => {
|
|
84
|
+
const next = segments[i + 1] || null;
|
|
85
|
+
if (!next) return 0;
|
|
86
|
+
|
|
87
|
+
let base;
|
|
88
|
+
if (Number.isFinite(seg?.pauseAfterMs)) {
|
|
89
|
+
base = seg.pauseAfterMs;
|
|
90
|
+
} else if (seg?.intent) {
|
|
91
|
+
base = intentBaseMs(seg.intent);
|
|
92
|
+
if ((seg?.speaker || null) !== (next?.speaker || null)) base += SPEAKER_CHANGE_BOOST_MS;
|
|
93
|
+
} else if (fallbackMs !== null) {
|
|
94
|
+
base = fallbackMs;
|
|
95
|
+
} else {
|
|
96
|
+
base = DEFAULT_PAUSE_AFTER_MS;
|
|
97
|
+
if ((seg?.speaker || null) !== (next?.speaker || null)) base += SPEAKER_CHANGE_BOOST_MS;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return Math.round(base * multiplier);
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
module.exports = {
|
|
105
|
+
INTENT_PAUSE_AFTER_MS,
|
|
106
|
+
DEFAULT_PAUSE_AFTER_MS,
|
|
107
|
+
SPEAKER_CHANGE_BOOST_MS,
|
|
108
|
+
PACE_MULTIPLIERS,
|
|
109
|
+
PACE_SPEED_MULTIPLIERS,
|
|
110
|
+
TRTC_SPEED_MIN,
|
|
111
|
+
TRTC_SPEED_MAX,
|
|
112
|
+
VALID_PACE,
|
|
113
|
+
resolvePaceMultiplier,
|
|
114
|
+
resolvePaceSpeedMultiplier,
|
|
115
|
+
composeTtsSpeed,
|
|
116
|
+
intentBaseMs,
|
|
117
|
+
computeGapsMs,
|
|
118
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* VoxFlow CLI — Terminal spinner utility
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Start a simple CLI spinner with a message.
|
|
7
|
+
* @param {string} message - Text to display before the spinner
|
|
8
|
+
* @returns {{ stop: (suffix: string) => void, update: (newMessage: string) => void }}
|
|
9
|
+
*/
|
|
10
|
+
function startSpinner(message) {
|
|
11
|
+
const frames = ['|', '/', '-', '\\'];
|
|
12
|
+
let i = 0;
|
|
13
|
+
let currentMessage = message;
|
|
14
|
+
process.stdout.write(message + ' ' + frames[0]);
|
|
15
|
+
const id = setInterval(() => {
|
|
16
|
+
i = (i + 1) % frames.length;
|
|
17
|
+
process.stdout.write('\b' + frames[i]);
|
|
18
|
+
}, 120);
|
|
19
|
+
return {
|
|
20
|
+
stop(suffix) {
|
|
21
|
+
clearInterval(id);
|
|
22
|
+
process.stdout.write('\b' + suffix + '\n');
|
|
23
|
+
},
|
|
24
|
+
update(newMessage) {
|
|
25
|
+
// Clear the current line and rewrite with new message + spinner frame
|
|
26
|
+
process.stdout.write('\r' + ' '.repeat(currentMessage.length + 4) + '\r');
|
|
27
|
+
currentMessage = newMessage;
|
|
28
|
+
process.stdout.write(newMessage + ' ' + frames[i]);
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
module.exports = { startSpinner };
|
package/lib/core/srt.js
ADDED
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* VoxFlow CLI — SRT subtitle parser and formatter
|
|
3
|
+
*
|
|
4
|
+
* Parses standard SRT files into Caption objects with millisecond timestamps.
|
|
5
|
+
* Supports extended [Speaker: name] tags for multi-speaker dubbing.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Parse an SRT timestamp string into milliseconds.
|
|
10
|
+
* @param {string} str - Timestamp in format "HH:MM:SS,mmm"
|
|
11
|
+
* @returns {number} Time in milliseconds
|
|
12
|
+
*/
|
|
13
|
+
function parseTimestamp(str) {
|
|
14
|
+
const match = str.trim().match(/^(\d{1,2}):(\d{2}):(\d{2})[,.](\d{3})$/);
|
|
15
|
+
if (!match) {
|
|
16
|
+
throw new Error(`Invalid SRT timestamp: "${str}"`);
|
|
17
|
+
}
|
|
18
|
+
const [, h, m, s, ms] = match;
|
|
19
|
+
return (
|
|
20
|
+
parseInt(h, 10) * 3600000 +
|
|
21
|
+
parseInt(m, 10) * 60000 +
|
|
22
|
+
parseInt(s, 10) * 1000 +
|
|
23
|
+
parseInt(ms, 10)
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Format milliseconds into an SRT timestamp string.
|
|
29
|
+
* @param {number} ms - Time in milliseconds
|
|
30
|
+
* @returns {string} Timestamp in format "HH:MM:SS,mmm"
|
|
31
|
+
*/
|
|
32
|
+
function formatTimestamp(ms) {
|
|
33
|
+
if (ms < 0) ms = 0;
|
|
34
|
+
const h = Math.floor(ms / 3600000);
|
|
35
|
+
ms %= 3600000;
|
|
36
|
+
const m = Math.floor(ms / 60000);
|
|
37
|
+
ms %= 60000;
|
|
38
|
+
const s = Math.floor(ms / 1000);
|
|
39
|
+
const remainder = ms % 1000;
|
|
40
|
+
return (
|
|
41
|
+
String(h).padStart(2, '0') + ':' +
|
|
42
|
+
String(m).padStart(2, '0') + ':' +
|
|
43
|
+
String(s).padStart(2, '0') + ',' +
|
|
44
|
+
String(remainder).padStart(3, '0')
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* @typedef {object} Caption
|
|
50
|
+
* @property {number} id - Sequence number (1-based)
|
|
51
|
+
* @property {number} startMs - Start time in milliseconds
|
|
52
|
+
* @property {number} endMs - End time in milliseconds
|
|
53
|
+
* @property {string} text - Subtitle text (may span multiple lines)
|
|
54
|
+
* @property {string} [speakerId] - Optional speaker name from [Speaker: xxx] tag
|
|
55
|
+
*/
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Parse SRT content into an array of Caption objects.
|
|
59
|
+
*
|
|
60
|
+
* Supports:
|
|
61
|
+
* - Standard SRT format (index, timestamp line, text, blank separator)
|
|
62
|
+
* - Extended [Speaker: name] tags in text lines
|
|
63
|
+
* - Both comma and dot as ms separator (00:00:01,000 or 00:00:01.000)
|
|
64
|
+
*
|
|
65
|
+
* @param {string} content - Raw SRT file content
|
|
66
|
+
* @returns {Caption[]} Parsed captions sorted by startMs
|
|
67
|
+
*/
|
|
68
|
+
function parseSrt(content) {
|
|
69
|
+
if (!content || content.trim().length === 0) {
|
|
70
|
+
return [];
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const captions = [];
|
|
74
|
+
// Normalize line endings and split into blocks by blank lines
|
|
75
|
+
const normalized = content.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
|
|
76
|
+
const blocks = normalized.split(/\n\s*\n/).filter((b) => b.trim().length > 0);
|
|
77
|
+
|
|
78
|
+
for (const block of blocks) {
|
|
79
|
+
const lines = block.trim().split('\n');
|
|
80
|
+
if (lines.length < 2) continue;
|
|
81
|
+
|
|
82
|
+
// Line 1: sequence number (may be missing or non-numeric in some SRT files)
|
|
83
|
+
let lineIdx = 0;
|
|
84
|
+
let id;
|
|
85
|
+
const firstLine = lines[0].trim();
|
|
86
|
+
|
|
87
|
+
// Check if first line is a number (sequence ID)
|
|
88
|
+
if (/^\d+$/.test(firstLine)) {
|
|
89
|
+
id = parseInt(firstLine, 10);
|
|
90
|
+
lineIdx = 1;
|
|
91
|
+
} else {
|
|
92
|
+
// First line might be the timestamp directly
|
|
93
|
+
id = captions.length + 1;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Line 2 (or 1): timestamp line "HH:MM:SS,mmm --> HH:MM:SS,mmm"
|
|
97
|
+
if (lineIdx >= lines.length) continue;
|
|
98
|
+
const tsLine = lines[lineIdx].trim();
|
|
99
|
+
const tsMatch = tsLine.match(
|
|
100
|
+
/^(\d{1,2}:\d{2}:\d{2}[,.]\d{3})\s*-->\s*(\d{1,2}:\d{2}:\d{2}[,.]\d{3})/
|
|
101
|
+
);
|
|
102
|
+
if (!tsMatch) continue; // skip blocks without valid timestamp
|
|
103
|
+
|
|
104
|
+
const startMs = parseTimestamp(tsMatch[1]);
|
|
105
|
+
const endMs = parseTimestamp(tsMatch[2]);
|
|
106
|
+
lineIdx++;
|
|
107
|
+
|
|
108
|
+
// Remaining lines: subtitle text
|
|
109
|
+
const textLines = lines.slice(lineIdx).filter((l) => l.trim().length > 0);
|
|
110
|
+
if (textLines.length === 0) continue;
|
|
111
|
+
|
|
112
|
+
const rawText = textLines.join('\n');
|
|
113
|
+
|
|
114
|
+
// Extract optional [Speaker: xxx] tag
|
|
115
|
+
let speakerId;
|
|
116
|
+
let text = rawText;
|
|
117
|
+
const speakerMatch = rawText.match(/^\[Speaker:\s*([^\]]+)\]\s*/i);
|
|
118
|
+
if (speakerMatch) {
|
|
119
|
+
speakerId = speakerMatch[1].trim();
|
|
120
|
+
text = rawText.slice(speakerMatch[0].length);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (text.trim().length === 0) continue;
|
|
124
|
+
|
|
125
|
+
captions.push({
|
|
126
|
+
id,
|
|
127
|
+
startMs,
|
|
128
|
+
endMs,
|
|
129
|
+
text: text.trim(),
|
|
130
|
+
...(speakerId ? { speakerId } : {}),
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Sort by startMs for safety
|
|
135
|
+
captions.sort((a, b) => a.startMs - b.startMs);
|
|
136
|
+
|
|
137
|
+
return captions;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Format an array of Caption objects back into SRT string.
|
|
142
|
+
*
|
|
143
|
+
* @param {Caption[]} captions - Array of caption objects
|
|
144
|
+
* @returns {string} Formatted SRT content
|
|
145
|
+
*/
|
|
146
|
+
function formatSrt(captions) {
|
|
147
|
+
return captions
|
|
148
|
+
.map((cap, i) => {
|
|
149
|
+
const id = cap.id || i + 1;
|
|
150
|
+
const start = formatTimestamp(cap.startMs);
|
|
151
|
+
const end = formatTimestamp(cap.endMs);
|
|
152
|
+
const speakerPrefix = cap.speakerId ? `[Speaker: ${cap.speakerId}] ` : '';
|
|
153
|
+
return `${id}\n${start} --> ${end}\n${speakerPrefix}${cap.text}`;
|
|
154
|
+
})
|
|
155
|
+
.join('\n\n') + '\n';
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// ─── ASR result → Caption[] converters ──────────────────────────────────────
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Convert Flash ASR result into Caption objects.
|
|
162
|
+
*
|
|
163
|
+
* Flash API returns an array of channel results, each containing a
|
|
164
|
+
* `sentence_list` with per-sentence start/end times and optional speaker_id.
|
|
165
|
+
*
|
|
166
|
+
* @param {Array} flashResult - flash_result array from /api/asr/flash
|
|
167
|
+
* @returns {Caption[]}
|
|
168
|
+
*/
|
|
169
|
+
function buildCaptionsFromFlash(flashResult) {
|
|
170
|
+
const captions = [];
|
|
171
|
+
let idx = 1;
|
|
172
|
+
|
|
173
|
+
for (const channel of flashResult) {
|
|
174
|
+
const sentences = channel.sentence_list || [];
|
|
175
|
+
for (const s of sentences) {
|
|
176
|
+
const cap = {
|
|
177
|
+
id: idx++,
|
|
178
|
+
startMs: s.start_time || 0,
|
|
179
|
+
endMs: s.end_time || 0,
|
|
180
|
+
text: (s.text || '').trim(),
|
|
181
|
+
};
|
|
182
|
+
if (s.speaker_id !== undefined && s.speaker_id !== null) {
|
|
183
|
+
cap.speakerId = `Speaker${s.speaker_id}`;
|
|
184
|
+
}
|
|
185
|
+
if (cap.text.length > 0) {
|
|
186
|
+
captions.push(cap);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return captions;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Convert Sentence ASR result into a single Caption.
|
|
196
|
+
*
|
|
197
|
+
* Sentence mode returns one text blob and optionally a wordList with
|
|
198
|
+
* per-word timestamps. If wordList is available, we can create more
|
|
199
|
+
* granular captions (one per sentence/segment), but for simplicity
|
|
200
|
+
* we produce a single caption spanning the full audio.
|
|
201
|
+
*
|
|
202
|
+
* @param {string} result - Recognition text
|
|
203
|
+
* @param {number} audioTime - Audio duration in seconds
|
|
204
|
+
* @param {Array} [wordList] - Optional word-level timestamps
|
|
205
|
+
* @returns {Caption[]}
|
|
206
|
+
*/
|
|
207
|
+
function buildCaptionsFromSentence(result, audioTime, wordList) {
|
|
208
|
+
if (!result || result.trim().length === 0) return [];
|
|
209
|
+
|
|
210
|
+
// If we have word-level data, try to split by natural pauses (>500ms gap)
|
|
211
|
+
if (wordList && wordList.length > 0) {
|
|
212
|
+
return buildCaptionsFromWordList(wordList, result);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Fallback: single caption for the full audio
|
|
216
|
+
return [
|
|
217
|
+
{
|
|
218
|
+
id: 1,
|
|
219
|
+
startMs: 0,
|
|
220
|
+
endMs: Math.round(audioTime * 1000),
|
|
221
|
+
text: result.trim(),
|
|
222
|
+
},
|
|
223
|
+
];
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Convert word-level timestamps into sentence-level captions.
|
|
228
|
+
*
|
|
229
|
+
* Splitting strategy (in priority order):
|
|
230
|
+
* 1. Natural pauses > 500ms between words
|
|
231
|
+
* 2. Sentence-ending punctuation (. ! ? etc.) when caption exceeds 5 seconds
|
|
232
|
+
* 3. Hard split at MAX_CAPTION_DURATION_MS (15s) on word boundaries
|
|
233
|
+
*
|
|
234
|
+
* Automatically detects whether to join words with spaces (alphabetic languages)
|
|
235
|
+
* or without (CJK languages).
|
|
236
|
+
*
|
|
237
|
+
* @param {Array} wordList - Array of {word, startTime, endTime} or {Word, StartTime, EndTime} (ms)
|
|
238
|
+
* @param {string} fullText - Full recognition text (fallback)
|
|
239
|
+
* @returns {Caption[]}
|
|
240
|
+
*/
|
|
241
|
+
function buildCaptionsFromWordList(wordList, fullText) {
|
|
242
|
+
if (!wordList || wordList.length === 0) {
|
|
243
|
+
return fullText
|
|
244
|
+
? [{ id: 1, startMs: 0, endMs: 0, text: fullText }]
|
|
245
|
+
: [];
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const PAUSE_THRESHOLD_MS = 500;
|
|
249
|
+
const MIN_DURATION_FOR_PUNCT_SPLIT_MS = 5000;
|
|
250
|
+
const MAX_CAPTION_DURATION_MS = 15000;
|
|
251
|
+
const SENTENCE_ENDERS = /[.!?。!?…]+$/;
|
|
252
|
+
|
|
253
|
+
// Support both casing: API returns {Word, StartTime, EndTime}, normalize here
|
|
254
|
+
const getWord = (w) => w.word || w.Word || '';
|
|
255
|
+
const getStart = (w) => w.startTime ?? w.StartTime ?? 0;
|
|
256
|
+
const getEnd = (w) => w.endTime ?? w.EndTime ?? 0;
|
|
257
|
+
|
|
258
|
+
// Detect if words need space separators (alphabetic vs CJK)
|
|
259
|
+
const sampleWords = wordList.slice(0, 10).map(getWord).join('');
|
|
260
|
+
const cjkChars = (sampleWords.match(/[\u4e00-\u9fff\u3040-\u309f\u30a0-\u30ff\uac00-\ud7af]/g) || []).length;
|
|
261
|
+
const needsSpaces = cjkChars < sampleWords.length * 0.3;
|
|
262
|
+
const joiner = needsSpaces ? ' ' : '';
|
|
263
|
+
|
|
264
|
+
const captions = [];
|
|
265
|
+
let currentWords = [];
|
|
266
|
+
let currentStart = getStart(wordList[0]);
|
|
267
|
+
let prevEnd = currentStart;
|
|
268
|
+
|
|
269
|
+
function flushCaption() {
|
|
270
|
+
if (currentWords.length === 0) return;
|
|
271
|
+
const text = currentWords.join(joiner).trim();
|
|
272
|
+
if (text.length > 0) {
|
|
273
|
+
captions.push({
|
|
274
|
+
id: captions.length + 1,
|
|
275
|
+
startMs: currentStart,
|
|
276
|
+
endMs: prevEnd,
|
|
277
|
+
text,
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
currentWords = [];
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
for (let i = 0; i < wordList.length; i++) {
|
|
284
|
+
const w = wordList[i];
|
|
285
|
+
const wordStart = getStart(w);
|
|
286
|
+
const wordEnd = getEnd(w);
|
|
287
|
+
const gap = wordStart - prevEnd;
|
|
288
|
+
const captionDuration = wordStart - currentStart;
|
|
289
|
+
|
|
290
|
+
// Split on natural pauses > 500ms
|
|
291
|
+
if (gap > PAUSE_THRESHOLD_MS && currentWords.length > 0) {
|
|
292
|
+
flushCaption();
|
|
293
|
+
currentStart = wordStart;
|
|
294
|
+
}
|
|
295
|
+
// Split on sentence-ending punctuation if caption is already > 5 seconds
|
|
296
|
+
else if (
|
|
297
|
+
currentWords.length > 0 &&
|
|
298
|
+
captionDuration > MIN_DURATION_FOR_PUNCT_SPLIT_MS &&
|
|
299
|
+
SENTENCE_ENDERS.test(currentWords[currentWords.length - 1])
|
|
300
|
+
) {
|
|
301
|
+
flushCaption();
|
|
302
|
+
currentStart = wordStart;
|
|
303
|
+
}
|
|
304
|
+
// Hard split if caption exceeds max duration
|
|
305
|
+
else if (
|
|
306
|
+
currentWords.length > 0 &&
|
|
307
|
+
captionDuration > MAX_CAPTION_DURATION_MS
|
|
308
|
+
) {
|
|
309
|
+
flushCaption();
|
|
310
|
+
currentStart = wordStart;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
currentWords.push(getWord(w));
|
|
314
|
+
prevEnd = wordEnd || prevEnd;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// Flush remaining
|
|
318
|
+
flushCaption();
|
|
319
|
+
|
|
320
|
+
return captions;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Convert File ASR (async) result into captions.
|
|
325
|
+
*
|
|
326
|
+
* The file mode Result field is a plain text string. If ResTextFormat was
|
|
327
|
+
* set to SRT (1), it may already be SRT formatted. Otherwise it is plain
|
|
328
|
+
* text that we wrap as a single caption.
|
|
329
|
+
*
|
|
330
|
+
* @param {string} result - Recognition result text
|
|
331
|
+
* @param {number} audioTime - Audio duration in seconds
|
|
332
|
+
* @returns {Caption[]}
|
|
333
|
+
*/
|
|
334
|
+
function buildCaptionsFromFile(result, audioTime) {
|
|
335
|
+
if (!result || result.trim().length === 0) return [];
|
|
336
|
+
|
|
337
|
+
// Check if the result is already SRT formatted
|
|
338
|
+
if (/^\d+\s*\n\d{2}:\d{2}:\d{2}[,.]\d{3}\s*-->/.test(result.trim())) {
|
|
339
|
+
return parseSrt(result);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// Plain text — wrap as a single caption
|
|
343
|
+
return [
|
|
344
|
+
{
|
|
345
|
+
id: 1,
|
|
346
|
+
startMs: 0,
|
|
347
|
+
endMs: Math.round(audioTime * 1000),
|
|
348
|
+
text: result.trim(),
|
|
349
|
+
},
|
|
350
|
+
];
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* Format captions as plain text (no timestamps).
|
|
355
|
+
*
|
|
356
|
+
* @param {Caption[]} captions
|
|
357
|
+
* @param {object} [opts]
|
|
358
|
+
* @param {boolean} [opts.includeSpeakers] - Prefix each line with speaker name
|
|
359
|
+
* @returns {string}
|
|
360
|
+
*/
|
|
361
|
+
function formatPlainText(captions, opts = {}) {
|
|
362
|
+
return captions
|
|
363
|
+
.map((cap) => {
|
|
364
|
+
const prefix =
|
|
365
|
+
opts.includeSpeakers && cap.speakerId
|
|
366
|
+
? `[${cap.speakerId}] `
|
|
367
|
+
: '';
|
|
368
|
+
return `${prefix}${cap.text}`;
|
|
369
|
+
})
|
|
370
|
+
.join('\n') + '\n';
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* Format captions as JSON (with full metadata).
|
|
375
|
+
*
|
|
376
|
+
* @param {Caption[]} captions
|
|
377
|
+
* @returns {string}
|
|
378
|
+
*/
|
|
379
|
+
function formatJson(captions) {
|
|
380
|
+
return JSON.stringify(captions, null, 2) + '\n';
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
module.exports = {
|
|
384
|
+
parseSrt,
|
|
385
|
+
formatSrt,
|
|
386
|
+
parseTimestamp,
|
|
387
|
+
formatTimestamp,
|
|
388
|
+
buildCaptionsFromFlash,
|
|
389
|
+
buildCaptionsFromSentence,
|
|
390
|
+
buildCaptionsFromWordList,
|
|
391
|
+
buildCaptionsFromFile,
|
|
392
|
+
formatPlainText,
|
|
393
|
+
formatJson,
|
|
394
|
+
};
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Anonymous, fire-and-forget event sink for the `voxflow` CLI.
|
|
5
|
+
*
|
|
6
|
+
* Used to answer one product question at a time without spinning up an
|
|
7
|
+
* analytics SDK. First event: `cli.slice.stage.launched` — feeds into the
|
|
8
|
+
* "should we build a Stage MCP?" decision (PR #3317 follow-up).
|
|
9
|
+
*
|
|
10
|
+
* Design rules — keep them, expanding scope is a separate review:
|
|
11
|
+
* • Anonymous. No JWT, no email, no userId. Random per-process session id
|
|
12
|
+
* so we can dedupe accidental duplicate events from a single launch.
|
|
13
|
+
* • Opt-out: VOXFLOW_TELEMETRY=0 OR DO_NOT_TRACK=1 disables. CI envs
|
|
14
|
+
* (CI=1, GITHUB_ACTIONS=true) auto-disable so test runs don't pollute.
|
|
15
|
+
* • Fail-silent: any network error, timeout, or thrown handler is
|
|
16
|
+
* swallowed. Telemetry MUST NEVER block a CLI command.
|
|
17
|
+
* • Allowlisted prop keys only — backend rejects unknown event names.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
const http = require('http');
|
|
21
|
+
const https = require('https');
|
|
22
|
+
const crypto = require('crypto');
|
|
23
|
+
|
|
24
|
+
const { API_BASE } = require('./config');
|
|
25
|
+
const { logger } = require('./logger');
|
|
26
|
+
|
|
27
|
+
let sessionId = null;
|
|
28
|
+
function getSessionId() {
|
|
29
|
+
if (!sessionId) sessionId = crypto.randomUUID();
|
|
30
|
+
return sessionId;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function isDisabled() {
|
|
34
|
+
if (process.env.VOXFLOW_TELEMETRY === '0' || process.env.VOXFLOW_TELEMETRY === 'false') return true;
|
|
35
|
+
if (process.env.DO_NOT_TRACK === '1' || process.env.DO_NOT_TRACK === 'true') return true;
|
|
36
|
+
if (process.env.CI || process.env.GITHUB_ACTIONS || process.env.NODE_ENV === 'test') return true;
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function readCliVersion() {
|
|
41
|
+
try {
|
|
42
|
+
return require('../../package.json').version || 'unknown';
|
|
43
|
+
} catch {
|
|
44
|
+
return 'unknown';
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Emit a telemetry event. Never throws; never returns a meaningful value
|
|
50
|
+
* to callers — it's intentionally fire-and-forget.
|
|
51
|
+
*
|
|
52
|
+
* @param {string} event Allowlisted event name (see backend/routes/cli-telemetry.js)
|
|
53
|
+
* @param {object} [props] Plain object; only theme / cli_version / node_version /
|
|
54
|
+
* platform / session_id keys are forwarded.
|
|
55
|
+
* @param {object} [opts]
|
|
56
|
+
* @param {string} [opts.apiBase] Override API base (used by tests)
|
|
57
|
+
*/
|
|
58
|
+
function emit(event, props = {}, opts = {}) {
|
|
59
|
+
if (isDisabled()) return;
|
|
60
|
+
|
|
61
|
+
const apiBase = opts.apiBase || API_BASE;
|
|
62
|
+
let url;
|
|
63
|
+
try {
|
|
64
|
+
url = new URL('/api/cli-telemetry', apiBase);
|
|
65
|
+
} catch {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const payload = JSON.stringify({
|
|
70
|
+
event,
|
|
71
|
+
props: {
|
|
72
|
+
cli_version: readCliVersion(),
|
|
73
|
+
node_version: process.version,
|
|
74
|
+
platform: process.platform,
|
|
75
|
+
session_id: getSessionId(),
|
|
76
|
+
...props
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
const mod = url.protocol === 'https:' ? https : http;
|
|
81
|
+
let req;
|
|
82
|
+
try {
|
|
83
|
+
req = mod.request(url, {
|
|
84
|
+
method: 'POST',
|
|
85
|
+
headers: {
|
|
86
|
+
'Content-Type': 'application/json',
|
|
87
|
+
'Content-Length': Buffer.byteLength(payload)
|
|
88
|
+
}
|
|
89
|
+
}, (res) => { res.resume(); });
|
|
90
|
+
} catch (err) {
|
|
91
|
+
logger.debug({ err: err.message }, '[telemetry] request build failed');
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
req.on('error', (err) => logger.debug({ err: err.message }, '[telemetry] network error'));
|
|
95
|
+
req.setTimeout(1500, () => req.destroy());
|
|
96
|
+
req.write(payload);
|
|
97
|
+
req.end();
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
module.exports = { emit, _isDisabled: isDisabled, _getSessionId: getSessionId };
|