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,448 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* VoxFlow CLI — ASR (Automatic Speech Recognition) API client
|
|
3
|
+
*
|
|
4
|
+
* Wraps the three backend ASR endpoints:
|
|
5
|
+
* - POST /api/asr/sentence (≤60 s, sync, base64 or URL)
|
|
6
|
+
* - POST /api/asr/flash (≤2 h, sync, URL only)
|
|
7
|
+
* - POST /api/asr/file (≤5 h, async → poll for result)
|
|
8
|
+
*
|
|
9
|
+
* Provides automatic mode selection based on audio duration.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const fs = require('fs');
|
|
13
|
+
const { request, throwApiError, throwNetworkError, ApiError } = require('./http');
|
|
14
|
+
|
|
15
|
+
// ─── Constants ──────────────────────────────────────────────────────────────
|
|
16
|
+
|
|
17
|
+
/** Upper duration limit for sentence recognition (ms) */
|
|
18
|
+
const SENTENCE_MAX_MS = 60_000;
|
|
19
|
+
|
|
20
|
+
/** Upper duration limit for flash recognition (ms) */
|
|
21
|
+
const FLASH_MAX_MS = 7_200_000;
|
|
22
|
+
|
|
23
|
+
/** Maximum file size for base64 upload (bytes). Above this, use COS URL. */
|
|
24
|
+
const BASE64_MAX_BYTES = 5 * 1024 * 1024;
|
|
25
|
+
|
|
26
|
+
/** Default polling interval for async file tasks (ms) */
|
|
27
|
+
const POLL_INTERVAL_MS = 3_000;
|
|
28
|
+
|
|
29
|
+
/** Maximum polling duration before giving up (ms) — 5 minutes */
|
|
30
|
+
const POLL_TIMEOUT_MS = 300_000;
|
|
31
|
+
|
|
32
|
+
// Task status codes returned by DescribeTaskStatus
|
|
33
|
+
const TASK_STATUS = {
|
|
34
|
+
WAITING: 0,
|
|
35
|
+
PROCESSING: 1,
|
|
36
|
+
SUCCESS: 2,
|
|
37
|
+
FAILED: 3,
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// ─── Mode detection ─────────────────────────────────────────────────────────
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Choose the best ASR mode based on audio duration and input type.
|
|
44
|
+
*
|
|
45
|
+
* @param {number} durationMs - Audio duration in milliseconds
|
|
46
|
+
* @param {boolean} hasUrl - Whether a URL is available (COS or remote)
|
|
47
|
+
* @param {number} fileSize - File size in bytes
|
|
48
|
+
* @returns {'sentence'|'flash'|'file'}
|
|
49
|
+
*/
|
|
50
|
+
function detectMode(durationMs, hasUrl, fileSize) {
|
|
51
|
+
if (durationMs <= SENTENCE_MAX_MS && fileSize <= BASE64_MAX_BYTES) {
|
|
52
|
+
return 'sentence';
|
|
53
|
+
}
|
|
54
|
+
if (durationMs <= FLASH_MAX_MS && hasUrl) {
|
|
55
|
+
return 'flash';
|
|
56
|
+
}
|
|
57
|
+
return 'file';
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// ─── Request helpers ────────────────────────────────────────────────────────
|
|
61
|
+
|
|
62
|
+
function authHeaders(token) {
|
|
63
|
+
return {
|
|
64
|
+
'Content-Type': 'application/json',
|
|
65
|
+
Authorization: `Bearer ${token}`,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// ─── Sentence recognition ───────────────────────────────────────────────────
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Recognize audio ≤ 60 s using SentenceRecognition (sync).
|
|
73
|
+
*
|
|
74
|
+
* @param {object} opts
|
|
75
|
+
* @param {string} opts.apiBase
|
|
76
|
+
* @param {string} opts.token
|
|
77
|
+
* @param {string} [opts.url] - COS / remote URL
|
|
78
|
+
* @param {string} [opts.filePath] - Local file (will be base64-encoded)
|
|
79
|
+
* @param {string} [opts.lang] - Engine model, e.g. '16k_zh' (default)
|
|
80
|
+
* @param {boolean}[opts.wordInfo] - Return word-level timestamps
|
|
81
|
+
* @returns {Promise<{result: string, audioTime: number, wordList?: Array, requestId: string, quota: object}>}
|
|
82
|
+
*/
|
|
83
|
+
async function recognizeSentence(opts) {
|
|
84
|
+
const { apiBase, token, url, filePath, lang = '16k_zh', wordInfo = false } = opts;
|
|
85
|
+
|
|
86
|
+
const body = {
|
|
87
|
+
EngSerViceType: lang, // Note: backend uses this exact (mis)spelling
|
|
88
|
+
VoiceFormat: 'wav',
|
|
89
|
+
SubServiceType: 2,
|
|
90
|
+
WordInfo: wordInfo ? 1 : 0,
|
|
91
|
+
ConvertNumMode: 1,
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
if (url) {
|
|
95
|
+
body.Url = url;
|
|
96
|
+
body.SourceType = 0;
|
|
97
|
+
} else if (filePath) {
|
|
98
|
+
const buf = fs.readFileSync(filePath);
|
|
99
|
+
body.Data = buf.toString('base64');
|
|
100
|
+
body.DataLen = buf.length;
|
|
101
|
+
body.SourceType = 1;
|
|
102
|
+
} else {
|
|
103
|
+
throw new Error('Either url or filePath is required for sentence recognition');
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
try {
|
|
107
|
+
const { status, data } = await request(
|
|
108
|
+
`${apiBase}/api/asr/sentence`,
|
|
109
|
+
{ method: 'POST', headers: authHeaders(token) },
|
|
110
|
+
body
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
if (status !== 200 || data.code !== 'success') {
|
|
114
|
+
throwApiError(status, data, 'ASR sentence');
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return {
|
|
118
|
+
result: data.result,
|
|
119
|
+
audioTime: data.audioTime,
|
|
120
|
+
wordList: data.wordList || [],
|
|
121
|
+
requestId: data.requestId,
|
|
122
|
+
quota: data.quota,
|
|
123
|
+
};
|
|
124
|
+
} catch (err) {
|
|
125
|
+
if (err instanceof ApiError) throw err;
|
|
126
|
+
throwNetworkError(err, apiBase);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// ─── Flash recognition ──────────────────────────────────────────────────────
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Recognize audio ≤ 2 h using Flash API (sync, URL only).
|
|
134
|
+
*
|
|
135
|
+
* @param {object} opts
|
|
136
|
+
* @param {string} opts.apiBase
|
|
137
|
+
* @param {string} opts.token
|
|
138
|
+
* @param {string} opts.url - Audio URL (required)
|
|
139
|
+
* @param {string} [opts.lang] - Engine type, e.g. '16k_zh'
|
|
140
|
+
* @param {boolean}[opts.speakerDiarization] - Enable speaker separation
|
|
141
|
+
* @param {number} [opts.speakerNumber] - Expected speaker count
|
|
142
|
+
* @returns {Promise<{flashResult: Array, audioDuration: number, requestId: string, quota: object}>}
|
|
143
|
+
*/
|
|
144
|
+
async function recognizeFlash(opts) {
|
|
145
|
+
const {
|
|
146
|
+
apiBase, token, url, lang = '16k_zh',
|
|
147
|
+
speakerDiarization = false, speakerNumber = 0,
|
|
148
|
+
} = opts;
|
|
149
|
+
|
|
150
|
+
if (!url) {
|
|
151
|
+
throw new Error('Flash recognition requires a URL (cannot use base64 data)');
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const body = {
|
|
155
|
+
engine_type: lang,
|
|
156
|
+
voice_format: 'wav',
|
|
157
|
+
url,
|
|
158
|
+
speaker_diarization: speakerDiarization ? 1 : 0,
|
|
159
|
+
speaker_number: speakerNumber,
|
|
160
|
+
filter_dirty: 0,
|
|
161
|
+
filter_modal: 0,
|
|
162
|
+
filter_punc: 0,
|
|
163
|
+
convert_num_mode: 1,
|
|
164
|
+
word_info: 1,
|
|
165
|
+
first_channel_only: 1,
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
try {
|
|
169
|
+
const { status, data } = await request(
|
|
170
|
+
`${apiBase}/api/asr/flash`,
|
|
171
|
+
{ method: 'POST', headers: authHeaders(token) },
|
|
172
|
+
body
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
if (status !== 200 || data.code !== 'success') {
|
|
176
|
+
throwApiError(status, data, 'ASR flash');
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return {
|
|
180
|
+
flashResult: data.flash_result || [],
|
|
181
|
+
audioDuration: data.audio_duration || 0,
|
|
182
|
+
requestId: data.request_id,
|
|
183
|
+
quota: data.quota,
|
|
184
|
+
};
|
|
185
|
+
} catch (err) {
|
|
186
|
+
if (err instanceof ApiError) throw err;
|
|
187
|
+
throwNetworkError(err, apiBase);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// ─── File recognition (async) ───────────────────────────────────────────────
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Submit a long audio file for async recognition (CreateRecTask).
|
|
195
|
+
*
|
|
196
|
+
* @param {object} opts
|
|
197
|
+
* @param {string} opts.apiBase
|
|
198
|
+
* @param {string} opts.token
|
|
199
|
+
* @param {string} [opts.url]
|
|
200
|
+
* @param {string} [opts.filePath] - Local file (base64, ≤5 MB only)
|
|
201
|
+
* @param {string} [opts.lang] - e.g. '16k_zh'
|
|
202
|
+
* @param {boolean}[opts.speakerDiarization]
|
|
203
|
+
* @param {number} [opts.speakerNumber]
|
|
204
|
+
* @returns {Promise<{taskId: number, requestId: string, quota: object}>}
|
|
205
|
+
*/
|
|
206
|
+
async function submitFileTask(opts) {
|
|
207
|
+
const {
|
|
208
|
+
apiBase, token, url, filePath, lang = '16k_zh',
|
|
209
|
+
speakerDiarization = false, speakerNumber = 0,
|
|
210
|
+
} = opts;
|
|
211
|
+
|
|
212
|
+
const body = {
|
|
213
|
+
EngineModelType: lang,
|
|
214
|
+
ChannelNum: 1,
|
|
215
|
+
ResTextFormat: 0,
|
|
216
|
+
FilterDirty: 0,
|
|
217
|
+
FilterModal: 0,
|
|
218
|
+
FilterPunc: 0,
|
|
219
|
+
ConvertNumMode: 1,
|
|
220
|
+
SpeakerDiarization: speakerDiarization ? 1 : 0,
|
|
221
|
+
SpeakerNumber: speakerNumber,
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
if (url) {
|
|
225
|
+
body.Url = url;
|
|
226
|
+
body.SourceType = 0;
|
|
227
|
+
} else if (filePath) {
|
|
228
|
+
const buf = fs.readFileSync(filePath);
|
|
229
|
+
if (buf.length > BASE64_MAX_BYTES) {
|
|
230
|
+
throw new Error(
|
|
231
|
+
`File too large for base64 upload (${(buf.length / 1024 / 1024).toFixed(1)} MB). ` +
|
|
232
|
+
'Upload to COS first or use flash mode with a URL.'
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
body.Data = buf.toString('base64');
|
|
236
|
+
body.DataLen = buf.length;
|
|
237
|
+
body.SourceType = 1;
|
|
238
|
+
} else {
|
|
239
|
+
throw new Error('Either url or filePath is required for file recognition');
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
try {
|
|
243
|
+
const { status, data } = await request(
|
|
244
|
+
`${apiBase}/api/asr/file`,
|
|
245
|
+
{ method: 'POST', headers: authHeaders(token) },
|
|
246
|
+
body
|
|
247
|
+
);
|
|
248
|
+
|
|
249
|
+
if (status !== 200 || data.code !== 'success') {
|
|
250
|
+
throwApiError(status, data, 'ASR file submit');
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
return {
|
|
254
|
+
taskId: data.taskId,
|
|
255
|
+
requestId: data.requestId,
|
|
256
|
+
quota: data.quota,
|
|
257
|
+
};
|
|
258
|
+
} catch (err) {
|
|
259
|
+
if (err instanceof ApiError) throw err;
|
|
260
|
+
throwNetworkError(err, apiBase);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Poll a file recognition task until it completes or fails.
|
|
266
|
+
*
|
|
267
|
+
* @param {object} opts
|
|
268
|
+
* @param {string} opts.apiBase
|
|
269
|
+
* @param {string} opts.token
|
|
270
|
+
* @param {number} opts.taskId
|
|
271
|
+
* @param {number} [opts.pollIntervalMs] - ms between polls (default 3000)
|
|
272
|
+
* @param {number} [opts.pollTimeoutMs] - max wait (default 300000)
|
|
273
|
+
* @param {function} [opts.onProgress] - callback(status, elapsed) for UI updates
|
|
274
|
+
* @returns {Promise<{result: string, audioTime: number, status: number}>}
|
|
275
|
+
*/
|
|
276
|
+
async function pollTaskResult(opts) {
|
|
277
|
+
const {
|
|
278
|
+
apiBase, token, taskId,
|
|
279
|
+
pollIntervalMs = POLL_INTERVAL_MS,
|
|
280
|
+
pollTimeoutMs = POLL_TIMEOUT_MS,
|
|
281
|
+
onProgress,
|
|
282
|
+
} = opts;
|
|
283
|
+
|
|
284
|
+
const started = Date.now();
|
|
285
|
+
|
|
286
|
+
// eslint-disable-next-line no-constant-condition -- task-poll loop, exits via throw/return
|
|
287
|
+
while (true) {
|
|
288
|
+
const elapsed = Date.now() - started;
|
|
289
|
+
if (elapsed > pollTimeoutMs) {
|
|
290
|
+
throw new Error(
|
|
291
|
+
`ASR task ${taskId} timed out after ${Math.round(elapsed / 1000)}s. ` +
|
|
292
|
+
'The task may still complete — check later with: voxflow asr --task-id ' + taskId
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
try {
|
|
297
|
+
const { status, data } = await request(
|
|
298
|
+
`${apiBase}/api/asr/result/${taskId}`,
|
|
299
|
+
{ method: 'GET', headers: authHeaders(token) }
|
|
300
|
+
);
|
|
301
|
+
|
|
302
|
+
if (status !== 200 || data.code !== 'success') {
|
|
303
|
+
throwApiError(status, data, 'ASR poll');
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
const taskData = data.data;
|
|
307
|
+
const taskStatus = taskData.Status;
|
|
308
|
+
|
|
309
|
+
if (onProgress) onProgress(taskStatus, elapsed);
|
|
310
|
+
|
|
311
|
+
if (taskStatus === TASK_STATUS.SUCCESS) {
|
|
312
|
+
return {
|
|
313
|
+
result: taskData.Result,
|
|
314
|
+
audioTime: taskData.AudioTime,
|
|
315
|
+
status: taskStatus,
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
if (taskStatus === TASK_STATUS.FAILED) {
|
|
320
|
+
throw new Error(
|
|
321
|
+
`ASR task ${taskId} failed: ${taskData.Result || 'Unknown error'}`
|
|
322
|
+
);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// Still waiting or processing — sleep then retry
|
|
326
|
+
} catch (err) {
|
|
327
|
+
if (err instanceof ApiError) throw err;
|
|
328
|
+
// Transient network errors during polling — log and retry
|
|
329
|
+
if (elapsed + pollIntervalMs < pollTimeoutMs) {
|
|
330
|
+
// swallow and retry
|
|
331
|
+
} else {
|
|
332
|
+
throwNetworkError(err, apiBase);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
await sleep(pollIntervalMs);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// ─── Unified recognize function ─────────────────────────────────────────────
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* High-level: recognize audio using the best available mode.
|
|
344
|
+
*
|
|
345
|
+
* @param {object} opts
|
|
346
|
+
* @param {string} opts.apiBase
|
|
347
|
+
* @param {string} opts.token
|
|
348
|
+
* @param {string} opts.mode - 'auto', 'sentence', 'flash', 'file'
|
|
349
|
+
* @param {string} [opts.url] - Remote or COS URL
|
|
350
|
+
* @param {string} [opts.filePath] - Local WAV file path
|
|
351
|
+
* @param {number} opts.durationMs - Audio duration in ms
|
|
352
|
+
* @param {number} [opts.fileSize] - File size in bytes
|
|
353
|
+
* @param {string} [opts.lang] - Engine model (default '16k_zh')
|
|
354
|
+
* @param {boolean} [opts.speakerDiarization]
|
|
355
|
+
* @param {number} [opts.speakerNumber]
|
|
356
|
+
* @param {boolean} [opts.wordInfo]
|
|
357
|
+
* @param {function}[opts.onProgress] - Polling progress callback
|
|
358
|
+
* @returns {Promise<{mode: string, result: string, flashResult?: Array, audioTime: number, wordList?: Array, quota: object}>}
|
|
359
|
+
*/
|
|
360
|
+
async function recognize(opts) {
|
|
361
|
+
const {
|
|
362
|
+
mode: requestedMode = 'auto',
|
|
363
|
+
url, durationMs, fileSize = 0,
|
|
364
|
+
} = opts;
|
|
365
|
+
|
|
366
|
+
// Determine actual mode
|
|
367
|
+
const hasUrl = !!url;
|
|
368
|
+
const actualMode = requestedMode === 'auto'
|
|
369
|
+
? detectMode(durationMs, hasUrl, fileSize)
|
|
370
|
+
: requestedMode;
|
|
371
|
+
|
|
372
|
+
switch (actualMode) {
|
|
373
|
+
case 'sentence': {
|
|
374
|
+
const res = await recognizeSentence(opts);
|
|
375
|
+
return {
|
|
376
|
+
mode: 'sentence',
|
|
377
|
+
result: res.result,
|
|
378
|
+
audioTime: res.audioTime,
|
|
379
|
+
wordList: res.wordList,
|
|
380
|
+
quota: res.quota,
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
case 'flash': {
|
|
385
|
+
if (!url) {
|
|
386
|
+
throw new Error(
|
|
387
|
+
'Flash mode requires a URL. Upload the file to COS first, or use --mode auto.'
|
|
388
|
+
);
|
|
389
|
+
}
|
|
390
|
+
const res = await recognizeFlash(opts);
|
|
391
|
+
// Flatten flash segments into a single result text
|
|
392
|
+
const fullText = (res.flashResult || [])
|
|
393
|
+
.flatMap((seg) =>
|
|
394
|
+
seg.sentence_list
|
|
395
|
+
? seg.sentence_list.map((s) => s.text)
|
|
396
|
+
: [seg.text]
|
|
397
|
+
)
|
|
398
|
+
.join('');
|
|
399
|
+
return {
|
|
400
|
+
mode: 'flash',
|
|
401
|
+
result: fullText,
|
|
402
|
+
flashResult: res.flashResult,
|
|
403
|
+
audioDuration: res.audioDuration,
|
|
404
|
+
audioTime: (res.audioDuration || 0) / 1000,
|
|
405
|
+
quota: res.quota,
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
case 'file': {
|
|
410
|
+
const submitRes = await submitFileTask(opts);
|
|
411
|
+
const pollRes = await pollTaskResult({
|
|
412
|
+
apiBase: opts.apiBase,
|
|
413
|
+
token: opts.token,
|
|
414
|
+
taskId: submitRes.taskId,
|
|
415
|
+
onProgress: opts.onProgress,
|
|
416
|
+
});
|
|
417
|
+
return {
|
|
418
|
+
mode: 'file',
|
|
419
|
+
result: pollRes.result,
|
|
420
|
+
audioTime: pollRes.audioTime,
|
|
421
|
+
taskId: submitRes.taskId,
|
|
422
|
+
quota: submitRes.quota,
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
default:
|
|
427
|
+
throw new Error(`Unknown ASR mode: ${actualMode}. Use: auto, sentence, flash, or file`);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// ─── Utility ────────────────────────────────────────────────────────────────
|
|
432
|
+
|
|
433
|
+
function sleep(ms) {
|
|
434
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
module.exports = {
|
|
438
|
+
recognize,
|
|
439
|
+
recognizeSentence,
|
|
440
|
+
recognizeFlash,
|
|
441
|
+
submitFileTask,
|
|
442
|
+
pollTaskResult,
|
|
443
|
+
detectMode,
|
|
444
|
+
SENTENCE_MAX_MS,
|
|
445
|
+
FLASH_MAX_MS,
|
|
446
|
+
BASE64_MAX_BYTES,
|
|
447
|
+
TASK_STATUS,
|
|
448
|
+
};
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* VoxFlow CLI — HTTP client for /api/asr/jobs/*.
|
|
3
|
+
*
|
|
4
|
+
* Thin wrappers over `request()` — they unwrap the standard
|
|
5
|
+
* `{ code: 'success', data: { ... } }` envelope and surface non-2xx as
|
|
6
|
+
* ApiError so callers can match on `err.code`/`err.status`.
|
|
7
|
+
*
|
|
8
|
+
* The backend lazy-polls Azure on every GET /api/asr/jobs/:id, so the CLI
|
|
9
|
+
* just calls getJob in a backoff loop without needing a separate "sync" RPC.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const { request, throwApiError, throwNetworkError } = require('./http');
|
|
13
|
+
|
|
14
|
+
function authHeaders(token) {
|
|
15
|
+
return {
|
|
16
|
+
'Content-Type': 'application/json',
|
|
17
|
+
Authorization: `Bearer ${token}`
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async function call(apiBase, method, path, token, body) {
|
|
22
|
+
let res;
|
|
23
|
+
try {
|
|
24
|
+
res = await request(`${apiBase}${path}`, {
|
|
25
|
+
method,
|
|
26
|
+
headers: authHeaders(token)
|
|
27
|
+
}, body);
|
|
28
|
+
} catch (err) {
|
|
29
|
+
throwNetworkError(err, apiBase);
|
|
30
|
+
}
|
|
31
|
+
if (res.status < 200 || res.status >= 300) {
|
|
32
|
+
throwApiError(res.status, res.data, `ASR jobs ${method} ${path}`);
|
|
33
|
+
}
|
|
34
|
+
return res.data?.data || res.data;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Create a new ASR job. Charges quota and submits to Azure server-side.
|
|
39
|
+
*
|
|
40
|
+
* @param {object} args
|
|
41
|
+
* @param {string} args.apiBase
|
|
42
|
+
* @param {string} args.token
|
|
43
|
+
* @param {string} args.sourceKey R2 key returned by uploadToR2()
|
|
44
|
+
* @param {string} [args.sourceFilename]
|
|
45
|
+
* @param {number} [args.sourceSize]
|
|
46
|
+
* @param {number} args.durationSec used to compute quota cost server-side
|
|
47
|
+
* @param {string} [args.language='auto']
|
|
48
|
+
* @param {object} [args.options] { wordTimestamps?, diarize?, speakerCount?, candidateLocales? }
|
|
49
|
+
*/
|
|
50
|
+
async function createJob({
|
|
51
|
+
apiBase, token, sourceKey, sourceFilename, sourceSize,
|
|
52
|
+
durationSec, language = 'auto', options = {}
|
|
53
|
+
}) {
|
|
54
|
+
const data = await call(apiBase, 'POST', '/api/asr/jobs', token, {
|
|
55
|
+
sourceKey,
|
|
56
|
+
sourceFilename,
|
|
57
|
+
sourceSize,
|
|
58
|
+
durationSec,
|
|
59
|
+
language,
|
|
60
|
+
options
|
|
61
|
+
});
|
|
62
|
+
return data.job;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async function getJob(apiBase, token, jobId) {
|
|
66
|
+
const data = await call(apiBase, 'GET', `/api/asr/jobs/${encodeURIComponent(jobId)}`, token);
|
|
67
|
+
return data.job;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async function listJobs(apiBase, token, { limit = 20, before } = {}) {
|
|
71
|
+
const params = new URLSearchParams();
|
|
72
|
+
if (limit) params.set('limit', String(limit));
|
|
73
|
+
if (before) params.set('before', before);
|
|
74
|
+
const qs = params.toString();
|
|
75
|
+
const data = await call(apiBase, 'GET', `/api/asr/jobs${qs ? `?${qs}` : ''}`, token);
|
|
76
|
+
return data.jobs || [];
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async function cancelJob(apiBase, token, jobId) {
|
|
80
|
+
const data = await call(apiBase, 'DELETE', `/api/asr/jobs/${encodeURIComponent(jobId)}`, token);
|
|
81
|
+
return data.job;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const TERMINAL_STATUSES = new Set(['succeeded', 'failed', 'cancelled']);
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Poll the backend until the job hits a terminal state (or `signal` aborts).
|
|
88
|
+
*
|
|
89
|
+
* Backoff schedule keeps the prompt cache warm for short jobs and slows down
|
|
90
|
+
* for long ones: 2s → 5s → 10s → 30s capped.
|
|
91
|
+
*
|
|
92
|
+
* @param {object} args
|
|
93
|
+
* @param {string} args.apiBase
|
|
94
|
+
* @param {string} args.token
|
|
95
|
+
* @param {string} args.jobId
|
|
96
|
+
* @param {function} [args.onProgress] (job) => void called every poll
|
|
97
|
+
* @param {AbortSignal} [args.signal]
|
|
98
|
+
* @returns {Promise<object>} terminal job row
|
|
99
|
+
*/
|
|
100
|
+
async function waitForTerminal({ apiBase, token, jobId, onProgress, signal }) {
|
|
101
|
+
const intervals = [2000, 5000, 10000, 30000];
|
|
102
|
+
let i = 0;
|
|
103
|
+
while (true) {
|
|
104
|
+
if (signal?.aborted) {
|
|
105
|
+
const e = new Error('Aborted');
|
|
106
|
+
e.code = 'aborted';
|
|
107
|
+
throw e;
|
|
108
|
+
}
|
|
109
|
+
const job = await getJob(apiBase, token, jobId);
|
|
110
|
+
if (onProgress) onProgress(job);
|
|
111
|
+
if (TERMINAL_STATUSES.has(job.status)) return job;
|
|
112
|
+
const delay = intervals[Math.min(i, intervals.length - 1)];
|
|
113
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
114
|
+
i++;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
module.exports = {
|
|
119
|
+
createJob,
|
|
120
|
+
getJob,
|
|
121
|
+
listJobs,
|
|
122
|
+
cancelJob,
|
|
123
|
+
waitForTerminal,
|
|
124
|
+
TERMINAL_STATUSES,
|
|
125
|
+
__internals: { call }
|
|
126
|
+
};
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* VoxFlow CLI — Local persistence for in-flight ASR jobs.
|
|
3
|
+
*
|
|
4
|
+
* Why: ASR jobs run minutes-to-hours server-side. If the user closes the
|
|
5
|
+
* terminal or hits Ctrl+C, the server-side job keeps going (Azure doesn't
|
|
6
|
+
* care that we walked away). This store keeps a tiny pointer file per job
|
|
7
|
+
* so the next `voxflow asr` invocation can find and resume them.
|
|
8
|
+
*
|
|
9
|
+
* Layout:
|
|
10
|
+
* ~/.config/voxflow/jobs/asr-<jobId>.json
|
|
11
|
+
*
|
|
12
|
+
* Schema (kept minimal — backend is the source of truth):
|
|
13
|
+
* {
|
|
14
|
+
* jobId, createdAt, status, source: { path, filename, durationSec },
|
|
15
|
+
* language, options, lastPolledAt
|
|
16
|
+
* }
|
|
17
|
+
*
|
|
18
|
+
* The file is rewritten on every poll so a stale `status` field always
|
|
19
|
+
* matches the last network observation. Cancel/cleanup deletes the file.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
const fs = require('fs');
|
|
23
|
+
const path = require('path');
|
|
24
|
+
const { getConfigDir } = require('./config');
|
|
25
|
+
|
|
26
|
+
function getJobsDir() {
|
|
27
|
+
const dir = path.join(getConfigDir(), 'jobs');
|
|
28
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
29
|
+
return dir;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function jobFilePath(jobId) {
|
|
33
|
+
// jobId is a UUID from the backend — safe to interpolate, but defend
|
|
34
|
+
// against future format changes by stripping anything path-unsafe.
|
|
35
|
+
const safe = String(jobId).replace(/[^A-Za-z0-9._-]/g, '_');
|
|
36
|
+
return path.join(getJobsDir(), `asr-${safe}.json`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function saveJob(jobId, record) {
|
|
40
|
+
const file = jobFilePath(jobId);
|
|
41
|
+
const payload = JSON.stringify({
|
|
42
|
+
jobId,
|
|
43
|
+
...record,
|
|
44
|
+
lastPolledAt: new Date().toISOString()
|
|
45
|
+
}, null, 2);
|
|
46
|
+
// atomic write: tmp + rename so a Ctrl+C mid-write doesn't corrupt.
|
|
47
|
+
const tmp = `${file}.tmp`;
|
|
48
|
+
fs.writeFileSync(tmp, payload);
|
|
49
|
+
fs.renameSync(tmp, file);
|
|
50
|
+
return file;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function loadJob(jobId) {
|
|
54
|
+
const file = jobFilePath(jobId);
|
|
55
|
+
if (!fs.existsSync(file)) return null;
|
|
56
|
+
try {
|
|
57
|
+
return JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
58
|
+
} catch {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function listJobs() {
|
|
64
|
+
const dir = getJobsDir();
|
|
65
|
+
const files = fs.readdirSync(dir).filter((f) => f.startsWith('asr-') && f.endsWith('.json'));
|
|
66
|
+
return files
|
|
67
|
+
.map((f) => {
|
|
68
|
+
try { return JSON.parse(fs.readFileSync(path.join(dir, f), 'utf8')); }
|
|
69
|
+
catch { return null; }
|
|
70
|
+
})
|
|
71
|
+
.filter(Boolean)
|
|
72
|
+
.sort((a, b) => (b.createdAt || '').localeCompare(a.createdAt || ''));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function deleteJob(jobId) {
|
|
76
|
+
const file = jobFilePath(jobId);
|
|
77
|
+
try { fs.unlinkSync(file); return true; } catch { return false; }
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Prune jobs that have been in a terminal state for more than `maxAgeDays`.
|
|
81
|
+
// Called opportunistically by the asr command so the dir doesn't grow forever.
|
|
82
|
+
function pruneTerminal(maxAgeDays = 30) {
|
|
83
|
+
const cutoff = Date.now() - maxAgeDays * 24 * 60 * 60 * 1000;
|
|
84
|
+
let removed = 0;
|
|
85
|
+
for (const job of listJobs()) {
|
|
86
|
+
if (!['succeeded', 'failed', 'cancelled'].includes(job.status)) continue;
|
|
87
|
+
const ts = Date.parse(job.lastPolledAt || job.createdAt || 0);
|
|
88
|
+
if (Number.isFinite(ts) && ts < cutoff) {
|
|
89
|
+
deleteJob(job.jobId);
|
|
90
|
+
removed++;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return removed;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
module.exports = {
|
|
97
|
+
saveJob,
|
|
98
|
+
loadJob,
|
|
99
|
+
listJobs,
|
|
100
|
+
deleteJob,
|
|
101
|
+
pruneTerminal,
|
|
102
|
+
getJobsDir,
|
|
103
|
+
// Test helpers
|
|
104
|
+
__internals: { jobFilePath }
|
|
105
|
+
};
|