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,593 @@
1
+ /**
2
+ * VoxFlow CLI — Translate command
3
+ *
4
+ * Translate SRT subtitles, plain text, or text files using the backend
5
+ * /api/llm/chat endpoint with batched prompts.
6
+ *
7
+ * Three input modes:
8
+ * - SRT mode (--srt <file>): Parse SRT, batch-translate captions, output translated SRT
9
+ * - Text mode (--text "..."): Translate a single text string, print to stdout
10
+ * - File mode (--input <file>): Read .txt/.md file, translate, write output file
11
+ *
12
+ * Cost: 1 quota per batch (10 captions). Using /api/llm/chat instead of
13
+ * /api/llm/translate saves 80-98% quota for SRT translation.
14
+ */
15
+
16
+ const fs = require('fs');
17
+ const path = require('path');
18
+ const { API_BASE, TRANSLATE_DEFAULTS } = require('../core/config');
19
+ const { chatCompletion, detectLanguage } = require('../core/llm-client');
20
+ const { parseSrt, formatSrt } = require('../core/srt');
21
+
22
+ // ─── Language mapping ────────────────────────────────────────────────────────
23
+
24
+ const LANG_MAP = {
25
+ zh: 'Chinese (Simplified)',
26
+ en: 'English',
27
+ ja: 'Japanese',
28
+ ko: 'Korean',
29
+ fr: 'French',
30
+ de: 'German',
31
+ es: 'Spanish',
32
+ pt: 'Portuguese',
33
+ ru: 'Russian',
34
+ ar: 'Arabic',
35
+ th: 'Thai',
36
+ vi: 'Vietnamese',
37
+ it: 'Italian',
38
+ id: 'Indonesian',
39
+ ms: 'Malay',
40
+ yue: 'Cantonese',
41
+ };
42
+
43
+ // ─── Internal helpers ────────────────────────────────────────────────────────
44
+
45
+ /**
46
+ * Split captions into batches of the given size.
47
+ * @param {Array} captions
48
+ * @param {number} batchSize
49
+ * @returns {Array<Array>}
50
+ */
51
+ function batchCaptions(captions, batchSize = 10) {
52
+ const batches = [];
53
+ for (let i = 0; i < captions.length; i += batchSize) {
54
+ batches.push(captions.slice(i, i + batchSize));
55
+ }
56
+ return batches;
57
+ }
58
+
59
+ /**
60
+ * Build the system + user prompt for a batch of captions.
61
+ * @param {Array} captions - Caption objects with .text and optional .speakerId
62
+ * @param {string} fromLang - Full language name (e.g. "Chinese (Simplified)")
63
+ * @param {string} toLang - Full language name (e.g. "English")
64
+ * @returns {{ system: string, user: string }}
65
+ */
66
+ function buildTranslationPrompt(captions, fromLang, toLang) {
67
+ const system = [
68
+ `You are a professional subtitle translator. Translate each numbered line from ${fromLang} to ${toLang}.`,
69
+ '',
70
+ 'Rules:',
71
+ '- Return ONLY the translated lines, one per number',
72
+ '- Keep the exact same numbering (1., 2., 3., ...)',
73
+ '- Preserve [Speaker: xxx] tags unchanged — do NOT translate speaker names',
74
+ '- Keep translations concise and natural for subtitles',
75
+ '- Do not add explanations, notes, or extra text',
76
+ ].join('\n');
77
+
78
+ const user = captions
79
+ .map((cap, i) => {
80
+ const prefix = cap.speakerId ? `[Speaker: ${cap.speakerId}] ` : '';
81
+ return `${i + 1}. ${prefix}${cap.text}`;
82
+ })
83
+ .join('\n');
84
+
85
+ return { system, user };
86
+ }
87
+
88
+ /**
89
+ * Parse the LLM response back into translated texts.
90
+ * Strategy: try numbered regex match first, then fall back to positional.
91
+ *
92
+ * @param {string} content - LLM response text
93
+ * @param {Array} originalCaptions - Original caption objects (for fallback)
94
+ * @returns {Array} Translated caption objects
95
+ */
96
+ function parseTranslationResponse(content, originalCaptions) {
97
+ const lines = content.trim().split('\n').filter(l => l.trim());
98
+ const translated = [];
99
+
100
+ for (let i = 0; i < originalCaptions.length; i++) {
101
+ const pattern = new RegExp(`^${i + 1}\\.\\s*(.+)$`);
102
+ const match = lines.find(l => pattern.test(l.trim()));
103
+
104
+ if (match) {
105
+ const text = match.trim().replace(pattern, '$1').trim();
106
+ // Strip speaker tag from translated text (we preserve original speakerId)
107
+ let cleanText = text;
108
+ const speakerMatch = text.match(/^\[Speaker:\s*[^\]]+\]\s*/i);
109
+ if (speakerMatch) {
110
+ cleanText = text.slice(speakerMatch[0].length);
111
+ }
112
+ translated.push({
113
+ ...originalCaptions[i],
114
+ text: cleanText || originalCaptions[i].text,
115
+ });
116
+ } else {
117
+ // Fallback: try positional match
118
+ if (i < lines.length) {
119
+ const fallbackText = lines[i].replace(/^\d+\.\s*/, '').trim();
120
+ let cleanText = fallbackText;
121
+ const speakerMatch = fallbackText.match(/^\[Speaker:\s*[^\]]+\]\s*/i);
122
+ if (speakerMatch) {
123
+ cleanText = fallbackText.slice(speakerMatch[0].length);
124
+ }
125
+ translated.push({
126
+ ...originalCaptions[i],
127
+ text: cleanText || originalCaptions[i].text,
128
+ });
129
+ } else {
130
+ // Could not find translation — keep original
131
+ translated.push({ ...originalCaptions[i] });
132
+ }
133
+ }
134
+ }
135
+
136
+ return translated;
137
+ }
138
+
139
+ /**
140
+ * Adjust subtitle timing for translated text length differences.
141
+ * Only adjusts when the length ratio differs by more than THRESHOLD.
142
+ *
143
+ * @param {Array} sourceCaptions - Original captions (for reference length)
144
+ * @param {Array} translatedCaptions - Translated captions
145
+ * @returns {Array} Captions with adjusted endMs
146
+ */
147
+ function realignTimings(sourceCaptions, translatedCaptions) {
148
+ const THRESHOLD = 0.3; // Only adjust if >30% length difference
149
+ const MIN_GAP_MS = 100;
150
+
151
+ const result = translatedCaptions.map((cap, i) => {
152
+ const source = sourceCaptions[i];
153
+ if (!source) return cap;
154
+
155
+ const sourceLen = source.text.length;
156
+ const targetLen = cap.text.length;
157
+ if (sourceLen === 0) return cap;
158
+
159
+ const ratio = targetLen / sourceLen;
160
+ if (ratio < (1 + THRESHOLD) && ratio > (1 - THRESHOLD)) {
161
+ return cap; // Similar length, no adjustment needed
162
+ }
163
+
164
+ const originalDuration = source.endMs - source.startMs;
165
+ let newDuration = Math.round(originalDuration * ratio);
166
+
167
+ // Clamp: don't exceed next caption's start
168
+ const nextStart = i < sourceCaptions.length - 1
169
+ ? sourceCaptions[i + 1].startMs
170
+ : Infinity;
171
+ const maxDuration = nextStart - cap.startMs - MIN_GAP_MS;
172
+ if (newDuration > maxDuration && maxDuration > 0) {
173
+ newDuration = maxDuration;
174
+ }
175
+
176
+ // Floor: at least 500ms
177
+ newDuration = Math.max(newDuration, 500);
178
+
179
+ return {
180
+ ...cap,
181
+ endMs: cap.startMs + newDuration,
182
+ };
183
+ });
184
+
185
+ return result;
186
+ }
187
+
188
+ // ─── Main command ────────────────────────────────────────────────────────────
189
+
190
+ /**
191
+ * Translate command entry point (with SIGINT guard).
192
+ *
193
+ * @param {object} opts
194
+ * @param {string} opts.token - JWT auth token
195
+ * @param {string} [opts.api] - Backend base URL
196
+ * @param {string} [opts.srt] - SRT file path
197
+ * @param {string} [opts.text] - Inline text to translate
198
+ * @param {string} [opts.input] - Text file path (.txt, .md)
199
+ * @param {string} [opts.from] - Source language code (or 'auto')
200
+ * @param {string} opts.to - Target language code (required)
201
+ * @param {string} [opts.output] - Output file path
202
+ * @param {boolean}[opts.realign] - Adjust subtitle timing
203
+ * @param {number} [opts.batchSize] - Captions per LLM call (default: 10)
204
+ */
205
+ async function translate(opts) {
206
+ const sigintHandler = () => {
207
+ console.log('\n\nTranslation cancelled.');
208
+ process.exit(130);
209
+ };
210
+ process.on('SIGINT', sigintHandler);
211
+
212
+ try {
213
+ return await _translate(opts);
214
+ } finally {
215
+ process.removeListener('SIGINT', sigintHandler);
216
+ }
217
+ }
218
+
219
+ async function _translate(opts) {
220
+ const {
221
+ token,
222
+ api = API_BASE,
223
+ srt,
224
+ text,
225
+ input,
226
+ from,
227
+ to,
228
+ output: userOutput,
229
+ realign = false,
230
+ batchSize = TRANSLATE_DEFAULTS.batchSize,
231
+ } = opts;
232
+
233
+ // Determine mode
234
+ if (srt) return _translateSrt({ token, api, srt, from, to, output: userOutput, realign, batchSize });
235
+ if (text) return _translateText({ token, api, text, from, to });
236
+ if (input) return _translateFile({ token, api, input, from, to, output: userOutput });
237
+
238
+ throw new Error('No input specified. Use --srt, --text, or --input');
239
+ }
240
+
241
+ // ─── SRT Translation ────────────────────────────────────────────────────────
242
+
243
+ async function _translateSrt({ token, api, srt: srtPath, from, to, output: userOutput, realign, batchSize }) {
244
+ console.log('\n=== VoxFlow Translate (SRT) ===');
245
+
246
+ // Read and parse SRT
247
+ const resolved = path.resolve(srtPath);
248
+ const content = fs.readFileSync(resolved, 'utf8');
249
+ const captions = parseSrt(content);
250
+
251
+ if (captions.length === 0) {
252
+ throw new Error(`SRT file is empty or invalid: ${resolved}`);
253
+ }
254
+
255
+ console.log(`Input: ${path.basename(resolved)}`);
256
+ console.log(`Captions: ${captions.length}`);
257
+
258
+ // Detect source language if not specified
259
+ const fromCode = from || await autoDetectLanguage(api, captions);
260
+ const fromLang = LANG_MAP[fromCode] || fromCode;
261
+ const toLang = LANG_MAP[to] || to;
262
+
263
+ console.log(`From: ${fromLang} (${fromCode})`);
264
+ console.log(`To: ${toLang} (${to})`);
265
+ console.log(`Realign: ${realign ? 'yes' : 'no'}`);
266
+
267
+ // Batch and translate
268
+ const batches = batchCaptions(captions, batchSize);
269
+ console.log(`Batches: ${batches.length} (batch size: ${batchSize})`);
270
+ console.log('');
271
+
272
+ let allTranslated = [];
273
+ let totalQuota = 0;
274
+
275
+ for (let bi = 0; bi < batches.length; bi++) {
276
+ const batch = batches[bi];
277
+ process.stdout.write(` [${bi + 1}/${batches.length}] Translating ${batch.length} captions...`);
278
+
279
+ const { system, user } = buildTranslationPrompt(batch, fromLang, toLang);
280
+ const result = await chatCompletion({
281
+ apiBase: api,
282
+ token,
283
+ messages: [
284
+ { role: 'system', content: system },
285
+ { role: 'user', content: user },
286
+ ],
287
+ temperature: TRANSLATE_DEFAULTS.temperature,
288
+ maxTokens: TRANSLATE_DEFAULTS.maxTokens,
289
+ });
290
+
291
+ const translated = parseTranslationResponse(result.content, batch);
292
+ allTranslated = allTranslated.concat(translated);
293
+ totalQuota++;
294
+
295
+ if (result.quota) {
296
+ console.log(` OK (remaining: ${result.quota.remaining})`);
297
+ } else {
298
+ console.log(' OK');
299
+ }
300
+ }
301
+
302
+ // Optional re-alignment
303
+ if (realign) {
304
+ console.log(' Re-aligning subtitle timing...');
305
+ allTranslated = realignTimings(captions, allTranslated);
306
+ }
307
+
308
+ // Renumber captions
309
+ allTranslated = allTranslated.map((cap, i) => ({ ...cap, id: i + 1 }));
310
+
311
+ // Format output
312
+ const outputContent = formatSrt(allTranslated);
313
+
314
+ // Determine output path
315
+ let outputPath;
316
+ if (userOutput) {
317
+ outputPath = path.resolve(userOutput);
318
+ } else {
319
+ const base = path.basename(resolved, path.extname(resolved));
320
+ const dir = path.dirname(resolved);
321
+ // Pre-build filename — see scripts/check-no-asset-rewrite.js
322
+ const fname = base + '-' + to + '.srt';
323
+ outputPath = path.resolve(dir, fname);
324
+ }
325
+
326
+ fs.writeFileSync(outputPath, outputContent, 'utf8');
327
+
328
+ // Summary
329
+ console.log(`\n=== Done ===`);
330
+ console.log(`Output: ${outputPath}`);
331
+ console.log(`Captions: ${allTranslated.length}`);
332
+ console.log(`Quota: ${totalQuota} used`);
333
+
334
+ // Preview
335
+ if (allTranslated.length > 0) {
336
+ console.log(`\n--- Preview ---`);
337
+ const preview = allTranslated.slice(0, 3);
338
+ for (const cap of preview) {
339
+ const speaker = cap.speakerId ? `[${cap.speakerId}] ` : '';
340
+ const textSnippet = cap.text.length > 60
341
+ ? cap.text.slice(0, 57) + '...'
342
+ : cap.text;
343
+ console.log(` ${cap.id}. ${speaker}${textSnippet}`);
344
+ }
345
+ if (allTranslated.length > 3) {
346
+ console.log(` ... (${allTranslated.length - 3} more)`);
347
+ }
348
+ }
349
+
350
+ return {
351
+ outputPath,
352
+ captionCount: allTranslated.length,
353
+ quotaUsed: totalQuota,
354
+ from: fromCode,
355
+ to,
356
+ };
357
+ }
358
+
359
+ // ─── Text Translation ────────────────────────────────────────────────────────
360
+
361
+ async function _translateText({ token, api, text, from, to }) {
362
+ console.log('\n=== VoxFlow Translate (Text) ===');
363
+
364
+ const fromCode = from || await autoDetectLanguage(api, [{ text }]);
365
+ const fromLang = LANG_MAP[fromCode] || fromCode;
366
+ const toLang = LANG_MAP[to] || to;
367
+
368
+ console.log(`From: ${fromLang} → To: ${toLang}`);
369
+
370
+ const result = await chatCompletion({
371
+ apiBase: api,
372
+ token,
373
+ messages: [
374
+ {
375
+ role: 'system',
376
+ content: `You are a professional translator. Translate the following text from ${fromLang} to ${toLang}. Return ONLY the translation, no explanations.`,
377
+ },
378
+ { role: 'user', content: text },
379
+ ],
380
+ temperature: TRANSLATE_DEFAULTS.temperature,
381
+ maxTokens: TRANSLATE_DEFAULTS.maxTokens,
382
+ });
383
+
384
+ const translated = result.content.trim();
385
+ console.log(`\n${translated}`);
386
+
387
+ const remaining = result.quota ? result.quota.remaining : '?';
388
+ console.log(`\n(Quota: 1 used, ${remaining} remaining)`);
389
+
390
+ return { text: translated, quotaUsed: 1, from: fromCode, to };
391
+ }
392
+
393
+ // ─── File Translation ────────────────────────────────────────────────────────
394
+
395
+ async function _translateFile({ token, api, input, from, to, output: userOutput }) {
396
+ console.log('\n=== VoxFlow Translate (File) ===');
397
+
398
+ const resolved = path.resolve(input);
399
+ const content = fs.readFileSync(resolved, 'utf8');
400
+
401
+ if (content.trim().length === 0) {
402
+ throw new Error(`Input file is empty: ${resolved}`);
403
+ }
404
+
405
+ console.log(`Input: ${path.basename(resolved)}`);
406
+ console.log(`Length: ${content.length} chars`);
407
+
408
+ const fromCode = from || await autoDetectLanguage(api, [{ text: content }]);
409
+ const fromLang = LANG_MAP[fromCode] || fromCode;
410
+ const toLang = LANG_MAP[to] || to;
411
+
412
+ console.log(`From: ${fromLang} → To: ${toLang}`);
413
+
414
+ const result = await chatCompletion({
415
+ apiBase: api,
416
+ token,
417
+ messages: [
418
+ {
419
+ role: 'system',
420
+ content: `You are a professional translator. Translate the following document from ${fromLang} to ${toLang}. Preserve the original formatting (paragraphs, line breaks, markdown). Return ONLY the translation.`,
421
+ },
422
+ { role: 'user', content },
423
+ ],
424
+ temperature: TRANSLATE_DEFAULTS.temperature,
425
+ maxTokens: Math.max(TRANSLATE_DEFAULTS.maxTokens, 4000), // files may need more tokens
426
+ });
427
+
428
+ const translated = result.content.trim();
429
+
430
+ // Determine output path
431
+ let outputPath;
432
+ if (userOutput) {
433
+ outputPath = path.resolve(userOutput);
434
+ } else {
435
+ const ext = path.extname(resolved);
436
+ const base = path.basename(resolved, ext);
437
+ const dir = path.dirname(resolved);
438
+ // Pre-build filename — see scripts/check-no-asset-rewrite.js
439
+ const fname = base + '-' + to + ext;
440
+ outputPath = path.resolve(dir, fname);
441
+ }
442
+
443
+ fs.writeFileSync(outputPath, translated + '\n', 'utf8');
444
+
445
+ const remaining = result.quota ? result.quota.remaining : '?';
446
+ console.log(`\n=== Done ===`);
447
+ console.log(`Output: ${outputPath}`);
448
+ console.log(`Quota: 1 used, ${remaining} remaining`);
449
+
450
+ return { outputPath, quotaUsed: 1, from: fromCode, to };
451
+ }
452
+
453
+ // ─── Auto-detect language ────────────────────────────────────────────────────
454
+
455
+ async function autoDetectLanguage(api, captions) {
456
+ // Take first few captions (or text) for detection
457
+ const sample = captions.slice(0, 3).map(c => c.text).join(' ');
458
+ const detected = await detectLanguage({ apiBase: api, text: sample });
459
+ return detected || 'auto';
460
+ }
461
+
462
+ // ─── Exports ─────────────────────────────────────────────────────────────────
463
+
464
+ // ─── CLI Handler ────────────────────────────────────────────────────────────
465
+
466
+ async function handle(args) {
467
+ const { parseFlag, parseIntFlag, parseBoolFlag, runWithRetry } = require('../core/args');
468
+ const { getToken, getTokenInfo } = require('../core/auth');
469
+ const { API_BASE } = require('../core/config');
470
+
471
+ const api = parseFlag(args, '--api') || API_BASE;
472
+ const explicitToken = parseFlag(args, '--token');
473
+
474
+ // Parse and validate before auth
475
+ const srt = parseFlag(args, '--srt');
476
+ const text = parseFlag(args, '--text');
477
+ const input = parseFlag(args, '--input');
478
+ const from = parseFlag(args, '--from');
479
+ const to = parseFlag(args, '--to');
480
+ const output = parseFlag(args, '--output', '-o');
481
+ const realign = parseBoolFlag(args, '--realign');
482
+ const batchSize = parseIntFlag(args, '--batch-size');
483
+
484
+ // Validate --to is required
485
+ if (!to && !parseBoolFlag(args, '--help')) {
486
+ console.error('Error: --to <lang> is required. Example: voxflow translate --srt file.srt --to en');
487
+ process.exit(1);
488
+ }
489
+
490
+ // Validate language codes
491
+ const validLangs = ['zh', 'en', 'ja', 'ko', 'fr', 'de', 'es', 'pt', 'ru', 'ar', 'th', 'vi', 'it', 'id', 'ms', 'yue'];
492
+ if (to && !validLangs.includes(to)) {
493
+ console.error(`Error: --to must be one of: ${validLangs.join(', ')} (got: "${to}")`);
494
+ process.exit(1);
495
+ }
496
+ if (from && !validLangs.includes(from) && from !== 'auto') {
497
+ console.error(`Error: --from must be one of: auto, ${validLangs.join(', ')} (got: "${from}")`);
498
+ process.exit(1);
499
+ }
500
+
501
+ // Validate input source
502
+ const sources = [srt, text, input].filter(Boolean).length;
503
+ if (sources === 0 && !parseBoolFlag(args, '--help')) {
504
+ console.error('Error: Provide one of: --srt <file>, --text <text>, --input <file>');
505
+ process.exit(1);
506
+ }
507
+ if (sources > 1) {
508
+ console.error('Error: Specify only one input: --srt, --text, or --input');
509
+ process.exit(1);
510
+ }
511
+
512
+ // Validate file existence
513
+ if (srt) {
514
+ const fs = require('fs');
515
+ const path = require('path');
516
+ const resolved = path.resolve(srt);
517
+ if (!fs.existsSync(resolved)) {
518
+ console.error(`Error: SRT file not found: ${resolved}`);
519
+ process.exit(1);
520
+ }
521
+ }
522
+ if (input) {
523
+ const fs = require('fs');
524
+ const path = require('path');
525
+ const resolved = path.resolve(input);
526
+ if (!fs.existsSync(resolved)) {
527
+ console.error(`Error: Input file not found: ${resolved}`);
528
+ process.exit(1);
529
+ }
530
+ }
531
+
532
+ // Validate batch size
533
+ if (batchSize !== undefined) {
534
+ if (isNaN(batchSize) || batchSize < 1 || batchSize > 20) {
535
+ console.error(`Error: --batch-size must be between 1 and 20 (got: "${parseFlag(args, '--batch-size')}")`);
536
+ process.exit(1);
537
+ }
538
+ }
539
+
540
+ // Authenticate after all validation
541
+ let token;
542
+ if (explicitToken) {
543
+ token = explicitToken;
544
+ } else {
545
+ token = await getToken({ api });
546
+ const info = getTokenInfo();
547
+ if (info) {
548
+ console.log(`\x1b[32mLogged in as ${info.email}\x1b[0m`);
549
+ }
550
+ }
551
+
552
+ const opts = {
553
+ token, api, srt, text, input, from, to, output, realign, batchSize,
554
+ };
555
+
556
+ await runWithRetry(translate, opts, api, explicitToken);
557
+ }
558
+
559
+ const meta = {
560
+ translate: {
561
+ usage: '[opts]',
562
+ description: 'Translate SRT subtitles, text, or files',
563
+ options: [
564
+ `--srt <file> SRT subtitle file to translate`,
565
+ `--text <text> Inline text to translate`,
566
+ `--input <file> Text file (.txt, .md) to translate`,
567
+ `--from <lang> Source language code (default: auto-detect)`,
568
+ `--to <lang> Target language code (required)`,
569
+ `--output <path> Output file path (default: <input>-<lang>.<ext>)`,
570
+ `--realign Adjust subtitle timing for target language length`,
571
+ `--batch-size <n> Captions per LLM call, 1-20 (default: ${TRANSLATE_DEFAULTS.batchSize})`,
572
+ ],
573
+ examples: [
574
+ 'voxflow translate --srt subtitles.srt --to en',
575
+ 'voxflow translate --text "你好世界" --to en',
576
+ 'voxflow translate --input article.txt --to en --output article-en.txt',
577
+ ],
578
+ },
579
+ };
580
+
581
+ module.exports = {
582
+ translate,
583
+ handle,
584
+ meta,
585
+ LANG_MAP,
586
+ // Expose internals for testing
587
+ _test: {
588
+ buildTranslationPrompt,
589
+ parseTranslationResponse,
590
+ realignTimings,
591
+ batchCaptions,
592
+ },
593
+ };