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,542 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* VoxFlow CLI — Dub command
|
|
3
|
+
*
|
|
4
|
+
* Video dubbing: SRT subtitle → per-sentence TTS → timeline-aligned audio.
|
|
5
|
+
* Supports multi-speaker voice mapping, dynamic speed compensation,
|
|
6
|
+
* video merging, BGM ducking, and patch mode for partial re-synthesis.
|
|
7
|
+
*
|
|
8
|
+
* Pipeline: SRT → parse captions → TTS each → timeline align → WAV/MP4
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const fs = require('fs');
|
|
12
|
+
const path = require('path');
|
|
13
|
+
const { DUB_DEFAULTS } = require('../core/config');
|
|
14
|
+
const { ApiError } = require('../core/http');
|
|
15
|
+
const { buildWav } = require('../core/audio');
|
|
16
|
+
const { parseSrt } = require('../core/srt');
|
|
17
|
+
const { buildTimelineAudio, msToBytes, BYTES_PER_MS } = require('../core/timeline');
|
|
18
|
+
const { synthesizeTTS } = require('../core/tts-synthesizer');
|
|
19
|
+
|
|
20
|
+
// ─── Voice Map Parsing ──────────────────────────────────────────────────────
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Parse a voices.json file for multi-speaker mapping.
|
|
24
|
+
* Format: { "SpeakerName": "voiceId", ... }
|
|
25
|
+
* @param {string} filePath
|
|
26
|
+
* @returns {object} Speaker → voiceId map
|
|
27
|
+
*/
|
|
28
|
+
function parseVoicesMap(filePath) {
|
|
29
|
+
if (!fs.existsSync(filePath)) {
|
|
30
|
+
throw new Error(`Voices map file not found: ${filePath}`);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
let raw;
|
|
34
|
+
try {
|
|
35
|
+
raw = JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
36
|
+
} catch (err) {
|
|
37
|
+
throw new Error(`Invalid JSON in voices map: ${err.message}`);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (typeof raw !== 'object' || raw === null || Array.isArray(raw)) {
|
|
41
|
+
throw new Error('Voices map must be a JSON object: { "SpeakerName": "voiceId", ... }');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
for (const [speaker, voiceId] of Object.entries(raw)) {
|
|
45
|
+
if (typeof voiceId !== 'string' || voiceId.trim().length === 0) {
|
|
46
|
+
throw new Error(`Invalid voice ID for speaker "${speaker}": must be a non-empty string`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return raw;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// ─── TTS Synthesis ──────────────────────────────────────────────────────────
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Synthesize a single caption text via TTS API.
|
|
57
|
+
* @returns {{ audio: Buffer, quota: object, durationMs: number }}
|
|
58
|
+
*/
|
|
59
|
+
async function synthesizeCaption(apiBase, token, text, voiceId, speed, format, index, total) {
|
|
60
|
+
const result = await synthesizeTTS({
|
|
61
|
+
apiBase, token, text, voiceId,
|
|
62
|
+
speed: speed ?? 1.0,
|
|
63
|
+
format: format || 'pcm',
|
|
64
|
+
index, total,
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// Calculate duration from PCM buffer size: bytes / (sampleRate * bytesPerSample) * 1000
|
|
68
|
+
const durationMs = result.audio.length / BYTES_PER_MS;
|
|
69
|
+
|
|
70
|
+
console.log(` OK (${(result.audio.length / 1024).toFixed(0)} KB, ${(durationMs / 1000).toFixed(1)}s)`);
|
|
71
|
+
return { audio: result.audio, quota: result.quota, durationMs };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// ─── Public API ─────────────────────────────────────────────────────────────
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Dub: convert SRT subtitles to timeline-aligned TTS audio.
|
|
78
|
+
*
|
|
79
|
+
* @param {object} opts
|
|
80
|
+
* @param {string} opts.token - JWT token
|
|
81
|
+
* @param {string} opts.api - API base URL
|
|
82
|
+
* @param {string} opts.srt - Path to SRT subtitle file
|
|
83
|
+
* @param {string} [opts.voice] - Default TTS voice ID
|
|
84
|
+
* @param {string} [opts.voicesMap] - Path to voices.json for multi-speaker
|
|
85
|
+
* @param {number} [opts.speed] - Default TTS speed (0.5-2.0)
|
|
86
|
+
* @param {boolean} [opts.speedAuto] - Enable dynamic speed compensation
|
|
87
|
+
* @param {string} [opts.video] - Path to video file (merge after dubbing)
|
|
88
|
+
* @param {string} [opts.bgm] - Path to BGM audio file
|
|
89
|
+
* @param {number} [opts.ducking] - BGM ducking level (0-1)
|
|
90
|
+
* @param {number} [opts.patch] - Patch mode: re-synthesize only this caption ID
|
|
91
|
+
* @param {string} [opts.output] - Output file path
|
|
92
|
+
* @returns {Promise<{outputPath: string, duration: number, quotaUsed: number, segmentCount: number, warnings: string[]}>}
|
|
93
|
+
*/
|
|
94
|
+
async function dub(opts) {
|
|
95
|
+
let isExiting = false;
|
|
96
|
+
|
|
97
|
+
const sigintHandler = () => {
|
|
98
|
+
if (isExiting) return;
|
|
99
|
+
isExiting = true;
|
|
100
|
+
console.log('\n\nDubbing cancelled.');
|
|
101
|
+
process.exit(130);
|
|
102
|
+
};
|
|
103
|
+
process.on('SIGINT', sigintHandler);
|
|
104
|
+
|
|
105
|
+
try {
|
|
106
|
+
return await _dub(opts);
|
|
107
|
+
} finally {
|
|
108
|
+
process.removeListener('SIGINT', sigintHandler);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async function _dub(opts) {
|
|
113
|
+
const srtPath = opts.srt;
|
|
114
|
+
if (!srtPath) {
|
|
115
|
+
throw new Error('No SRT file provided. Usage: voxflow dub --srt <file.srt>');
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const resolvedSrt = path.resolve(srtPath);
|
|
119
|
+
if (!fs.existsSync(resolvedSrt)) {
|
|
120
|
+
throw new Error(`SRT file not found: ${resolvedSrt}`);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const srtContent = fs.readFileSync(resolvedSrt, 'utf8');
|
|
124
|
+
const captions = parseSrt(srtContent);
|
|
125
|
+
if (captions.length === 0) {
|
|
126
|
+
throw new Error('SRT file contains no valid captions');
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const defaultVoice = opts.voice || DUB_DEFAULTS.voice;
|
|
130
|
+
const defaultSpeed = opts.speed ?? DUB_DEFAULTS.speed;
|
|
131
|
+
const speedAuto = opts.speedAuto || false;
|
|
132
|
+
const toleranceMs = DUB_DEFAULTS.toleranceMs;
|
|
133
|
+
const api = opts.api;
|
|
134
|
+
const token = opts.token;
|
|
135
|
+
const patchId = opts.patch;
|
|
136
|
+
const warnings = [];
|
|
137
|
+
|
|
138
|
+
// Load multi-speaker voice map if provided
|
|
139
|
+
let voicesMap = null;
|
|
140
|
+
if (opts.voicesMap) {
|
|
141
|
+
voicesMap = parseVoicesMap(path.resolve(opts.voicesMap));
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Determine output path
|
|
145
|
+
let outputPath = opts.output;
|
|
146
|
+
const hasVideo = !!opts.video;
|
|
147
|
+
const defaultExt = hasVideo ? '.mp4' : '.wav';
|
|
148
|
+
if (!outputPath) {
|
|
149
|
+
const ts = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
|
|
150
|
+
outputPath = path.resolve(`dub-${ts}${defaultExt}`);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Print header
|
|
154
|
+
console.log('\n=== VoxFlow Dub ===');
|
|
155
|
+
console.log(`SRT: ${srtPath} (${captions.length} captions)`);
|
|
156
|
+
console.log(`Voice: ${defaultVoice}${voicesMap ? ` + voices map (${Object.keys(voicesMap).length} speakers)` : ''}`);
|
|
157
|
+
console.log(`Speed: ${defaultSpeed}${speedAuto ? ' (auto-compensate)' : ''}`);
|
|
158
|
+
if (hasVideo) console.log(`Video: ${opts.video}`);
|
|
159
|
+
if (opts.bgm) console.log(`BGM: ${opts.bgm} (ducking: ${opts.ducking ?? DUB_DEFAULTS.ducking})`);
|
|
160
|
+
if (patchId != null) console.log(`Patch: caption #${patchId}`);
|
|
161
|
+
console.log(`Output: ${outputPath}`);
|
|
162
|
+
|
|
163
|
+
// ─── Patch mode: only re-synthesize one caption ───────────────────────
|
|
164
|
+
|
|
165
|
+
if (patchId != null) {
|
|
166
|
+
return _dubPatch(opts, captions, outputPath, warnings, voicesMap);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// ─── Full synthesis ───────────────────────────────────────────────────
|
|
170
|
+
|
|
171
|
+
console.log(`\n[1/2] Synthesizing TTS audio (${captions.length} captions)...`);
|
|
172
|
+
|
|
173
|
+
const segments = []; // { startMs, endMs, audioBuffer }
|
|
174
|
+
let lastQuota = null;
|
|
175
|
+
let quotaUsed = 0;
|
|
176
|
+
|
|
177
|
+
for (let i = 0; i < captions.length; i++) {
|
|
178
|
+
const cap = captions[i];
|
|
179
|
+
const targetMs = cap.endMs - cap.startMs;
|
|
180
|
+
|
|
181
|
+
// Determine voice for this caption
|
|
182
|
+
let voiceId = defaultVoice;
|
|
183
|
+
if (voicesMap && cap.speakerId && voicesMap[cap.speakerId]) {
|
|
184
|
+
voiceId = voicesMap[cap.speakerId];
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// First synthesis
|
|
188
|
+
let result = await synthesizeCaption(api, token, cap.text, voiceId, defaultSpeed, 'pcm', i, captions.length);
|
|
189
|
+
quotaUsed++;
|
|
190
|
+
lastQuota = result.quota;
|
|
191
|
+
|
|
192
|
+
// Dynamic speed compensation
|
|
193
|
+
if (speedAuto && result.durationMs > targetMs + toleranceMs) {
|
|
194
|
+
const alpha = result.durationMs / targetMs;
|
|
195
|
+
|
|
196
|
+
if (alpha <= 2.0) {
|
|
197
|
+
const newSpeed = Math.min(defaultSpeed * alpha, 2.0);
|
|
198
|
+
process.stdout.write(` ↳ Re-synth #${cap.id} (${(result.durationMs / 1000).toFixed(1)}s > ${(targetMs / 1000).toFixed(1)}s, speed: ${newSpeed.toFixed(2)})...`);
|
|
199
|
+
|
|
200
|
+
result = await synthesizeCaption(api, token, cap.text, voiceId, newSpeed, 'pcm', i, captions.length);
|
|
201
|
+
quotaUsed++;
|
|
202
|
+
lastQuota = result.quota;
|
|
203
|
+
} else {
|
|
204
|
+
// Cannot fit even at max speed
|
|
205
|
+
const msg = `Caption #${cap.id}: audio too long (${(result.durationMs / 1000).toFixed(1)}s for ${(targetMs / 1000).toFixed(1)}s slot, alpha=${alpha.toFixed(1)}). Consider shortening text.`;
|
|
206
|
+
warnings.push(msg);
|
|
207
|
+
console.log(` ⚠ OVERFLOW: ${msg}`);
|
|
208
|
+
|
|
209
|
+
// Try with max speed anyway
|
|
210
|
+
const result2 = await synthesizeCaption(api, token, cap.text, voiceId, 2.0, 'pcm', i, captions.length);
|
|
211
|
+
quotaUsed++;
|
|
212
|
+
lastQuota = result2.quota;
|
|
213
|
+
result = result2;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
segments.push({
|
|
218
|
+
startMs: cap.startMs,
|
|
219
|
+
endMs: cap.endMs,
|
|
220
|
+
audioBuffer: result.audio,
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// ─── Build timeline audio ─────────────────────────────────────────────
|
|
225
|
+
|
|
226
|
+
console.log('\n[2/2] Building timeline audio...');
|
|
227
|
+
const { wav, duration } = buildTimelineAudio(segments);
|
|
228
|
+
|
|
229
|
+
// Determine intermediate WAV path (always .wav, never the user's target .mp3/.mp4)
|
|
230
|
+
const outputExt = path.extname(outputPath).toLowerCase();
|
|
231
|
+
const needsAudioConvert = !hasVideo && outputExt !== '.wav';
|
|
232
|
+
const wavOutputPath = hasVideo
|
|
233
|
+
? outputPath.replace(/\.[^.]+$/, '.tmp.wav') // temp WAV for video merge
|
|
234
|
+
: needsAudioConvert
|
|
235
|
+
? outputPath.slice(0, -outputExt.length) + '.tmp.wav' // temp WAV for format conversion
|
|
236
|
+
: outputPath;
|
|
237
|
+
|
|
238
|
+
const outDir = path.dirname(wavOutputPath);
|
|
239
|
+
fs.mkdirSync(outDir, { recursive: true });
|
|
240
|
+
fs.writeFileSync(wavOutputPath, wav);
|
|
241
|
+
|
|
242
|
+
// Write transcript file (derive from final outputPath, not temp wav)
|
|
243
|
+
const textPath = outputPath.replace(/\.[^.]+$/, '.txt');
|
|
244
|
+
const textContent = captions.map((cap) => {
|
|
245
|
+
const speaker = cap.speakerId ? `|${cap.speakerId}` : '';
|
|
246
|
+
const voiceId = voicesMap && cap.speakerId && voicesMap[cap.speakerId]
|
|
247
|
+
? `|${voicesMap[cap.speakerId]}`
|
|
248
|
+
: '';
|
|
249
|
+
return `[${cap.id}${speaker}${voiceId}] ${cap.text}`;
|
|
250
|
+
}).join('\n\n');
|
|
251
|
+
fs.writeFileSync(textPath, textContent, 'utf8');
|
|
252
|
+
|
|
253
|
+
// ─── Post-processing: BGM mixing and/or video merge ─────────────────
|
|
254
|
+
|
|
255
|
+
const needsFfmpeg = hasVideo || opts.bgm || needsAudioConvert;
|
|
256
|
+
if (needsFfmpeg) {
|
|
257
|
+
const { checkFfmpeg, mergeAudioVideo, mixWithBgm, convertAudioFormat } = require('../core/ffmpeg');
|
|
258
|
+
const ffmpegInfo = await checkFfmpeg();
|
|
259
|
+
if (!ffmpegInfo.available) {
|
|
260
|
+
throw new Error(
|
|
261
|
+
'ffmpeg is required for BGM mixing / video merging / audio format conversion. Install it:\n' +
|
|
262
|
+
' macOS: brew install ffmpeg\n' +
|
|
263
|
+
' Ubuntu: sudo apt install ffmpeg\n' +
|
|
264
|
+
' Windows: https://ffmpeg.org/download.html'
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
let currentAudio = wavOutputPath;
|
|
269
|
+
|
|
270
|
+
// Mix with BGM if provided
|
|
271
|
+
if (opts.bgm) {
|
|
272
|
+
const bgmOutputPath = wavOutputPath.replace('.tmp.wav', '-mixed.tmp.wav').replace(/(?<!\.tmp)\.wav$/, '-mixed.wav');
|
|
273
|
+
console.log(` Mixing BGM (ducking: ${opts.ducking ?? DUB_DEFAULTS.ducking})...`);
|
|
274
|
+
await mixWithBgm(wavOutputPath, opts.bgm, bgmOutputPath, {
|
|
275
|
+
ducking: opts.ducking ?? DUB_DEFAULTS.ducking,
|
|
276
|
+
});
|
|
277
|
+
currentAudio = bgmOutputPath;
|
|
278
|
+
|
|
279
|
+
// Replace the work WAV with the mixed version
|
|
280
|
+
if (!hasVideo) {
|
|
281
|
+
fs.copyFileSync(currentAudio, wavOutputPath);
|
|
282
|
+
try { fs.unlinkSync(currentAudio); } catch {}
|
|
283
|
+
currentAudio = wavOutputPath;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Merge with video if provided
|
|
288
|
+
if (hasVideo) {
|
|
289
|
+
console.log(' Merging with video...');
|
|
290
|
+
await mergeAudioVideo(opts.video, currentAudio, outputPath);
|
|
291
|
+
|
|
292
|
+
// Clean up temp WAV files
|
|
293
|
+
try {
|
|
294
|
+
if (wavOutputPath !== outputPath) fs.unlinkSync(wavOutputPath);
|
|
295
|
+
if (opts.bgm) {
|
|
296
|
+
const mixed = wavOutputPath.replace('.tmp.wav', '-mixed.tmp.wav').replace(/(?<!\.tmp)\.wav$/, '-mixed.wav');
|
|
297
|
+
if (fs.existsSync(mixed)) fs.unlinkSync(mixed);
|
|
298
|
+
}
|
|
299
|
+
} catch {
|
|
300
|
+
// ignore cleanup errors
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Convert audio format if needed (e.g. WAV → MP3 for audio-only output)
|
|
305
|
+
if (needsAudioConvert && !hasVideo) {
|
|
306
|
+
await convertAudioFormat(wavOutputPath, outputPath);
|
|
307
|
+
try { fs.unlinkSync(wavOutputPath); } catch { /* ignore */ }
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// ─── Summary ──────────────────────────────────────────────────────────
|
|
312
|
+
|
|
313
|
+
console.log(`\n=== Done ===`);
|
|
314
|
+
console.log(`Output: ${outputPath} (${(fs.statSync(outputPath).size / 1024).toFixed(1)} KB)`);
|
|
315
|
+
console.log(`Duration: ${duration.toFixed(1)}s`);
|
|
316
|
+
console.log(`Transcript: ${textPath}`);
|
|
317
|
+
console.log(`Captions: ${captions.length}`);
|
|
318
|
+
console.log(`Quota: ${quotaUsed} used, ${lastQuota?.remaining ?? '?'} remaining`);
|
|
319
|
+
|
|
320
|
+
if (warnings.length > 0) {
|
|
321
|
+
console.log(`\nWarnings (${warnings.length}):`);
|
|
322
|
+
for (const w of warnings) {
|
|
323
|
+
console.log(` ⚠ ${w}`);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
return { outputPath, textPath, duration, quotaUsed, segmentCount: captions.length, warnings };
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// ─── Patch Mode ─────────────────────────────────────────────────────────────
|
|
331
|
+
|
|
332
|
+
async function _dubPatch(opts, captions, outputPath, warnings, voicesMap) {
|
|
333
|
+
const patchId = opts.patch;
|
|
334
|
+
const api = opts.api;
|
|
335
|
+
const token = opts.token;
|
|
336
|
+
const defaultVoice = opts.voice || DUB_DEFAULTS.voice;
|
|
337
|
+
const defaultSpeed = opts.speed ?? DUB_DEFAULTS.speed;
|
|
338
|
+
|
|
339
|
+
// Find the caption to patch
|
|
340
|
+
const capIndex = captions.findIndex((c) => c.id === patchId);
|
|
341
|
+
if (capIndex === -1) {
|
|
342
|
+
throw new Error(`Caption #${patchId} not found in SRT. Available IDs: ${captions.map((c) => c.id).join(', ')}`);
|
|
343
|
+
}
|
|
344
|
+
const cap = captions[capIndex];
|
|
345
|
+
|
|
346
|
+
// Check that existing output exists
|
|
347
|
+
const existingWav = outputPath.replace(/\.[^.]+$/, '.wav');
|
|
348
|
+
if (!fs.existsSync(existingWav)) {
|
|
349
|
+
throw new Error(
|
|
350
|
+
`Patch mode requires an existing output file. ` +
|
|
351
|
+
`Run a full dub first, then use --patch to update individual captions.`
|
|
352
|
+
);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// Determine voice
|
|
356
|
+
let voiceId = defaultVoice;
|
|
357
|
+
if (voicesMap && cap.speakerId && voicesMap[cap.speakerId]) {
|
|
358
|
+
voiceId = voicesMap[cap.speakerId];
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
console.log(`\n[Patch] Re-synthesizing caption #${patchId}: "${cap.text.slice(0, 40)}..."`);
|
|
362
|
+
|
|
363
|
+
const result = await synthesizeCaption(api, token, cap.text, voiceId, defaultSpeed, 'pcm', 0, 1);
|
|
364
|
+
|
|
365
|
+
// Read existing WAV, extract PCM, replace the segment
|
|
366
|
+
const existingBuf = fs.readFileSync(existingWav);
|
|
367
|
+
const pcmData = existingBuf.subarray(44); // skip WAV header
|
|
368
|
+
|
|
369
|
+
const startByte = msToBytes(cap.startMs);
|
|
370
|
+
const endByte = msToBytes(cap.endMs);
|
|
371
|
+
|
|
372
|
+
// Clear the old segment region
|
|
373
|
+
pcmData.fill(0, startByte, Math.min(endByte, pcmData.length));
|
|
374
|
+
|
|
375
|
+
// Copy new audio into the region
|
|
376
|
+
const copyLen = Math.min(result.audio.length, endByte - startByte, pcmData.length - startByte);
|
|
377
|
+
if (copyLen > 0) {
|
|
378
|
+
result.audio.copy(pcmData, startByte, 0, copyLen);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// Rebuild WAV
|
|
382
|
+
const { wav } = buildWav([pcmData], 0);
|
|
383
|
+
fs.writeFileSync(existingWav, wav);
|
|
384
|
+
|
|
385
|
+
console.log(`\n=== Patch Done ===`);
|
|
386
|
+
console.log(`Updated: caption #${patchId} in ${existingWav}`);
|
|
387
|
+
console.log(`Quota: 1 used, ${result.quota?.remaining ?? '?'} remaining`);
|
|
388
|
+
|
|
389
|
+
return {
|
|
390
|
+
outputPath: existingWav,
|
|
391
|
+
textPath: existingWav.replace(/\.wav$/i, '.txt'),
|
|
392
|
+
duration: pcmData.length / (BYTES_PER_MS * 1000),
|
|
393
|
+
quotaUsed: 1,
|
|
394
|
+
segmentCount: 1,
|
|
395
|
+
warnings,
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// ─── CLI Handler ────────────────────────────────────────────────────────────
|
|
400
|
+
|
|
401
|
+
async function handle(args) {
|
|
402
|
+
const { parseFlag, parseIntFlag, parseFloatFlag, parseBoolFlag, validateSpeed, runWithRetry } = require('../core/args');
|
|
403
|
+
const { getToken, getTokenInfo } = require('../core/auth');
|
|
404
|
+
const { API_BASE, getConfigDir } = require('../core/config');
|
|
405
|
+
const { warnIfMissingFfmpeg } = require('../core/ffmpeg');
|
|
406
|
+
|
|
407
|
+
// Check FFmpeg availability (one-time hint)
|
|
408
|
+
await warnIfMissingFfmpeg(getConfigDir(), 'dub');
|
|
409
|
+
|
|
410
|
+
const api = parseFlag(args, '--api') || API_BASE;
|
|
411
|
+
const explicitToken = parseFlag(args, '--token');
|
|
412
|
+
|
|
413
|
+
// Parse and validate before auth
|
|
414
|
+
const srt = parseFlag(args, '--srt');
|
|
415
|
+
const video = parseFlag(args, '--video');
|
|
416
|
+
const output = parseFlag(args, '--output', '-o');
|
|
417
|
+
const speed = parseFloatFlag(args, '--speed');
|
|
418
|
+
const ducking = parseFloatFlag(args, '--ducking');
|
|
419
|
+
const patch = parseIntFlag(args, '--patch');
|
|
420
|
+
|
|
421
|
+
// Validate SRT file
|
|
422
|
+
if (!srt && !parseBoolFlag(args, '--help')) {
|
|
423
|
+
console.error('Error: --srt <file> is required. Usage: voxflow dub --srt <file.srt>');
|
|
424
|
+
process.exit(1);
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
if (srt) {
|
|
428
|
+
const fs = require('fs');
|
|
429
|
+
const path = require('path');
|
|
430
|
+
const resolved = path.resolve(srt);
|
|
431
|
+
if (!fs.existsSync(resolved)) {
|
|
432
|
+
console.error(`Error: SRT file not found: ${resolved}`);
|
|
433
|
+
process.exit(1);
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// Validate video file
|
|
438
|
+
if (video) {
|
|
439
|
+
const fs = require('fs');
|
|
440
|
+
const path = require('path');
|
|
441
|
+
const resolved = path.resolve(video);
|
|
442
|
+
if (!fs.existsSync(resolved)) {
|
|
443
|
+
console.error(`Error: Video file not found: ${resolved}`);
|
|
444
|
+
process.exit(1);
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
// Validate voices map
|
|
449
|
+
const voicesMap = parseFlag(args, '--voices');
|
|
450
|
+
if (voicesMap) {
|
|
451
|
+
const fs = require('fs');
|
|
452
|
+
const path = require('path');
|
|
453
|
+
const resolved = path.resolve(voicesMap);
|
|
454
|
+
if (!fs.existsSync(resolved)) {
|
|
455
|
+
console.error(`Error: Voices map file not found: ${resolved}`);
|
|
456
|
+
process.exit(1);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// Validate BGM file
|
|
461
|
+
const bgm = parseFlag(args, '--bgm');
|
|
462
|
+
if (bgm) {
|
|
463
|
+
const fs = require('fs');
|
|
464
|
+
const path = require('path');
|
|
465
|
+
const resolved = path.resolve(bgm);
|
|
466
|
+
if (!fs.existsSync(resolved)) {
|
|
467
|
+
console.error(`Error: BGM file not found: ${resolved}`);
|
|
468
|
+
process.exit(1);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
validateSpeed(args, speed);
|
|
473
|
+
|
|
474
|
+
// Validate output extension
|
|
475
|
+
if (output) {
|
|
476
|
+
const validExts = video ? ['.mp4', '.mkv', '.mov'] : ['.wav', '.mp3'];
|
|
477
|
+
const hasValidExt = validExts.some(ext => output.toLowerCase().endsWith(ext));
|
|
478
|
+
if (!hasValidExt) {
|
|
479
|
+
const extList = validExts.join(', ');
|
|
480
|
+
console.error(`Error: --output path must end with ${extList}`);
|
|
481
|
+
process.exit(1);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
if (ducking !== undefined) {
|
|
486
|
+
if (isNaN(ducking) || ducking < 0 || ducking > 1.0) {
|
|
487
|
+
console.error(`Error: --ducking must be between 0 and 1.0 (got: "${parseFlag(args, '--ducking')}")`);
|
|
488
|
+
process.exit(1);
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
// Authenticate after validation
|
|
493
|
+
let token;
|
|
494
|
+
if (explicitToken) {
|
|
495
|
+
token = explicitToken;
|
|
496
|
+
} else {
|
|
497
|
+
token = await getToken({ api });
|
|
498
|
+
const info = getTokenInfo();
|
|
499
|
+
if (info) {
|
|
500
|
+
console.log(`\x1b[32mLogged in as ${info.email}\x1b[0m`);
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
const opts = {
|
|
505
|
+
token, api, srt, video, output, speed, patch,
|
|
506
|
+
voice: parseFlag(args, '--voice'),
|
|
507
|
+
voicesMap,
|
|
508
|
+
speedAuto: parseBoolFlag(args, '--speed-auto'),
|
|
509
|
+
bgm,
|
|
510
|
+
ducking,
|
|
511
|
+
};
|
|
512
|
+
|
|
513
|
+
await runWithRetry(dub, opts, api, explicitToken);
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
const meta = {
|
|
517
|
+
dub: {
|
|
518
|
+
usage: '[opts]',
|
|
519
|
+
description: 'Dub video/audio from SRT subtitles (timeline-aligned TTS)',
|
|
520
|
+
options: [
|
|
521
|
+
`--srt <file> SRT subtitle file (required)`,
|
|
522
|
+
`--video <file> Video file — merge dubbed audio into video`,
|
|
523
|
+
`--voice <id> Default TTS voice ID (default: ${DUB_DEFAULTS.voice})`,
|
|
524
|
+
`--voices <file> JSON speaker→voiceId map for multi-speaker dubbing`,
|
|
525
|
+
`--speed <n> TTS speed 0.5-2.0 (default: ${DUB_DEFAULTS.speed})`,
|
|
526
|
+
`--speed-auto Auto-adjust speed when audio overflows time slot`,
|
|
527
|
+
`--bgm <file> Background music file to mix in`,
|
|
528
|
+
`--ducking <n> BGM volume ducking 0-1.0 (default: ${DUB_DEFAULTS.ducking})`,
|
|
529
|
+
`--patch <id> Re-synthesize a single caption by ID (patch mode)`,
|
|
530
|
+
`--output <path> Output file path (default: ./dub-<timestamp>.wav)`,
|
|
531
|
+
],
|
|
532
|
+
examples: [
|
|
533
|
+
'voxflow dub --srt subtitles.srt',
|
|
534
|
+
'voxflow dub --srt subtitles.srt --video input.mp4 --output dubbed.mp4',
|
|
535
|
+
'voxflow dub --srt subtitles.srt --voices speakers.json --speed-auto',
|
|
536
|
+
'voxflow dub --srt subtitles.srt --bgm music.mp3 --ducking 0.3',
|
|
537
|
+
'voxflow dub --srt subtitles.srt --patch 5 --output dub-existing.wav',
|
|
538
|
+
],
|
|
539
|
+
},
|
|
540
|
+
};
|
|
541
|
+
|
|
542
|
+
module.exports = { dub, handle, meta, ApiError, _test: { parseVoicesMap } };
|