voxflow 1.15.0 → 1.15.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (453) hide show
  1. package/README.md +34 -0
  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-render.js +282 -0
  383. package/lib/commands/slice-stage.js +264 -0
  384. package/lib/commands/slice.js +346 -0
  385. package/lib/commands/slides/constants.js +108 -0
  386. package/lib/commands/slides/html-renderer.js +338 -0
  387. package/lib/commands/slides/index.js +345 -0
  388. package/lib/commands/slides.js +11 -0
  389. package/lib/commands/story.js +302 -0
  390. package/lib/commands/summarize.js +532 -0
  391. package/lib/commands/synthesize.js +261 -0
  392. package/lib/commands/translate.js +593 -0
  393. package/lib/commands/upgrade.js +249 -0
  394. package/lib/commands/video-translate.js +577 -0
  395. package/lib/commands/voices.js +292 -0
  396. package/lib/core/agent-env.js +104 -0
  397. package/lib/core/args.js +107 -0
  398. package/lib/core/asr-client.js +448 -0
  399. package/lib/core/asr-jobs-client.js +126 -0
  400. package/lib/core/asr-jobs-store.js +105 -0
  401. package/lib/core/asr-r2-upload.js +181 -0
  402. package/lib/core/asr-upload.js +132 -0
  403. package/lib/core/audio-extract.js +150 -0
  404. package/lib/core/audio.js +219 -0
  405. package/lib/core/auth.js +880 -0
  406. package/lib/core/config.js +197 -0
  407. package/lib/core/feedback.js +64 -0
  408. package/lib/core/ffmpeg.js +476 -0
  409. package/lib/core/http.js +188 -0
  410. package/lib/core/image-client.js +55 -0
  411. package/lib/core/intent-params.js +11 -0
  412. package/lib/core/llm-client.js +76 -0
  413. package/lib/core/logger.js +208 -0
  414. package/lib/core/mic-recorder.js +182 -0
  415. package/lib/core/pause-markers.js +94 -0
  416. package/lib/core/podcast-pacing.js +118 -0
  417. package/lib/core/spinner.js +33 -0
  418. package/lib/core/srt.js +394 -0
  419. package/lib/core/telemetry.js +100 -0
  420. package/lib/core/timeline.js +92 -0
  421. package/lib/core/tts-synthesizer.js +70 -0
  422. package/lib/core/update-check.js +185 -0
  423. package/lib/core/url-download.js +148 -0
  424. package/lib/core/whisper-local.js +279 -0
  425. package/lib/internal/deck-validator.js +488 -0
  426. package/lib/internal/slice-themes.json +370 -0
  427. package/lib/stage-core/cloud-render.js +170 -0
  428. package/lib/stage-core/deck-format.js +133 -0
  429. package/lib/stage-core/edit-prompt.js +104 -0
  430. package/lib/stage-core/event-bus.js +31 -0
  431. package/lib/stage-core/port.js +46 -0
  432. package/lib/stage-core/server.js +352 -0
  433. package/lib/stage-core/snapshot-store.js +198 -0
  434. package/lib/stage-core/watcher.js +106 -0
  435. package/lib/stage-ui/slice/template.js +1672 -0
  436. package/package.json +9 -4
  437. package/skills/.claude-plugin/marketplace.json +22 -0
  438. package/skills/.claude-plugin/plugin.json +25 -0
  439. package/skills/LICENSE +21 -0
  440. package/skills/README.md +120 -0
  441. package/skills/hub/SKILL.md +317 -0
  442. package/skills/podcast/SKILL.md +146 -0
  443. package/skills/slice/SKILL.md +205 -0
  444. package/skills/slice/agents/openai.yaml +4 -0
  445. package/skills/slice/references/deck-schema.md +183 -0
  446. package/skills/slice/references/example-decks.md +108 -0
  447. package/skills/slice/references/themes.md +172 -0
  448. package/skills/transcribe/SKILL.md +473 -0
  449. package/skills/video/SKILL.md +261 -0
  450. package/skills/voxflow-slice/SKILL.md +271 -0
  451. package/skills/voxflow-slice/examples/article.md +13 -0
  452. package/skills/voxflow-slice/examples/expected-deck.json +39 -0
  453. package/skills/voxflow-slice/examples/validate.mjs +46 -0
