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,577 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* VoxFlow CLI — Video Translate command
|
|
3
|
+
*
|
|
4
|
+
* End-to-end video translation pipeline:
|
|
5
|
+
* 1. Extract audio from video (FFmpeg)
|
|
6
|
+
* 2. Transcribe audio → SRT (ASR, forced flash mode for fine-grained timestamps)
|
|
7
|
+
* 3. Translate SRT → target language (LLM)
|
|
8
|
+
* 4. TTS dub + burn subtitles + merge back into video (Dub + FFmpeg)
|
|
9
|
+
*
|
|
10
|
+
* Chains existing asr(), translate(), dub() functions programmatically.
|
|
11
|
+
* All intermediate files go in a temp directory — user only sees the final MP4.
|
|
12
|
+
*
|
|
13
|
+
* Cost: ~2-4 quota (1 ASR + 1 translate batch + 1-N TTS per caption)
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const fs = require('fs');
|
|
17
|
+
const path = require('path');
|
|
18
|
+
const os = require('os');
|
|
19
|
+
const { checkFfmpeg, extractAudio } = require('../core/ffmpeg');
|
|
20
|
+
const { asr } = require('./asr');
|
|
21
|
+
const { translate } = require('./translate');
|
|
22
|
+
const { dub } = require('./dub');
|
|
23
|
+
const { detectLanguage } = require('../core/llm-client');
|
|
24
|
+
const { parseSrt } = require('../core/srt');
|
|
25
|
+
const { API_BASE, VIDEO_TRANSLATE_DEFAULTS } = require('../core/config');
|
|
26
|
+
|
|
27
|
+
// ─── Language → ASR engine mapping ──────────────────────────────────────────
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Map short language codes (used by --from) to Tencent ASR engine types.
|
|
31
|
+
* Falls back to 16k_zh (Chinese) if not found.
|
|
32
|
+
*/
|
|
33
|
+
const LANG_TO_ASR_ENGINE = {
|
|
34
|
+
zh: '16k_zh',
|
|
35
|
+
en: '16k_en',
|
|
36
|
+
ja: '16k_ja',
|
|
37
|
+
ko: '16k_ko',
|
|
38
|
+
// Mixed modes
|
|
39
|
+
'zh-en': '16k_zh_en',
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
function resolveAsrLang(fromCode, explicitAsrLang) {
|
|
43
|
+
// Explicit --asr-lang takes priority
|
|
44
|
+
if (explicitAsrLang) return explicitAsrLang;
|
|
45
|
+
// Map from --from code
|
|
46
|
+
if (fromCode && LANG_TO_ASR_ENGINE[fromCode]) return LANG_TO_ASR_ENGINE[fromCode];
|
|
47
|
+
// Default to Chinese
|
|
48
|
+
return '16k_zh';
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function looksUntranslated(translatedText, sourceText, targetLang) {
|
|
52
|
+
if (translatedText === sourceText) return true;
|
|
53
|
+
const latinTargets = ['en', 'fr', 'de', 'es', 'pt', 'it', 'vi'];
|
|
54
|
+
if (latinTargets.includes(targetLang)) {
|
|
55
|
+
const cjkChars = (translatedText.match(/[一-鿿-ヿ가-]/g) || []).length;
|
|
56
|
+
const totalChars = translatedText.replace(/\s/g, '').length;
|
|
57
|
+
if (totalChars > 0 && cjkChars / totalChars > 0.5) return true;
|
|
58
|
+
}
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// ─── Main command ────────────────────────────────────────────────────────────
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Video-translate command entry point (with SIGINT guard).
|
|
66
|
+
*
|
|
67
|
+
* @param {object} opts
|
|
68
|
+
* @param {string} opts.token - JWT auth token
|
|
69
|
+
* @param {string} [opts.api] - Backend base URL
|
|
70
|
+
* @param {string} opts.input - Input video file path
|
|
71
|
+
* @param {string} [opts.from] - Source language code (auto-detect if omitted)
|
|
72
|
+
* @param {string} opts.to - Target language code (required)
|
|
73
|
+
* @param {string} [opts.voice] - TTS voice ID
|
|
74
|
+
* @param {string} [opts.voicesMap] - Voice mapping JSON file path
|
|
75
|
+
* @param {boolean}[opts.realign] - Adjust subtitle timing for target language
|
|
76
|
+
* @param {string} [opts.output] - Output MP4 path
|
|
77
|
+
* @param {boolean}[opts.keepIntermediates] - Keep temp files after completion
|
|
78
|
+
* @param {number} [opts.batchSize] - Translation batch size
|
|
79
|
+
* @param {number} [opts.speed] - TTS speed (0.5-2.0)
|
|
80
|
+
* @param {string} [opts.asrMode] - Override ASR mode: sentence, flash, file, auto
|
|
81
|
+
* @param {string} [opts.asrLang] - Override ASR engine type: 16k_zh, 16k_en, etc.
|
|
82
|
+
* @param {string} [opts.engine] - ASR engine: auto, local, cloud (default: auto)
|
|
83
|
+
* @param {string} [opts.model] - Whisper model for local engine: tiny, base, small, medium, large
|
|
84
|
+
* @returns {Promise<object>} Result with outputPath and stage details
|
|
85
|
+
*/
|
|
86
|
+
async function videoTranslate(opts) {
|
|
87
|
+
const sigintHandler = () => {
|
|
88
|
+
console.log('\n\nVideo translation cancelled.');
|
|
89
|
+
process.exit(130);
|
|
90
|
+
};
|
|
91
|
+
process.on('SIGINT', sigintHandler);
|
|
92
|
+
|
|
93
|
+
// Background refresh: video-translate routinely outlives the 1h Supabase
|
|
94
|
+
// JWT (long video → ASR poll → batch translate → per-caption TTS). Refresh
|
|
95
|
+
// proactively at expiresAt-5min so the user doesn't see "Token expired,
|
|
96
|
+
// refreshing..." mid-pipeline.
|
|
97
|
+
const { startBackgroundRefresh } = require('../core/auth');
|
|
98
|
+
const stopRefresh = startBackgroundRefresh({ api: opts.api });
|
|
99
|
+
|
|
100
|
+
try {
|
|
101
|
+
return await _videoTranslate(opts);
|
|
102
|
+
} finally {
|
|
103
|
+
stopRefresh();
|
|
104
|
+
process.removeListener('SIGINT', sigintHandler);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async function _videoTranslate(opts) {
|
|
109
|
+
const {
|
|
110
|
+
token,
|
|
111
|
+
api = API_BASE,
|
|
112
|
+
input,
|
|
113
|
+
from,
|
|
114
|
+
to,
|
|
115
|
+
voice,
|
|
116
|
+
voicesMap,
|
|
117
|
+
realign = false,
|
|
118
|
+
output: userOutput,
|
|
119
|
+
keepIntermediates = false,
|
|
120
|
+
batchSize = VIDEO_TRANSLATE_DEFAULTS.batchSize,
|
|
121
|
+
speed = VIDEO_TRANSLATE_DEFAULTS.speed,
|
|
122
|
+
asrMode,
|
|
123
|
+
asrLang,
|
|
124
|
+
engine = 'auto',
|
|
125
|
+
model,
|
|
126
|
+
} = opts;
|
|
127
|
+
|
|
128
|
+
const resolvedInput = path.resolve(input);
|
|
129
|
+
const inputBasename = path.basename(resolvedInput, path.extname(resolvedInput));
|
|
130
|
+
|
|
131
|
+
console.log('\n=== VoxFlow Video Translate ===');
|
|
132
|
+
console.log(`Input: ${path.basename(resolvedInput)}`);
|
|
133
|
+
console.log(`Target: ${to}`);
|
|
134
|
+
console.log('');
|
|
135
|
+
|
|
136
|
+
// Create temp directory for intermediate files
|
|
137
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'voxflow-vtranslate-'));
|
|
138
|
+
|
|
139
|
+
let totalQuota = 0;
|
|
140
|
+
const stages = {};
|
|
141
|
+
|
|
142
|
+
try {
|
|
143
|
+
// ─── Stage 1: Check FFmpeg ──────────────────────────────────────────
|
|
144
|
+
process.stdout.write('[1/5] Checking FFmpeg... ');
|
|
145
|
+
const ffmpegInfo = await checkFfmpeg();
|
|
146
|
+
if (!ffmpegInfo.available) {
|
|
147
|
+
throw new Error('FFmpeg is required for video-translate. Install: https://ffmpeg.org/download.html');
|
|
148
|
+
}
|
|
149
|
+
console.log(`OK (${ffmpegInfo.version})`);
|
|
150
|
+
|
|
151
|
+
// ─── Stage 2: Extract audio + ASR ───────────────────────────────────
|
|
152
|
+
process.stdout.write('[2/5] Transcribing audio... ');
|
|
153
|
+
const audioPath = path.join(tmpDir, 'extracted-audio.wav');
|
|
154
|
+
await extractAudio(resolvedInput, audioPath);
|
|
155
|
+
|
|
156
|
+
const asrOutputPath = path.join(tmpDir, 'source.srt');
|
|
157
|
+
const effectiveAsrLang = resolveAsrLang(from, asrLang);
|
|
158
|
+
const asrOpts = {
|
|
159
|
+
token,
|
|
160
|
+
api,
|
|
161
|
+
input: audioPath,
|
|
162
|
+
format: 'srt',
|
|
163
|
+
output: asrOutputPath,
|
|
164
|
+
lang: effectiveAsrLang,
|
|
165
|
+
engine,
|
|
166
|
+
// Force 'flash' mode for video-translate: ensures fine-grained per-sentence
|
|
167
|
+
// timestamps (one caption per sentence) instead of 'sentence' mode which
|
|
168
|
+
// dumps everything into a single giant block.
|
|
169
|
+
mode: asrMode || 'flash',
|
|
170
|
+
};
|
|
171
|
+
if (model) asrOpts.model = model;
|
|
172
|
+
|
|
173
|
+
let asrResult;
|
|
174
|
+
try {
|
|
175
|
+
asrResult = await asr(asrOpts);
|
|
176
|
+
} catch (asrErr) {
|
|
177
|
+
// Flash mode can fail on some Tencent backend issues — fallback to azure
|
|
178
|
+
if (!asrMode && engine !== 'local' && asrErr.message && asrErr.message.includes('500')) {
|
|
179
|
+
console.log(`\n ⚠ Flash ASR failed, falling back to Azure...`);
|
|
180
|
+
asrOpts.engine = 'azure';
|
|
181
|
+
asrOpts.mode = undefined;
|
|
182
|
+
// Map Tencent lang codes to Azure BCP-47
|
|
183
|
+
const azureLangMap = { '16k_zh': 'zh-CN', '16k_en': 'en-US', '16k_ja': 'ja-JP', '16k_ko': 'ko-KR', '16k_zh_en': 'auto' };
|
|
184
|
+
asrOpts.lang = azureLangMap[effectiveAsrLang] || 'auto';
|
|
185
|
+
asrResult = await asr(asrOpts);
|
|
186
|
+
} else {
|
|
187
|
+
throw asrErr;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (asrResult.captionCount === 0) {
|
|
192
|
+
throw new Error('ASR produced no captions. The video may have no audible speech.');
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
stages.asr = {
|
|
196
|
+
mode: asrResult.mode,
|
|
197
|
+
duration: asrResult.duration,
|
|
198
|
+
captionCount: asrResult.captionCount,
|
|
199
|
+
quotaUsed: asrResult.quotaUsed,
|
|
200
|
+
};
|
|
201
|
+
totalQuota += asrResult.quotaUsed;
|
|
202
|
+
console.log(`${asrResult.captionCount} captions (${asrResult.mode} mode)`);
|
|
203
|
+
|
|
204
|
+
// ─── Detect source language if not specified ────────────────────────
|
|
205
|
+
let fromCode = from;
|
|
206
|
+
if (!fromCode) {
|
|
207
|
+
const srtContent = fs.readFileSync(asrOutputPath, 'utf8');
|
|
208
|
+
const captions = parseSrt(srtContent);
|
|
209
|
+
const sample = captions.slice(0, 3).map(c => c.text).join(' ');
|
|
210
|
+
fromCode = await detectLanguage({ apiBase: api, text: sample }) || 'auto';
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// ─── Stage 3: Translate ─────────────────────────────────────────────
|
|
214
|
+
process.stdout.write(`[3/5] Translating (${fromCode} → ${to})... `);
|
|
215
|
+
const translatedSrtPath = path.join(tmpDir, `translated-${to}.srt`);
|
|
216
|
+
const translateResult = await translate({
|
|
217
|
+
token,
|
|
218
|
+
api,
|
|
219
|
+
srt: asrOutputPath,
|
|
220
|
+
from: fromCode,
|
|
221
|
+
to,
|
|
222
|
+
output: translatedSrtPath,
|
|
223
|
+
realign,
|
|
224
|
+
batchSize,
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
stages.translate = {
|
|
228
|
+
from: translateResult.from,
|
|
229
|
+
to: translateResult.to,
|
|
230
|
+
captionCount: translateResult.captionCount,
|
|
231
|
+
quotaUsed: translateResult.quotaUsed,
|
|
232
|
+
};
|
|
233
|
+
totalQuota += translateResult.quotaUsed;
|
|
234
|
+
|
|
235
|
+
// ─── Translation quality fix: re-translate untranslated captions ────
|
|
236
|
+
const translatedContent = fs.readFileSync(translatedSrtPath, 'utf8');
|
|
237
|
+
const translatedCaptions = parseSrt(translatedContent);
|
|
238
|
+
const sourceCaptions = parseSrt(fs.readFileSync(asrOutputPath, 'utf8'));
|
|
239
|
+
const { formatSrt } = require('../core/srt');
|
|
240
|
+
const { chatCompletion } = require('../core/llm-client');
|
|
241
|
+
const { TRANSLATE_DEFAULTS } = require('../core/config');
|
|
242
|
+
|
|
243
|
+
let fixedCount = 0;
|
|
244
|
+
for (let ci = 0; ci < translatedCaptions.length && ci < sourceCaptions.length; ci++) {
|
|
245
|
+
if (looksUntranslated(translatedCaptions[ci].text, sourceCaptions[ci].text, to)) {
|
|
246
|
+
// This caption was not translated — retry individually (up to 2 attempts)
|
|
247
|
+
const { LANG_MAP } = require('./translate');
|
|
248
|
+
const toLang = LANG_MAP[to] || to;
|
|
249
|
+
const fromLang = LANG_MAP[fromCode] || fromCode;
|
|
250
|
+
|
|
251
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
252
|
+
try {
|
|
253
|
+
if (attempt > 0) await new Promise(r => setTimeout(r, 2000)); // wait 2s before retry
|
|
254
|
+
const singleResult = await chatCompletion({
|
|
255
|
+
apiBase: api,
|
|
256
|
+
token,
|
|
257
|
+
messages: [
|
|
258
|
+
{ role: 'system', content: `Translate this subtitle from ${fromLang} to ${toLang}. Return ONLY the translation, nothing else.` },
|
|
259
|
+
{ role: 'user', content: sourceCaptions[ci].text },
|
|
260
|
+
],
|
|
261
|
+
temperature: TRANSLATE_DEFAULTS.temperature,
|
|
262
|
+
maxTokens: TRANSLATE_DEFAULTS.maxTokens,
|
|
263
|
+
});
|
|
264
|
+
const fixed = singleResult.content.trim();
|
|
265
|
+
if (fixed && fixed !== sourceCaptions[ci].text) {
|
|
266
|
+
translatedCaptions[ci].text = fixed;
|
|
267
|
+
fixedCount++;
|
|
268
|
+
break; // success
|
|
269
|
+
}
|
|
270
|
+
totalQuota++;
|
|
271
|
+
} catch {
|
|
272
|
+
// Retry on next iteration or give up
|
|
273
|
+
totalQuota++;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Write the fixed SRT back
|
|
280
|
+
if (fixedCount > 0) {
|
|
281
|
+
const fixedSrt = formatSrt(translatedCaptions);
|
|
282
|
+
fs.writeFileSync(translatedSrtPath, fixedSrt, 'utf8');
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
const finalUntranslated = translatedCaptions.filter((c, i) => i < sourceCaptions.length && looksUntranslated(c.text, sourceCaptions[i].text, to)).length;
|
|
286
|
+
if (fixedCount > 0) {
|
|
287
|
+
console.log(`${translateResult.captionCount} captions translated (${fixedCount} fixed on retry)`);
|
|
288
|
+
} else {
|
|
289
|
+
console.log(`${translateResult.captionCount} captions translated`);
|
|
290
|
+
}
|
|
291
|
+
if (finalUntranslated > 0) {
|
|
292
|
+
console.log(` ⚠ ${finalUntranslated} caption(s) could not be translated`);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// ─── Stage 4: Dub + Video merge ─────────────────────────────────────
|
|
296
|
+
process.stdout.write('[4/5] Dubbing and merging video... ');
|
|
297
|
+
|
|
298
|
+
// Determine final output path (use temp for now; subtitle burn-in is stage 5)
|
|
299
|
+
// Pre-build filename — see scripts/check-no-asset-rewrite.js (ncc rewrite trap)
|
|
300
|
+
const defaultFname = inputBasename + '-' + to + '.mp4';
|
|
301
|
+
const outputPath = userOutput
|
|
302
|
+
? path.resolve(userOutput)
|
|
303
|
+
: path.resolve(path.dirname(resolvedInput), defaultFname);
|
|
304
|
+
|
|
305
|
+
// Dub into a temp file first — subtitle burn-in happens in stage 5
|
|
306
|
+
const dubbedTempPath = path.join(tmpDir, 'dubbed-nosubs.mp4');
|
|
307
|
+
|
|
308
|
+
const dubResult = await dub({
|
|
309
|
+
token,
|
|
310
|
+
api,
|
|
311
|
+
srt: translatedSrtPath,
|
|
312
|
+
voice,
|
|
313
|
+
voicesMap,
|
|
314
|
+
speed,
|
|
315
|
+
speedAuto: true, // Always enable speed-auto for video-translate
|
|
316
|
+
video: resolvedInput,
|
|
317
|
+
output: dubbedTempPath,
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
stages.dub = {
|
|
321
|
+
segmentCount: dubResult.segmentCount,
|
|
322
|
+
duration: dubResult.duration,
|
|
323
|
+
quotaUsed: dubResult.quotaUsed,
|
|
324
|
+
warnings: dubResult.warnings,
|
|
325
|
+
};
|
|
326
|
+
totalQuota += dubResult.quotaUsed;
|
|
327
|
+
console.log(`${dubResult.segmentCount} segments dubbed`);
|
|
328
|
+
|
|
329
|
+
// ─── Stage 5: Burn subtitles into video ─────────────────────────────
|
|
330
|
+
process.stdout.write('[5/5] Burning subtitles into video... ');
|
|
331
|
+
|
|
332
|
+
// Copy subtitle file to a simple path (avoids ffmpeg path parsing issues)
|
|
333
|
+
const subTempPath = path.join(tmpDir, 'subs.srt');
|
|
334
|
+
fs.copyFileSync(translatedSrtPath, subTempPath);
|
|
335
|
+
|
|
336
|
+
let subtitlesBurned = false;
|
|
337
|
+
try {
|
|
338
|
+
const { runCommand } = require('../core/ffmpeg');
|
|
339
|
+
await runCommand('ffmpeg', [
|
|
340
|
+
'-i', dubbedTempPath,
|
|
341
|
+
'-vf', `subtitles=${subTempPath}:force_style='FontSize=18,PrimaryColour=&Hffffff,OutlineColour=&H000000,Outline=2,Shadow=1,Alignment=2,Bold=1'`,
|
|
342
|
+
'-c:a', 'copy',
|
|
343
|
+
'-y',
|
|
344
|
+
outputPath,
|
|
345
|
+
]);
|
|
346
|
+
subtitlesBurned = true;
|
|
347
|
+
console.log('OK');
|
|
348
|
+
} catch (err) {
|
|
349
|
+
// Subtitle burn-in failed (likely no libass) — fall back to video without subs
|
|
350
|
+
console.log(`skip (${err.message?.includes('subtitles') ? 'libass not available' : 'failed'})`);
|
|
351
|
+
// Move dubbed file as final output
|
|
352
|
+
fs.copyFileSync(dubbedTempPath, outputPath);
|
|
353
|
+
// Save SRT alongside output for external use
|
|
354
|
+
const srtOutputPath = outputPath.replace(/\.[^.]+$/, `.${to}.srt`);
|
|
355
|
+
fs.copyFileSync(translatedSrtPath, srtOutputPath);
|
|
356
|
+
console.log(` → Subtitle file saved separately: ${srtOutputPath}`);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// ─── Copy intermediates if requested ────────────────────────────────
|
|
360
|
+
if (keepIntermediates) {
|
|
361
|
+
const intermediateDir = path.resolve(
|
|
362
|
+
path.dirname(outputPath),
|
|
363
|
+
`${inputBasename}-${to}-intermediates`,
|
|
364
|
+
);
|
|
365
|
+
fs.mkdirSync(intermediateDir, { recursive: true });
|
|
366
|
+
|
|
367
|
+
const filesToCopy = [
|
|
368
|
+
['extracted-audio.wav', audioPath],
|
|
369
|
+
['source.srt', asrOutputPath],
|
|
370
|
+
[`translated-${to}.srt`, translatedSrtPath],
|
|
371
|
+
];
|
|
372
|
+
|
|
373
|
+
for (const [name, src] of filesToCopy) {
|
|
374
|
+
if (fs.existsSync(src)) {
|
|
375
|
+
fs.copyFileSync(src, path.join(intermediateDir, name));
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
console.log(`\nIntermediates saved: ${intermediateDir}`);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// ─── Always save SRT alongside output (useful for external players) ──
|
|
383
|
+
const srtOutputPath = outputPath.replace(/\.[^.]+$/, `.srt`);
|
|
384
|
+
fs.copyFileSync(translatedSrtPath, srtOutputPath);
|
|
385
|
+
|
|
386
|
+
// ─── Summary ────────────────────────────────────────────────────────
|
|
387
|
+
console.log('\n=== Done ===');
|
|
388
|
+
console.log(`Output: ${outputPath}`);
|
|
389
|
+
console.log(`Subtitle: ${srtOutputPath}`);
|
|
390
|
+
console.log(`Language: ${fromCode} → ${to}`);
|
|
391
|
+
console.log(`Captions: ${translateResult.captionCount}`);
|
|
392
|
+
console.log(`Duration: ${dubResult.duration.toFixed(1)}s`);
|
|
393
|
+
console.log(`Quota: ${totalQuota} used`);
|
|
394
|
+
|
|
395
|
+
if (stages.dub.warnings && stages.dub.warnings.length > 0) {
|
|
396
|
+
console.log(`\nWarnings:`);
|
|
397
|
+
for (const w of stages.dub.warnings) {
|
|
398
|
+
console.log(` - ${w}`);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// ─── Auto-open the output video ─────────────────────────────────────
|
|
403
|
+
try {
|
|
404
|
+
const open = require('open');
|
|
405
|
+
await open(outputPath);
|
|
406
|
+
} catch {
|
|
407
|
+
// open() may fail in headless/CI environments — ignore silently
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
return {
|
|
411
|
+
outputPath,
|
|
412
|
+
srtOutputPath,
|
|
413
|
+
from: fromCode,
|
|
414
|
+
to,
|
|
415
|
+
captionCount: translateResult.captionCount,
|
|
416
|
+
quotaUsed: totalQuota,
|
|
417
|
+
stages,
|
|
418
|
+
};
|
|
419
|
+
} finally {
|
|
420
|
+
// Cleanup temp directory
|
|
421
|
+
if (!keepIntermediates) {
|
|
422
|
+
try {
|
|
423
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
424
|
+
} catch {
|
|
425
|
+
// Best-effort cleanup
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// ─── Exports ─────────────────────────────────────────────────────────────────
|
|
432
|
+
|
|
433
|
+
// ─── CLI Handler ────────────────────────────────────────────────────────────
|
|
434
|
+
|
|
435
|
+
async function handle(args) {
|
|
436
|
+
const { parseFlag, parseIntFlag, parseFloatFlag, parseBoolFlag, validateSpeed, runWithRetry } = require('../core/args');
|
|
437
|
+
const { getToken, getTokenInfo } = require('../core/auth');
|
|
438
|
+
const { API_BASE } = require('../core/config');
|
|
439
|
+
|
|
440
|
+
const api = parseFlag(args, '--api') || API_BASE;
|
|
441
|
+
const explicitToken = parseFlag(args, '--token');
|
|
442
|
+
|
|
443
|
+
// Parse and validate before auth
|
|
444
|
+
const input = parseFlag(args, '--input');
|
|
445
|
+
const from = parseFlag(args, '--from');
|
|
446
|
+
const to = parseFlag(args, '--to');
|
|
447
|
+
const voice = parseFlag(args, '--voice');
|
|
448
|
+
const voicesMap = parseFlag(args, '--voices');
|
|
449
|
+
const output = parseFlag(args, '--output', '-o');
|
|
450
|
+
const realign = parseBoolFlag(args, '--realign');
|
|
451
|
+
const keepIntermediates = parseBoolFlag(args, '--keep-intermediates');
|
|
452
|
+
const batchSize = parseIntFlag(args, '--batch-size');
|
|
453
|
+
const speed = parseFloatFlag(args, '--speed');
|
|
454
|
+
const asrMode = parseFlag(args, '--asr-mode');
|
|
455
|
+
const asrLang = parseFlag(args, '--asr-lang');
|
|
456
|
+
const engine = parseFlag(args, '--engine');
|
|
457
|
+
const model = parseFlag(args, '--model');
|
|
458
|
+
|
|
459
|
+
// Validate --input is required
|
|
460
|
+
if (!input) {
|
|
461
|
+
console.error('Error: --input <video-file> is required. Example: voxflow video-translate --input video.mp4 --to en');
|
|
462
|
+
process.exit(1);
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// Validate --to is required
|
|
466
|
+
if (!to) {
|
|
467
|
+
console.error('Error: --to <lang> is required. Example: voxflow video-translate --input video.mp4 --to en');
|
|
468
|
+
process.exit(1);
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
// Validate language codes
|
|
472
|
+
const validLangs = ['zh', 'en', 'ja', 'ko', 'fr', 'de', 'es', 'pt', 'ru', 'ar', 'th', 'vi', 'it'];
|
|
473
|
+
if (to && !validLangs.includes(to)) {
|
|
474
|
+
console.error(`Error: --to must be one of: ${validLangs.join(', ')} (got: "${to}")`);
|
|
475
|
+
process.exit(1);
|
|
476
|
+
}
|
|
477
|
+
if (from && !validLangs.includes(from) && from !== 'auto') {
|
|
478
|
+
console.error(`Error: --from must be one of: auto, ${validLangs.join(', ')} (got: "${from}")`);
|
|
479
|
+
process.exit(1);
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
// Validate input file exists
|
|
483
|
+
if (input) {
|
|
484
|
+
const fs = require('fs');
|
|
485
|
+
const path = require('path');
|
|
486
|
+
const resolved = path.resolve(input);
|
|
487
|
+
if (!fs.existsSync(resolved)) {
|
|
488
|
+
console.error(`Error: Video file not found: ${resolved}`);
|
|
489
|
+
process.exit(1);
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
// Validate speed
|
|
494
|
+
validateSpeed(args, speed);
|
|
495
|
+
|
|
496
|
+
// Validate batch size
|
|
497
|
+
if (batchSize !== undefined && (isNaN(batchSize) || batchSize < 1 || batchSize > 20)) {
|
|
498
|
+
console.error(`Error: --batch-size must be between 1 and 20 (got: "${parseFlag(args, '--batch-size')}")`);
|
|
499
|
+
process.exit(1);
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
// Validate ASR mode
|
|
503
|
+
const validAsrModes = ['auto', 'sentence', 'flash', 'file'];
|
|
504
|
+
if (asrMode && !validAsrModes.includes(asrMode)) {
|
|
505
|
+
console.error(`Error: --asr-mode must be one of: ${validAsrModes.join(', ')} (got: "${asrMode}")`);
|
|
506
|
+
process.exit(1);
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
// Validate engine
|
|
510
|
+
const validEngines = ['auto', 'local', 'cloud', 'whisper', 'tencent'];
|
|
511
|
+
if (engine && !validEngines.includes(engine)) {
|
|
512
|
+
console.error(`Error: --engine must be one of: ${validEngines.join(', ')} (got: "${engine}")`);
|
|
513
|
+
process.exit(1);
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
// Validate voices map file exists
|
|
517
|
+
if (voicesMap) {
|
|
518
|
+
const fs = require('fs');
|
|
519
|
+
const path = require('path');
|
|
520
|
+
const resolved = path.resolve(voicesMap);
|
|
521
|
+
if (!fs.existsSync(resolved)) {
|
|
522
|
+
console.error(`Error: Voices map file not found: ${resolved}`);
|
|
523
|
+
process.exit(1);
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
// Authenticate after all validation
|
|
528
|
+
let token;
|
|
529
|
+
if (explicitToken) {
|
|
530
|
+
token = explicitToken;
|
|
531
|
+
} else {
|
|
532
|
+
token = await getToken({ api });
|
|
533
|
+
const info = getTokenInfo();
|
|
534
|
+
if (info) {
|
|
535
|
+
console.log(`\x1b[32mLogged in as ${info.email}\x1b[0m`);
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
const opts = {
|
|
540
|
+
token, api, input, from, to, voice, voicesMap, output,
|
|
541
|
+
realign, keepIntermediates, batchSize, speed, asrMode, asrLang,
|
|
542
|
+
engine, model,
|
|
543
|
+
};
|
|
544
|
+
|
|
545
|
+
await runWithRetry(videoTranslate, opts, api, explicitToken);
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
const meta = {
|
|
549
|
+
'video-translate': {
|
|
550
|
+
usage: '[opts]',
|
|
551
|
+
description: 'Translate entire video: ASR → translate → dub → merge',
|
|
552
|
+
options: [
|
|
553
|
+
`--input <file> Input video file (required)`,
|
|
554
|
+
`--to <lang> Target language code (required)`,
|
|
555
|
+
`--from <lang> Source language code (default: auto-detect)`,
|
|
556
|
+
`--voice <id> TTS voice ID for dubbed audio`,
|
|
557
|
+
`--voices <file> JSON speaker→voiceId map for multi-speaker dubbing`,
|
|
558
|
+
`--realign Adjust subtitle timing for target language length`,
|
|
559
|
+
`--speed <n> TTS speed 0.5-2.0 (default: ${require('../core/config').VIDEO_TRANSLATE_DEFAULTS.speed})`,
|
|
560
|
+
`--batch-size <n> Translation batch size, 1-20 (default: ${require('../core/config').VIDEO_TRANSLATE_DEFAULTS.batchSize})`,
|
|
561
|
+
`--keep-intermediates Keep intermediate files (SRT, audio) for debugging`,
|
|
562
|
+
`--output <path> Output MP4 path (default: <input>-<lang>.mp4)`,
|
|
563
|
+
`--asr-mode <mode> Override ASR mode: auto, sentence, flash, file`,
|
|
564
|
+
`--asr-lang <engine> Override ASR engine: 16k_zh, 16k_en, 16k_ja, 16k_ko, etc.`,
|
|
565
|
+
`--engine <engine> ASR engine: auto, local, cloud (default: auto)`,
|
|
566
|
+
`--model <model> Whisper model for local engine: tiny, base, small, medium, large`,
|
|
567
|
+
],
|
|
568
|
+
examples: [
|
|
569
|
+
'voxflow video-translate --input video.mp4 --to en',
|
|
570
|
+
'voxflow video-translate --input video.mp4 --from zh --to en --realign',
|
|
571
|
+
'voxflow video-translate --input video.mp4 --to ja --voice v-male-Bk7vD3xP',
|
|
572
|
+
'voxflow video-translate --input video.mp4 --to en --engine local',
|
|
573
|
+
],
|
|
574
|
+
},
|
|
575
|
+
};
|
|
576
|
+
|
|
577
|
+
module.exports = { videoTranslate, handle, meta };
|