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.
Files changed (454) hide show
  1. package/README.md +69 -2
  2. package/bin/voxflow.js +27 -0
  3. package/dist/index.js +1 -1
  4. package/dist/remotion-bundle/02a2fb2eb80bc7bf.woff2 +0 -0
  5. package/dist/remotion-bundle/052ca5351e5e06ba.woff2 +0 -0
  6. package/dist/remotion-bundle/05853dd28f4019cb.woff2 +0 -0
  7. package/dist/remotion-bundle/072ead3737f7c0d0.woff2 +0 -0
  8. package/dist/remotion-bundle/07d4248613c86a2e.woff2 +0 -0
  9. package/dist/remotion-bundle/0884a5c2d1d2d99b.woff2 +0 -0
  10. package/dist/remotion-bundle/0b0e185b2752095e.woff2 +0 -0
  11. package/dist/remotion-bundle/0e66c11bde067d91.woff2 +0 -0
  12. package/dist/remotion-bundle/0f7794cfba2c5d21.woff2 +0 -0
  13. package/dist/remotion-bundle/0fdbae5a4365783a.woff2 +0 -0
  14. package/dist/remotion-bundle/112.bundle.js +11 -0
  15. package/dist/remotion-bundle/112.bundle.js.map +1 -0
  16. package/dist/remotion-bundle/113.bundle.js +11 -0
  17. package/dist/remotion-bundle/113.bundle.js.map +1 -0
  18. package/dist/remotion-bundle/119cae0c4c16f7ed.woff2 +0 -0
  19. package/dist/remotion-bundle/14725f649fd1e78c.woff2 +0 -0
  20. package/dist/remotion-bundle/14abe9e3f95f7888.woff2 +0 -0
  21. package/dist/remotion-bundle/163.bundle.js +14678 -0
  22. package/dist/remotion-bundle/163.bundle.js.map +1 -0
  23. package/dist/remotion-bundle/1808c54072bf6d14.woff2 +0 -0
  24. package/dist/remotion-bundle/18948bec3e3012fe.woff2 +0 -0
  25. package/dist/remotion-bundle/1a661c60d0fc84fc.woff2 +0 -0
  26. package/dist/remotion-bundle/1af94941e1bc7e1e.woff2 +0 -0
  27. package/dist/remotion-bundle/1bee0219595f606c.woff2 +0 -0
  28. package/dist/remotion-bundle/1bfd5da7ce9d4ec4.woff2 +0 -0
  29. package/dist/remotion-bundle/1c158d56f1884f3f.woff2 +0 -0
  30. package/dist/remotion-bundle/1cf5e88e667610eb.woff2 +0 -0
  31. package/dist/remotion-bundle/1d431bd10f53c481.woff2 +0 -0
  32. package/dist/remotion-bundle/1d701a81a7670db2.woff2 +0 -0
  33. package/dist/remotion-bundle/1da0fecad4240f16.woff2 +0 -0
  34. package/dist/remotion-bundle/1ed14d3d0c5c63fe.woff2 +0 -0
  35. package/dist/remotion-bundle/1edfecf40e586f53.woff2 +0 -0
  36. package/dist/remotion-bundle/1f479711bc34b054.woff +0 -0
  37. package/dist/remotion-bundle/1f86e54a0ff5fcd1.woff2 +0 -0
  38. package/dist/remotion-bundle/2043ea87d9aabd11.woff2 +0 -0
  39. package/dist/remotion-bundle/20563c39ee8a0e40.woff2 +0 -0
  40. package/dist/remotion-bundle/20c231590fd12c44.woff2 +0 -0
  41. package/dist/remotion-bundle/20ce61713f754c07.woff2 +0 -0
  42. package/dist/remotion-bundle/21eb9306fce24bb1.woff2 +0 -0
  43. package/dist/remotion-bundle/244bf71c0cc851af.woff2 +0 -0
  44. package/dist/remotion-bundle/274d4cfc02bffbcb.woff2 +0 -0
  45. package/dist/remotion-bundle/275.bundle.js +21 -0
  46. package/dist/remotion-bundle/275.bundle.js.map +1 -0
  47. package/dist/remotion-bundle/2958f540b39513dc.woff2 +0 -0
  48. package/dist/remotion-bundle/2a168b98fd97722e.woff2 +0 -0
  49. package/dist/remotion-bundle/2d1f6373937ab55f.woff2 +0 -0
  50. package/dist/remotion-bundle/2d213ae47ff6daa9.woff2 +0 -0
  51. package/dist/remotion-bundle/2e4b1f04fcd05047.woff2 +0 -0
  52. package/dist/remotion-bundle/304170d98f4c4563.woff2 +0 -0
  53. package/dist/remotion-bundle/30d02e136e7a5642.woff2 +0 -0
  54. package/dist/remotion-bundle/3135562b52a714cd.woff2 +0 -0
  55. package/dist/remotion-bundle/313713af2c8144e9.woff2 +0 -0
  56. package/dist/remotion-bundle/325fa4108d2285b9.woff2 +0 -0
  57. package/dist/remotion-bundle/338e927ed3345e0c.woff2 +0 -0
  58. package/dist/remotion-bundle/35fc6b190365bc17.woff2 +0 -0
  59. package/dist/remotion-bundle/37a51f1122d4efc5.woff2 +0 -0
  60. package/dist/remotion-bundle/39a4d63e02736f5e.woff2 +0 -0
  61. package/dist/remotion-bundle/3a00e0d62dfc4171.woff2 +0 -0
  62. package/dist/remotion-bundle/3a6955e6561affe1.woff2 +0 -0
  63. package/dist/remotion-bundle/3c573945aef49b89.woff2 +0 -0
  64. package/dist/remotion-bundle/3cdbfbfa23b516a5.woff2 +0 -0
  65. package/dist/remotion-bundle/3e42f85a9e64ca8a.woff2 +0 -0
  66. package/dist/remotion-bundle/3e83eaf1ec859415.woff2 +0 -0
  67. package/dist/remotion-bundle/3f3c8c90de1250ee.woff2 +0 -0
  68. package/dist/remotion-bundle/434.bundle.js +205 -0
  69. package/dist/remotion-bundle/434.bundle.js.map +1 -0
  70. package/dist/remotion-bundle/44ffc6ca4d781692.woff2 +0 -0
  71. package/dist/remotion-bundle/4670d9c4580b09eb.woff2 +0 -0
  72. package/dist/remotion-bundle/479756881b302824.woff2 +0 -0
  73. package/dist/remotion-bundle/481b82134bfa9c82.woff2 +0 -0
  74. package/dist/remotion-bundle/48d27029626f4328.woff2 +0 -0
  75. package/dist/remotion-bundle/49b7b2a30329c511.woff2 +0 -0
  76. package/dist/remotion-bundle/4c8b25a1a9337045.woff2 +0 -0
  77. package/dist/remotion-bundle/4cba14788ca9259b.woff2 +0 -0
  78. package/dist/remotion-bundle/4cd6c589c004a6a7.woff2 +0 -0
  79. package/dist/remotion-bundle/4cd8d79c1021608d.woff2 +0 -0
  80. package/dist/remotion-bundle/4d8fa99b3f00f9f0.woff2 +0 -0
  81. package/dist/remotion-bundle/4e7805a643f86d53.woff2 +0 -0
  82. package/dist/remotion-bundle/4ff91be454542e3f.woff2 +0 -0
  83. package/dist/remotion-bundle/504cbcba1f63591b.woff2 +0 -0
  84. package/dist/remotion-bundle/5202d792e5791d6c.woff2 +0 -0
  85. package/dist/remotion-bundle/534db5ad4770cc1d.woff2 +0 -0
  86. package/dist/remotion-bundle/53b9568eb85f866b.woff2 +0 -0
  87. package/dist/remotion-bundle/543ad386ca171de9.woff2 +0 -0
  88. package/dist/remotion-bundle/54798e55bbf7976e.woff2 +0 -0
  89. package/dist/remotion-bundle/580.bundle.js +11 -0
  90. package/dist/remotion-bundle/580.bundle.js.map +1 -0
  91. package/dist/remotion-bundle/58d174d1193af6d1.woff2 +0 -0
  92. package/dist/remotion-bundle/591d29ff3ff53c80.woff2 +0 -0
  93. package/dist/remotion-bundle/5c28c4f4824383c6.woff2 +0 -0
  94. package/dist/remotion-bundle/5da9740d2ce894c8.woff2 +0 -0
  95. package/dist/remotion-bundle/6197735364642360.woff2 +0 -0
  96. package/dist/remotion-bundle/6265a4335724080f.woff2 +0 -0
  97. package/dist/remotion-bundle/633f5e4f6394daa7.woff2 +0 -0
  98. package/dist/remotion-bundle/637d95ace6a69c49.woff2 +0 -0
  99. package/dist/remotion-bundle/648e04a04dacff8f.woff2 +0 -0
  100. package/dist/remotion-bundle/64a6e83045a008b2.woff2 +0 -0
  101. package/dist/remotion-bundle/651.bundle.js +11 -0
  102. package/dist/remotion-bundle/651.bundle.js.map +1 -0
  103. package/dist/remotion-bundle/65e2a988c070facc.woff2 +0 -0
  104. package/dist/remotion-bundle/66a2f6ce5cc69105.woff2 +0 -0
  105. package/dist/remotion-bundle/690.bundle.js +3479 -0
  106. package/dist/remotion-bundle/690.bundle.js.map +1 -0
  107. package/dist/remotion-bundle/690ff55252ca715d.woff2 +0 -0
  108. package/dist/remotion-bundle/6a01a1cff49314fc.woff2 +0 -0
  109. package/dist/remotion-bundle/6cbc32670982986c.woff2 +0 -0
  110. package/dist/remotion-bundle/6d3cc42ae547f454.woff2 +0 -0
  111. package/dist/remotion-bundle/6d8f4cfa1ddc0830.woff2 +0 -0
  112. package/dist/remotion-bundle/6e4d7c6ae65e2dc3.woff2 +0 -0
  113. package/dist/remotion-bundle/6e86418bbcefb2e8.woff2 +0 -0
  114. package/dist/remotion-bundle/6ee02884b29cf7fb.woff2 +0 -0
  115. package/dist/remotion-bundle/6f436a74c9e3252c.woff2 +0 -0
  116. package/dist/remotion-bundle/78c8022f1657618b.woff2 +0 -0
  117. package/dist/remotion-bundle/7c5444169792bca4.woff2 +0 -0
  118. package/dist/remotion-bundle/7c86bddd9d997212.woff2 +0 -0
  119. package/dist/remotion-bundle/7e1284684767f584.woff2 +0 -0
  120. package/dist/remotion-bundle/7e81c17522d182b2.woff2 +0 -0
  121. package/dist/remotion-bundle/7eb87be198f7858c.woff2 +0 -0
  122. package/dist/remotion-bundle/8060c928f948aab5.woff2 +0 -0
  123. package/dist/remotion-bundle/80bc9dfbea2b35ae.woff2 +0 -0
  124. package/dist/remotion-bundle/811b83f69963bb48.woff2 +0 -0
  125. package/dist/remotion-bundle/813.bundle.js +117511 -0
  126. package/dist/remotion-bundle/813.bundle.js.map +1 -0
  127. package/dist/remotion-bundle/84df492e349f82e9.woff2 +0 -0
  128. package/dist/remotion-bundle/8501bfd73eb36f2b.woff2 +0 -0
  129. package/dist/remotion-bundle/854236a8376093fe.woff2 +0 -0
  130. package/dist/remotion-bundle/8571d74529082753.woff2 +0 -0
  131. package/dist/remotion-bundle/860bf44f8e6f4b5d.woff2 +0 -0
  132. package/dist/remotion-bundle/879.bundle.js +64 -0
  133. package/dist/remotion-bundle/879.bundle.js.map +1 -0
  134. package/dist/remotion-bundle/887dd482f848d56f.woff2 +0 -0
  135. package/dist/remotion-bundle/89b2132e85fbbb5a.woff2 +0 -0
  136. package/dist/remotion-bundle/8ba60d6c306010c2.woff2 +0 -0
  137. package/dist/remotion-bundle/8c7c4dadea897806.woff2 +0 -0
  138. package/dist/remotion-bundle/8c943f9999706f61.woff2 +0 -0
  139. package/dist/remotion-bundle/8f2a718c90575cc9.woff2 +0 -0
  140. package/dist/remotion-bundle/906b6edb3e1772c9.woff2 +0 -0
  141. package/dist/remotion-bundle/930ff9daccdf14eb.woff2 +0 -0
  142. package/dist/remotion-bundle/934db2f1c403c4d0.woff2 +0 -0
  143. package/dist/remotion-bundle/938.bundle.js +451 -0
  144. package/dist/remotion-bundle/938.bundle.js.map +1 -0
  145. package/dist/remotion-bundle/967.bundle.js +4462 -0
  146. package/dist/remotion-bundle/967.bundle.js.map +1 -0
  147. package/dist/remotion-bundle/9684a1093d3c02ce.woff2 +0 -0
  148. package/dist/remotion-bundle/973dcd0faa6116cc.woff2 +0 -0
  149. package/dist/remotion-bundle/9745400694e76cd8.woff2 +0 -0
  150. package/dist/remotion-bundle/999ef957bed3bdca.woff2 +0 -0
  151. package/dist/remotion-bundle/99a3d67c8b0f43e3.woff2 +0 -0
  152. package/dist/remotion-bundle/a0586c3e03127283.woff2 +0 -0
  153. package/dist/remotion-bundle/a0eb654fdae46269.woff2 +0 -0
  154. package/dist/remotion-bundle/a20e35d3b08f7994.woff2 +0 -0
  155. package/dist/remotion-bundle/a2dcaced7c8c25ab.woff2 +0 -0
  156. package/dist/remotion-bundle/a79255a972a2681a.woff2 +0 -0
  157. package/dist/remotion-bundle/a804b352cb9fec1a.woff2 +0 -0
  158. package/dist/remotion-bundle/aae7117164e1eabc.woff2 +0 -0
  159. package/dist/remotion-bundle/affd121385d0442d.woff2 +0 -0
  160. package/dist/remotion-bundle/b19a6083987ee0d7.woff2 +0 -0
  161. package/dist/remotion-bundle/b1b2bd04d8637981.woff2 +0 -0
  162. package/dist/remotion-bundle/b2c07f341486be87.woff2 +0 -0
  163. package/dist/remotion-bundle/b33d8f82e575c4ce.woff2 +0 -0
  164. package/dist/remotion-bundle/b366c0bed35ef491.woff2 +0 -0
  165. package/dist/remotion-bundle/b41e857ec1b85642.woff2 +0 -0
  166. package/dist/remotion-bundle/b420bb34ccf23e7f.woff2 +0 -0
  167. package/dist/remotion-bundle/b4f7bf4efb0c0ccf.woff2 +0 -0
  168. package/dist/remotion-bundle/b60fe5eca03cff93.woff2 +0 -0
  169. package/dist/remotion-bundle/b6bd31a336e64bce.woff2 +0 -0
  170. package/dist/remotion-bundle/b6d2befba3dfefeb.woff2 +0 -0
  171. package/dist/remotion-bundle/b75f39ab06c43bf4.woff2 +0 -0
  172. package/dist/remotion-bundle/b77880e8c413d4fd.woff2 +0 -0
  173. package/dist/remotion-bundle/b7e38ec441e4a77a.woff2 +0 -0
  174. package/dist/remotion-bundle/b83baa383ff0bf2b.woff2 +0 -0
  175. package/dist/remotion-bundle/b9ad7b6c0a11450a.woff2 +0 -0
  176. package/dist/remotion-bundle/baf84486e8ae3aaf.woff2 +0 -0
  177. package/dist/remotion-bundle/bc047b1f6869cffa.woff2 +0 -0
  178. package/dist/remotion-bundle/bf4f3ac6e93f33aa.woff2 +0 -0
  179. package/dist/remotion-bundle/bf6835ffec5897a2.woff2 +0 -0
  180. package/dist/remotion-bundle/bf8885f581eb1724.woff2 +0 -0
  181. package/dist/remotion-bundle/bundle.js +83376 -0
  182. package/dist/remotion-bundle/bundle.js.map +1 -0
  183. package/dist/remotion-bundle/c03f046bccd789d0.woff2 +0 -0
  184. package/dist/remotion-bundle/c0bb1f8962b73bc3.woff2 +0 -0
  185. package/dist/remotion-bundle/c1003f9a7db6e1cf.woff2 +0 -0
  186. package/dist/remotion-bundle/c15d83fb1e199515.woff2 +0 -0
  187. package/dist/remotion-bundle/c28e7e5d310f73ef.woff2 +0 -0
  188. package/dist/remotion-bundle/c2b840274db78aea.woff2 +0 -0
  189. package/dist/remotion-bundle/c3000e3299d4e45f.woff2 +0 -0
  190. package/dist/remotion-bundle/c83ce886e5288510.woff2 +0 -0
  191. package/dist/remotion-bundle/c87a5a64d4ac0918.woff2 +0 -0
  192. package/dist/remotion-bundle/c8a7e0d049e965fa.woff2 +0 -0
  193. package/dist/remotion-bundle/c949a35d3a3b1faf.woff2 +0 -0
  194. package/dist/remotion-bundle/c9618c9b9ac2bc78.woff2 +0 -0
  195. package/dist/remotion-bundle/ca3add3b84152d5b.woff2 +0 -0
  196. package/dist/remotion-bundle/cad9dd036408d707.woff2 +0 -0
  197. package/dist/remotion-bundle/cbb24916619df439.woff2 +0 -0
  198. package/dist/remotion-bundle/cc054f0b5514e177.woff2 +0 -0
  199. package/dist/remotion-bundle/ccc248ed9312bc71.woff2 +0 -0
  200. package/dist/remotion-bundle/cd9d623aa07af925.woff2 +0 -0
  201. package/dist/remotion-bundle/ce2ba7a321bd1247.woff2 +0 -0
  202. package/dist/remotion-bundle/cf72455f79a29b14.woff2 +0 -0
  203. package/dist/remotion-bundle/d267cbfefab452ac.woff2 +0 -0
  204. package/dist/remotion-bundle/d435cff46a64955f.woff +0 -0
  205. package/dist/remotion-bundle/d494d07f67e363f6.woff2 +0 -0
  206. package/dist/remotion-bundle/d7aa0cc1fa47bf38.woff2 +0 -0
  207. package/dist/remotion-bundle/d7c5ca93d885160a.woff2 +0 -0
  208. package/dist/remotion-bundle/d855d3e252db74e2.woff2 +0 -0
  209. package/dist/remotion-bundle/d8f13d47f02f82c2.woff2 +0 -0
  210. package/dist/remotion-bundle/d9567cce2ee11019.woff2 +0 -0
  211. package/dist/remotion-bundle/db8d4456fc75dd86.woff +0 -0
  212. package/dist/remotion-bundle/dc274628378c47ee.woff2 +0 -0
  213. package/dist/remotion-bundle/dc3e06947bb69903.woff2 +0 -0
  214. package/dist/remotion-bundle/dd67040ac3b6d523.woff2 +0 -0
  215. package/dist/remotion-bundle/e0b04bd488f953f4.woff2 +0 -0
  216. package/dist/remotion-bundle/e2a572ff95089370.woff2 +0 -0
  217. package/dist/remotion-bundle/e2e18a86b1c2b0cc.woff2 +0 -0
  218. package/dist/remotion-bundle/e3a78ee2fc9c6931.woff2 +0 -0
  219. package/dist/remotion-bundle/e654c9d547605a9f.woff2 +0 -0
  220. package/dist/remotion-bundle/e67a3a64c129927c.woff2 +0 -0
  221. package/dist/remotion-bundle/e6be28b4203cd6ce.woff2 +0 -0
  222. package/dist/remotion-bundle/e841907ad9b0a191.woff +0 -0
  223. package/dist/remotion-bundle/e889d1541c69fffa.woff2 +0 -0
  224. package/dist/remotion-bundle/e88ef8c76373a9e2.woff2 +0 -0
  225. package/dist/remotion-bundle/e9c72f4bc37defef.woff2 +0 -0
  226. package/dist/remotion-bundle/e9e35f863403a255.woff2 +0 -0
  227. package/dist/remotion-bundle/eb23b37b009375da.woff2 +0 -0
  228. package/dist/remotion-bundle/ee1342b741625721.woff2 +0 -0
  229. package/dist/remotion-bundle/f07da88543a57ec9.woff2 +0 -0
  230. package/dist/remotion-bundle/f522982115306f8a.woff2 +0 -0
  231. package/dist/remotion-bundle/f8449bd864e6d8bc.woff2 +0 -0
  232. package/dist/remotion-bundle/f906dd5bd95ff9ab.woff2 +0 -0
  233. package/dist/remotion-bundle/f9e9e9413e3c38bb.woff2 +0 -0
  234. package/dist/remotion-bundle/fa5a5b16280994a8.woff2 +0 -0
  235. package/dist/remotion-bundle/favicon.ico +0 -0
  236. package/dist/remotion-bundle/fb19c0517725599b.woff2 +0 -0
  237. package/dist/remotion-bundle/fcaf24232f684b9b.woff2 +0 -0
  238. package/dist/remotion-bundle/fe09e084a3eea8cf.woff2 +0 -0
  239. package/dist/remotion-bundle/ff38d5317df7345a.woff2 +0 -0
  240. package/dist/remotion-bundle/ffe7ea1ea08f455a.woff2 +0 -0
  241. package/dist/remotion-bundle/index.html +49 -0
  242. package/dist/remotion-bundle/public/paper-slide/female-kefu-xiaomei/communication/0.mp3 +0 -0
  243. package/dist/remotion-bundle/public/paper-slide/female-kefu-xiaomei/communication/1.mp3 +0 -0
  244. package/dist/remotion-bundle/public/paper-slide/female-kefu-xiaomei/communication/2.mp3 +0 -0
  245. package/dist/remotion-bundle/public/paper-slide/female-kefu-xiaomei/communication/3.mp3 +0 -0
  246. package/dist/remotion-bundle/public/paper-slide/female-kefu-xiaoxin/career/0.mp3 +0 -0
  247. package/dist/remotion-bundle/public/paper-slide/female-kefu-xiaoxin/career/1.mp3 +0 -0
  248. package/dist/remotion-bundle/public/paper-slide/female-kefu-xiaoxin/career/2.mp3 +0 -0
  249. package/dist/remotion-bundle/public/paper-slide/female-kefu-xiaoxin/career/3.mp3 +0 -0
  250. package/dist/remotion-bundle/public/paper-slide/female-kefu-xiaoyue/parenting/0.mp3 +0 -0
  251. package/dist/remotion-bundle/public/paper-slide/female-kefu-xiaoyue/parenting/1.mp3 +0 -0
  252. package/dist/remotion-bundle/public/paper-slide/female-kefu-xiaoyue/parenting/2.mp3 +0 -0
  253. package/dist/remotion-bundle/public/paper-slide/female-kefu-xiaoyue/parenting/3.mp3 +0 -0
  254. package/dist/remotion-bundle/public/paper-slide/male-kefu-xiaoxu/time-trap/0.mp3 +0 -0
  255. package/dist/remotion-bundle/public/paper-slide/male-kefu-xiaoxu/time-trap/1.mp3 +0 -0
  256. package/dist/remotion-bundle/public/paper-slide/male-kefu-xiaoxu/time-trap/2.mp3 +0 -0
  257. package/dist/remotion-bundle/public/paper-slide/male-kefu-xiaoxu/time-trap/3.mp3 +0 -0
  258. package/dist/remotion-bundle/public/paper-slide/v-female-A6b7WpG3/cognition/0.mp3 +0 -0
  259. package/dist/remotion-bundle/public/paper-slide/v-female-A6b7WpG3/cognition/1.mp3 +0 -0
  260. package/dist/remotion-bundle/public/paper-slide/v-female-A6b7WpG3/cognition/2.mp3 +0 -0
  261. package/dist/remotion-bundle/public/paper-slide/v-female-A6b7WpG3/cognition/3.mp3 +0 -0
  262. package/dist/remotion-bundle/public/paper-slide/v-female-A6b7WpG3/growth/0.mp3 +0 -0
  263. package/dist/remotion-bundle/public/paper-slide/v-female-A6b7WpG3/growth/1.mp3 +0 -0
  264. package/dist/remotion-bundle/public/paper-slide/v-female-A6b7WpG3/growth/2.mp3 +0 -0
  265. package/dist/remotion-bundle/public/paper-slide/v-female-A6b7WpG3/growth/3.mp3 +0 -0
  266. package/dist/remotion-bundle/public/paper-slide/v-female-A6b7WpG3/parenting/0.mp3 +0 -0
  267. package/dist/remotion-bundle/public/paper-slide/v-female-A6b7WpG3/parenting/1.mp3 +0 -0
  268. package/dist/remotion-bundle/public/paper-slide/v-female-A6b7WpG3/parenting/2.mp3 +0 -0
  269. package/dist/remotion-bundle/public/paper-slide/v-female-A6b7WpG3/parenting/3.mp3 +0 -0
  270. package/dist/remotion-bundle/public/paper-slide/v-female-A6b7WpG3/soothing/0.mp3 +0 -0
  271. package/dist/remotion-bundle/public/paper-slide/v-female-A6b7WpG3/soothing/1.mp3 +0 -0
  272. package/dist/remotion-bundle/public/paper-slide/v-female-A6b7WpG3/soothing/2.mp3 +0 -0
  273. package/dist/remotion-bundle/public/paper-slide/v-female-A6b7WpG3/soothing/3.mp3 +0 -0
  274. package/dist/remotion-bundle/public/paper-slide/v-female-R2s4N9qJ/cognition/0.mp3 +0 -0
  275. package/dist/remotion-bundle/public/paper-slide/v-female-R2s4N9qJ/cognition/1.mp3 +0 -0
  276. package/dist/remotion-bundle/public/paper-slide/v-female-R2s4N9qJ/cognition/2.mp3 +0 -0
  277. package/dist/remotion-bundle/public/paper-slide/v-female-R2s4N9qJ/cognition/3.mp3 +0 -0
  278. package/dist/remotion-bundle/public/paper-slide/v-male-Bk7vD3xP/decision/0.mp3 +0 -0
  279. package/dist/remotion-bundle/public/paper-slide/v-male-Bk7vD3xP/decision/1.mp3 +0 -0
  280. package/dist/remotion-bundle/public/paper-slide/v-male-Bk7vD3xP/decision/2.mp3 +0 -0
  281. package/dist/remotion-bundle/public/paper-slide/v-male-Bk7vD3xP/decision/3.mp3 +0 -0
  282. package/dist/remotion-bundle/public/paper-slide/v-male-W1tH9jVc/manager/0.mp3 +0 -0
  283. package/dist/remotion-bundle/public/paper-slide/v-male-W1tH9jVc/manager/1.mp3 +0 -0
  284. package/dist/remotion-bundle/public/paper-slide/v-male-W1tH9jVc/manager/2.mp3 +0 -0
  285. package/dist/remotion-bundle/public/paper-slide/v-male-W1tH9jVc/manager/3.mp3 +0 -0
  286. package/dist/remotion-bundle/public/paper-slide/v-male-W1tH9jVc/manager/4.mp3 +0 -0
  287. package/dist/remotion-bundle/public/paper-slide/v-male-s5NqE0rZ/founder/0.mp3 +0 -0
  288. package/dist/remotion-bundle/public/paper-slide/v-male-s5NqE0rZ/founder/1.mp3 +0 -0
  289. package/dist/remotion-bundle/public/paper-slide/v-male-s5NqE0rZ/founder/2.mp3 +0 -0
  290. package/dist/remotion-bundle/public/paper-slide/v-male-s5NqE0rZ/founder/3.mp3 +0 -0
  291. package/dist/remotion-bundle/public/paper-slide-experiments/career-advice/0.mp3 +0 -0
  292. package/dist/remotion-bundle/public/paper-slide-experiments/career-advice/1.mp3 +0 -0
  293. package/dist/remotion-bundle/public/paper-slide-experiments/career-advice/2.mp3 +0 -0
  294. package/dist/remotion-bundle/public/paper-slide-experiments/career-advice/3.mp3 +0 -0
  295. package/dist/remotion-bundle/public/paper-slide-experiments/career-advice/4.mp3 +0 -0
  296. package/dist/remotion-bundle/public/paper-slide-experiments/founder-lesson/0.mp3 +0 -0
  297. package/dist/remotion-bundle/public/paper-slide-experiments/founder-lesson/1.mp3 +0 -0
  298. package/dist/remotion-bundle/public/paper-slide-experiments/founder-lesson/2.mp3 +0 -0
  299. package/dist/remotion-bundle/public/paper-slide-experiments/founder-lesson/3.mp3 +0 -0
  300. package/dist/remotion-bundle/public/paper-slide-experiments/founder-lesson/4.mp3 +0 -0
  301. package/dist/remotion-bundle/public/paper-slide-experiments/incident-review/0.mp3 +0 -0
  302. package/dist/remotion-bundle/public/paper-slide-experiments/incident-review/1.mp3 +0 -0
  303. package/dist/remotion-bundle/public/paper-slide-experiments/incident-review/2.mp3 +0 -0
  304. package/dist/remotion-bundle/public/paper-slide-experiments/incident-review/3.mp3 +0 -0
  305. package/dist/remotion-bundle/public/paper-slide-experiments/incident-review/4.mp3 +0 -0
  306. package/dist/remotion-bundle/public/paper-slide-experiments/learning-loop/0.mp3 +0 -0
  307. package/dist/remotion-bundle/public/paper-slide-experiments/learning-loop/1.mp3 +0 -0
  308. package/dist/remotion-bundle/public/paper-slide-experiments/learning-loop/2.mp3 +0 -0
  309. package/dist/remotion-bundle/public/paper-slide-experiments/learning-loop/3.mp3 +0 -0
  310. package/dist/remotion-bundle/public/paper-slide-experiments/learning-loop/4.mp3 +0 -0
  311. package/dist/remotion-bundle/public/paper-slide-experiments/meeting-closure/0.mp3 +0 -0
  312. package/dist/remotion-bundle/public/paper-slide-experiments/meeting-closure/1.mp3 +0 -0
  313. package/dist/remotion-bundle/public/paper-slide-experiments/meeting-closure/2.mp3 +0 -0
  314. package/dist/remotion-bundle/public/paper-slide-experiments/meeting-closure/3.mp3 +0 -0
  315. package/dist/remotion-bundle/public/paper-slide-experiments/product-update/0.mp3 +0 -0
  316. package/dist/remotion-bundle/public/paper-slide-experiments/product-update/1.mp3 +0 -0
  317. package/dist/remotion-bundle/public/paper-slide-experiments/product-update/2.mp3 +0 -0
  318. package/dist/remotion-bundle/public/paper-slide-experiments/product-update/3.mp3 +0 -0
  319. package/dist/remotion-bundle/public/paper-slide-experiments/research-reading/0.mp3 +0 -0
  320. package/dist/remotion-bundle/public/paper-slide-experiments/research-reading/1.mp3 +0 -0
  321. package/dist/remotion-bundle/public/paper-slide-experiments/research-reading/2.mp3 +0 -0
  322. package/dist/remotion-bundle/public/paper-slide-experiments/research-reading/3.mp3 +0 -0
  323. package/dist/remotion-bundle/public/paper-slide-experiments/sales-enablement/0.mp3 +0 -0
  324. package/dist/remotion-bundle/public/paper-slide-experiments/sales-enablement/1.mp3 +0 -0
  325. package/dist/remotion-bundle/public/paper-slide-experiments/sales-enablement/2.mp3 +0 -0
  326. package/dist/remotion-bundle/public/paper-slide-experiments/sales-enablement/3.mp3 +0 -0
  327. package/dist/remotion-bundle/public/paper-slide-experiments/sales-enablement/4.mp3 +0 -0
  328. package/dist/remotion-bundle/public/voiceover/ai-life/card-0.mp3 +0 -0
  329. package/dist/remotion-bundle/public/voiceover/ai-life/card-1.mp3 +0 -0
  330. package/dist/remotion-bundle/public/voiceover/ai-life/card-2.mp3 +0 -0
  331. package/dist/remotion-bundle/public/voiceover/ai-life/card-3.mp3 +0 -0
  332. package/dist/remotion-bundle/public/voiceover/ai-life/card-4.mp3 +0 -0
  333. package/dist/remotion-bundle/public/voiceover/ai-life/card-5.mp3 +0 -0
  334. package/dist/remotion-bundle/public/voiceover/coffee-science/card-0.mp3 +0 -0
  335. package/dist/remotion-bundle/public/voiceover/coffee-science/card-1.mp3 +0 -0
  336. package/dist/remotion-bundle/public/voiceover/coffee-science/card-2.mp3 +0 -0
  337. package/dist/remotion-bundle/public/voiceover/coffee-science/card-3.mp3 +0 -0
  338. package/dist/remotion-bundle/public/voiceover/coffee-science/card-4.mp3 +0 -0
  339. package/dist/remotion-bundle/public/voiceover/coffee-science/card-5.mp3 +0 -0
  340. package/dist/remotion-bundle/public/voiceover/coffee-science/card-6.mp3 +0 -0
  341. package/dist/remotion-bundle/public/voiceover/reading-secrets/card-0.mp3 +0 -0
  342. package/dist/remotion-bundle/public/voiceover/reading-secrets/card-1.mp3 +0 -0
  343. package/dist/remotion-bundle/public/voiceover/reading-secrets/card-2.mp3 +0 -0
  344. package/dist/remotion-bundle/public/voiceover/reading-secrets/card-3.mp3 +0 -0
  345. package/dist/remotion-bundle/public/voiceover/reading-secrets/card-4.mp3 +0 -0
  346. package/dist/remotion-bundle/public/voiceover/reading-secrets/card-5.mp3 +0 -0
  347. package/dist/remotion-bundle/public/voiceover/reading-secrets/card-6.mp3 +0 -0
  348. package/dist/remotion-bundle/public/voiceover/remote-work/card-0.mp3 +0 -0
  349. package/dist/remotion-bundle/public/voiceover/remote-work/card-1.mp3 +0 -0
  350. package/dist/remotion-bundle/public/voiceover/remote-work/card-2.mp3 +0 -0
  351. package/dist/remotion-bundle/public/voiceover/remote-work/card-3.mp3 +0 -0
  352. package/dist/remotion-bundle/public/voiceover/remote-work/card-4.mp3 +0 -0
  353. package/dist/remotion-bundle/public/voiceover/remote-work/card-5.mp3 +0 -0
  354. package/dist/remotion-bundle/source-map-helper.wasm +0 -0
  355. package/lib/cli.js +270 -0
  356. package/lib/commands/_registry.js +48 -0
  357. package/lib/commands/add.js +242 -0
  358. package/lib/commands/asr/azure-transcribe.js +336 -0
  359. package/lib/commands/asr/cloud-transcribe.js +384 -0
  360. package/lib/commands/asr/helpers.js +76 -0
  361. package/lib/commands/asr/index.js +236 -0
  362. package/lib/commands/asr/local-transcribe.js +125 -0
  363. package/lib/commands/asr-jobs.js +257 -0
  364. package/lib/commands/asr.js +11 -0
  365. package/lib/commands/auth-cmds.js +358 -0
  366. package/lib/commands/dub.js +542 -0
  367. package/lib/commands/explain.js +512 -0
  368. package/lib/commands/feedback.js +152 -0
  369. package/lib/commands/image.js +207 -0
  370. package/lib/commands/mcp-key.js +166 -0
  371. package/lib/commands/narrate.js +639 -0
  372. package/lib/commands/picstory-templates.js +276 -0
  373. package/lib/commands/picstory.js +547 -0
  374. package/lib/commands/podcast/dialogue.js +109 -0
  375. package/lib/commands/podcast/generate.js +127 -0
  376. package/lib/commands/podcast/index.js +561 -0
  377. package/lib/commands/podcast/synthesize.js +188 -0
  378. package/lib/commands/podcast.js +11 -0
  379. package/lib/commands/present.js +519 -0
  380. package/lib/commands/publish.js +415 -0
  381. package/lib/commands/skills.js +473 -0
  382. package/lib/commands/slice-preview.js +266 -0
  383. package/lib/commands/slice-render.js +282 -0
  384. package/lib/commands/slice-stage.js +264 -0
  385. package/lib/commands/slice.js +343 -0
  386. package/lib/commands/slides/constants.js +108 -0
  387. package/lib/commands/slides/html-renderer.js +338 -0
  388. package/lib/commands/slides/index.js +345 -0
  389. package/lib/commands/slides.js +11 -0
  390. package/lib/commands/story.js +302 -0
  391. package/lib/commands/summarize.js +532 -0
  392. package/lib/commands/synthesize.js +261 -0
  393. package/lib/commands/translate.js +593 -0
  394. package/lib/commands/upgrade.js +249 -0
  395. package/lib/commands/video-translate.js +577 -0
  396. package/lib/commands/voices.js +292 -0
  397. package/lib/core/agent-env.js +104 -0
  398. package/lib/core/args.js +107 -0
  399. package/lib/core/asr-client.js +448 -0
  400. package/lib/core/asr-jobs-client.js +126 -0
  401. package/lib/core/asr-jobs-store.js +105 -0
  402. package/lib/core/asr-r2-upload.js +181 -0
  403. package/lib/core/asr-upload.js +132 -0
  404. package/lib/core/audio-extract.js +150 -0
  405. package/lib/core/audio.js +219 -0
  406. package/lib/core/auth.js +880 -0
  407. package/lib/core/config.js +197 -0
  408. package/lib/core/feedback.js +64 -0
  409. package/lib/core/ffmpeg.js +476 -0
  410. package/lib/core/http.js +188 -0
  411. package/lib/core/image-client.js +55 -0
  412. package/lib/core/intent-params.js +11 -0
  413. package/lib/core/llm-client.js +76 -0
  414. package/lib/core/logger.js +208 -0
  415. package/lib/core/mic-recorder.js +182 -0
  416. package/lib/core/pause-markers.js +94 -0
  417. package/lib/core/podcast-pacing.js +118 -0
  418. package/lib/core/spinner.js +33 -0
  419. package/lib/core/srt.js +394 -0
  420. package/lib/core/telemetry.js +100 -0
  421. package/lib/core/timeline.js +92 -0
  422. package/lib/core/tts-synthesizer.js +70 -0
  423. package/lib/core/update-check.js +185 -0
  424. package/lib/core/url-download.js +148 -0
  425. package/lib/core/whisper-local.js +279 -0
  426. package/lib/internal/deck-validator.js +488 -0
  427. package/lib/internal/slice-themes.json +370 -0
  428. package/lib/stage-core/cloud-render.js +170 -0
  429. package/lib/stage-core/deck-format.js +133 -0
  430. package/lib/stage-core/edit-prompt.js +104 -0
  431. package/lib/stage-core/event-bus.js +31 -0
  432. package/lib/stage-core/port.js +46 -0
  433. package/lib/stage-core/server.js +352 -0
  434. package/lib/stage-core/snapshot-store.js +198 -0
  435. package/lib/stage-core/watcher.js +106 -0
  436. package/lib/stage-ui/slice/template.js +1672 -0
  437. package/package.json +9 -4
  438. package/skills/.claude-plugin/marketplace.json +22 -0
  439. package/skills/.claude-plugin/plugin.json +25 -0
  440. package/skills/LICENSE +21 -0
  441. package/skills/README.md +120 -0
  442. package/skills/hub/SKILL.md +317 -0
  443. package/skills/podcast/SKILL.md +146 -0
  444. package/skills/slice/SKILL.md +205 -0
  445. package/skills/slice/agents/openai.yaml +4 -0
  446. package/skills/slice/references/deck-schema.md +183 -0
  447. package/skills/slice/references/example-decks.md +108 -0
  448. package/skills/slice/references/themes.md +172 -0
  449. package/skills/transcribe/SKILL.md +473 -0
  450. package/skills/video/SKILL.md +261 -0
  451. package/skills/voxflow-slice/SKILL.md +271 -0
  452. package/skills/voxflow-slice/examples/article.md +13 -0
  453. package/skills/voxflow-slice/examples/expected-deck.json +39 -0
  454. 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 } };