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,345 @@
1
+ /**
2
+ * VoxFlow CLI — Slides command
3
+ *
4
+ * Thin orchestrator: delegates to submodules for constants, LLM generation,
5
+ * TTS narration, and HTML rendering/export.
6
+ *
7
+ * Submodules:
8
+ * ./constants.js — themes, templates, system prompt
9
+ * ./html-renderer.js — layout renderers, CSS, HTML export builder
10
+ */
11
+
12
+ 'use strict';
13
+
14
+ const fs = require('fs');
15
+ const { SLIDES_DEFAULTS } = require('../../core/config');
16
+ const { request, throwApiError, throwNetworkError } = require('../../core/http');
17
+ const { chatCompletion } = require('../../core/llm-client');
18
+ const { buildWav } = require('../../core/audio');
19
+ const { BYTES_PER_MS } = require('../../core/timeline');
20
+ const { startSpinner } = require('../../core/spinner');
21
+ const {
22
+ VALID_THEMES, VALID_TEMPLATES, VALID_MODELS,
23
+ TEMPLATE_HINTS, TEMPLATE_STRUCTURES,
24
+ SLIDES_SYSTEM_PROMPT, VALID_MODEL_ALIASES,
25
+ } = require('./constants');
26
+ const { buildExportHTML } = require('./html-renderer');
27
+
28
+ // ─── LLM Slide Generation ───────────────────────────────────────────────────
29
+
30
+ async function generateSlides(apiBase, token, text, template, model) {
31
+ const templateHint = TEMPLATE_HINTS[template] || '';
32
+ const structure = TEMPLATE_STRUCTURES[template] || [];
33
+
34
+ let userContent = text;
35
+ if (templateHint) {
36
+ userContent += `\n\nTemplate hint: ${templateHint}`;
37
+ }
38
+ if (structure.length > 0) {
39
+ const structureDesc = structure.map((s, i) => ` Slide ${i + 1}: layout="${s.layout}" — ${s.hint}`).join('\n');
40
+ userContent += `\n\nSuggested structure:\n${structureDesc}`;
41
+ }
42
+
43
+ const messages = [
44
+ { role: 'system', content: SLIDES_SYSTEM_PROMPT },
45
+ { role: 'user', content: userContent },
46
+ ];
47
+
48
+ const modelPreset = VALID_MODEL_ALIASES.includes(model) ? model : 'swift';
49
+
50
+ const result = await chatCompletion({
51
+ apiBase,
52
+ token,
53
+ messages,
54
+ temperature: 0.7,
55
+ maxTokens: 4000,
56
+ modelPreset,
57
+ });
58
+
59
+ let jsonStr = result.content.trim();
60
+ const fenceMatch = jsonStr.match(/```(?:json)?\s*([\s\S]*?)```/);
61
+ if (fenceMatch) jsonStr = fenceMatch[1].trim();
62
+
63
+ const data = JSON.parse(jsonStr);
64
+
65
+ if (!data.slides || !Array.isArray(data.slides) || data.slides.length < 2) {
66
+ throw new Error('LLM returned invalid slide structure (need at least 2 slides)');
67
+ }
68
+
69
+ return { data, quota: result.quota };
70
+ }
71
+
72
+ // ─── TTS Synthesis ──────────────────────────────────────────────────────────
73
+
74
+ async function synthesizeNarration(apiBase, token, text, voiceId, speed, index, total) {
75
+ process.stdout.write(` TTS slide [${index + 1}/${total}]...`);
76
+
77
+ let status, data;
78
+ try {
79
+ ({ status, data } = await request(`${apiBase}/api/tts/synthesize`, {
80
+ method: 'POST',
81
+ headers: {
82
+ 'Content-Type': 'application/json',
83
+ 'Authorization': `Bearer ${token}`,
84
+ },
85
+ timeoutMs: 180_000,
86
+ }, {
87
+ text,
88
+ voiceId,
89
+ speed,
90
+ volume: 1.0,
91
+ }));
92
+ } catch (err) {
93
+ console.log(' FAIL');
94
+ throwNetworkError(err, apiBase);
95
+ }
96
+
97
+ if (status !== 200 || data.code !== 'success') {
98
+ console.log(' FAIL');
99
+ throwApiError(status, data, `TTS slide ${index + 1}`);
100
+ }
101
+
102
+ const pcmBuffer = Buffer.from(data.audio, 'base64');
103
+ const durationMs = Math.round(pcmBuffer.length / BYTES_PER_MS);
104
+ console.log(` OK (${(pcmBuffer.length / 1024).toFixed(0)} KB, ${(durationMs / 1000).toFixed(1)}s)`);
105
+ return { pcm: pcmBuffer, durationMs, quota: data.quota };
106
+ }
107
+
108
+ // ─── Public API ─────────────────────────────────────────────────────────────
109
+
110
+ async function slides(opts) {
111
+ const {
112
+ token,
113
+ api: apiBase,
114
+ text: inputText,
115
+ voice = SLIDES_DEFAULTS.voice,
116
+ speed = SLIDES_DEFAULTS.speed,
117
+ theme = SLIDES_DEFAULTS.theme,
118
+ template = SLIDES_DEFAULTS.template,
119
+ model = SLIDES_DEFAULTS.model,
120
+ noAudio = false,
121
+ } = opts;
122
+
123
+ const sigintHandler = () => {
124
+ console.log('\n\nGeneration cancelled.');
125
+ process.exit(0);
126
+ };
127
+ process.on('SIGINT', sigintHandler);
128
+
129
+ try {
130
+ const totalSteps = noAudio ? 2 : 3;
131
+ let stepNum = 1;
132
+
133
+ // Step 1: Generate slide data via LLM
134
+ console.log(`\n[${stepNum}/${totalSteps}] Generating slides from text...`);
135
+ console.log(` Theme: ${theme}, Template: ${template}, Model: ${model}`);
136
+
137
+ const spinner = startSpinner(' Calling LLM...');
138
+ let slidesData;
139
+ let llmQuota = null;
140
+ try {
141
+ const result = await generateSlides(apiBase, token, inputText, template, model);
142
+ slidesData = result.data;
143
+ llmQuota = result.quota;
144
+ spinner.stop(`OK (${slidesData.slides.length} slides)`);
145
+ } catch (err) {
146
+ spinner.stop('FAIL');
147
+ throw new Error(`Slide generation failed: ${err.message}`);
148
+ }
149
+
150
+ const slideCount = slidesData.slides.length;
151
+ const layoutList = slidesData.slides.map(s => s.layout).join(', ');
152
+ console.log(` Slides: ${slideCount} (${layoutList})`);
153
+
154
+ stepNum++;
155
+
156
+ // Step 2: TTS narration (optional)
157
+ let firstQuota = llmQuota;
158
+ let lastQuota = llmQuota;
159
+
160
+ if (!noAudio) {
161
+ const narrations = slidesData.slides.filter(s => s.narration);
162
+ console.log(`\n[${stepNum}/${totalSteps}] Synthesizing narration (${narrations.length} slides with narration)...`);
163
+
164
+ for (let i = 0; i < slidesData.slides.length; i++) {
165
+ const slide = slidesData.slides[i];
166
+ if (!slide.narration) {
167
+ slide.audio = { durationMs: 5000 };
168
+ continue;
169
+ }
170
+
171
+ const result = await synthesizeNarration(
172
+ apiBase, token, slide.narration, voice, speed, i, slideCount
173
+ );
174
+ if (!firstQuota && result.quota) firstQuota = result.quota;
175
+ lastQuota = result.quota;
176
+
177
+ const { wav } = buildWav([result.pcm], 0);
178
+ const dataUri = `data:audio/wav;base64,${wav.toString('base64')}`;
179
+
180
+ slide.audio = {
181
+ durationMs: result.durationMs,
182
+ dataUri,
183
+ };
184
+ }
185
+
186
+ stepNum++;
187
+ } else {
188
+ for (const slide of slidesData.slides) {
189
+ slide.audio = { durationMs: 5000 };
190
+ }
191
+ }
192
+
193
+ // Final: Generate HTML and save
194
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
195
+ const outputPath = opts.output || `slides-${timestamp}.html`;
196
+
197
+ console.log(`\n[${stepNum}/${totalSteps}] Writing HTML...`);
198
+
199
+ const html = buildExportHTML(slidesData, theme);
200
+ fs.writeFileSync(outputPath, html, 'utf8');
201
+
202
+ const fileStat = fs.statSync(outputPath);
203
+ const fileSizeKb = (fileStat.size / 1024).toFixed(0);
204
+
205
+ console.log('\n=== Output ===');
206
+ console.log(` File: ${outputPath} (${fileSizeKb} KB)`);
207
+ console.log(` Slides: ${slideCount} (${layoutList})`);
208
+ console.log(` Theme: ${theme}`);
209
+ console.log(` Template: ${template}`);
210
+ if (!noAudio) {
211
+ const totalDuration = slidesData.slides.reduce((sum, s) => sum + (s.audio?.durationMs || 5000), 0);
212
+ console.log(` Duration: ${formatDuration(totalDuration)}`);
213
+ }
214
+ if (lastQuota) {
215
+ const quotaUsed = firstQuota && lastQuota
216
+ ? (firstQuota.remaining - lastQuota.remaining)
217
+ : slideCount * 100;
218
+ console.log(` Quota: ${quotaUsed} used, ${lastQuota.remaining ?? '?'} remaining`);
219
+ }
220
+ console.log('\n Open in browser to view and present.');
221
+
222
+ return { outputPath, slideCount };
223
+ } finally {
224
+ process.removeListener('SIGINT', sigintHandler);
225
+ }
226
+ }
227
+
228
+ function formatDuration(ms) {
229
+ const totalSec = Math.round(ms / 1000);
230
+ const min = Math.floor(totalSec / 60);
231
+ const sec = totalSec % 60;
232
+ if (min === 0) return `${sec}s`;
233
+ return `${min}m${sec.toString().padStart(2, '0')}s`;
234
+ }
235
+
236
+ // ─── CLI Handler ────────────────────────────────────────────────────────────
237
+
238
+ async function handle(args) {
239
+ const { parseFlag, parseFloatFlag, parseBoolFlag, validateSpeed, runWithRetry } = require('../../core/args');
240
+ const { getToken, getTokenInfo } = require('../../core/auth');
241
+ const { API_BASE } = require('../../core/config');
242
+
243
+ const api = parseFlag(args, '--api') || API_BASE;
244
+ const explicitToken = parseFlag(args, '--token');
245
+
246
+ const speed = parseFloatFlag(args, '--speed');
247
+ const output = parseFlag(args, '--output', '-o');
248
+
249
+ validateSpeed(args, speed);
250
+
251
+ if (output) {
252
+ if (!output.toLowerCase().endsWith('.html')) {
253
+ console.error('Error: --output path must end with .html');
254
+ process.exit(1);
255
+ }
256
+ }
257
+
258
+ const theme = parseFlag(args, '--theme');
259
+ if (theme && !VALID_THEMES.includes(theme)) {
260
+ console.error(`Error: --theme must be one of: ${VALID_THEMES.join(', ')} (got: "${theme}")`);
261
+ process.exit(1);
262
+ }
263
+
264
+ const template = parseFlag(args, '--template');
265
+ if (template && !VALID_TEMPLATES.includes(template)) {
266
+ console.error(`Error: --template must be one of: ${VALID_TEMPLATES.join(', ')} (got: "${template}")`);
267
+ process.exit(1);
268
+ }
269
+
270
+ const model = parseFlag(args, '--model');
271
+ if (model && !VALID_MODELS.includes(model)) {
272
+ console.error(`Error: --model must be one of: ${VALID_MODELS.join(', ')} (got: "${model}")`);
273
+ process.exit(1);
274
+ }
275
+
276
+ let text = parseFlag(args, '--text');
277
+ if (!text) {
278
+ const valuedFlags = new Set([
279
+ '--text', '--voice', '--speed', '--theme', '--template',
280
+ '--model', '--output', '-o', '--token', '--api',
281
+ ]);
282
+ for (let i = 0; i < args.length; i++) {
283
+ if (args[i].startsWith('-')) {
284
+ if (valuedFlags.has(args[i])) i++;
285
+ continue;
286
+ }
287
+ text = args[i];
288
+ break;
289
+ }
290
+ }
291
+
292
+ if (!text) {
293
+ console.error('Error: No text provided. Usage: voxflow slides "your topic" [options]');
294
+ process.exit(1);
295
+ }
296
+
297
+ let token;
298
+ if (explicitToken) {
299
+ token = explicitToken;
300
+ } else {
301
+ token = await getToken({ api });
302
+ const info = getTokenInfo();
303
+ if (info) {
304
+ console.log(`\x1b[32mLogged in as ${info.email}\x1b[0m`);
305
+ }
306
+ }
307
+
308
+ const slidesOpts = {
309
+ token, api, text,
310
+ theme: theme || undefined,
311
+ template: template || undefined,
312
+ model: model || undefined,
313
+ voice: parseFlag(args, '--voice') || undefined,
314
+ speed, output,
315
+ noAudio: parseBoolFlag(args, '--no-audio'),
316
+ };
317
+
318
+ await runWithRetry(slides, slidesOpts, api, explicitToken);
319
+ }
320
+
321
+ const meta = {
322
+ slides: {
323
+ usage: '"topic" [opts]',
324
+ description: 'Generate AI presentation slides as HTML',
325
+ options: [
326
+ `<text> Topic or text (positional arg or --text)`,
327
+ `--text <text> Topic to generate slides about`,
328
+ `--voice <id> TTS voice ID (default: ${SLIDES_DEFAULTS.voice})`,
329
+ `--speed <n> TTS speed 0.5-2.0 (default: ${SLIDES_DEFAULTS.speed})`,
330
+ `--theme <name> Theme: midnight, paper, ember, forest, ocean (default: ${SLIDES_DEFAULTS.theme})`,
331
+ `--template <name> Template: product, report, tutorial, pitch, free (default: ${SLIDES_DEFAULTS.template})`,
332
+ `--model <id> Model: swift, balanced, pro, creative (default: ${SLIDES_DEFAULTS.model})`,
333
+ `--no-audio Skip TTS synthesis, generate slides only`,
334
+ `--output <path> Output HTML file (default: ./slides-<timestamp>.html)`,
335
+ ],
336
+ examples: [
337
+ 'voxflow slides "AI in Healthcare"',
338
+ 'voxflow slides "Q4 Revenue Report" --template report --theme paper',
339
+ 'voxflow slides "React Tutorial" --template tutorial --model balanced',
340
+ 'voxflow slides "Startup Pitch" --template pitch --theme ocean --no-audio',
341
+ ],
342
+ },
343
+ };
344
+
345
+ module.exports = { slides, handle, meta, VALID_THEMES, VALID_TEMPLATES, VALID_MODELS };
@@ -0,0 +1,11 @@
1
+ /**
2
+ * VoxFlow CLI — Slides command
3
+ *
4
+ * Thin dispatcher — delegates to submodules in ./slides/.
5
+ * See slides/index.js for the orchestrator, slides/constants.js,
6
+ * slides/html-renderer.js for layout rendering and CSS.
7
+ */
8
+
9
+ 'use strict';
10
+
11
+ module.exports = require('./slides/index');
@@ -0,0 +1,302 @@
1
+ /**
2
+ * VoxFlow CLI — Story command
3
+ *
4
+ * Generates a story with TTS narration.
5
+ * Pipeline: LLM → parse paragraphs → TTS per paragraph → WAV
6
+ */
7
+
8
+ const fs = require('fs');
9
+ const path = require('path');
10
+ const { STORY_DEFAULTS } = require('../core/config');
11
+ const { request, throwApiError, throwNetworkError, ApiError } = require('../core/http');
12
+ const { parseParagraphs, buildWav, createSilence } = require('../core/audio');
13
+ const { startSpinner } = require('../core/spinner');
14
+ const { synthesizeTTS } = require('../core/tts-synthesizer');
15
+
16
+ // ─── Step 1: Generate Story ─────────────────────────────────────────────────
17
+
18
+ async function generateStory(apiBase, token, topic) {
19
+ const spinner = startSpinner('\n[1/3] Generating story text...');
20
+
21
+ let status, data;
22
+ try {
23
+ ({ status, data } = await request(`${apiBase}/api/llm/chat`, {
24
+ method: 'POST',
25
+ headers: {
26
+ 'Content-Type': 'application/json',
27
+ 'Authorization': `Bearer ${token}`,
28
+ },
29
+ timeoutMs: 180_000,
30
+ }, {
31
+ messages: [{ role: 'user', content: topic }],
32
+ stream: false,
33
+ temperature: 0.8,
34
+ }));
35
+ } catch (err) {
36
+ spinner.stop('FAIL');
37
+ throwNetworkError(err, apiBase);
38
+ }
39
+
40
+ if (status !== 200 || data.code !== 'success') {
41
+ spinner.stop('FAIL');
42
+ throwApiError(status, data, 'LLM');
43
+ }
44
+
45
+ const story = data.content;
46
+ const quota = data.quota;
47
+ spinner.stop('OK');
48
+ console.log(` ${story.length} chars. Quota remaining: ${quota?.remaining ?? '?'}`);
49
+ return { story, quota };
50
+ }
51
+
52
+ // ─── Step 2: TTS Synthesis ──────────────────────────────────────────────────
53
+
54
+ async function synthesizeParagraph(apiBase, token, text, voiceId, speed, index, total) {
55
+ const result = await synthesizeTTS({
56
+ apiBase, token, text, voiceId, speed,
57
+ index, total,
58
+ });
59
+
60
+ console.log(` OK (${(result.audio.length / 1024).toFixed(0)} KB)`);
61
+ return { pcm: result.audio, quota: result.quota };
62
+ }
63
+
64
+ async function synthesizeAll(apiBase, token, paragraphs, voiceId, speed) {
65
+ console.log(`\n[2/3] Synthesizing TTS audio (${paragraphs.length} segments)...`);
66
+
67
+ const pcmBuffers = [];
68
+ let lastQuota = null;
69
+ for (let i = 0; i < paragraphs.length; i++) {
70
+ const result = await synthesizeParagraph(apiBase, token, paragraphs[i], voiceId, speed, i, paragraphs.length);
71
+ pcmBuffers.push(result.pcm);
72
+ lastQuota = result.quota;
73
+ }
74
+
75
+ return { pcmBuffers, quota: lastQuota };
76
+ }
77
+
78
+ // ─── Public API ─────────────────────────────────────────────────────────────
79
+
80
+ /**
81
+ * Generate a story with TTS narration.
82
+ *
83
+ * @param {object} opts
84
+ * @param {string} opts.token - JWT token
85
+ * @param {string} opts.api - API base URL
86
+ * @param {string} [opts.voice] - TTS voice ID
87
+ * @param {string} [opts.topic] - Story prompt
88
+ * @param {string} [opts.output] - Output WAV path
89
+ * @param {number} [opts.paragraphs]- Number of paragraphs
90
+ * @param {number} [opts.speed] - TTS speed
91
+ * @param {number} [opts.silence] - Silence gap between paragraphs (seconds)
92
+ * @returns {Promise<{outputPath: string, textPath: string, duration: number, quotaUsed: number}>}
93
+ */
94
+ async function story(opts) {
95
+ // SIGINT handler for graceful cancellation
96
+ const sigintHandler = () => {
97
+ console.log('\n\nGeneration cancelled.');
98
+ process.exit(130);
99
+ };
100
+ process.on('SIGINT', sigintHandler);
101
+
102
+ try {
103
+ return await _story(opts);
104
+ } finally {
105
+ process.removeListener('SIGINT', sigintHandler);
106
+ }
107
+ }
108
+
109
+ function buildStoryPrompt(userTopic, paragraphCount) {
110
+ if (userTopic) {
111
+ return `${userTopic}\n\n要求:\n1. 严格输出 ${paragraphCount} 段(不多不少)\n2. 段落之间用且仅用一个空行分隔\n3. 不要添加段落编号、标题或前言,直接输出正文`;
112
+ }
113
+ return `请写一个适合5岁儿童的短故事,要求:
114
+ 1. 严格输出 ${paragraphCount} 段(不多不少),每段 2-3 句话
115
+ 2. 每段描述一个清晰的画面场景
116
+ 3. 语言简单易懂,充满童趣
117
+ 4. 段落之间用且仅用一个空行分隔
118
+ 5. 不要添加段落编号、标题或前言,直接输出故事内容`;
119
+ }
120
+
121
+ /**
122
+ * Clamp parsed paragraphs to exactly `target` count.
123
+ *
124
+ * The LLM doesn't always strictly honor the prompt constraint — if it
125
+ * returns more paragraphs than requested, we merge consecutive ones into
126
+ * roughly equal-sized buckets. If it returns fewer, we keep what we got.
127
+ *
128
+ * Issue #2916: `voxflow story --topic ... --paragraphs 1` was returning
129
+ * 5+ paragraphs even after the prompt fix. Hard-clamp guarantees the user
130
+ * gets exactly N TTS segments and quota cost is what they expected.
131
+ */
132
+ function clampParagraphs(paragraphs, target) {
133
+ if (!Array.isArray(paragraphs)) return [];
134
+ if (paragraphs.length <= target || target < 1) return paragraphs;
135
+ const buckets = Array.from({ length: target }, () => []);
136
+ for (let i = 0; i < paragraphs.length; i++) {
137
+ const slot = Math.min(target - 1, Math.floor((i * target) / paragraphs.length));
138
+ buckets[slot].push(paragraphs[i]);
139
+ }
140
+ return buckets.map(b => b.join(' ')).filter(s => s.trim().length > 0);
141
+ }
142
+
143
+ async function _story(opts) {
144
+ const voice = opts.voice || STORY_DEFAULTS.voice;
145
+ const paragraphCount = opts.paragraphs || STORY_DEFAULTS.paragraphs;
146
+ const speed = opts.speed ?? STORY_DEFAULTS.speed;
147
+ const silence = opts.silence ?? STORY_DEFAULTS.silence;
148
+ const api = opts.api;
149
+ const token = opts.token;
150
+
151
+ // When the user passes --topic, the default paragraph-structure prompt
152
+ // was dropped, so --paragraphs was silently ignored (issue #2916).
153
+ // buildStoryPrompt always injects the paragraph constraint.
154
+ const topic = buildStoryPrompt(opts.topic, paragraphCount);
155
+
156
+ // Default output path
157
+ let outputPath = opts.output;
158
+ if (!outputPath) {
159
+ const ts = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
160
+ outputPath = path.resolve(`story-${ts}.wav`);
161
+ }
162
+
163
+ console.log('\n=== VoxFlow Story Generator ===');
164
+ console.log(`Voice: ${voice}`);
165
+ console.log(`API: ${api}`);
166
+ console.log(`Paragraphs: ${paragraphCount}`);
167
+ console.log(`Speed: ${speed}`);
168
+ console.log(`Output: ${outputPath}`);
169
+
170
+ // Step 1: LLM
171
+ const { story: storyText } = await generateStory(api, token, topic);
172
+
173
+ // Step 2: Parse + clamp to requested paragraph count
174
+ const rawParagraphs = parseParagraphs(storyText);
175
+ if (rawParagraphs.length === 0) {
176
+ throw new Error('No paragraphs found in generated story');
177
+ }
178
+ const paragraphs = clampParagraphs(rawParagraphs, paragraphCount);
179
+ if (rawParagraphs.length !== paragraphs.length) {
180
+ console.log(` ${rawParagraphs.length} paragraphs from LLM → merged to ${paragraphs.length} (--paragraphs ${paragraphCount})`);
181
+ } else {
182
+ console.log(` ${paragraphs.length} paragraphs`);
183
+ }
184
+
185
+ // Step 3: TTS
186
+ const { pcmBuffers, quota: lastQuota } = await synthesizeAll(api, token, paragraphs, voice, speed);
187
+
188
+ // Step 4: WAV
189
+ console.log('\n[3/3] Merging audio...');
190
+ const { wav, duration } = buildWav(pcmBuffers, silence);
191
+
192
+ // Write files
193
+ const outDir = path.dirname(outputPath);
194
+ fs.mkdirSync(outDir, { recursive: true });
195
+
196
+ const outputExt = path.extname(outputPath).toLowerCase();
197
+ const needsConvert = outputExt !== '.wav';
198
+
199
+ if (needsConvert) {
200
+ // Write WAV to a temp file, then convert to the target format
201
+ const tempWavPath = outputPath.replace(/\.[^.]+$/, '.tmp.wav');
202
+ fs.writeFileSync(tempWavPath, wav);
203
+
204
+ const { checkFfmpeg, convertAudioFormat } = require('../core/ffmpeg');
205
+ const ffmpegInfo = await checkFfmpeg();
206
+ if (!ffmpegInfo.available) {
207
+ // Fallback: keep the WAV temp file as the output
208
+ fs.renameSync(tempWavPath, outputPath);
209
+ console.log(`\n Warning: ffmpeg not found, saved as WAV in ${outputPath}`);
210
+ } else {
211
+ await convertAudioFormat(tempWavPath, outputPath);
212
+ try { fs.unlinkSync(tempWavPath); } catch { /* ignore */ }
213
+ }
214
+ } else {
215
+ fs.writeFileSync(outputPath, wav);
216
+ }
217
+
218
+ // Derive text path using path.extname for robustness (works for any extension)
219
+ const textPath = outputPath.slice(0, -path.extname(outputPath).length) + '.txt';
220
+ const textContent = paragraphs.map((p, i) => `[${i + 1}] ${p}`).join('\n\n');
221
+ fs.writeFileSync(textPath, textContent, 'utf8');
222
+
223
+ const quotaUsed = 1 + paragraphs.length;
224
+ console.log(`\n=== Done ===`);
225
+ console.log(`Output: ${outputPath} (${(fs.statSync(outputPath).size / 1024).toFixed(1)} KB, ${duration.toFixed(1)}s)`);
226
+ console.log(`Story: ${textPath}`);
227
+ console.log(`Quota: ${quotaUsed} used, ${lastQuota?.remaining ?? '?'} remaining`);
228
+
229
+ return { outputPath, textPath, duration, quotaUsed };
230
+ }
231
+
232
+ // ─── CLI Handler ────────────────────────────────────────────────────────────
233
+
234
+ async function handle(args) {
235
+ const { parseFlag, parseIntFlag, parseFloatFlag, validateSpeed, validateSilence, validateOutput, runWithRetry } = require('../core/args');
236
+ const { getToken, getTokenInfo } = require('../core/auth');
237
+ const { API_BASE } = require('../core/config');
238
+
239
+ const api = parseFlag(args, '--api') || API_BASE;
240
+ const explicitToken = parseFlag(args, '--token');
241
+
242
+ // Parse and validate options before auth
243
+ const paragraphs = parseIntFlag(args, '--paragraphs');
244
+ const speed = parseFloatFlag(args, '--speed');
245
+ const silence = parseFloatFlag(args, '--silence');
246
+ const output = parseFlag(args, '--output', '-o');
247
+
248
+ if (paragraphs !== undefined) {
249
+ if (isNaN(paragraphs) || paragraphs < 1 || paragraphs > 20) {
250
+ console.error(`Error: --paragraphs must be an integer between 1 and 20 (got: "${parseFlag(args, '--paragraphs')}")`);
251
+ process.exit(1);
252
+ }
253
+ }
254
+
255
+ validateSpeed(args, speed);
256
+ validateSilence(args, silence);
257
+ validateOutput(output);
258
+
259
+ // Authenticate after validation
260
+ let token;
261
+ if (explicitToken) {
262
+ token = explicitToken;
263
+ } else {
264
+ token = await getToken({ api });
265
+ const info = getTokenInfo();
266
+ if (info) {
267
+ console.log(`\x1b[32mLogged in as ${info.email}\x1b[0m`);
268
+ }
269
+ }
270
+
271
+ const opts = {
272
+ token, api,
273
+ topic: parseFlag(args, '--topic'),
274
+ voice: parseFlag(args, '--voice'),
275
+ output, paragraphs, speed, silence,
276
+ };
277
+
278
+ await runWithRetry(story, opts, api, explicitToken);
279
+ }
280
+
281
+ const meta = {
282
+ story: {
283
+ usage: '[opts]',
284
+ description: 'Generate a story with TTS narration',
285
+ options: [
286
+ `--topic <text> Story topic (default: children's story)`,
287
+ `--voice <id> TTS voice ID (default: ${STORY_DEFAULTS.voice})`,
288
+ `--output <path> Output WAV path (default: ./story-<timestamp>.wav)`,
289
+ `--paragraphs <n> Paragraph count, 1-20 (default: ${STORY_DEFAULTS.paragraphs})`,
290
+ `--speed <n> TTS speed 0.5-2.0 (default: ${STORY_DEFAULTS.speed})`,
291
+ `--silence <sec> Silence between paragraphs, 0-5.0 (default: ${STORY_DEFAULTS.silence})`,
292
+ ],
293
+ examples: [
294
+ 'voxflow story --topic "三只小猪"',
295
+ 'voxflow story --topic "space adventure" --voice v-male-Bk7vD3xP --paragraphs 10',
296
+ ],
297
+ },
298
+ // Alias picked up by the CLI router (issue #2389)
299
+ generate: { alias: 'story', description: 'Alias for story' },
300
+ };
301
+
302
+ module.exports = { story, handle, meta, ApiError, _test: { parseParagraphs, buildWav, createSilence, buildStoryPrompt, clampParagraphs } };