voxflow 1.14.0 → 1.15.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +69 -2
- package/bin/voxflow.js +27 -0
- package/dist/index.js +1 -1
- package/dist/remotion-bundle/02a2fb2eb80bc7bf.woff2 +0 -0
- package/dist/remotion-bundle/052ca5351e5e06ba.woff2 +0 -0
- package/dist/remotion-bundle/05853dd28f4019cb.woff2 +0 -0
- package/dist/remotion-bundle/072ead3737f7c0d0.woff2 +0 -0
- package/dist/remotion-bundle/07d4248613c86a2e.woff2 +0 -0
- package/dist/remotion-bundle/0884a5c2d1d2d99b.woff2 +0 -0
- package/dist/remotion-bundle/0b0e185b2752095e.woff2 +0 -0
- package/dist/remotion-bundle/0e66c11bde067d91.woff2 +0 -0
- package/dist/remotion-bundle/0f7794cfba2c5d21.woff2 +0 -0
- package/dist/remotion-bundle/0fdbae5a4365783a.woff2 +0 -0
- package/dist/remotion-bundle/112.bundle.js +11 -0
- package/dist/remotion-bundle/112.bundle.js.map +1 -0
- package/dist/remotion-bundle/113.bundle.js +11 -0
- package/dist/remotion-bundle/113.bundle.js.map +1 -0
- package/dist/remotion-bundle/119cae0c4c16f7ed.woff2 +0 -0
- package/dist/remotion-bundle/14725f649fd1e78c.woff2 +0 -0
- package/dist/remotion-bundle/14abe9e3f95f7888.woff2 +0 -0
- package/dist/remotion-bundle/163.bundle.js +14678 -0
- package/dist/remotion-bundle/163.bundle.js.map +1 -0
- package/dist/remotion-bundle/1808c54072bf6d14.woff2 +0 -0
- package/dist/remotion-bundle/18948bec3e3012fe.woff2 +0 -0
- package/dist/remotion-bundle/1a661c60d0fc84fc.woff2 +0 -0
- package/dist/remotion-bundle/1af94941e1bc7e1e.woff2 +0 -0
- package/dist/remotion-bundle/1bee0219595f606c.woff2 +0 -0
- package/dist/remotion-bundle/1bfd5da7ce9d4ec4.woff2 +0 -0
- package/dist/remotion-bundle/1c158d56f1884f3f.woff2 +0 -0
- package/dist/remotion-bundle/1cf5e88e667610eb.woff2 +0 -0
- package/dist/remotion-bundle/1d431bd10f53c481.woff2 +0 -0
- package/dist/remotion-bundle/1d701a81a7670db2.woff2 +0 -0
- package/dist/remotion-bundle/1da0fecad4240f16.woff2 +0 -0
- package/dist/remotion-bundle/1ed14d3d0c5c63fe.woff2 +0 -0
- package/dist/remotion-bundle/1edfecf40e586f53.woff2 +0 -0
- package/dist/remotion-bundle/1f479711bc34b054.woff +0 -0
- package/dist/remotion-bundle/1f86e54a0ff5fcd1.woff2 +0 -0
- package/dist/remotion-bundle/2043ea87d9aabd11.woff2 +0 -0
- package/dist/remotion-bundle/20563c39ee8a0e40.woff2 +0 -0
- package/dist/remotion-bundle/20c231590fd12c44.woff2 +0 -0
- package/dist/remotion-bundle/20ce61713f754c07.woff2 +0 -0
- package/dist/remotion-bundle/21eb9306fce24bb1.woff2 +0 -0
- package/dist/remotion-bundle/244bf71c0cc851af.woff2 +0 -0
- package/dist/remotion-bundle/274d4cfc02bffbcb.woff2 +0 -0
- package/dist/remotion-bundle/275.bundle.js +21 -0
- package/dist/remotion-bundle/275.bundle.js.map +1 -0
- package/dist/remotion-bundle/2958f540b39513dc.woff2 +0 -0
- package/dist/remotion-bundle/2a168b98fd97722e.woff2 +0 -0
- package/dist/remotion-bundle/2d1f6373937ab55f.woff2 +0 -0
- package/dist/remotion-bundle/2d213ae47ff6daa9.woff2 +0 -0
- package/dist/remotion-bundle/2e4b1f04fcd05047.woff2 +0 -0
- package/dist/remotion-bundle/304170d98f4c4563.woff2 +0 -0
- package/dist/remotion-bundle/30d02e136e7a5642.woff2 +0 -0
- package/dist/remotion-bundle/3135562b52a714cd.woff2 +0 -0
- package/dist/remotion-bundle/313713af2c8144e9.woff2 +0 -0
- package/dist/remotion-bundle/325fa4108d2285b9.woff2 +0 -0
- package/dist/remotion-bundle/338e927ed3345e0c.woff2 +0 -0
- package/dist/remotion-bundle/35fc6b190365bc17.woff2 +0 -0
- package/dist/remotion-bundle/37a51f1122d4efc5.woff2 +0 -0
- package/dist/remotion-bundle/39a4d63e02736f5e.woff2 +0 -0
- package/dist/remotion-bundle/3a00e0d62dfc4171.woff2 +0 -0
- package/dist/remotion-bundle/3a6955e6561affe1.woff2 +0 -0
- package/dist/remotion-bundle/3c573945aef49b89.woff2 +0 -0
- package/dist/remotion-bundle/3cdbfbfa23b516a5.woff2 +0 -0
- package/dist/remotion-bundle/3e42f85a9e64ca8a.woff2 +0 -0
- package/dist/remotion-bundle/3e83eaf1ec859415.woff2 +0 -0
- package/dist/remotion-bundle/3f3c8c90de1250ee.woff2 +0 -0
- package/dist/remotion-bundle/434.bundle.js +205 -0
- package/dist/remotion-bundle/434.bundle.js.map +1 -0
- package/dist/remotion-bundle/44ffc6ca4d781692.woff2 +0 -0
- package/dist/remotion-bundle/4670d9c4580b09eb.woff2 +0 -0
- package/dist/remotion-bundle/479756881b302824.woff2 +0 -0
- package/dist/remotion-bundle/481b82134bfa9c82.woff2 +0 -0
- package/dist/remotion-bundle/48d27029626f4328.woff2 +0 -0
- package/dist/remotion-bundle/49b7b2a30329c511.woff2 +0 -0
- package/dist/remotion-bundle/4c8b25a1a9337045.woff2 +0 -0
- package/dist/remotion-bundle/4cba14788ca9259b.woff2 +0 -0
- package/dist/remotion-bundle/4cd6c589c004a6a7.woff2 +0 -0
- package/dist/remotion-bundle/4cd8d79c1021608d.woff2 +0 -0
- package/dist/remotion-bundle/4d8fa99b3f00f9f0.woff2 +0 -0
- package/dist/remotion-bundle/4e7805a643f86d53.woff2 +0 -0
- package/dist/remotion-bundle/4ff91be454542e3f.woff2 +0 -0
- package/dist/remotion-bundle/504cbcba1f63591b.woff2 +0 -0
- package/dist/remotion-bundle/5202d792e5791d6c.woff2 +0 -0
- package/dist/remotion-bundle/534db5ad4770cc1d.woff2 +0 -0
- package/dist/remotion-bundle/53b9568eb85f866b.woff2 +0 -0
- package/dist/remotion-bundle/543ad386ca171de9.woff2 +0 -0
- package/dist/remotion-bundle/54798e55bbf7976e.woff2 +0 -0
- package/dist/remotion-bundle/580.bundle.js +11 -0
- package/dist/remotion-bundle/580.bundle.js.map +1 -0
- package/dist/remotion-bundle/58d174d1193af6d1.woff2 +0 -0
- package/dist/remotion-bundle/591d29ff3ff53c80.woff2 +0 -0
- package/dist/remotion-bundle/5c28c4f4824383c6.woff2 +0 -0
- package/dist/remotion-bundle/5da9740d2ce894c8.woff2 +0 -0
- package/dist/remotion-bundle/6197735364642360.woff2 +0 -0
- package/dist/remotion-bundle/6265a4335724080f.woff2 +0 -0
- package/dist/remotion-bundle/633f5e4f6394daa7.woff2 +0 -0
- package/dist/remotion-bundle/637d95ace6a69c49.woff2 +0 -0
- package/dist/remotion-bundle/648e04a04dacff8f.woff2 +0 -0
- package/dist/remotion-bundle/64a6e83045a008b2.woff2 +0 -0
- package/dist/remotion-bundle/651.bundle.js +11 -0
- package/dist/remotion-bundle/651.bundle.js.map +1 -0
- package/dist/remotion-bundle/65e2a988c070facc.woff2 +0 -0
- package/dist/remotion-bundle/66a2f6ce5cc69105.woff2 +0 -0
- package/dist/remotion-bundle/690.bundle.js +3479 -0
- package/dist/remotion-bundle/690.bundle.js.map +1 -0
- package/dist/remotion-bundle/690ff55252ca715d.woff2 +0 -0
- package/dist/remotion-bundle/6a01a1cff49314fc.woff2 +0 -0
- package/dist/remotion-bundle/6cbc32670982986c.woff2 +0 -0
- package/dist/remotion-bundle/6d3cc42ae547f454.woff2 +0 -0
- package/dist/remotion-bundle/6d8f4cfa1ddc0830.woff2 +0 -0
- package/dist/remotion-bundle/6e4d7c6ae65e2dc3.woff2 +0 -0
- package/dist/remotion-bundle/6e86418bbcefb2e8.woff2 +0 -0
- package/dist/remotion-bundle/6ee02884b29cf7fb.woff2 +0 -0
- package/dist/remotion-bundle/6f436a74c9e3252c.woff2 +0 -0
- package/dist/remotion-bundle/78c8022f1657618b.woff2 +0 -0
- package/dist/remotion-bundle/7c5444169792bca4.woff2 +0 -0
- package/dist/remotion-bundle/7c86bddd9d997212.woff2 +0 -0
- package/dist/remotion-bundle/7e1284684767f584.woff2 +0 -0
- package/dist/remotion-bundle/7e81c17522d182b2.woff2 +0 -0
- package/dist/remotion-bundle/7eb87be198f7858c.woff2 +0 -0
- package/dist/remotion-bundle/8060c928f948aab5.woff2 +0 -0
- package/dist/remotion-bundle/80bc9dfbea2b35ae.woff2 +0 -0
- package/dist/remotion-bundle/811b83f69963bb48.woff2 +0 -0
- package/dist/remotion-bundle/813.bundle.js +117511 -0
- package/dist/remotion-bundle/813.bundle.js.map +1 -0
- package/dist/remotion-bundle/84df492e349f82e9.woff2 +0 -0
- package/dist/remotion-bundle/8501bfd73eb36f2b.woff2 +0 -0
- package/dist/remotion-bundle/854236a8376093fe.woff2 +0 -0
- package/dist/remotion-bundle/8571d74529082753.woff2 +0 -0
- package/dist/remotion-bundle/860bf44f8e6f4b5d.woff2 +0 -0
- package/dist/remotion-bundle/879.bundle.js +64 -0
- package/dist/remotion-bundle/879.bundle.js.map +1 -0
- package/dist/remotion-bundle/887dd482f848d56f.woff2 +0 -0
- package/dist/remotion-bundle/89b2132e85fbbb5a.woff2 +0 -0
- package/dist/remotion-bundle/8ba60d6c306010c2.woff2 +0 -0
- package/dist/remotion-bundle/8c7c4dadea897806.woff2 +0 -0
- package/dist/remotion-bundle/8c943f9999706f61.woff2 +0 -0
- package/dist/remotion-bundle/8f2a718c90575cc9.woff2 +0 -0
- package/dist/remotion-bundle/906b6edb3e1772c9.woff2 +0 -0
- package/dist/remotion-bundle/930ff9daccdf14eb.woff2 +0 -0
- package/dist/remotion-bundle/934db2f1c403c4d0.woff2 +0 -0
- package/dist/remotion-bundle/938.bundle.js +451 -0
- package/dist/remotion-bundle/938.bundle.js.map +1 -0
- package/dist/remotion-bundle/967.bundle.js +4462 -0
- package/dist/remotion-bundle/967.bundle.js.map +1 -0
- package/dist/remotion-bundle/9684a1093d3c02ce.woff2 +0 -0
- package/dist/remotion-bundle/973dcd0faa6116cc.woff2 +0 -0
- package/dist/remotion-bundle/9745400694e76cd8.woff2 +0 -0
- package/dist/remotion-bundle/999ef957bed3bdca.woff2 +0 -0
- package/dist/remotion-bundle/99a3d67c8b0f43e3.woff2 +0 -0
- package/dist/remotion-bundle/a0586c3e03127283.woff2 +0 -0
- package/dist/remotion-bundle/a0eb654fdae46269.woff2 +0 -0
- package/dist/remotion-bundle/a20e35d3b08f7994.woff2 +0 -0
- package/dist/remotion-bundle/a2dcaced7c8c25ab.woff2 +0 -0
- package/dist/remotion-bundle/a79255a972a2681a.woff2 +0 -0
- package/dist/remotion-bundle/a804b352cb9fec1a.woff2 +0 -0
- package/dist/remotion-bundle/aae7117164e1eabc.woff2 +0 -0
- package/dist/remotion-bundle/affd121385d0442d.woff2 +0 -0
- package/dist/remotion-bundle/b19a6083987ee0d7.woff2 +0 -0
- package/dist/remotion-bundle/b1b2bd04d8637981.woff2 +0 -0
- package/dist/remotion-bundle/b2c07f341486be87.woff2 +0 -0
- package/dist/remotion-bundle/b33d8f82e575c4ce.woff2 +0 -0
- package/dist/remotion-bundle/b366c0bed35ef491.woff2 +0 -0
- package/dist/remotion-bundle/b41e857ec1b85642.woff2 +0 -0
- package/dist/remotion-bundle/b420bb34ccf23e7f.woff2 +0 -0
- package/dist/remotion-bundle/b4f7bf4efb0c0ccf.woff2 +0 -0
- package/dist/remotion-bundle/b60fe5eca03cff93.woff2 +0 -0
- package/dist/remotion-bundle/b6bd31a336e64bce.woff2 +0 -0
- package/dist/remotion-bundle/b6d2befba3dfefeb.woff2 +0 -0
- package/dist/remotion-bundle/b75f39ab06c43bf4.woff2 +0 -0
- package/dist/remotion-bundle/b77880e8c413d4fd.woff2 +0 -0
- package/dist/remotion-bundle/b7e38ec441e4a77a.woff2 +0 -0
- package/dist/remotion-bundle/b83baa383ff0bf2b.woff2 +0 -0
- package/dist/remotion-bundle/b9ad7b6c0a11450a.woff2 +0 -0
- package/dist/remotion-bundle/baf84486e8ae3aaf.woff2 +0 -0
- package/dist/remotion-bundle/bc047b1f6869cffa.woff2 +0 -0
- package/dist/remotion-bundle/bf4f3ac6e93f33aa.woff2 +0 -0
- package/dist/remotion-bundle/bf6835ffec5897a2.woff2 +0 -0
- package/dist/remotion-bundle/bf8885f581eb1724.woff2 +0 -0
- package/dist/remotion-bundle/bundle.js +83376 -0
- package/dist/remotion-bundle/bundle.js.map +1 -0
- package/dist/remotion-bundle/c03f046bccd789d0.woff2 +0 -0
- package/dist/remotion-bundle/c0bb1f8962b73bc3.woff2 +0 -0
- package/dist/remotion-bundle/c1003f9a7db6e1cf.woff2 +0 -0
- package/dist/remotion-bundle/c15d83fb1e199515.woff2 +0 -0
- package/dist/remotion-bundle/c28e7e5d310f73ef.woff2 +0 -0
- package/dist/remotion-bundle/c2b840274db78aea.woff2 +0 -0
- package/dist/remotion-bundle/c3000e3299d4e45f.woff2 +0 -0
- package/dist/remotion-bundle/c83ce886e5288510.woff2 +0 -0
- package/dist/remotion-bundle/c87a5a64d4ac0918.woff2 +0 -0
- package/dist/remotion-bundle/c8a7e0d049e965fa.woff2 +0 -0
- package/dist/remotion-bundle/c949a35d3a3b1faf.woff2 +0 -0
- package/dist/remotion-bundle/c9618c9b9ac2bc78.woff2 +0 -0
- package/dist/remotion-bundle/ca3add3b84152d5b.woff2 +0 -0
- package/dist/remotion-bundle/cad9dd036408d707.woff2 +0 -0
- package/dist/remotion-bundle/cbb24916619df439.woff2 +0 -0
- package/dist/remotion-bundle/cc054f0b5514e177.woff2 +0 -0
- package/dist/remotion-bundle/ccc248ed9312bc71.woff2 +0 -0
- package/dist/remotion-bundle/cd9d623aa07af925.woff2 +0 -0
- package/dist/remotion-bundle/ce2ba7a321bd1247.woff2 +0 -0
- package/dist/remotion-bundle/cf72455f79a29b14.woff2 +0 -0
- package/dist/remotion-bundle/d267cbfefab452ac.woff2 +0 -0
- package/dist/remotion-bundle/d435cff46a64955f.woff +0 -0
- package/dist/remotion-bundle/d494d07f67e363f6.woff2 +0 -0
- package/dist/remotion-bundle/d7aa0cc1fa47bf38.woff2 +0 -0
- package/dist/remotion-bundle/d7c5ca93d885160a.woff2 +0 -0
- package/dist/remotion-bundle/d855d3e252db74e2.woff2 +0 -0
- package/dist/remotion-bundle/d8f13d47f02f82c2.woff2 +0 -0
- package/dist/remotion-bundle/d9567cce2ee11019.woff2 +0 -0
- package/dist/remotion-bundle/db8d4456fc75dd86.woff +0 -0
- package/dist/remotion-bundle/dc274628378c47ee.woff2 +0 -0
- package/dist/remotion-bundle/dc3e06947bb69903.woff2 +0 -0
- package/dist/remotion-bundle/dd67040ac3b6d523.woff2 +0 -0
- package/dist/remotion-bundle/e0b04bd488f953f4.woff2 +0 -0
- package/dist/remotion-bundle/e2a572ff95089370.woff2 +0 -0
- package/dist/remotion-bundle/e2e18a86b1c2b0cc.woff2 +0 -0
- package/dist/remotion-bundle/e3a78ee2fc9c6931.woff2 +0 -0
- package/dist/remotion-bundle/e654c9d547605a9f.woff2 +0 -0
- package/dist/remotion-bundle/e67a3a64c129927c.woff2 +0 -0
- package/dist/remotion-bundle/e6be28b4203cd6ce.woff2 +0 -0
- package/dist/remotion-bundle/e841907ad9b0a191.woff +0 -0
- package/dist/remotion-bundle/e889d1541c69fffa.woff2 +0 -0
- package/dist/remotion-bundle/e88ef8c76373a9e2.woff2 +0 -0
- package/dist/remotion-bundle/e9c72f4bc37defef.woff2 +0 -0
- package/dist/remotion-bundle/e9e35f863403a255.woff2 +0 -0
- package/dist/remotion-bundle/eb23b37b009375da.woff2 +0 -0
- package/dist/remotion-bundle/ee1342b741625721.woff2 +0 -0
- package/dist/remotion-bundle/f07da88543a57ec9.woff2 +0 -0
- package/dist/remotion-bundle/f522982115306f8a.woff2 +0 -0
- package/dist/remotion-bundle/f8449bd864e6d8bc.woff2 +0 -0
- package/dist/remotion-bundle/f906dd5bd95ff9ab.woff2 +0 -0
- package/dist/remotion-bundle/f9e9e9413e3c38bb.woff2 +0 -0
- package/dist/remotion-bundle/fa5a5b16280994a8.woff2 +0 -0
- package/dist/remotion-bundle/favicon.ico +0 -0
- package/dist/remotion-bundle/fb19c0517725599b.woff2 +0 -0
- package/dist/remotion-bundle/fcaf24232f684b9b.woff2 +0 -0
- package/dist/remotion-bundle/fe09e084a3eea8cf.woff2 +0 -0
- package/dist/remotion-bundle/ff38d5317df7345a.woff2 +0 -0
- package/dist/remotion-bundle/ffe7ea1ea08f455a.woff2 +0 -0
- package/dist/remotion-bundle/index.html +49 -0
- package/dist/remotion-bundle/public/paper-slide/female-kefu-xiaomei/communication/0.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/female-kefu-xiaomei/communication/1.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/female-kefu-xiaomei/communication/2.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/female-kefu-xiaomei/communication/3.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/female-kefu-xiaoxin/career/0.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/female-kefu-xiaoxin/career/1.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/female-kefu-xiaoxin/career/2.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/female-kefu-xiaoxin/career/3.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/female-kefu-xiaoyue/parenting/0.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/female-kefu-xiaoyue/parenting/1.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/female-kefu-xiaoyue/parenting/2.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/female-kefu-xiaoyue/parenting/3.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/male-kefu-xiaoxu/time-trap/0.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/male-kefu-xiaoxu/time-trap/1.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/male-kefu-xiaoxu/time-trap/2.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/male-kefu-xiaoxu/time-trap/3.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/v-female-A6b7WpG3/cognition/0.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/v-female-A6b7WpG3/cognition/1.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/v-female-A6b7WpG3/cognition/2.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/v-female-A6b7WpG3/cognition/3.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/v-female-A6b7WpG3/growth/0.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/v-female-A6b7WpG3/growth/1.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/v-female-A6b7WpG3/growth/2.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/v-female-A6b7WpG3/growth/3.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/v-female-A6b7WpG3/parenting/0.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/v-female-A6b7WpG3/parenting/1.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/v-female-A6b7WpG3/parenting/2.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/v-female-A6b7WpG3/parenting/3.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/v-female-A6b7WpG3/soothing/0.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/v-female-A6b7WpG3/soothing/1.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/v-female-A6b7WpG3/soothing/2.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/v-female-A6b7WpG3/soothing/3.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/v-female-R2s4N9qJ/cognition/0.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/v-female-R2s4N9qJ/cognition/1.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/v-female-R2s4N9qJ/cognition/2.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/v-female-R2s4N9qJ/cognition/3.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/v-male-Bk7vD3xP/decision/0.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/v-male-Bk7vD3xP/decision/1.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/v-male-Bk7vD3xP/decision/2.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/v-male-Bk7vD3xP/decision/3.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/v-male-W1tH9jVc/manager/0.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/v-male-W1tH9jVc/manager/1.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/v-male-W1tH9jVc/manager/2.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/v-male-W1tH9jVc/manager/3.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/v-male-W1tH9jVc/manager/4.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/v-male-s5NqE0rZ/founder/0.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/v-male-s5NqE0rZ/founder/1.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/v-male-s5NqE0rZ/founder/2.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/v-male-s5NqE0rZ/founder/3.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide-experiments/career-advice/0.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide-experiments/career-advice/1.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide-experiments/career-advice/2.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide-experiments/career-advice/3.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide-experiments/career-advice/4.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide-experiments/founder-lesson/0.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide-experiments/founder-lesson/1.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide-experiments/founder-lesson/2.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide-experiments/founder-lesson/3.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide-experiments/founder-lesson/4.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide-experiments/incident-review/0.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide-experiments/incident-review/1.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide-experiments/incident-review/2.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide-experiments/incident-review/3.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide-experiments/incident-review/4.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide-experiments/learning-loop/0.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide-experiments/learning-loop/1.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide-experiments/learning-loop/2.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide-experiments/learning-loop/3.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide-experiments/learning-loop/4.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide-experiments/meeting-closure/0.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide-experiments/meeting-closure/1.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide-experiments/meeting-closure/2.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide-experiments/meeting-closure/3.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide-experiments/product-update/0.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide-experiments/product-update/1.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide-experiments/product-update/2.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide-experiments/product-update/3.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide-experiments/research-reading/0.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide-experiments/research-reading/1.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide-experiments/research-reading/2.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide-experiments/research-reading/3.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide-experiments/sales-enablement/0.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide-experiments/sales-enablement/1.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide-experiments/sales-enablement/2.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide-experiments/sales-enablement/3.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide-experiments/sales-enablement/4.mp3 +0 -0
- package/dist/remotion-bundle/public/voiceover/ai-life/card-0.mp3 +0 -0
- package/dist/remotion-bundle/public/voiceover/ai-life/card-1.mp3 +0 -0
- package/dist/remotion-bundle/public/voiceover/ai-life/card-2.mp3 +0 -0
- package/dist/remotion-bundle/public/voiceover/ai-life/card-3.mp3 +0 -0
- package/dist/remotion-bundle/public/voiceover/ai-life/card-4.mp3 +0 -0
- package/dist/remotion-bundle/public/voiceover/ai-life/card-5.mp3 +0 -0
- package/dist/remotion-bundle/public/voiceover/coffee-science/card-0.mp3 +0 -0
- package/dist/remotion-bundle/public/voiceover/coffee-science/card-1.mp3 +0 -0
- package/dist/remotion-bundle/public/voiceover/coffee-science/card-2.mp3 +0 -0
- package/dist/remotion-bundle/public/voiceover/coffee-science/card-3.mp3 +0 -0
- package/dist/remotion-bundle/public/voiceover/coffee-science/card-4.mp3 +0 -0
- package/dist/remotion-bundle/public/voiceover/coffee-science/card-5.mp3 +0 -0
- package/dist/remotion-bundle/public/voiceover/coffee-science/card-6.mp3 +0 -0
- package/dist/remotion-bundle/public/voiceover/reading-secrets/card-0.mp3 +0 -0
- package/dist/remotion-bundle/public/voiceover/reading-secrets/card-1.mp3 +0 -0
- package/dist/remotion-bundle/public/voiceover/reading-secrets/card-2.mp3 +0 -0
- package/dist/remotion-bundle/public/voiceover/reading-secrets/card-3.mp3 +0 -0
- package/dist/remotion-bundle/public/voiceover/reading-secrets/card-4.mp3 +0 -0
- package/dist/remotion-bundle/public/voiceover/reading-secrets/card-5.mp3 +0 -0
- package/dist/remotion-bundle/public/voiceover/reading-secrets/card-6.mp3 +0 -0
- package/dist/remotion-bundle/public/voiceover/remote-work/card-0.mp3 +0 -0
- package/dist/remotion-bundle/public/voiceover/remote-work/card-1.mp3 +0 -0
- package/dist/remotion-bundle/public/voiceover/remote-work/card-2.mp3 +0 -0
- package/dist/remotion-bundle/public/voiceover/remote-work/card-3.mp3 +0 -0
- package/dist/remotion-bundle/public/voiceover/remote-work/card-4.mp3 +0 -0
- package/dist/remotion-bundle/public/voiceover/remote-work/card-5.mp3 +0 -0
- package/dist/remotion-bundle/source-map-helper.wasm +0 -0
- package/lib/cli.js +270 -0
- package/lib/commands/_registry.js +48 -0
- package/lib/commands/add.js +242 -0
- package/lib/commands/asr/azure-transcribe.js +336 -0
- package/lib/commands/asr/cloud-transcribe.js +384 -0
- package/lib/commands/asr/helpers.js +76 -0
- package/lib/commands/asr/index.js +236 -0
- package/lib/commands/asr/local-transcribe.js +125 -0
- package/lib/commands/asr-jobs.js +257 -0
- package/lib/commands/asr.js +11 -0
- package/lib/commands/auth-cmds.js +358 -0
- package/lib/commands/dub.js +542 -0
- package/lib/commands/explain.js +512 -0
- package/lib/commands/feedback.js +152 -0
- package/lib/commands/image.js +207 -0
- package/lib/commands/mcp-key.js +166 -0
- package/lib/commands/narrate.js +639 -0
- package/lib/commands/picstory-templates.js +276 -0
- package/lib/commands/picstory.js +547 -0
- package/lib/commands/podcast/dialogue.js +109 -0
- package/lib/commands/podcast/generate.js +127 -0
- package/lib/commands/podcast/index.js +561 -0
- package/lib/commands/podcast/synthesize.js +188 -0
- package/lib/commands/podcast.js +11 -0
- package/lib/commands/present.js +519 -0
- package/lib/commands/publish.js +415 -0
- package/lib/commands/skills.js +473 -0
- package/lib/commands/slice-preview.js +266 -0
- package/lib/commands/slice-render.js +282 -0
- package/lib/commands/slice-stage.js +264 -0
- package/lib/commands/slice.js +343 -0
- package/lib/commands/slides/constants.js +108 -0
- package/lib/commands/slides/html-renderer.js +338 -0
- package/lib/commands/slides/index.js +345 -0
- package/lib/commands/slides.js +11 -0
- package/lib/commands/story.js +302 -0
- package/lib/commands/summarize.js +532 -0
- package/lib/commands/synthesize.js +261 -0
- package/lib/commands/translate.js +593 -0
- package/lib/commands/upgrade.js +249 -0
- package/lib/commands/video-translate.js +577 -0
- package/lib/commands/voices.js +292 -0
- package/lib/core/agent-env.js +104 -0
- package/lib/core/args.js +107 -0
- package/lib/core/asr-client.js +448 -0
- package/lib/core/asr-jobs-client.js +126 -0
- package/lib/core/asr-jobs-store.js +105 -0
- package/lib/core/asr-r2-upload.js +181 -0
- package/lib/core/asr-upload.js +132 -0
- package/lib/core/audio-extract.js +150 -0
- package/lib/core/audio.js +219 -0
- package/lib/core/auth.js +880 -0
- package/lib/core/config.js +197 -0
- package/lib/core/feedback.js +64 -0
- package/lib/core/ffmpeg.js +476 -0
- package/lib/core/http.js +188 -0
- package/lib/core/image-client.js +55 -0
- package/lib/core/intent-params.js +11 -0
- package/lib/core/llm-client.js +76 -0
- package/lib/core/logger.js +208 -0
- package/lib/core/mic-recorder.js +182 -0
- package/lib/core/pause-markers.js +94 -0
- package/lib/core/podcast-pacing.js +118 -0
- package/lib/core/spinner.js +33 -0
- package/lib/core/srt.js +394 -0
- package/lib/core/telemetry.js +100 -0
- package/lib/core/timeline.js +92 -0
- package/lib/core/tts-synthesizer.js +70 -0
- package/lib/core/update-check.js +185 -0
- package/lib/core/url-download.js +148 -0
- package/lib/core/whisper-local.js +279 -0
- package/lib/internal/deck-validator.js +488 -0
- package/lib/internal/slice-themes.json +370 -0
- package/lib/stage-core/cloud-render.js +170 -0
- package/lib/stage-core/deck-format.js +133 -0
- package/lib/stage-core/edit-prompt.js +104 -0
- package/lib/stage-core/event-bus.js +31 -0
- package/lib/stage-core/port.js +46 -0
- package/lib/stage-core/server.js +352 -0
- package/lib/stage-core/snapshot-store.js +198 -0
- package/lib/stage-core/watcher.js +106 -0
- package/lib/stage-ui/slice/template.js +1672 -0
- package/package.json +9 -4
- package/skills/.claude-plugin/marketplace.json +22 -0
- package/skills/.claude-plugin/plugin.json +25 -0
- package/skills/LICENSE +21 -0
- package/skills/README.md +120 -0
- package/skills/hub/SKILL.md +317 -0
- package/skills/podcast/SKILL.md +146 -0
- package/skills/slice/SKILL.md +205 -0
- package/skills/slice/agents/openai.yaml +4 -0
- package/skills/slice/references/deck-schema.md +183 -0
- package/skills/slice/references/example-decks.md +108 -0
- package/skills/slice/references/themes.md +172 -0
- package/skills/transcribe/SKILL.md +473 -0
- package/skills/video/SKILL.md +261 -0
- package/skills/voxflow-slice/SKILL.md +271 -0
- package/skills/voxflow-slice/examples/article.md +13 -0
- package/skills/voxflow-slice/examples/expected-deck.json +39 -0
- package/skills/voxflow-slice/examples/validate.mjs +46 -0
|
@@ -0,0 +1,593 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* VoxFlow CLI — Translate command
|
|
3
|
+
*
|
|
4
|
+
* Translate SRT subtitles, plain text, or text files using the backend
|
|
5
|
+
* /api/llm/chat endpoint with batched prompts.
|
|
6
|
+
*
|
|
7
|
+
* Three input modes:
|
|
8
|
+
* - SRT mode (--srt <file>): Parse SRT, batch-translate captions, output translated SRT
|
|
9
|
+
* - Text mode (--text "..."): Translate a single text string, print to stdout
|
|
10
|
+
* - File mode (--input <file>): Read .txt/.md file, translate, write output file
|
|
11
|
+
*
|
|
12
|
+
* Cost: 1 quota per batch (10 captions). Using /api/llm/chat instead of
|
|
13
|
+
* /api/llm/translate saves 80-98% quota for SRT translation.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const fs = require('fs');
|
|
17
|
+
const path = require('path');
|
|
18
|
+
const { API_BASE, TRANSLATE_DEFAULTS } = require('../core/config');
|
|
19
|
+
const { chatCompletion, detectLanguage } = require('../core/llm-client');
|
|
20
|
+
const { parseSrt, formatSrt } = require('../core/srt');
|
|
21
|
+
|
|
22
|
+
// ─── Language mapping ────────────────────────────────────────────────────────
|
|
23
|
+
|
|
24
|
+
const LANG_MAP = {
|
|
25
|
+
zh: 'Chinese (Simplified)',
|
|
26
|
+
en: 'English',
|
|
27
|
+
ja: 'Japanese',
|
|
28
|
+
ko: 'Korean',
|
|
29
|
+
fr: 'French',
|
|
30
|
+
de: 'German',
|
|
31
|
+
es: 'Spanish',
|
|
32
|
+
pt: 'Portuguese',
|
|
33
|
+
ru: 'Russian',
|
|
34
|
+
ar: 'Arabic',
|
|
35
|
+
th: 'Thai',
|
|
36
|
+
vi: 'Vietnamese',
|
|
37
|
+
it: 'Italian',
|
|
38
|
+
id: 'Indonesian',
|
|
39
|
+
ms: 'Malay',
|
|
40
|
+
yue: 'Cantonese',
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
// ─── Internal helpers ────────────────────────────────────────────────────────
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Split captions into batches of the given size.
|
|
47
|
+
* @param {Array} captions
|
|
48
|
+
* @param {number} batchSize
|
|
49
|
+
* @returns {Array<Array>}
|
|
50
|
+
*/
|
|
51
|
+
function batchCaptions(captions, batchSize = 10) {
|
|
52
|
+
const batches = [];
|
|
53
|
+
for (let i = 0; i < captions.length; i += batchSize) {
|
|
54
|
+
batches.push(captions.slice(i, i + batchSize));
|
|
55
|
+
}
|
|
56
|
+
return batches;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Build the system + user prompt for a batch of captions.
|
|
61
|
+
* @param {Array} captions - Caption objects with .text and optional .speakerId
|
|
62
|
+
* @param {string} fromLang - Full language name (e.g. "Chinese (Simplified)")
|
|
63
|
+
* @param {string} toLang - Full language name (e.g. "English")
|
|
64
|
+
* @returns {{ system: string, user: string }}
|
|
65
|
+
*/
|
|
66
|
+
function buildTranslationPrompt(captions, fromLang, toLang) {
|
|
67
|
+
const system = [
|
|
68
|
+
`You are a professional subtitle translator. Translate each numbered line from ${fromLang} to ${toLang}.`,
|
|
69
|
+
'',
|
|
70
|
+
'Rules:',
|
|
71
|
+
'- Return ONLY the translated lines, one per number',
|
|
72
|
+
'- Keep the exact same numbering (1., 2., 3., ...)',
|
|
73
|
+
'- Preserve [Speaker: xxx] tags unchanged — do NOT translate speaker names',
|
|
74
|
+
'- Keep translations concise and natural for subtitles',
|
|
75
|
+
'- Do not add explanations, notes, or extra text',
|
|
76
|
+
].join('\n');
|
|
77
|
+
|
|
78
|
+
const user = captions
|
|
79
|
+
.map((cap, i) => {
|
|
80
|
+
const prefix = cap.speakerId ? `[Speaker: ${cap.speakerId}] ` : '';
|
|
81
|
+
return `${i + 1}. ${prefix}${cap.text}`;
|
|
82
|
+
})
|
|
83
|
+
.join('\n');
|
|
84
|
+
|
|
85
|
+
return { system, user };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Parse the LLM response back into translated texts.
|
|
90
|
+
* Strategy: try numbered regex match first, then fall back to positional.
|
|
91
|
+
*
|
|
92
|
+
* @param {string} content - LLM response text
|
|
93
|
+
* @param {Array} originalCaptions - Original caption objects (for fallback)
|
|
94
|
+
* @returns {Array} Translated caption objects
|
|
95
|
+
*/
|
|
96
|
+
function parseTranslationResponse(content, originalCaptions) {
|
|
97
|
+
const lines = content.trim().split('\n').filter(l => l.trim());
|
|
98
|
+
const translated = [];
|
|
99
|
+
|
|
100
|
+
for (let i = 0; i < originalCaptions.length; i++) {
|
|
101
|
+
const pattern = new RegExp(`^${i + 1}\\.\\s*(.+)$`);
|
|
102
|
+
const match = lines.find(l => pattern.test(l.trim()));
|
|
103
|
+
|
|
104
|
+
if (match) {
|
|
105
|
+
const text = match.trim().replace(pattern, '$1').trim();
|
|
106
|
+
// Strip speaker tag from translated text (we preserve original speakerId)
|
|
107
|
+
let cleanText = text;
|
|
108
|
+
const speakerMatch = text.match(/^\[Speaker:\s*[^\]]+\]\s*/i);
|
|
109
|
+
if (speakerMatch) {
|
|
110
|
+
cleanText = text.slice(speakerMatch[0].length);
|
|
111
|
+
}
|
|
112
|
+
translated.push({
|
|
113
|
+
...originalCaptions[i],
|
|
114
|
+
text: cleanText || originalCaptions[i].text,
|
|
115
|
+
});
|
|
116
|
+
} else {
|
|
117
|
+
// Fallback: try positional match
|
|
118
|
+
if (i < lines.length) {
|
|
119
|
+
const fallbackText = lines[i].replace(/^\d+\.\s*/, '').trim();
|
|
120
|
+
let cleanText = fallbackText;
|
|
121
|
+
const speakerMatch = fallbackText.match(/^\[Speaker:\s*[^\]]+\]\s*/i);
|
|
122
|
+
if (speakerMatch) {
|
|
123
|
+
cleanText = fallbackText.slice(speakerMatch[0].length);
|
|
124
|
+
}
|
|
125
|
+
translated.push({
|
|
126
|
+
...originalCaptions[i],
|
|
127
|
+
text: cleanText || originalCaptions[i].text,
|
|
128
|
+
});
|
|
129
|
+
} else {
|
|
130
|
+
// Could not find translation — keep original
|
|
131
|
+
translated.push({ ...originalCaptions[i] });
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return translated;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Adjust subtitle timing for translated text length differences.
|
|
141
|
+
* Only adjusts when the length ratio differs by more than THRESHOLD.
|
|
142
|
+
*
|
|
143
|
+
* @param {Array} sourceCaptions - Original captions (for reference length)
|
|
144
|
+
* @param {Array} translatedCaptions - Translated captions
|
|
145
|
+
* @returns {Array} Captions with adjusted endMs
|
|
146
|
+
*/
|
|
147
|
+
function realignTimings(sourceCaptions, translatedCaptions) {
|
|
148
|
+
const THRESHOLD = 0.3; // Only adjust if >30% length difference
|
|
149
|
+
const MIN_GAP_MS = 100;
|
|
150
|
+
|
|
151
|
+
const result = translatedCaptions.map((cap, i) => {
|
|
152
|
+
const source = sourceCaptions[i];
|
|
153
|
+
if (!source) return cap;
|
|
154
|
+
|
|
155
|
+
const sourceLen = source.text.length;
|
|
156
|
+
const targetLen = cap.text.length;
|
|
157
|
+
if (sourceLen === 0) return cap;
|
|
158
|
+
|
|
159
|
+
const ratio = targetLen / sourceLen;
|
|
160
|
+
if (ratio < (1 + THRESHOLD) && ratio > (1 - THRESHOLD)) {
|
|
161
|
+
return cap; // Similar length, no adjustment needed
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const originalDuration = source.endMs - source.startMs;
|
|
165
|
+
let newDuration = Math.round(originalDuration * ratio);
|
|
166
|
+
|
|
167
|
+
// Clamp: don't exceed next caption's start
|
|
168
|
+
const nextStart = i < sourceCaptions.length - 1
|
|
169
|
+
? sourceCaptions[i + 1].startMs
|
|
170
|
+
: Infinity;
|
|
171
|
+
const maxDuration = nextStart - cap.startMs - MIN_GAP_MS;
|
|
172
|
+
if (newDuration > maxDuration && maxDuration > 0) {
|
|
173
|
+
newDuration = maxDuration;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Floor: at least 500ms
|
|
177
|
+
newDuration = Math.max(newDuration, 500);
|
|
178
|
+
|
|
179
|
+
return {
|
|
180
|
+
...cap,
|
|
181
|
+
endMs: cap.startMs + newDuration,
|
|
182
|
+
};
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
return result;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// ─── Main command ────────────────────────────────────────────────────────────
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Translate command entry point (with SIGINT guard).
|
|
192
|
+
*
|
|
193
|
+
* @param {object} opts
|
|
194
|
+
* @param {string} opts.token - JWT auth token
|
|
195
|
+
* @param {string} [opts.api] - Backend base URL
|
|
196
|
+
* @param {string} [opts.srt] - SRT file path
|
|
197
|
+
* @param {string} [opts.text] - Inline text to translate
|
|
198
|
+
* @param {string} [opts.input] - Text file path (.txt, .md)
|
|
199
|
+
* @param {string} [opts.from] - Source language code (or 'auto')
|
|
200
|
+
* @param {string} opts.to - Target language code (required)
|
|
201
|
+
* @param {string} [opts.output] - Output file path
|
|
202
|
+
* @param {boolean}[opts.realign] - Adjust subtitle timing
|
|
203
|
+
* @param {number} [opts.batchSize] - Captions per LLM call (default: 10)
|
|
204
|
+
*/
|
|
205
|
+
async function translate(opts) {
|
|
206
|
+
const sigintHandler = () => {
|
|
207
|
+
console.log('\n\nTranslation cancelled.');
|
|
208
|
+
process.exit(130);
|
|
209
|
+
};
|
|
210
|
+
process.on('SIGINT', sigintHandler);
|
|
211
|
+
|
|
212
|
+
try {
|
|
213
|
+
return await _translate(opts);
|
|
214
|
+
} finally {
|
|
215
|
+
process.removeListener('SIGINT', sigintHandler);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
async function _translate(opts) {
|
|
220
|
+
const {
|
|
221
|
+
token,
|
|
222
|
+
api = API_BASE,
|
|
223
|
+
srt,
|
|
224
|
+
text,
|
|
225
|
+
input,
|
|
226
|
+
from,
|
|
227
|
+
to,
|
|
228
|
+
output: userOutput,
|
|
229
|
+
realign = false,
|
|
230
|
+
batchSize = TRANSLATE_DEFAULTS.batchSize,
|
|
231
|
+
} = opts;
|
|
232
|
+
|
|
233
|
+
// Determine mode
|
|
234
|
+
if (srt) return _translateSrt({ token, api, srt, from, to, output: userOutput, realign, batchSize });
|
|
235
|
+
if (text) return _translateText({ token, api, text, from, to });
|
|
236
|
+
if (input) return _translateFile({ token, api, input, from, to, output: userOutput });
|
|
237
|
+
|
|
238
|
+
throw new Error('No input specified. Use --srt, --text, or --input');
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// ─── SRT Translation ────────────────────────────────────────────────────────
|
|
242
|
+
|
|
243
|
+
async function _translateSrt({ token, api, srt: srtPath, from, to, output: userOutput, realign, batchSize }) {
|
|
244
|
+
console.log('\n=== VoxFlow Translate (SRT) ===');
|
|
245
|
+
|
|
246
|
+
// Read and parse SRT
|
|
247
|
+
const resolved = path.resolve(srtPath);
|
|
248
|
+
const content = fs.readFileSync(resolved, 'utf8');
|
|
249
|
+
const captions = parseSrt(content);
|
|
250
|
+
|
|
251
|
+
if (captions.length === 0) {
|
|
252
|
+
throw new Error(`SRT file is empty or invalid: ${resolved}`);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
console.log(`Input: ${path.basename(resolved)}`);
|
|
256
|
+
console.log(`Captions: ${captions.length}`);
|
|
257
|
+
|
|
258
|
+
// Detect source language if not specified
|
|
259
|
+
const fromCode = from || await autoDetectLanguage(api, captions);
|
|
260
|
+
const fromLang = LANG_MAP[fromCode] || fromCode;
|
|
261
|
+
const toLang = LANG_MAP[to] || to;
|
|
262
|
+
|
|
263
|
+
console.log(`From: ${fromLang} (${fromCode})`);
|
|
264
|
+
console.log(`To: ${toLang} (${to})`);
|
|
265
|
+
console.log(`Realign: ${realign ? 'yes' : 'no'}`);
|
|
266
|
+
|
|
267
|
+
// Batch and translate
|
|
268
|
+
const batches = batchCaptions(captions, batchSize);
|
|
269
|
+
console.log(`Batches: ${batches.length} (batch size: ${batchSize})`);
|
|
270
|
+
console.log('');
|
|
271
|
+
|
|
272
|
+
let allTranslated = [];
|
|
273
|
+
let totalQuota = 0;
|
|
274
|
+
|
|
275
|
+
for (let bi = 0; bi < batches.length; bi++) {
|
|
276
|
+
const batch = batches[bi];
|
|
277
|
+
process.stdout.write(` [${bi + 1}/${batches.length}] Translating ${batch.length} captions...`);
|
|
278
|
+
|
|
279
|
+
const { system, user } = buildTranslationPrompt(batch, fromLang, toLang);
|
|
280
|
+
const result = await chatCompletion({
|
|
281
|
+
apiBase: api,
|
|
282
|
+
token,
|
|
283
|
+
messages: [
|
|
284
|
+
{ role: 'system', content: system },
|
|
285
|
+
{ role: 'user', content: user },
|
|
286
|
+
],
|
|
287
|
+
temperature: TRANSLATE_DEFAULTS.temperature,
|
|
288
|
+
maxTokens: TRANSLATE_DEFAULTS.maxTokens,
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
const translated = parseTranslationResponse(result.content, batch);
|
|
292
|
+
allTranslated = allTranslated.concat(translated);
|
|
293
|
+
totalQuota++;
|
|
294
|
+
|
|
295
|
+
if (result.quota) {
|
|
296
|
+
console.log(` OK (remaining: ${result.quota.remaining})`);
|
|
297
|
+
} else {
|
|
298
|
+
console.log(' OK');
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Optional re-alignment
|
|
303
|
+
if (realign) {
|
|
304
|
+
console.log(' Re-aligning subtitle timing...');
|
|
305
|
+
allTranslated = realignTimings(captions, allTranslated);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// Renumber captions
|
|
309
|
+
allTranslated = allTranslated.map((cap, i) => ({ ...cap, id: i + 1 }));
|
|
310
|
+
|
|
311
|
+
// Format output
|
|
312
|
+
const outputContent = formatSrt(allTranslated);
|
|
313
|
+
|
|
314
|
+
// Determine output path
|
|
315
|
+
let outputPath;
|
|
316
|
+
if (userOutput) {
|
|
317
|
+
outputPath = path.resolve(userOutput);
|
|
318
|
+
} else {
|
|
319
|
+
const base = path.basename(resolved, path.extname(resolved));
|
|
320
|
+
const dir = path.dirname(resolved);
|
|
321
|
+
// Pre-build filename — see scripts/check-no-asset-rewrite.js
|
|
322
|
+
const fname = base + '-' + to + '.srt';
|
|
323
|
+
outputPath = path.resolve(dir, fname);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
fs.writeFileSync(outputPath, outputContent, 'utf8');
|
|
327
|
+
|
|
328
|
+
// Summary
|
|
329
|
+
console.log(`\n=== Done ===`);
|
|
330
|
+
console.log(`Output: ${outputPath}`);
|
|
331
|
+
console.log(`Captions: ${allTranslated.length}`);
|
|
332
|
+
console.log(`Quota: ${totalQuota} used`);
|
|
333
|
+
|
|
334
|
+
// Preview
|
|
335
|
+
if (allTranslated.length > 0) {
|
|
336
|
+
console.log(`\n--- Preview ---`);
|
|
337
|
+
const preview = allTranslated.slice(0, 3);
|
|
338
|
+
for (const cap of preview) {
|
|
339
|
+
const speaker = cap.speakerId ? `[${cap.speakerId}] ` : '';
|
|
340
|
+
const textSnippet = cap.text.length > 60
|
|
341
|
+
? cap.text.slice(0, 57) + '...'
|
|
342
|
+
: cap.text;
|
|
343
|
+
console.log(` ${cap.id}. ${speaker}${textSnippet}`);
|
|
344
|
+
}
|
|
345
|
+
if (allTranslated.length > 3) {
|
|
346
|
+
console.log(` ... (${allTranslated.length - 3} more)`);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
return {
|
|
351
|
+
outputPath,
|
|
352
|
+
captionCount: allTranslated.length,
|
|
353
|
+
quotaUsed: totalQuota,
|
|
354
|
+
from: fromCode,
|
|
355
|
+
to,
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// ─── Text Translation ────────────────────────────────────────────────────────
|
|
360
|
+
|
|
361
|
+
async function _translateText({ token, api, text, from, to }) {
|
|
362
|
+
console.log('\n=== VoxFlow Translate (Text) ===');
|
|
363
|
+
|
|
364
|
+
const fromCode = from || await autoDetectLanguage(api, [{ text }]);
|
|
365
|
+
const fromLang = LANG_MAP[fromCode] || fromCode;
|
|
366
|
+
const toLang = LANG_MAP[to] || to;
|
|
367
|
+
|
|
368
|
+
console.log(`From: ${fromLang} → To: ${toLang}`);
|
|
369
|
+
|
|
370
|
+
const result = await chatCompletion({
|
|
371
|
+
apiBase: api,
|
|
372
|
+
token,
|
|
373
|
+
messages: [
|
|
374
|
+
{
|
|
375
|
+
role: 'system',
|
|
376
|
+
content: `You are a professional translator. Translate the following text from ${fromLang} to ${toLang}. Return ONLY the translation, no explanations.`,
|
|
377
|
+
},
|
|
378
|
+
{ role: 'user', content: text },
|
|
379
|
+
],
|
|
380
|
+
temperature: TRANSLATE_DEFAULTS.temperature,
|
|
381
|
+
maxTokens: TRANSLATE_DEFAULTS.maxTokens,
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
const translated = result.content.trim();
|
|
385
|
+
console.log(`\n${translated}`);
|
|
386
|
+
|
|
387
|
+
const remaining = result.quota ? result.quota.remaining : '?';
|
|
388
|
+
console.log(`\n(Quota: 1 used, ${remaining} remaining)`);
|
|
389
|
+
|
|
390
|
+
return { text: translated, quotaUsed: 1, from: fromCode, to };
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// ─── File Translation ────────────────────────────────────────────────────────
|
|
394
|
+
|
|
395
|
+
async function _translateFile({ token, api, input, from, to, output: userOutput }) {
|
|
396
|
+
console.log('\n=== VoxFlow Translate (File) ===');
|
|
397
|
+
|
|
398
|
+
const resolved = path.resolve(input);
|
|
399
|
+
const content = fs.readFileSync(resolved, 'utf8');
|
|
400
|
+
|
|
401
|
+
if (content.trim().length === 0) {
|
|
402
|
+
throw new Error(`Input file is empty: ${resolved}`);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
console.log(`Input: ${path.basename(resolved)}`);
|
|
406
|
+
console.log(`Length: ${content.length} chars`);
|
|
407
|
+
|
|
408
|
+
const fromCode = from || await autoDetectLanguage(api, [{ text: content }]);
|
|
409
|
+
const fromLang = LANG_MAP[fromCode] || fromCode;
|
|
410
|
+
const toLang = LANG_MAP[to] || to;
|
|
411
|
+
|
|
412
|
+
console.log(`From: ${fromLang} → To: ${toLang}`);
|
|
413
|
+
|
|
414
|
+
const result = await chatCompletion({
|
|
415
|
+
apiBase: api,
|
|
416
|
+
token,
|
|
417
|
+
messages: [
|
|
418
|
+
{
|
|
419
|
+
role: 'system',
|
|
420
|
+
content: `You are a professional translator. Translate the following document from ${fromLang} to ${toLang}. Preserve the original formatting (paragraphs, line breaks, markdown). Return ONLY the translation.`,
|
|
421
|
+
},
|
|
422
|
+
{ role: 'user', content },
|
|
423
|
+
],
|
|
424
|
+
temperature: TRANSLATE_DEFAULTS.temperature,
|
|
425
|
+
maxTokens: Math.max(TRANSLATE_DEFAULTS.maxTokens, 4000), // files may need more tokens
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
const translated = result.content.trim();
|
|
429
|
+
|
|
430
|
+
// Determine output path
|
|
431
|
+
let outputPath;
|
|
432
|
+
if (userOutput) {
|
|
433
|
+
outputPath = path.resolve(userOutput);
|
|
434
|
+
} else {
|
|
435
|
+
const ext = path.extname(resolved);
|
|
436
|
+
const base = path.basename(resolved, ext);
|
|
437
|
+
const dir = path.dirname(resolved);
|
|
438
|
+
// Pre-build filename — see scripts/check-no-asset-rewrite.js
|
|
439
|
+
const fname = base + '-' + to + ext;
|
|
440
|
+
outputPath = path.resolve(dir, fname);
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
fs.writeFileSync(outputPath, translated + '\n', 'utf8');
|
|
444
|
+
|
|
445
|
+
const remaining = result.quota ? result.quota.remaining : '?';
|
|
446
|
+
console.log(`\n=== Done ===`);
|
|
447
|
+
console.log(`Output: ${outputPath}`);
|
|
448
|
+
console.log(`Quota: 1 used, ${remaining} remaining`);
|
|
449
|
+
|
|
450
|
+
return { outputPath, quotaUsed: 1, from: fromCode, to };
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// ─── Auto-detect language ────────────────────────────────────────────────────
|
|
454
|
+
|
|
455
|
+
async function autoDetectLanguage(api, captions) {
|
|
456
|
+
// Take first few captions (or text) for detection
|
|
457
|
+
const sample = captions.slice(0, 3).map(c => c.text).join(' ');
|
|
458
|
+
const detected = await detectLanguage({ apiBase: api, text: sample });
|
|
459
|
+
return detected || 'auto';
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
// ─── Exports ─────────────────────────────────────────────────────────────────
|
|
463
|
+
|
|
464
|
+
// ─── CLI Handler ────────────────────────────────────────────────────────────
|
|
465
|
+
|
|
466
|
+
async function handle(args) {
|
|
467
|
+
const { parseFlag, parseIntFlag, parseBoolFlag, runWithRetry } = require('../core/args');
|
|
468
|
+
const { getToken, getTokenInfo } = require('../core/auth');
|
|
469
|
+
const { API_BASE } = require('../core/config');
|
|
470
|
+
|
|
471
|
+
const api = parseFlag(args, '--api') || API_BASE;
|
|
472
|
+
const explicitToken = parseFlag(args, '--token');
|
|
473
|
+
|
|
474
|
+
// Parse and validate before auth
|
|
475
|
+
const srt = parseFlag(args, '--srt');
|
|
476
|
+
const text = parseFlag(args, '--text');
|
|
477
|
+
const input = parseFlag(args, '--input');
|
|
478
|
+
const from = parseFlag(args, '--from');
|
|
479
|
+
const to = parseFlag(args, '--to');
|
|
480
|
+
const output = parseFlag(args, '--output', '-o');
|
|
481
|
+
const realign = parseBoolFlag(args, '--realign');
|
|
482
|
+
const batchSize = parseIntFlag(args, '--batch-size');
|
|
483
|
+
|
|
484
|
+
// Validate --to is required
|
|
485
|
+
if (!to && !parseBoolFlag(args, '--help')) {
|
|
486
|
+
console.error('Error: --to <lang> is required. Example: voxflow translate --srt file.srt --to en');
|
|
487
|
+
process.exit(1);
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
// Validate language codes
|
|
491
|
+
const validLangs = ['zh', 'en', 'ja', 'ko', 'fr', 'de', 'es', 'pt', 'ru', 'ar', 'th', 'vi', 'it', 'id', 'ms', 'yue'];
|
|
492
|
+
if (to && !validLangs.includes(to)) {
|
|
493
|
+
console.error(`Error: --to must be one of: ${validLangs.join(', ')} (got: "${to}")`);
|
|
494
|
+
process.exit(1);
|
|
495
|
+
}
|
|
496
|
+
if (from && !validLangs.includes(from) && from !== 'auto') {
|
|
497
|
+
console.error(`Error: --from must be one of: auto, ${validLangs.join(', ')} (got: "${from}")`);
|
|
498
|
+
process.exit(1);
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
// Validate input source
|
|
502
|
+
const sources = [srt, text, input].filter(Boolean).length;
|
|
503
|
+
if (sources === 0 && !parseBoolFlag(args, '--help')) {
|
|
504
|
+
console.error('Error: Provide one of: --srt <file>, --text <text>, --input <file>');
|
|
505
|
+
process.exit(1);
|
|
506
|
+
}
|
|
507
|
+
if (sources > 1) {
|
|
508
|
+
console.error('Error: Specify only one input: --srt, --text, or --input');
|
|
509
|
+
process.exit(1);
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
// Validate file existence
|
|
513
|
+
if (srt) {
|
|
514
|
+
const fs = require('fs');
|
|
515
|
+
const path = require('path');
|
|
516
|
+
const resolved = path.resolve(srt);
|
|
517
|
+
if (!fs.existsSync(resolved)) {
|
|
518
|
+
console.error(`Error: SRT file not found: ${resolved}`);
|
|
519
|
+
process.exit(1);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
if (input) {
|
|
523
|
+
const fs = require('fs');
|
|
524
|
+
const path = require('path');
|
|
525
|
+
const resolved = path.resolve(input);
|
|
526
|
+
if (!fs.existsSync(resolved)) {
|
|
527
|
+
console.error(`Error: Input file not found: ${resolved}`);
|
|
528
|
+
process.exit(1);
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
// Validate batch size
|
|
533
|
+
if (batchSize !== undefined) {
|
|
534
|
+
if (isNaN(batchSize) || batchSize < 1 || batchSize > 20) {
|
|
535
|
+
console.error(`Error: --batch-size must be between 1 and 20 (got: "${parseFlag(args, '--batch-size')}")`);
|
|
536
|
+
process.exit(1);
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
// Authenticate after all validation
|
|
541
|
+
let token;
|
|
542
|
+
if (explicitToken) {
|
|
543
|
+
token = explicitToken;
|
|
544
|
+
} else {
|
|
545
|
+
token = await getToken({ api });
|
|
546
|
+
const info = getTokenInfo();
|
|
547
|
+
if (info) {
|
|
548
|
+
console.log(`\x1b[32mLogged in as ${info.email}\x1b[0m`);
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
const opts = {
|
|
553
|
+
token, api, srt, text, input, from, to, output, realign, batchSize,
|
|
554
|
+
};
|
|
555
|
+
|
|
556
|
+
await runWithRetry(translate, opts, api, explicitToken);
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
const meta = {
|
|
560
|
+
translate: {
|
|
561
|
+
usage: '[opts]',
|
|
562
|
+
description: 'Translate SRT subtitles, text, or files',
|
|
563
|
+
options: [
|
|
564
|
+
`--srt <file> SRT subtitle file to translate`,
|
|
565
|
+
`--text <text> Inline text to translate`,
|
|
566
|
+
`--input <file> Text file (.txt, .md) to translate`,
|
|
567
|
+
`--from <lang> Source language code (default: auto-detect)`,
|
|
568
|
+
`--to <lang> Target language code (required)`,
|
|
569
|
+
`--output <path> Output file path (default: <input>-<lang>.<ext>)`,
|
|
570
|
+
`--realign Adjust subtitle timing for target language length`,
|
|
571
|
+
`--batch-size <n> Captions per LLM call, 1-20 (default: ${TRANSLATE_DEFAULTS.batchSize})`,
|
|
572
|
+
],
|
|
573
|
+
examples: [
|
|
574
|
+
'voxflow translate --srt subtitles.srt --to en',
|
|
575
|
+
'voxflow translate --text "你好世界" --to en',
|
|
576
|
+
'voxflow translate --input article.txt --to en --output article-en.txt',
|
|
577
|
+
],
|
|
578
|
+
},
|
|
579
|
+
};
|
|
580
|
+
|
|
581
|
+
module.exports = {
|
|
582
|
+
translate,
|
|
583
|
+
handle,
|
|
584
|
+
meta,
|
|
585
|
+
LANG_MAP,
|
|
586
|
+
// Expose internals for testing
|
|
587
|
+
_test: {
|
|
588
|
+
buildTranslationPrompt,
|
|
589
|
+
parseTranslationResponse,
|
|
590
|
+
realignTimings,
|
|
591
|
+
batchCaptions,
|
|
592
|
+
},
|
|
593
|
+
};
|