@@ -0,0 +1,639 @@
1
+ /**
2
+ * VoxFlow CLI — Narrate command
3
+ *
4
+ * Convert file, text, or script to multi-segment TTS audio.
5
+ * Three input modes:
6
+ * 1. Plain text: --text "..." or stdin pipe
7
+ * 2. File: --input file.txt / file.md
8
+ * 3. Script: --script script.json (per-segment voice/speed control)
9
+ *
10
+ * Supports output formats: WAV (default), MP3.
11
+ * Pipeline: read input → split segments → TTS each → concatenate → output
12
+ */
13
+
14
+ const fs = require('fs');
15
+ const path = require('path');
16
+ const { NARRATE_DEFAULTS } = require('../core/config');
17
+ const { ApiError } = require('../core/http');
18
+ const { parseParagraphs, buildWav, concatAudioBuffers, getFileExtension } = require('../core/audio');
19
+ const { synthesizeTTS } = require('../core/tts-synthesizer');
20
+
21
+ // ─── Script Parsing ──────────────────────────────────────────────────────────
22
+
23
+ /**
24
+ * Parse and validate a script JSON file.
25
+ * @param {string} filePath - Path to script.json
26
+ * @returns {{ segments: Array<{text: string, voiceId?: string, speed?: number, volume?: number, pitch?: number}>, silence: number, output?: string }}
27
+ */
28
+ function parseScript(filePath) {
29
+ if (!fs.existsSync(filePath)) {
30
+ throw new Error(`Script file not found: ${filePath}`);
31
+ }
32
+
33
+ let raw;
34
+ try {
35
+ raw = JSON.parse(fs.readFileSync(filePath, 'utf8'));
36
+ } catch (err) {
37
+ throw new Error(`Invalid JSON in script file: ${err.message}`);
38
+ }
39
+
40
+ if (!raw.segments || !Array.isArray(raw.segments) || raw.segments.length === 0) {
41
+ throw new Error('Script must have a non-empty "segments" array');
42
+ }
43
+
44
+ for (let i = 0; i < raw.segments.length; i++) {
45
+ const seg = raw.segments[i];
46
+ if (!seg.text || typeof seg.text !== 'string' || seg.text.trim().length === 0) {
47
+ throw new Error(`Segment ${i + 1} must have a non-empty "text" field`);
48
+ }
49
+ }
50
+
51
+ return {
52
+ segments: raw.segments.map((s) => ({
53
+ text: s.text.trim(),
54
+ voiceId: s.voiceId || undefined,
55
+ speed: s.speed != null ? Number(s.speed) : undefined,
56
+ volume: s.volume != null ? Number(s.volume) : undefined,
57
+ pitch: s.pitch != null ? Number(s.pitch) : undefined,
58
+ })),
59
+ silence: raw.silence != null ? Number(raw.silence) : NARRATE_DEFAULTS.silence,
60
+ output: raw.output || undefined,
61
+ };
62
+ }
63
+
64
+ // ─── Markdown Stripping ──────────────────────────────────────────────────────
65
+
66
+ /**
67
+ * Strip basic markdown syntax for cleaner TTS input.
68
+ * Removes: headings (#), bold (**), italic (*), links [text](url), images, code blocks.
69
+ */
70
+ function stripMarkdown(text) {
71
+ return text
72
+ // Remove code blocks (``` ... ```)
73
+ .replace(/```[\s\S]*?```/g, '')
74
+ // Remove inline code (`...`)
75
+ .replace(/`([^`]+)`/g, '$1')
76
+ // Remove images ![alt](url)
77
+ .replace(/!\[[^\]]*\]\([^)]*\)/g, '')
78
+ // Convert links [text](url) → text
79
+ .replace(/\[([^\]]+)\]\([^)]*\)/g, '$1')
80
+ // Remove headings (# ... )
81
+ .replace(/^#{1,6}\s+/gm, '')
82
+ // Remove bold/italic markers
83
+ .replace(/\*{1,3}([^*]+)\*{1,3}/g, '$1')
84
+ .replace(/_{1,3}([^_]+)_{1,3}/g, '$1')
85
+ // Remove horizontal rules
86
+ .replace(/^[-*_]{3,}\s*$/gm, '')
87
+ // Remove blockquote markers
88
+ .replace(/^>\s?/gm, '')
89
+ // Clean up extra blank lines
90
+ .replace(/\n{3,}/g, '\n\n')
91
+ .trim();
92
+ }
93
+
94
+ // ─── Stdin Reading ───────────────────────────────────────────────────────────
95
+
96
+ /**
97
+ * Read all data from stdin (for pipe mode).
98
+ * @returns {Promise<string>}
99
+ */
100
+ async function readStdin() {
101
+ const chunks = [];
102
+ for await (const chunk of process.stdin) {
103
+ chunks.push(chunk);
104
+ }
105
+ return Buffer.concat(chunks).toString('utf8');
106
+ }
107
+
108
+ // ─── TTS Synthesis ───────────────────────────────────────────────────────────
109
+
110
+ async function synthesizeSegment(apiBase, token, text, voiceId, speed, volume, pitch, format, index, total) {
111
+ const result = await synthesizeTTS({
112
+ apiBase, token, text, voiceId,
113
+ speed: speed ?? 1.0,
114
+ volume: volume ?? 1.0,
115
+ pitch,
116
+ format: format || 'pcm',
117
+ index, total,
118
+ });
119
+
120
+ const formatLabel = format === 'mp3' ? 'MP3' : format === 'wav' ? 'WAV' : 'PCM';
121
+ console.log(` OK (${(result.audio.length / 1024).toFixed(0)} KB ${formatLabel})`);
122
+ return result;
123
+ }
124
+
125
+ // ─── Risk scanner (pre-flight) ───────────────────────────────────────────────
126
+
127
+ /**
128
+ * Heuristic check for segments likely to trigger TTS provider edge cases.
129
+ * Most prominent: Issue #2901 — Chinese TTS voices return tts_failed on
130
+ * isolated short Latin fragments (single brand names, short English headings).
131
+ *
132
+ * Returns an array of { index, severity, message, text }. Empty array when
133
+ * no risks are detected. Does NOT block — purely informational.
134
+ */
135
+ function scanSegmentRisks(segments) {
136
+ const risks = [];
137
+ for (let i = 0; i < segments.length; i++) {
138
+ const text = (segments[i].text || '').trim();
139
+ const isShort = text.length > 0 && text.length < 30;
140
+ const isLatinOnly = /^[A-Za-z0-9 .,_\-:;'"!?@#$%&*()/]+$/.test(text);
141
+ const hasLatinWord = /[A-Za-z]{2,}/.test(text);
142
+
143
+ if (isShort && isLatinOnly) {
144
+ risks.push({
145
+ index: i + 1,
146
+ severity: 'high',
147
+ text,
148
+ message: `pure-Latin short line (${text.length} chars) — Chinese TTS often returns tts_failed (Issue #2901). Try padding with Chinese context.`,
149
+ });
150
+ } else if (text.length < 25 && hasLatinWord) {
151
+ // Mixed CJK + Latin short text. Empirically the threshold below which
152
+ // a Chinese voice on a Chinese-mode call still fails on the embedded
153
+ // Latin words — saw 17-char "测试 VoxFlow 的 narrate 命令" trigger it.
154
+ risks.push({
155
+ index: i + 1,
156
+ severity: 'medium',
157
+ text,
158
+ message: `short line (${text.length} chars) with Latin word(s) — may fail; CLI will auto-retry with Chinese context wrap.`,
159
+ });
160
+ }
161
+ }
162
+ return risks;
163
+ }
164
+
165
+ /**
166
+ * Determine whether a segment is a candidate for the #2901 auto-wrap retry.
167
+ * We only retry when the failure is plausibly a Chinese-TTS-vs-Latin issue
168
+ * AND the text is short enough that wrapping with Chinese context is sane.
169
+ */
170
+ function shouldAutoWrap(text) {
171
+ const t = (text || '').trim();
172
+ if (!t || t.length > 60) return false;
173
+ // Latin-only OR latin-heavy short text where adding Chinese context
174
+ // empirically rescues the synthesis.
175
+ const isLatinOnly = /^[A-Za-z0-9 .,_\-:;'"!?@#$%&*()/]+$/.test(t);
176
+ const latinChars = (t.match(/[A-Za-z]/g) || []).length;
177
+ return isLatinOnly || (latinChars / t.length > 0.4 && t.length < 30);
178
+ }
179
+
180
+ /**
181
+ * Wrap pseudonym: prefix with a short Chinese phrase that gives the TTS
182
+ * model enough Chinese context to switch into Mandarin mode for the trailing
183
+ * Latin tokens. Empirically, "如下:" works well; ending with "。" caps the
184
+ * sentence so prosody settles.
185
+ */
186
+ function chineseContextWrap(text) {
187
+ return `如下:${text}。`;
188
+ }
189
+
190
+ /**
191
+ * Detect a TTS-provider rejection from an ApiError. The CLI's `throwApiError`
192
+ * wraps any 5xx into `code='server_error'` and embeds the backend's own code
193
+ * (e.g. `tts_failed`) into the message in the `[tts_failed]` form. We pattern
194
+ * match on that marker to distinguish #2901-style provider rejections from
195
+ * real server faults (which we shouldn't auto-retry — that just amplifies
196
+ * load on a struggling backend).
197
+ */
198
+ function isTtsProviderRejection(err) {
199
+ if (!(err instanceof ApiError)) return false;
200
+ if (err.code === 'tts_failed') return true; // future-proof: if propagated cleanly
201
+ return /\[tts_failed\]/.test(err.message || '');
202
+ }
203
+
204
+ /**
205
+ * Synthesize one segment with one auto-retry on `tts_failed` using a
206
+ * Chinese-context wrap. Returns the API result on success; the audio belongs
207
+ * to the wrapped text, but the user-visible transcript still shows the
208
+ * original — that's intentional, the wrap is silent compensation.
209
+ *
210
+ * Throws every other error class (token_expired, quota_exceeded, network)
211
+ * unchanged so the outer loop can fast-fail.
212
+ */
213
+ async function synthesizeWithRetry(opts, attempts = { wrap: 0 }) {
214
+ try {
215
+ return await synthesizeSegment(
216
+ opts.apiBase, opts.token, opts.text, opts.voiceId,
217
+ opts.speed, opts.volume, opts.pitch, opts.format,
218
+ opts.index, opts.total,
219
+ );
220
+ } catch (err) {
221
+ if (isTtsProviderRejection(err) && attempts.wrap === 0 && shouldAutoWrap(opts.text)) {
222
+ const wrapped = chineseContextWrap(opts.text);
223
+ process.stdout.write(`\n ↻ retrying with Chinese context wrap: ${JSON.stringify(wrapped)}\n TTS [${opts.index + 1}/${opts.total}]...`);
224
+ return await synthesizeWithRetry({ ...opts, text: wrapped }, { wrap: 1 });
225
+ }
226
+ throw err;
227
+ }
228
+ }
229
+
230
+ // ─── Public API ──────────────────────────────────────────────────────────────
231
+
232
+ /**
233
+ * Narrate text, file, or script to a WAV file.
234
+ *
235
+ * @param {object} opts
236
+ * @param {string} opts.token - JWT token
237
+ * @param {string} opts.api - API base URL
238
+ * @param {string} [opts.text] - Inline text to narrate
239
+ * @param {string} [opts.input] - Path to .txt or .md file
240
+ * @param {string} [opts.script] - Path to .json script file
241
+ * @param {string} [opts.voice] - Default TTS voice ID
242
+ * @param {number} [opts.speed] - Default TTS speed
243
+ * @param {string} [opts.format] - Output format: pcm, wav, mp3 (default: pcm → WAV)
244
+ * @param {number} [opts.silence] - Silence gap between segments (seconds)
245
+ * @param {string} [opts.output] - Output WAV path
246
+ * @returns {Promise<{outputPath: string, textPath: string, duration: number, quotaUsed: number, segmentCount: number, format: string}>}
247
+ */
248
+ async function narrate(opts) {
249
+ let isExiting = false;
250
+
251
+ const sigintHandler = () => {
252
+ if (isExiting) return;
253
+ isExiting = true;
254
+ console.log('\n\nNarration cancelled.');
255
+ process.exit(130);
256
+ };
257
+ process.on('SIGINT', sigintHandler);
258
+
259
+ try {
260
+ return await _narrate(opts);
261
+ } finally {
262
+ process.removeListener('SIGINT', sigintHandler);
263
+ }
264
+ }
265
+
266
+ async function _narrate(opts) {
267
+ const defaultVoice = opts.voice || NARRATE_DEFAULTS.voice;
268
+ const defaultSpeed = opts.speed ?? NARRATE_DEFAULTS.speed;
269
+ const format = opts.format || 'pcm'; // API format: pcm | wav | mp3
270
+ const api = opts.api;
271
+ const token = opts.token;
272
+
273
+ let segments; // Array of { text, voiceId?, speed?, volume?, pitch? }
274
+ let silence;
275
+ let outputPath;
276
+ let inputLabel;
277
+
278
+ // ─── Determine input source ────────────────────────────────────────────
279
+
280
+ if (opts.script) {
281
+ // Script mode: JSON with per-segment control
282
+ const script = parseScript(opts.script);
283
+ segments = script.segments;
284
+ silence = opts.silence ?? script.silence;
285
+ outputPath = opts.output || script.output;
286
+ inputLabel = `script: ${opts.script} (${segments.length} segments)`;
287
+
288
+ } else if (opts.input) {
289
+ // File mode: read .txt or .md
290
+ const filePath = path.resolve(opts.input);
291
+ if (!fs.existsSync(filePath)) {
292
+ throw new Error(`Input file not found: ${filePath}`);
293
+ }
294
+
295
+ let content = fs.readFileSync(filePath, 'utf8');
296
+
297
+ // Strip markdown for .md files
298
+ const ext = path.extname(filePath).toLowerCase();
299
+ if (ext === '.md' || ext === '.markdown') {
300
+ content = stripMarkdown(content);
301
+ }
302
+
303
+ const paragraphs = parseParagraphs(content);
304
+ if (paragraphs.length === 0) {
305
+ throw new Error('No text content found in input file');
306
+ }
307
+
308
+ segments = paragraphs.map((text) => ({ text }));
309
+ silence = opts.silence ?? NARRATE_DEFAULTS.silence;
310
+ outputPath = opts.output;
311
+ inputLabel = `file: ${opts.input} (${segments.length} paragraphs)`;
312
+
313
+ } else if (opts.text) {
314
+ // Plain text mode
315
+ const paragraphs = parseParagraphs(opts.text);
316
+ if (paragraphs.length === 0) {
317
+ throw new Error('No text content provided');
318
+ }
319
+
320
+ segments = paragraphs.map((text) => ({ text }));
321
+ silence = opts.silence ?? NARRATE_DEFAULTS.silence;
322
+ outputPath = opts.output;
323
+ inputLabel = `text: ${opts.text.length} chars (${segments.length} paragraphs)`;
324
+
325
+ } else if (!process.stdin.isTTY) {
326
+ // Stdin pipe mode
327
+ const stdinText = await readStdin();
328
+ if (!stdinText || stdinText.trim().length === 0) {
329
+ throw new Error('No input provided via stdin');
330
+ }
331
+
332
+ const paragraphs = parseParagraphs(stdinText);
333
+ if (paragraphs.length === 0) {
334
+ throw new Error('No text content found in stdin input');
335
+ }
336
+
337
+ segments = paragraphs.map((text) => ({ text }));
338
+ silence = opts.silence ?? NARRATE_DEFAULTS.silence;
339
+ inputLabel = `stdin (${segments.length} paragraphs)`;
340
+
341
+ } else {
342
+ throw new Error(
343
+ 'No input provided. Use one of:\n' +
344
+ ' --input <file.txt> Read a text or markdown file\n' +
345
+ ' --text "text" Provide inline text\n' +
346
+ ' --script <file.json> Use a script with per-segment control\n' +
347
+ ' echo "text" | voxflow narrate Pipe from stdin'
348
+ );
349
+ }
350
+
351
+ // Default output path. Prefer the input file's basename — matches the
352
+ // user's mental model: `voxflow narrate --input design.md` → `design.wav`.
353
+ // Falls back to a timestamped name only when there's no input file
354
+ // (--text mode, --script JSON without explicit output, stdin pipe).
355
+ //
356
+ // Use path.join(process.cwd(), ...) explicitly. path.resolve() should
357
+ // do the same, but in the ncc-bundled dist/index.js it occasionally
358
+ // resolved against the bundle's runtime dir during 1.10.9 verify —
359
+ // output ended up at node_modules/voxflow/dist/cli/<file>. Joining
360
+ // process.cwd() explicitly side-steps that.
361
+ // CRITICAL: ncc's static asset analyzer rewrites
362
+ // path.join(<dynamic>, `${expr}${ext}`)
363
+ // into __webpack_require__.ab + 'cli/' + expr + ext when the template
364
+ // starts with a non-literal — output ended up at dist/cli/<file> in
365
+ // 1.10.10/.11. Build the filename string FIRST so path.join sees two
366
+ // opaque arguments and the analyzer leaves it alone.
367
+ const ext = getFileExtension(format);
368
+ if (!outputPath) {
369
+ let filename;
370
+ if (opts.input) {
371
+ const base = path.basename(opts.input, path.extname(opts.input));
372
+ filename = base + ext;
373
+ } else {
374
+ const ts = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
375
+ filename = 'narration-' + ts + ext;
376
+ }
377
+ outputPath = path.join(process.cwd(), filename);
378
+ }
379
+ if (!outputPath.endsWith(ext)) {
380
+ // Replace wrong extension or append
381
+ outputPath = outputPath.replace(/\.(wav|mp3|pcm)$/i, '') + ext;
382
+ }
383
+
384
+ // Print header
385
+ console.log('\n=== VoxFlow Narrate ===');
386
+ console.log(`Input: ${inputLabel}`);
387
+ console.log(`Voice: ${defaultVoice}${opts.script ? ' (may be overridden per segment)' : ''}`);
388
+ console.log(`Format: ${format === 'pcm' ? 'wav (pcm)' : format}`);
389
+ console.log(`Speed: ${defaultSpeed}`);
390
+ if (format === 'mp3') {
391
+ console.log(`Output: ${outputPath}`);
392
+ console.log(` (MP3 mode: no silence inserted between segments)`);
393
+ } else {
394
+ console.log(`Silence: ${silence}s`);
395
+ console.log(`Output: ${outputPath}`);
396
+ }
397
+
398
+ // ─── Pre-flight risk scan ──────────────────────────────────────────────
399
+ // Flag segments likely to hit known provider edge cases (e.g. #2901)
400
+ // BEFORE making any API calls. Informational only — does not block.
401
+
402
+ const risks = scanSegmentRisks(segments);
403
+ if (risks.length > 0) {
404
+ console.log('');
405
+ console.log(`⚠ ${risks.length} segment(s) may trigger TTS edge cases:`);
406
+ for (const r of risks) {
407
+ const preview = r.text.length > 50 ? `${r.text.slice(0, 50)}…` : r.text;
408
+ console.log(` [${r.severity}] segment ${r.index}: ${JSON.stringify(preview)}`);
409
+ console.log(` → ${r.message}`);
410
+ }
411
+ console.log(' CLI will auto-retry with Chinese context wrap on tts_failed.');
412
+ }
413
+
414
+ // ─── TTS synthesis loop ────────────────────────────────────────────────
415
+
416
+ console.log(`\n[1/2] Synthesizing TTS audio (${segments.length} segments)...`);
417
+
418
+ const audioBuffers = [];
419
+ const skipped = []; // [{ index, text, message }]
420
+ const succeededSegments = [];
421
+ let lastQuota = null;
422
+
423
+ for (let i = 0; i < segments.length; i++) {
424
+ const seg = segments[i];
425
+ try {
426
+ const result = await synthesizeWithRetry({
427
+ apiBase: api, token,
428
+ text: seg.text,
429
+ voiceId: seg.voiceId || defaultVoice,
430
+ speed: seg.speed ?? defaultSpeed,
431
+ volume: seg.volume,
432
+ pitch: seg.pitch,
433
+ format,
434
+ index: i, total: segments.length,
435
+ });
436
+ audioBuffers.push(result.audio);
437
+ succeededSegments.push(seg);
438
+ lastQuota = result.quota;
439
+ } catch (err) {
440
+ // Hard-fail conditions: user must act, no point continuing.
441
+ // 'quota_exceeded' kept as alias to remain backward-compatible with
442
+ // older bundled CLI builds that still emit it (renamed to
443
+ // 'insufficient_quota' in 2026-05 to disambiguate from 'rate_limited').
444
+ if (err instanceof ApiError && [
445
+ 'insufficient_quota', 'quota_exceeded',
446
+ 'token_expired', 'network_error'
447
+ ].includes(err.code)) {
448
+ throw err;
449
+ }
450
+ // Soft-fail: log + skip the segment, keep narrating the rest.
451
+ // Common cause: TTS provider rejects a particular text (e.g. Chinese voice
452
+ // on a pure-English short fragment left over from Markdown headings, see #2901).
453
+ const preview = seg.text.length > 60 ? `${seg.text.slice(0, 60)}…` : seg.text;
454
+ console.log(` ⚠ Segment ${i + 1} skipped: ${err.message}`);
455
+ console.log(` text: ${JSON.stringify(preview)}`);
456
+ skipped.push({ index: i + 1, text: seg.text, message: err.message });
457
+ }
458
+ }
459
+
460
+ if (audioBuffers.length === 0) {
461
+ // All segments failed. Build a concrete, actionable error rather than just
462
+ // re-throwing the first message: list the offending segments and suggest
463
+ // the most common fix (#2901 — pad short Latin lines with Chinese context).
464
+ const lines = [
465
+ `All ${segments.length} segments failed TTS synthesis.`,
466
+ '',
467
+ 'Failed segments:',
468
+ ...skipped.map((s) => {
469
+ const preview = s.text.length > 60 ? `${s.text.slice(0, 60)}…` : s.text;
470
+ return ` [${s.index}] ${JSON.stringify(preview)}\n ${s.message}`;
471
+ }),
472
+ '',
473
+ 'How to fix:',
474
+ ' • Short pure-English lines (brand names, English headings) often fail',
475
+ ' on Chinese TTS voices (Issue #2901). Pad with Chinese context, e.g.',
476
+ ' "VoxFlow" → "VoxFlow 工具介绍" or merge into adjacent paragraphs.',
477
+ ' • CLI already auto-retries with Chinese wrap; if that also failed, the',
478
+ ' provider may be down — try `voxflow voices` to check connectivity.',
479
+ ' • Re-run with -vv to see full TTS request/response for each segment.',
480
+ ];
481
+ throw new Error(lines.join('\n'));
482
+ }
483
+
484
+ // ─── Build WAV ─────────────────────────────────────────────────────────
485
+
486
+ console.log('\n[2/2] Merging audio...');
487
+ const { audio, wav, duration } = format === 'mp3' || format === 'wav'
488
+ ? concatAudioBuffers(audioBuffers, format, silence)
489
+ : buildWav(audioBuffers, silence);
490
+
491
+ const outBuffer = audio || wav;
492
+
493
+ // Write files
494
+ const outDir = path.dirname(outputPath);
495
+ fs.mkdirSync(outDir, { recursive: true });
496
+ fs.writeFileSync(outputPath, outBuffer);
497
+
498
+ const textPath = outputPath.replace(/\.(wav|mp3)$/i, '.txt');
499
+ const textContent = succeededSegments.map((s, i) => {
500
+ const prefix = s.voiceId ? `[${i + 1}|${s.voiceId}]` : `[${i + 1}]`;
501
+ return `${prefix} ${s.text}`;
502
+ }).join('\n\n');
503
+ fs.writeFileSync(textPath, textContent, 'utf8');
504
+
505
+ const quotaUsed = succeededSegments.length;
506
+ console.log(`\n=== Done ===`);
507
+ console.log(`Output: ${outputPath} (${(outBuffer.length / 1024).toFixed(1)} KB, ${duration.toFixed(1)}s)`);
508
+ console.log(`Transcript: ${textPath}`);
509
+ console.log(`Segments: ${succeededSegments.length}/${segments.length}${skipped.length ? ` (${skipped.length} skipped)` : ''}`);
510
+ console.log(`Quota: ${quotaUsed} used, ${lastQuota?.remaining ?? '?'} remaining`);
511
+
512
+ return {
513
+ outputPath,
514
+ textPath,
515
+ duration,
516
+ quotaUsed,
517
+ segmentCount: succeededSegments.length,
518
+ skippedCount: skipped.length,
519
+ format,
520
+ };
521
+ }
522
+
523
+ // ─── CLI Handler ────────────────────────────────────────────────────────────
524
+
525
+ async function handle(args) {
526
+ const { parseFlag, parseFloatFlag, validateSpeed, validateSilence, validateOutput, validateFormat, runWithRetry } = require('../core/args');
527
+ const { getToken, getTokenInfo } = require('../core/auth');
528
+ const { API_BASE } = require('../core/config');
529
+
530
+ const api = parseFlag(args, '--api') || API_BASE;
531
+ const explicitToken = parseFlag(args, '--token');
532
+
533
+ // Parse and validate before auth
534
+ const input = parseFlag(args, '--input');
535
+ const text = parseFlag(args, '--text');
536
+ const script = parseFlag(args, '--script');
537
+ const speed = parseFloatFlag(args, '--speed');
538
+ const silence = parseFloatFlag(args, '--silence');
539
+ const output = parseFlag(args, '--output', '-o');
540
+ const format = parseFlag(args, '--format');
541
+
542
+ validateSpeed(args, speed);
543
+ validateSilence(args, silence);
544
+ validateOutput(output, format);
545
+ validateFormat(format);
546
+
547
+ // Validate file existence for --input and --script
548
+ if (input) {
549
+ const fs = require('fs');
550
+ const path = require('path');
551
+ const resolved = path.resolve(input);
552
+ if (!fs.existsSync(resolved)) {
553
+ console.error(`Error: Input file not found: ${resolved}`);
554
+ // Common user mistake: passing inline text to --input. Detect it
555
+ // (no slash, no extension, contains non-ASCII or spaces) and suggest
556
+ // the right flag instead of just blaming "file not found".
557
+ const looksLikeText = !input.includes('/') &&
558
+ !/\.[a-z0-9]{1,4}$/i.test(input) &&
559
+ (/[^\x20-\x7e]/.test(input) || input.includes(' '));
560
+ if (looksLikeText) {
561
+ console.error('');
562
+ console.error(' Hint: --input expects a FILE PATH. For inline text use:');
563
+ console.error(` voxflow narrate --text ${JSON.stringify(input)}`);
564
+ console.error(` voxflow say ${JSON.stringify(input)} # simpler for one-liners`);
565
+ }
566
+ process.exit(1);
567
+ }
568
+ }
569
+
570
+ if (script) {
571
+ const fs = require('fs');
572
+ const path = require('path');
573
+ const resolved = path.resolve(script);
574
+ if (!fs.existsSync(resolved)) {
575
+ console.error(`Error: Script file not found: ${resolved}`);
576
+ process.exit(1);
577
+ }
578
+ }
579
+
580
+ // Authenticate after validation
581
+ let token;
582
+ if (explicitToken) {
583
+ token = explicitToken;
584
+ } else {
585
+ token = await getToken({ api });
586
+ const info = getTokenInfo();
587
+ if (info) {
588
+ console.log(`\x1b[32mLogged in as ${info.email}\x1b[0m`);
589
+ }
590
+ }
591
+
592
+ const opts = {
593
+ token, api,
594
+ input, text, script,
595
+ voice: parseFlag(args, '--voice'),
596
+ output, speed, silence,
597
+ format: format || undefined,
598
+ };
599
+
600
+ await runWithRetry(narrate, opts, api, explicitToken);
601
+ }
602
+
603
+ const meta = {
604
+ narrate: {
605
+ usage: '[opts]',
606
+ description: 'Narrate a file, text, or script to audio',
607
+ options: [
608
+ `--input <file> Input .txt or .md file`,
609
+ `--text <text> Inline text to narrate`,
610
+ `--script <file> JSON script with per-segment voice/speed control`,
611
+ `--voice <id> Default voice ID (default: ${NARRATE_DEFAULTS.voice})`,
612
+ `--format <fmt> Output format: pcm, wav, mp3 (default: pcm → WAV)`,
613
+ `--speed <n> TTS speed 0.5-2.0 (default: ${NARRATE_DEFAULTS.speed})`,
614
+ `--silence <sec> Silence between segments, 0-5.0 (default: ${NARRATE_DEFAULTS.silence})`,
615
+ `--output <path> Output file path (default: matches input basename, e.g. design.md → design.wav)`,
616
+ ],
617
+ examples: [
618
+ 'voxflow narrate --input article.txt --voice v-female-R2s4N9qJ',
619
+ 'voxflow narrate --script narration-script.json',
620
+ 'echo "Hello" | voxflow narrate --output hello.wav',
621
+ ],
622
+ },
623
+ };
624
+
625
+ module.exports = {
626
+ narrate,
627
+ handle,
628
+ meta,
629
+ ApiError,
630
+ _test: {
631
+ parseScript,
632
+ stripMarkdown,
633
+ scanSegmentRisks,
634
+ shouldAutoWrap,
635
+ chineseContextWrap,
636
+ synthesizeWithRetry,
637
+ isTtsProviderRejection,
638
+ },
639
+ };