@vibeframe/cli 0.55.0 → 0.55.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 (497) hide show
  1. package/dist/index.js +63450 -233
  2. package/package.json +11 -6
  3. package/dist/agent/adapters/claude.d.ts +0 -15
  4. package/dist/agent/adapters/claude.d.ts.map +0 -1
  5. package/dist/agent/adapters/claude.js +0 -119
  6. package/dist/agent/adapters/claude.js.map +0 -1
  7. package/dist/agent/adapters/gemini.d.ts +0 -15
  8. package/dist/agent/adapters/gemini.d.ts.map +0 -1
  9. package/dist/agent/adapters/gemini.js +0 -132
  10. package/dist/agent/adapters/gemini.js.map +0 -1
  11. package/dist/agent/adapters/index.d.ts +0 -28
  12. package/dist/agent/adapters/index.d.ts.map +0 -1
  13. package/dist/agent/adapters/index.js +0 -43
  14. package/dist/agent/adapters/index.js.map +0 -1
  15. package/dist/agent/adapters/ollama.d.ts +0 -20
  16. package/dist/agent/adapters/ollama.d.ts.map +0 -1
  17. package/dist/agent/adapters/ollama.js +0 -186
  18. package/dist/agent/adapters/ollama.js.map +0 -1
  19. package/dist/agent/adapters/openai.d.ts +0 -15
  20. package/dist/agent/adapters/openai.d.ts.map +0 -1
  21. package/dist/agent/adapters/openai.js +0 -92
  22. package/dist/agent/adapters/openai.js.map +0 -1
  23. package/dist/agent/adapters/openrouter.d.ts +0 -16
  24. package/dist/agent/adapters/openrouter.d.ts.map +0 -1
  25. package/dist/agent/adapters/openrouter.js +0 -100
  26. package/dist/agent/adapters/openrouter.js.map +0 -1
  27. package/dist/agent/adapters/openrouter.test.d.ts +0 -5
  28. package/dist/agent/adapters/openrouter.test.d.ts.map +0 -1
  29. package/dist/agent/adapters/openrouter.test.js +0 -73
  30. package/dist/agent/adapters/openrouter.test.js.map +0 -1
  31. package/dist/agent/adapters/xai.d.ts +0 -15
  32. package/dist/agent/adapters/xai.d.ts.map +0 -1
  33. package/dist/agent/adapters/xai.js +0 -95
  34. package/dist/agent/adapters/xai.js.map +0 -1
  35. package/dist/agent/index.d.ts +0 -69
  36. package/dist/agent/index.d.ts.map +0 -1
  37. package/dist/agent/index.js +0 -180
  38. package/dist/agent/index.js.map +0 -1
  39. package/dist/agent/memory/index.d.ts +0 -70
  40. package/dist/agent/memory/index.d.ts.map +0 -1
  41. package/dist/agent/memory/index.js +0 -132
  42. package/dist/agent/memory/index.js.map +0 -1
  43. package/dist/agent/prompts/system.d.ts +0 -6
  44. package/dist/agent/prompts/system.d.ts.map +0 -1
  45. package/dist/agent/prompts/system.js +0 -103
  46. package/dist/agent/prompts/system.js.map +0 -1
  47. package/dist/agent/tools/ai-editing.d.ts +0 -15
  48. package/dist/agent/tools/ai-editing.d.ts.map +0 -1
  49. package/dist/agent/tools/ai-editing.js +0 -763
  50. package/dist/agent/tools/ai-editing.js.map +0 -1
  51. package/dist/agent/tools/ai-generation.d.ts +0 -13
  52. package/dist/agent/tools/ai-generation.d.ts.map +0 -1
  53. package/dist/agent/tools/ai-generation.js +0 -973
  54. package/dist/agent/tools/ai-generation.js.map +0 -1
  55. package/dist/agent/tools/ai-pipeline.d.ts +0 -14
  56. package/dist/agent/tools/ai-pipeline.d.ts.map +0 -1
  57. package/dist/agent/tools/ai-pipeline.js +0 -961
  58. package/dist/agent/tools/ai-pipeline.js.map +0 -1
  59. package/dist/agent/tools/ai.d.ts +0 -13
  60. package/dist/agent/tools/ai.d.ts.map +0 -1
  61. package/dist/agent/tools/ai.js +0 -19
  62. package/dist/agent/tools/ai.js.map +0 -1
  63. package/dist/agent/tools/batch.d.ts +0 -6
  64. package/dist/agent/tools/batch.d.ts.map +0 -1
  65. package/dist/agent/tools/batch.js +0 -383
  66. package/dist/agent/tools/batch.js.map +0 -1
  67. package/dist/agent/tools/e2e.test.d.ts +0 -26
  68. package/dist/agent/tools/e2e.test.d.ts.map +0 -1
  69. package/dist/agent/tools/e2e.test.js +0 -397
  70. package/dist/agent/tools/e2e.test.js.map +0 -1
  71. package/dist/agent/tools/export.d.ts +0 -6
  72. package/dist/agent/tools/export.d.ts.map +0 -1
  73. package/dist/agent/tools/export.js +0 -171
  74. package/dist/agent/tools/export.js.map +0 -1
  75. package/dist/agent/tools/filesystem.d.ts +0 -6
  76. package/dist/agent/tools/filesystem.d.ts.map +0 -1
  77. package/dist/agent/tools/filesystem.js +0 -212
  78. package/dist/agent/tools/filesystem.js.map +0 -1
  79. package/dist/agent/tools/index.d.ts +0 -66
  80. package/dist/agent/tools/index.d.ts.map +0 -1
  81. package/dist/agent/tools/index.js +0 -123
  82. package/dist/agent/tools/index.js.map +0 -1
  83. package/dist/agent/tools/integration.test.d.ts +0 -11
  84. package/dist/agent/tools/integration.test.d.ts.map +0 -1
  85. package/dist/agent/tools/integration.test.js +0 -659
  86. package/dist/agent/tools/integration.test.js.map +0 -1
  87. package/dist/agent/tools/media.d.ts +0 -6
  88. package/dist/agent/tools/media.d.ts.map +0 -1
  89. package/dist/agent/tools/media.js +0 -616
  90. package/dist/agent/tools/media.js.map +0 -1
  91. package/dist/agent/tools/project.d.ts +0 -6
  92. package/dist/agent/tools/project.d.ts.map +0 -1
  93. package/dist/agent/tools/project.js +0 -284
  94. package/dist/agent/tools/project.js.map +0 -1
  95. package/dist/agent/tools/scene.d.ts +0 -17
  96. package/dist/agent/tools/scene.d.ts.map +0 -1
  97. package/dist/agent/tools/scene.js +0 -263
  98. package/dist/agent/tools/scene.js.map +0 -1
  99. package/dist/agent/tools/scene.test.d.ts +0 -2
  100. package/dist/agent/tools/scene.test.d.ts.map +0 -1
  101. package/dist/agent/tools/scene.test.js +0 -139
  102. package/dist/agent/tools/scene.test.js.map +0 -1
  103. package/dist/agent/tools/timeline.d.ts +0 -6
  104. package/dist/agent/tools/timeline.d.ts.map +0 -1
  105. package/dist/agent/tools/timeline.js +0 -875
  106. package/dist/agent/tools/timeline.js.map +0 -1
  107. package/dist/agent/types.d.ts +0 -59
  108. package/dist/agent/types.d.ts.map +0 -1
  109. package/dist/agent/types.js +0 -5
  110. package/dist/agent/types.js.map +0 -1
  111. package/dist/commands/_shared/scene-audio-mux.d.ts +0 -75
  112. package/dist/commands/_shared/scene-audio-mux.d.ts.map +0 -1
  113. package/dist/commands/_shared/scene-audio-mux.js +0 -159
  114. package/dist/commands/_shared/scene-audio-mux.js.map +0 -1
  115. package/dist/commands/_shared/scene-audio-mux.test.d.ts +0 -7
  116. package/dist/commands/_shared/scene-audio-mux.test.d.ts.map +0 -1
  117. package/dist/commands/_shared/scene-audio-mux.test.js +0 -94
  118. package/dist/commands/_shared/scene-audio-mux.test.js.map +0 -1
  119. package/dist/commands/_shared/scene-audio-scan.d.ts +0 -94
  120. package/dist/commands/_shared/scene-audio-scan.d.ts.map +0 -1
  121. package/dist/commands/_shared/scene-audio-scan.js +0 -144
  122. package/dist/commands/_shared/scene-audio-scan.js.map +0 -1
  123. package/dist/commands/_shared/scene-audio-scan.test.d.ts +0 -6
  124. package/dist/commands/_shared/scene-audio-scan.test.d.ts.map +0 -1
  125. package/dist/commands/_shared/scene-audio-scan.test.js +0 -190
  126. package/dist/commands/_shared/scene-audio-scan.test.js.map +0 -1
  127. package/dist/commands/_shared/scene-html-emit.d.ts +0 -120
  128. package/dist/commands/_shared/scene-html-emit.d.ts.map +0 -1
  129. package/dist/commands/_shared/scene-html-emit.js +0 -422
  130. package/dist/commands/_shared/scene-html-emit.js.map +0 -1
  131. package/dist/commands/_shared/scene-html-emit.test.d.ts +0 -2
  132. package/dist/commands/_shared/scene-html-emit.test.d.ts.map +0 -1
  133. package/dist/commands/_shared/scene-html-emit.test.js +0 -396
  134. package/dist/commands/_shared/scene-html-emit.test.js.map +0 -1
  135. package/dist/commands/_shared/scene-lint.d.ts +0 -110
  136. package/dist/commands/_shared/scene-lint.d.ts.map +0 -1
  137. package/dist/commands/_shared/scene-lint.js +0 -207
  138. package/dist/commands/_shared/scene-lint.js.map +0 -1
  139. package/dist/commands/_shared/scene-lint.test.d.ts +0 -2
  140. package/dist/commands/_shared/scene-lint.test.d.ts.map +0 -1
  141. package/dist/commands/_shared/scene-lint.test.js +0 -208
  142. package/dist/commands/_shared/scene-lint.test.js.map +0 -1
  143. package/dist/commands/_shared/scene-project.d.ts +0 -101
  144. package/dist/commands/_shared/scene-project.d.ts.map +0 -1
  145. package/dist/commands/_shared/scene-project.js +0 -281
  146. package/dist/commands/_shared/scene-project.js.map +0 -1
  147. package/dist/commands/_shared/scene-project.test.d.ts +0 -2
  148. package/dist/commands/_shared/scene-project.test.d.ts.map +0 -1
  149. package/dist/commands/_shared/scene-project.test.js +0 -203
  150. package/dist/commands/_shared/scene-project.test.js.map +0 -1
  151. package/dist/commands/_shared/scene-render.d.ts +0 -85
  152. package/dist/commands/_shared/scene-render.d.ts.map +0 -1
  153. package/dist/commands/_shared/scene-render.js +0 -178
  154. package/dist/commands/_shared/scene-render.js.map +0 -1
  155. package/dist/commands/_shared/scene-render.test.d.ts +0 -2
  156. package/dist/commands/_shared/scene-render.test.d.ts.map +0 -1
  157. package/dist/commands/_shared/scene-render.test.js +0 -145
  158. package/dist/commands/_shared/scene-render.test.js.map +0 -1
  159. package/dist/commands/_shared/segments-to-scenes.d.ts +0 -85
  160. package/dist/commands/_shared/segments-to-scenes.d.ts.map +0 -1
  161. package/dist/commands/_shared/segments-to-scenes.js +0 -202
  162. package/dist/commands/_shared/segments-to-scenes.js.map +0 -1
  163. package/dist/commands/_shared/segments-to-scenes.test.d.ts +0 -2
  164. package/dist/commands/_shared/segments-to-scenes.test.d.ts.map +0 -1
  165. package/dist/commands/_shared/segments-to-scenes.test.js +0 -219
  166. package/dist/commands/_shared/segments-to-scenes.test.js.map +0 -1
  167. package/dist/commands/_shared/tts-resolve.d.ts +0 -75
  168. package/dist/commands/_shared/tts-resolve.d.ts.map +0 -1
  169. package/dist/commands/_shared/tts-resolve.js +0 -89
  170. package/dist/commands/_shared/tts-resolve.js.map +0 -1
  171. package/dist/commands/_shared/tts-resolve.test.d.ts +0 -6
  172. package/dist/commands/_shared/tts-resolve.test.d.ts.map +0 -1
  173. package/dist/commands/_shared/tts-resolve.test.js +0 -147
  174. package/dist/commands/_shared/tts-resolve.test.js.map +0 -1
  175. package/dist/commands/agent.d.ts +0 -21
  176. package/dist/commands/agent.d.ts.map +0 -1
  177. package/dist/commands/agent.js +0 -285
  178. package/dist/commands/agent.js.map +0 -1
  179. package/dist/commands/ai-analyze.d.ts +0 -106
  180. package/dist/commands/ai-analyze.d.ts.map +0 -1
  181. package/dist/commands/ai-analyze.js +0 -320
  182. package/dist/commands/ai-analyze.js.map +0 -1
  183. package/dist/commands/ai-animated-caption.d.ts +0 -64
  184. package/dist/commands/ai-animated-caption.d.ts.map +0 -1
  185. package/dist/commands/ai-animated-caption.js +0 -272
  186. package/dist/commands/ai-animated-caption.js.map +0 -1
  187. package/dist/commands/ai-audio.d.ts +0 -98
  188. package/dist/commands/ai-audio.d.ts.map +0 -1
  189. package/dist/commands/ai-audio.js +0 -1059
  190. package/dist/commands/ai-audio.js.map +0 -1
  191. package/dist/commands/ai-broll.d.ts +0 -15
  192. package/dist/commands/ai-broll.d.ts.map +0 -1
  193. package/dist/commands/ai-broll.js +0 -421
  194. package/dist/commands/ai-broll.js.map +0 -1
  195. package/dist/commands/ai-edit-cli.d.ts +0 -14
  196. package/dist/commands/ai-edit-cli.d.ts.map +0 -1
  197. package/dist/commands/ai-edit-cli.js +0 -585
  198. package/dist/commands/ai-edit-cli.js.map +0 -1
  199. package/dist/commands/ai-edit.d.ts +0 -398
  200. package/dist/commands/ai-edit.d.ts.map +0 -1
  201. package/dist/commands/ai-edit.js +0 -1060
  202. package/dist/commands/ai-edit.js.map +0 -1
  203. package/dist/commands/ai-fill-gaps.d.ts +0 -14
  204. package/dist/commands/ai-fill-gaps.d.ts.map +0 -1
  205. package/dist/commands/ai-fill-gaps.js +0 -447
  206. package/dist/commands/ai-fill-gaps.js.map +0 -1
  207. package/dist/commands/ai-helpers.d.ts +0 -20
  208. package/dist/commands/ai-helpers.d.ts.map +0 -1
  209. package/dist/commands/ai-helpers.js +0 -59
  210. package/dist/commands/ai-helpers.js.map +0 -1
  211. package/dist/commands/ai-highlights.d.ts +0 -127
  212. package/dist/commands/ai-highlights.d.ts.map +0 -1
  213. package/dist/commands/ai-highlights.js +0 -1053
  214. package/dist/commands/ai-highlights.js.map +0 -1
  215. package/dist/commands/ai-image.d.ts +0 -76
  216. package/dist/commands/ai-image.d.ts.map +0 -1
  217. package/dist/commands/ai-image.js +0 -858
  218. package/dist/commands/ai-image.js.map +0 -1
  219. package/dist/commands/ai-motion.d.ts +0 -50
  220. package/dist/commands/ai-motion.d.ts.map +0 -1
  221. package/dist/commands/ai-motion.js +0 -297
  222. package/dist/commands/ai-motion.js.map +0 -1
  223. package/dist/commands/ai-narrate.d.ts +0 -66
  224. package/dist/commands/ai-narrate.d.ts.map +0 -1
  225. package/dist/commands/ai-narrate.js +0 -332
  226. package/dist/commands/ai-narrate.js.map +0 -1
  227. package/dist/commands/ai-review.d.ts +0 -57
  228. package/dist/commands/ai-review.d.ts.map +0 -1
  229. package/dist/commands/ai-review.js +0 -270
  230. package/dist/commands/ai-review.js.map +0 -1
  231. package/dist/commands/ai-script-pipeline-cli.d.ts +0 -9
  232. package/dist/commands/ai-script-pipeline-cli.d.ts.map +0 -1
  233. package/dist/commands/ai-script-pipeline-cli.js +0 -502
  234. package/dist/commands/ai-script-pipeline-cli.js.map +0 -1
  235. package/dist/commands/ai-script-pipeline.d.ts +0 -305
  236. package/dist/commands/ai-script-pipeline.d.ts.map +0 -1
  237. package/dist/commands/ai-script-pipeline.js +0 -1546
  238. package/dist/commands/ai-script-pipeline.js.map +0 -1
  239. package/dist/commands/ai-suggest-edit.d.ts +0 -14
  240. package/dist/commands/ai-suggest-edit.d.ts.map +0 -1
  241. package/dist/commands/ai-suggest-edit.js +0 -215
  242. package/dist/commands/ai-suggest-edit.js.map +0 -1
  243. package/dist/commands/ai-video-fx.d.ts +0 -14
  244. package/dist/commands/ai-video-fx.d.ts.map +0 -1
  245. package/dist/commands/ai-video-fx.js +0 -396
  246. package/dist/commands/ai-video-fx.js.map +0 -1
  247. package/dist/commands/ai-video.d.ts +0 -91
  248. package/dist/commands/ai-video.d.ts.map +0 -1
  249. package/dist/commands/ai-video.js +0 -1055
  250. package/dist/commands/ai-video.js.map +0 -1
  251. package/dist/commands/ai-viral.d.ts +0 -15
  252. package/dist/commands/ai-viral.d.ts.map +0 -1
  253. package/dist/commands/ai-viral.js +0 -507
  254. package/dist/commands/ai-viral.js.map +0 -1
  255. package/dist/commands/ai-visual-fx.d.ts +0 -14
  256. package/dist/commands/ai-visual-fx.d.ts.map +0 -1
  257. package/dist/commands/ai-visual-fx.js +0 -521
  258. package/dist/commands/ai-visual-fx.js.map +0 -1
  259. package/dist/commands/ai.d.ts +0 -38
  260. package/dist/commands/ai.d.ts.map +0 -1
  261. package/dist/commands/ai.js +0 -225
  262. package/dist/commands/ai.js.map +0 -1
  263. package/dist/commands/ai.test.d.ts +0 -2
  264. package/dist/commands/ai.test.d.ts.map +0 -1
  265. package/dist/commands/ai.test.js +0 -557
  266. package/dist/commands/ai.test.js.map +0 -1
  267. package/dist/commands/analyze.d.ts +0 -16
  268. package/dist/commands/analyze.d.ts.map +0 -1
  269. package/dist/commands/analyze.js +0 -247
  270. package/dist/commands/analyze.js.map +0 -1
  271. package/dist/commands/audio.d.ts +0 -18
  272. package/dist/commands/audio.d.ts.map +0 -1
  273. package/dist/commands/audio.js +0 -539
  274. package/dist/commands/audio.js.map +0 -1
  275. package/dist/commands/batch.d.ts +0 -3
  276. package/dist/commands/batch.d.ts.map +0 -1
  277. package/dist/commands/batch.js +0 -419
  278. package/dist/commands/batch.js.map +0 -1
  279. package/dist/commands/batch.test.d.ts +0 -2
  280. package/dist/commands/batch.test.d.ts.map +0 -1
  281. package/dist/commands/batch.test.js +0 -203
  282. package/dist/commands/batch.test.js.map +0 -1
  283. package/dist/commands/context.d.ts +0 -7
  284. package/dist/commands/context.d.ts.map +0 -1
  285. package/dist/commands/context.js +0 -66
  286. package/dist/commands/context.js.map +0 -1
  287. package/dist/commands/demo.d.ts +0 -7
  288. package/dist/commands/demo.d.ts.map +0 -1
  289. package/dist/commands/demo.js +0 -192
  290. package/dist/commands/demo.js.map +0 -1
  291. package/dist/commands/detect.d.ts +0 -56
  292. package/dist/commands/detect.d.ts.map +0 -1
  293. package/dist/commands/detect.js +0 -464
  294. package/dist/commands/detect.js.map +0 -1
  295. package/dist/commands/doctor.d.ts +0 -6
  296. package/dist/commands/doctor.d.ts.map +0 -1
  297. package/dist/commands/doctor.js +0 -322
  298. package/dist/commands/doctor.js.map +0 -1
  299. package/dist/commands/edit-cmd.d.ts +0 -112
  300. package/dist/commands/edit-cmd.d.ts.map +0 -1
  301. package/dist/commands/edit-cmd.js +0 -1041
  302. package/dist/commands/edit-cmd.js.map +0 -1
  303. package/dist/commands/export.d.ts +0 -61
  304. package/dist/commands/export.d.ts.map +0 -1
  305. package/dist/commands/export.js +0 -980
  306. package/dist/commands/export.js.map +0 -1
  307. package/dist/commands/export.test.d.ts +0 -2
  308. package/dist/commands/export.test.d.ts.map +0 -1
  309. package/dist/commands/export.test.js +0 -107
  310. package/dist/commands/export.test.js.map +0 -1
  311. package/dist/commands/generate.d.ts +0 -84
  312. package/dist/commands/generate.d.ts.map +0 -1
  313. package/dist/commands/generate.js +0 -2030
  314. package/dist/commands/generate.js.map +0 -1
  315. package/dist/commands/media.d.ts +0 -3
  316. package/dist/commands/media.d.ts.map +0 -1
  317. package/dist/commands/media.js +0 -163
  318. package/dist/commands/media.js.map +0 -1
  319. package/dist/commands/output.d.ts +0 -51
  320. package/dist/commands/output.d.ts.map +0 -1
  321. package/dist/commands/output.js +0 -219
  322. package/dist/commands/output.js.map +0 -1
  323. package/dist/commands/output.test.d.ts +0 -2
  324. package/dist/commands/output.test.d.ts.map +0 -1
  325. package/dist/commands/output.test.js +0 -151
  326. package/dist/commands/output.test.js.map +0 -1
  327. package/dist/commands/pipeline.d.ts +0 -19
  328. package/dist/commands/pipeline.d.ts.map +0 -1
  329. package/dist/commands/pipeline.js +0 -345
  330. package/dist/commands/pipeline.js.map +0 -1
  331. package/dist/commands/project.d.ts +0 -3
  332. package/dist/commands/project.d.ts.map +0 -1
  333. package/dist/commands/project.js +0 -181
  334. package/dist/commands/project.js.map +0 -1
  335. package/dist/commands/project.test.d.ts +0 -2
  336. package/dist/commands/project.test.d.ts.map +0 -1
  337. package/dist/commands/project.test.js +0 -105
  338. package/dist/commands/project.test.js.map +0 -1
  339. package/dist/commands/run.d.ts +0 -15
  340. package/dist/commands/run.d.ts.map +0 -1
  341. package/dist/commands/run.js +0 -204
  342. package/dist/commands/run.js.map +0 -1
  343. package/dist/commands/sanitize.d.ts +0 -21
  344. package/dist/commands/sanitize.d.ts.map +0 -1
  345. package/dist/commands/sanitize.js +0 -56
  346. package/dist/commands/sanitize.js.map +0 -1
  347. package/dist/commands/scene.d.ts +0 -86
  348. package/dist/commands/scene.d.ts.map +0 -1
  349. package/dist/commands/scene.js +0 -728
  350. package/dist/commands/scene.js.map +0 -1
  351. package/dist/commands/scene.test.d.ts +0 -2
  352. package/dist/commands/scene.test.d.ts.map +0 -1
  353. package/dist/commands/scene.test.js +0 -219
  354. package/dist/commands/scene.test.js.map +0 -1
  355. package/dist/commands/schema.d.ts +0 -13
  356. package/dist/commands/schema.d.ts.map +0 -1
  357. package/dist/commands/schema.js +0 -218
  358. package/dist/commands/schema.js.map +0 -1
  359. package/dist/commands/setup.d.ts +0 -6
  360. package/dist/commands/setup.d.ts.map +0 -1
  361. package/dist/commands/setup.js +0 -461
  362. package/dist/commands/setup.js.map +0 -1
  363. package/dist/commands/timeline.d.ts +0 -3
  364. package/dist/commands/timeline.d.ts.map +0 -1
  365. package/dist/commands/timeline.js +0 -609
  366. package/dist/commands/timeline.js.map +0 -1
  367. package/dist/commands/timeline.test.d.ts +0 -2
  368. package/dist/commands/timeline.test.d.ts.map +0 -1
  369. package/dist/commands/timeline.test.js +0 -334
  370. package/dist/commands/timeline.test.js.map +0 -1
  371. package/dist/commands/validate.d.ts +0 -34
  372. package/dist/commands/validate.d.ts.map +0 -1
  373. package/dist/commands/validate.js +0 -70
  374. package/dist/commands/validate.js.map +0 -1
  375. package/dist/config/config.test.d.ts +0 -2
  376. package/dist/config/config.test.d.ts.map +0 -1
  377. package/dist/config/config.test.js +0 -164
  378. package/dist/config/config.test.js.map +0 -1
  379. package/dist/config/index.d.ts +0 -35
  380. package/dist/config/index.d.ts.map +0 -1
  381. package/dist/config/index.js +0 -101
  382. package/dist/config/index.js.map +0 -1
  383. package/dist/config/schema.d.ts +0 -52
  384. package/dist/config/schema.d.ts.map +0 -1
  385. package/dist/config/schema.js +0 -44
  386. package/dist/config/schema.js.map +0 -1
  387. package/dist/engine/index.d.ts +0 -3
  388. package/dist/engine/index.d.ts.map +0 -1
  389. package/dist/engine/index.js +0 -2
  390. package/dist/engine/index.js.map +0 -1
  391. package/dist/engine/project.d.ts +0 -84
  392. package/dist/engine/project.d.ts.map +0 -1
  393. package/dist/engine/project.js +0 -355
  394. package/dist/engine/project.js.map +0 -1
  395. package/dist/engine/project.test.d.ts +0 -2
  396. package/dist/engine/project.test.d.ts.map +0 -1
  397. package/dist/engine/project.test.js +0 -599
  398. package/dist/engine/project.test.js.map +0 -1
  399. package/dist/index.d.ts +0 -7
  400. package/dist/index.d.ts.map +0 -1
  401. package/dist/index.js.map +0 -1
  402. package/dist/pipeline/executor.d.ts +0 -19
  403. package/dist/pipeline/executor.d.ts.map +0 -1
  404. package/dist/pipeline/executor.js +0 -397
  405. package/dist/pipeline/executor.js.map +0 -1
  406. package/dist/pipeline/index.d.ts +0 -4
  407. package/dist/pipeline/index.d.ts.map +0 -1
  408. package/dist/pipeline/index.js +0 -2
  409. package/dist/pipeline/index.js.map +0 -1
  410. package/dist/pipeline/renderers/__tests__/html-template.test.d.ts +0 -2
  411. package/dist/pipeline/renderers/__tests__/html-template.test.d.ts.map +0 -1
  412. package/dist/pipeline/renderers/__tests__/html-template.test.js +0 -170
  413. package/dist/pipeline/renderers/__tests__/html-template.test.js.map +0 -1
  414. package/dist/pipeline/renderers/__tests__/integration.test.d.ts +0 -2
  415. package/dist/pipeline/renderers/__tests__/integration.test.d.ts.map +0 -1
  416. package/dist/pipeline/renderers/__tests__/integration.test.js +0 -175
  417. package/dist/pipeline/renderers/__tests__/integration.test.js.map +0 -1
  418. package/dist/pipeline/renderers/chrome.d.ts +0 -26
  419. package/dist/pipeline/renderers/chrome.d.ts.map +0 -1
  420. package/dist/pipeline/renderers/chrome.js +0 -48
  421. package/dist/pipeline/renderers/chrome.js.map +0 -1
  422. package/dist/pipeline/renderers/html-clips.d.ts +0 -20
  423. package/dist/pipeline/renderers/html-clips.d.ts.map +0 -1
  424. package/dist/pipeline/renderers/html-clips.js +0 -50
  425. package/dist/pipeline/renderers/html-clips.js.map +0 -1
  426. package/dist/pipeline/renderers/html-runtime.d.ts +0 -7
  427. package/dist/pipeline/renderers/html-runtime.d.ts.map +0 -1
  428. package/dist/pipeline/renderers/html-runtime.js +0 -80
  429. package/dist/pipeline/renderers/html-runtime.js.map +0 -1
  430. package/dist/pipeline/renderers/html-template.d.ts +0 -3
  431. package/dist/pipeline/renderers/html-template.d.ts.map +0 -1
  432. package/dist/pipeline/renderers/html-template.js +0 -36
  433. package/dist/pipeline/renderers/html-template.js.map +0 -1
  434. package/dist/pipeline/renderers/hyperframes.d.ts +0 -8
  435. package/dist/pipeline/renderers/hyperframes.d.ts.map +0 -1
  436. package/dist/pipeline/renderers/hyperframes.js +0 -63
  437. package/dist/pipeline/renderers/hyperframes.js.map +0 -1
  438. package/dist/pipeline/renderers/project-builder.d.ts +0 -8
  439. package/dist/pipeline/renderers/project-builder.d.ts.map +0 -1
  440. package/dist/pipeline/renderers/project-builder.js +0 -65
  441. package/dist/pipeline/renderers/project-builder.js.map +0 -1
  442. package/dist/pipeline/renderers/types.d.ts +0 -41
  443. package/dist/pipeline/renderers/types.d.ts.map +0 -1
  444. package/dist/pipeline/renderers/types.js +0 -11
  445. package/dist/pipeline/renderers/types.js.map +0 -1
  446. package/dist/pipeline/resolver.d.ts +0 -20
  447. package/dist/pipeline/resolver.d.ts.map +0 -1
  448. package/dist/pipeline/resolver.js +0 -74
  449. package/dist/pipeline/resolver.js.map +0 -1
  450. package/dist/pipeline/types.d.ts +0 -82
  451. package/dist/pipeline/types.d.ts.map +0 -1
  452. package/dist/pipeline/types.js +0 -8
  453. package/dist/pipeline/types.js.map +0 -1
  454. package/dist/utils/api-key.d.ts +0 -33
  455. package/dist/utils/api-key.d.ts.map +0 -1
  456. package/dist/utils/api-key.js +0 -228
  457. package/dist/utils/api-key.js.map +0 -1
  458. package/dist/utils/api-key.test.d.ts +0 -2
  459. package/dist/utils/api-key.test.d.ts.map +0 -1
  460. package/dist/utils/api-key.test.js +0 -35
  461. package/dist/utils/api-key.test.js.map +0 -1
  462. package/dist/utils/audio.d.ts +0 -23
  463. package/dist/utils/audio.d.ts.map +0 -1
  464. package/dist/utils/audio.js +0 -79
  465. package/dist/utils/audio.js.map +0 -1
  466. package/dist/utils/exec-safe.d.ts +0 -22
  467. package/dist/utils/exec-safe.d.ts.map +0 -1
  468. package/dist/utils/exec-safe.js +0 -62
  469. package/dist/utils/exec-safe.js.map +0 -1
  470. package/dist/utils/first-run.d.ts +0 -17
  471. package/dist/utils/first-run.d.ts.map +0 -1
  472. package/dist/utils/first-run.js +0 -73
  473. package/dist/utils/first-run.js.map +0 -1
  474. package/dist/utils/input-validation.d.ts +0 -27
  475. package/dist/utils/input-validation.d.ts.map +0 -1
  476. package/dist/utils/input-validation.js +0 -67
  477. package/dist/utils/input-validation.js.map +0 -1
  478. package/dist/utils/provider-resolver.d.ts +0 -19
  479. package/dist/utils/provider-resolver.d.ts.map +0 -1
  480. package/dist/utils/provider-resolver.js +0 -75
  481. package/dist/utils/provider-resolver.js.map +0 -1
  482. package/dist/utils/remotion.d.ts +0 -231
  483. package/dist/utils/remotion.d.ts.map +0 -1
  484. package/dist/utils/remotion.js +0 -879
  485. package/dist/utils/remotion.js.map +0 -1
  486. package/dist/utils/subtitle.d.ts +0 -65
  487. package/dist/utils/subtitle.d.ts.map +0 -1
  488. package/dist/utils/subtitle.js +0 -135
  489. package/dist/utils/subtitle.js.map +0 -1
  490. package/dist/utils/subtitle.test.d.ts +0 -2
  491. package/dist/utils/subtitle.test.d.ts.map +0 -1
  492. package/dist/utils/subtitle.test.js +0 -175
  493. package/dist/utils/subtitle.test.js.map +0 -1
  494. package/dist/utils/tty.d.ts +0 -45
  495. package/dist/utils/tty.d.ts.map +0 -1
  496. package/dist/utils/tty.js +0 -231
  497. package/dist/utils/tty.js.map +0 -1
@@ -1,1546 +0,0 @@
1
- /**
2
- * @module ai-script-pipeline
3
- *
4
- * Script-to-video pipeline and scene regeneration execute functions.
5
- *
6
- * CLI commands: script-to-video, regenerate-scene
7
- *
8
- * Execute functions:
9
- * executeScriptToVideo - Full pipeline: storyboard -> TTS -> images -> videos -> project
10
- * executeRegenerateScene - Re-generate specific scene(s) in an existing project
11
- *
12
- * Also exports shared helpers: uploadToImgbb, extendVideoToTarget,
13
- * generateVideoWithRetryKling, generateVideoWithRetryRunway, generateVideoWithRetryVeo, waitForVideoWithRetry
14
- *
15
- * @dependencies Claude (storyboard), ElevenLabs (TTS), OpenAI/Gemini (images),
16
- * Kling/Runway (video), FFmpeg (assembly/extension)
17
- */
18
- import { readFile, writeFile, mkdir, unlink, rename } from "node:fs/promises";
19
- import { resolve, basename, dirname, extname } from "node:path";
20
- import { existsSync } from "node:fs";
21
- import { stringify as yamlStringify, parse as yamlParse } from "yaml";
22
- import chalk from "chalk";
23
- import { GeminiProvider, OpenAIProvider, OpenAIImageProvider, ClaudeProvider, ElevenLabsProvider, KlingProvider, RunwayProvider, GrokProvider, } from "@vibeframe/ai-providers";
24
- import { getApiKey } from "../utils/api-key.js";
25
- import { getApiKeyFromConfig } from "../config/index.js";
26
- import { Project } from "../engine/index.js";
27
- import { getAudioDuration, getVideoDuration, extendVideoNaturally } from "../utils/audio.js";
28
- import { applyTextOverlays } from "./ai-edit.js";
29
- import { executeReview } from "./ai-review.js";
30
- import { execSafe } from "../utils/exec-safe.js";
31
- import { downloadVideo } from "./ai-helpers.js";
32
- /** Default retry count for video generation API calls. */
33
- export const DEFAULT_VIDEO_RETRIES = 2;
34
- /** Delay between retries in milliseconds. */
35
- export const RETRY_DELAY_MS = 5000;
36
- /**
37
- * Sleep helper
38
- */
39
- export function sleep(ms) {
40
- return new Promise((resolve) => setTimeout(resolve, ms));
41
- }
42
- /**
43
- * Upload image to ImgBB and return the URL
44
- * Used for Kling v2.5/v2.6 image-to-video which requires URL (not base64)
45
- */
46
- export async function uploadToImgbb(imageBuffer, apiKey) {
47
- try {
48
- const base64Image = imageBuffer.toString("base64");
49
- const formData = new URLSearchParams();
50
- formData.append("key", apiKey);
51
- formData.append("image", base64Image);
52
- const response = await fetch("https://api.imgbb.com/1/upload", {
53
- method: "POST",
54
- body: formData,
55
- });
56
- if (!response.ok) {
57
- return { success: false, error: `ImgBB API error (${response.status}): ${response.statusText}` };
58
- }
59
- const data = (await response.json());
60
- if (data.success && data.data?.url) {
61
- return { success: true, url: data.data.url };
62
- }
63
- else {
64
- return { success: false, error: data.error?.message || "Upload failed" };
65
- }
66
- }
67
- catch (err) {
68
- return { success: false, error: String(err) };
69
- }
70
- }
71
- /**
72
- * Extend a video to target duration using Kling extend API when possible,
73
- * with fallback to FFmpeg-based extendVideoNaturally.
74
- *
75
- * When the extension ratio > 1.4 and a Kling provider + videoId are available,
76
- * uses the Kling video-extend API for natural continuation instead of freeze frames.
77
- */
78
- export async function extendVideoToTarget(videoPath, targetDuration, outputDir, sceneLabel, options) {
79
- const actualDuration = await getVideoDuration(videoPath);
80
- if (actualDuration >= targetDuration - 0.1)
81
- return;
82
- const ratio = targetDuration / actualDuration;
83
- const extendedPath = resolve(outputDir, `${basename(videoPath, ".mp4")}-extended.mp4`);
84
- // Try Kling extend API for large gaps (ratio > 1.4) where freeze frames look bad
85
- if (ratio > 1.4 && options?.kling && options?.videoId) {
86
- try {
87
- options.onProgress?.(`${sceneLabel}: Extending via Kling API...`);
88
- const extendResult = await options.kling.extendVideo(options.videoId, {
89
- duration: "5",
90
- });
91
- if (extendResult.status !== "failed" && extendResult.id) {
92
- const waitResult = await options.kling.waitForExtendCompletion(extendResult.id, (status) => {
93
- options.onProgress?.(`${sceneLabel}: extend ${status.status}...`);
94
- }, 600000);
95
- if (waitResult.status === "completed" && waitResult.videoUrl) {
96
- // Download extended video
97
- const extendedVideoPath = resolve(outputDir, `${basename(videoPath, ".mp4")}-kling-ext.mp4`);
98
- const buffer = await downloadVideo(waitResult.videoUrl);
99
- await writeFile(extendedVideoPath, buffer);
100
- // Concatenate original + extension
101
- const concatPath = resolve(outputDir, `${basename(videoPath, ".mp4")}-concat.mp4`);
102
- const listPath = resolve(outputDir, `${basename(videoPath, ".mp4")}-concat.txt`);
103
- await writeFile(listPath, `file '${videoPath}'\nfile '${extendedVideoPath}'`, "utf-8");
104
- await execSafe("ffmpeg", ["-y", "-f", "concat", "-safe", "0", "-i", listPath, "-c", "copy", concatPath]);
105
- // Trim to exact target duration if concatenated video is longer
106
- const concatDuration = await getVideoDuration(concatPath);
107
- if (concatDuration > targetDuration + 0.5) {
108
- await execSafe("ffmpeg", ["-y", "-i", concatPath, "-t", targetDuration.toFixed(2), "-c", "copy", extendedPath]);
109
- await unlink(concatPath);
110
- }
111
- else {
112
- await rename(concatPath, extendedPath);
113
- }
114
- // Cleanup temp files
115
- await unlink(extendedVideoPath).catch(() => { });
116
- await unlink(listPath).catch(() => { });
117
- await unlink(videoPath);
118
- await rename(extendedPath, videoPath);
119
- return;
120
- }
121
- }
122
- // If Kling extend failed, fall through to FFmpeg fallback
123
- options.onProgress?.(`${sceneLabel}: Kling extend failed, using FFmpeg fallback...`);
124
- }
125
- catch {
126
- options.onProgress?.(`${sceneLabel}: Kling extend error, using FFmpeg fallback...`);
127
- }
128
- }
129
- // FFmpeg-based fallback (slowdown + frame interpolation + freeze frame)
130
- await extendVideoNaturally(videoPath, targetDuration, extendedPath);
131
- await unlink(videoPath);
132
- await rename(extendedPath, videoPath);
133
- }
134
- /**
135
- * Log a provider failure to stderr. Centralizes the "what went wrong" output
136
- * so call sites stop silently dropping provider errors into failedScenes.
137
- * Safe to call with unknown errors, string messages, or VideoResult shapes.
138
- */
139
- export function logSceneFailure(provider, sceneLabel, err) {
140
- let msg;
141
- if (err instanceof Error) {
142
- msg = err.message;
143
- }
144
- else if (typeof err === "string") {
145
- msg = err;
146
- }
147
- else if (err && typeof err === "object" && "error" in err && typeof err.error === "string") {
148
- msg = err.error;
149
- }
150
- else {
151
- msg = String(err);
152
- }
153
- console.error(chalk.dim(`\n [${provider} ${sceneLabel}: ${msg}]`));
154
- }
155
- /**
156
- * Generate video with retry logic for Grok provider
157
- */
158
- export async function generateVideoWithRetryGrok(grok, segment, options, maxRetries, onProgress) {
159
- const prompt = segment.visualStyle
160
- ? `${segment.visuals}. Style: ${segment.visualStyle}`
161
- : segment.visuals;
162
- for (let attempt = 0; attempt <= maxRetries; attempt++) {
163
- try {
164
- const result = await grok.generateVideo(prompt, {
165
- prompt,
166
- duration: options.duration,
167
- aspectRatio: options.aspectRatio,
168
- referenceImage: options.referenceImage,
169
- });
170
- if (result.status !== "failed" && result.id) {
171
- return { requestId: result.id };
172
- }
173
- // Provider returned status: "failed" with an error message — don't
174
- // discard it. Surface via onProgress + stderr so the caller can see WHY.
175
- const providerErr = result.error || "Grok returned failed status";
176
- if (attempt < maxRetries) {
177
- onProgress?.(`⚠ ${providerErr.slice(0, 50)}... retry ${attempt + 1}/${maxRetries}`);
178
- await sleep(RETRY_DELAY_MS);
179
- }
180
- else {
181
- console.error(chalk.dim(`\n [Grok error: ${providerErr}]`));
182
- }
183
- }
184
- catch (err) {
185
- const errMsg = err instanceof Error ? err.message : String(err);
186
- if (attempt < maxRetries) {
187
- onProgress?.(`⚠ Error: ${errMsg.slice(0, 50)}... retry ${attempt + 1}/${maxRetries}`);
188
- await sleep(RETRY_DELAY_MS);
189
- }
190
- else {
191
- console.error(chalk.dim(`\n [Grok error: ${errMsg}]`));
192
- }
193
- }
194
- }
195
- return null;
196
- }
197
- /**
198
- * Generate video with retry logic for Kling provider
199
- * Supports image-to-video with URL (v2.5/v2.6 models)
200
- */
201
- export async function generateVideoWithRetryKling(kling, segment, options, maxRetries, onProgress) {
202
- // Build detailed prompt from storyboard segment
203
- const prompt = segment.visualStyle
204
- ? `${segment.visuals}. Style: ${segment.visualStyle}`
205
- : segment.visuals;
206
- for (let attempt = 0; attempt <= maxRetries; attempt++) {
207
- try {
208
- const result = await kling.generateVideo(prompt, {
209
- prompt,
210
- // Pass reference image (base64 or URL) - KlingProvider handles v1.5 fallback for base64
211
- referenceImage: options.referenceImage,
212
- duration: options.duration,
213
- aspectRatio: options.aspectRatio,
214
- mode: "std", // Use std mode for faster generation
215
- });
216
- if (result.status !== "failed" && result.id) {
217
- return {
218
- taskId: result.id,
219
- type: options.referenceImage ? "image2video" : "text2video",
220
- };
221
- }
222
- const providerErr = result.error || "Kling returned failed status";
223
- if (attempt < maxRetries) {
224
- onProgress?.(`⚠ ${providerErr.slice(0, 50)}... retry ${attempt + 1}/${maxRetries}`);
225
- await sleep(RETRY_DELAY_MS);
226
- }
227
- else {
228
- console.error(chalk.dim(`\n [Kling error: ${providerErr}]`));
229
- }
230
- }
231
- catch (err) {
232
- const errMsg = err instanceof Error ? err.message : String(err);
233
- if (attempt < maxRetries) {
234
- onProgress?.(`⚠ Error: ${errMsg.slice(0, 50)}... retry ${attempt + 1}/${maxRetries}`);
235
- await sleep(RETRY_DELAY_MS);
236
- }
237
- else {
238
- console.error(chalk.dim(`\n [Kling error: ${errMsg}]`));
239
- }
240
- }
241
- }
242
- return null;
243
- }
244
- /**
245
- * Generate video with retry logic for Runway provider
246
- */
247
- export async function generateVideoWithRetryRunway(runway, segment, referenceImage, options, maxRetries, onProgress) {
248
- for (let attempt = 0; attempt <= maxRetries; attempt++) {
249
- try {
250
- const result = await runway.generateVideo(segment.visuals, {
251
- prompt: segment.visuals,
252
- referenceImage,
253
- duration: options.duration,
254
- aspectRatio: options.aspectRatio,
255
- });
256
- if (result.status !== "failed" && result.id) {
257
- return { taskId: result.id };
258
- }
259
- const providerErr = result.error || "Runway returned failed status";
260
- if (attempt < maxRetries) {
261
- onProgress?.(`⚠ ${providerErr.slice(0, 50)}... retry ${attempt + 1}/${maxRetries}`);
262
- await sleep(RETRY_DELAY_MS);
263
- }
264
- else {
265
- console.error(chalk.dim(`\n [Runway error: ${providerErr}]`));
266
- }
267
- }
268
- catch (err) {
269
- const errMsg = err instanceof Error ? err.message : String(err);
270
- if (attempt < maxRetries) {
271
- onProgress?.(`⚠ Error: ${errMsg.slice(0, 50)}... retry ${attempt + 1}/${maxRetries}`);
272
- await sleep(RETRY_DELAY_MS);
273
- }
274
- else {
275
- console.error(chalk.dim(`\n [Runway error: ${errMsg}]`));
276
- }
277
- }
278
- }
279
- return null;
280
- }
281
- /**
282
- * Generate video with retry logic for Veo (Gemini) provider
283
- */
284
- export async function generateVideoWithRetryVeo(gemini, segment, options, maxRetries, onProgress) {
285
- const prompt = segment.visualStyle
286
- ? `${segment.visuals}. Style: ${segment.visualStyle}`
287
- : segment.visuals;
288
- for (let attempt = 0; attempt <= maxRetries; attempt++) {
289
- try {
290
- const result = await gemini.generateVideo(prompt, {
291
- prompt,
292
- referenceImage: options.referenceImage,
293
- duration: options.duration,
294
- aspectRatio: options.aspectRatio,
295
- model: "veo-3.1-fast-generate-preview",
296
- });
297
- if (result.status !== "failed" && result.id) {
298
- return { operationName: result.id };
299
- }
300
- const providerErr = result.error || "Veo returned failed status";
301
- if (attempt < maxRetries) {
302
- onProgress?.(`⚠ ${providerErr.slice(0, 50)}... retry ${attempt + 1}/${maxRetries}`);
303
- await sleep(RETRY_DELAY_MS);
304
- }
305
- else {
306
- console.error(chalk.dim(`\n [Veo error: ${providerErr}]`));
307
- }
308
- }
309
- catch (err) {
310
- const errMsg = err instanceof Error ? err.message : String(err);
311
- if (attempt < maxRetries) {
312
- onProgress?.(`⚠ Error: ${errMsg.slice(0, 50)}... retry ${attempt + 1}/${maxRetries}`);
313
- await sleep(RETRY_DELAY_MS);
314
- }
315
- else {
316
- console.error(chalk.dim(`\n [Veo error: ${errMsg}]`));
317
- }
318
- }
319
- }
320
- return null;
321
- }
322
- /**
323
- * Wait for video completion with retry logic
324
- */
325
- export async function waitForVideoWithRetry(provider, taskId, providerType, maxRetries, onProgress, timeout) {
326
- for (let attempt = 0; attempt <= maxRetries; attempt++) {
327
- try {
328
- let result;
329
- if (providerType === "kling") {
330
- result = await provider.waitForCompletion(taskId, "image2video", (status) => onProgress?.(status.status || "processing"), timeout || 600000);
331
- }
332
- else {
333
- result = await provider.waitForCompletion(taskId, (status) => {
334
- const progress = status.progress !== undefined ? `${status.progress}%` : status.status;
335
- onProgress?.(progress || "processing");
336
- }, timeout || 300000);
337
- }
338
- if (result.status === "completed" && result.videoUrl) {
339
- return { videoUrl: result.videoUrl };
340
- }
341
- // If failed, try resubmitting on next attempt
342
- if (attempt < maxRetries) {
343
- onProgress?.(`⚠ Failed, will need resubmission...`);
344
- return null; // Signal need for resubmission
345
- }
346
- }
347
- catch (err) {
348
- if (attempt < maxRetries) {
349
- onProgress?.(`⚠ Error waiting, retry ${attempt + 1}/${maxRetries}...`);
350
- await sleep(RETRY_DELAY_MS);
351
- }
352
- }
353
- }
354
- return null;
355
- }
356
- /**
357
- * Execute the full script-to-video pipeline programmatically.
358
- *
359
- * Pipeline stages:
360
- * 1. Generate storyboard with Claude
361
- * 2. Generate per-scene voiceovers with ElevenLabs TTS
362
- * 3. Generate scene images (OpenAI/Gemini)
363
- * 4. Generate scene videos (Kling/Runway) with extension to match narration
364
- * 4.5. Apply text overlays if present in storyboard
365
- * 5. Assemble .vibe.json project file
366
- * 6. Optional AI review and auto-fix (Gemini)
367
- *
368
- * @param options - Pipeline configuration
369
- * @returns Result with paths to all generated assets and project file
370
- */
371
- export async function executeScriptToVideo(options) {
372
- const outputDir = options.outputDir || "script-video-output";
373
- try {
374
- // Get storyboard provider API key
375
- const storyboardProvider = options.storyboardProvider || "claude";
376
- let storyboardApiKey;
377
- if (storyboardProvider === "openai") {
378
- storyboardApiKey = (await getApiKey("OPENAI_API_KEY", "OpenAI")) ?? undefined;
379
- if (!storyboardApiKey) {
380
- return { success: false, outputDir, scenes: 0, error: "OpenAI API key required for storyboard generation (--storyboard-provider openai). Run 'vibe setup' or set OPENAI_API_KEY in .env" };
381
- }
382
- }
383
- else if (storyboardProvider === "gemini") {
384
- storyboardApiKey = (await getApiKey("GOOGLE_API_KEY", "Google")) ?? undefined;
385
- if (!storyboardApiKey) {
386
- return { success: false, outputDir, scenes: 0, error: "Google API key required for storyboard generation (--storyboard-provider gemini). Run 'vibe setup' or set GOOGLE_API_KEY in .env" };
387
- }
388
- }
389
- else {
390
- // Default: Claude
391
- storyboardApiKey = (await getApiKey("ANTHROPIC_API_KEY", "Anthropic")) ?? undefined;
392
- if (!storyboardApiKey) {
393
- return { success: false, outputDir, scenes: 0, error: "Anthropic API key required for storyboard generation. Run 'vibe setup' or set ANTHROPIC_API_KEY in .env" };
394
- }
395
- }
396
- // Get image provider API key
397
- let imageApiKey;
398
- const imageProvider = options.imageProvider || "openai";
399
- if (imageProvider === "openai" || imageProvider === "dalle") {
400
- imageApiKey = (await getApiKey("OPENAI_API_KEY", "OpenAI")) ?? undefined;
401
- if (!imageApiKey) {
402
- return { success: false, outputDir, scenes: 0, error: "OpenAI API key required for image generation. Run 'vibe setup' or set OPENAI_API_KEY in .env" };
403
- }
404
- }
405
- else if (imageProvider === "gemini") {
406
- imageApiKey = (await getApiKey("GOOGLE_API_KEY", "Google")) ?? undefined;
407
- if (!imageApiKey) {
408
- return { success: false, outputDir, scenes: 0, error: "Google API key required for Gemini image generation. Run 'vibe setup' or set GOOGLE_API_KEY in .env" };
409
- }
410
- }
411
- else if (imageProvider === "grok") {
412
- imageApiKey = (await getApiKey("XAI_API_KEY", "xAI")) ?? undefined;
413
- if (!imageApiKey) {
414
- return { success: false, outputDir, scenes: 0, error: "xAI API key required for Grok image generation. Run 'vibe setup' or set XAI_API_KEY in .env" };
415
- }
416
- }
417
- let elevenlabsApiKey;
418
- if (!options.noVoiceover) {
419
- elevenlabsApiKey = (await getApiKey("ELEVENLABS_API_KEY", "ElevenLabs")) ?? undefined;
420
- if (!elevenlabsApiKey) {
421
- return { success: false, outputDir, scenes: 0, error: "ElevenLabs API key required for voiceover (or use noVoiceover option). Run 'vibe setup' or set ELEVENLABS_API_KEY in .env" };
422
- }
423
- }
424
- let videoApiKey;
425
- if (!options.imagesOnly) {
426
- const generatorKeyMap = {
427
- grok: { envVar: "XAI_API_KEY", name: "xAI (Grok)" },
428
- kling: { envVar: "KLING_API_KEY", name: "Kling" },
429
- runway: { envVar: "RUNWAY_API_SECRET", name: "Runway" },
430
- veo: { envVar: "GOOGLE_API_KEY", name: "Google (Veo)" },
431
- };
432
- const generator = options.generator || "grok";
433
- const generatorInfo = generatorKeyMap[generator];
434
- if (!generatorInfo) {
435
- return { success: false, outputDir, scenes: 0, error: `Invalid generator: ${options.generator}. Available: ${Object.keys(generatorKeyMap).join(", ")}` };
436
- }
437
- videoApiKey = (await getApiKey(generatorInfo.envVar, generatorInfo.name)) ?? undefined;
438
- if (!videoApiKey) {
439
- return { success: false, outputDir, scenes: 0, error: `${generatorInfo.name} API key required (or use imagesOnly option). Run 'vibe setup' or set ${generatorInfo.envVar} in .env` };
440
- }
441
- }
442
- // Create output directory
443
- const absOutputDir = resolve(process.cwd(), outputDir);
444
- if (!existsSync(absOutputDir)) {
445
- await mkdir(absOutputDir, { recursive: true });
446
- }
447
- // Step 1: Generate storyboard
448
- options.onProgress?.(`Analyzing script with ${storyboardProvider}...`);
449
- let segments;
450
- const creativityOpts = { creativity: options.creativity };
451
- if (storyboardProvider === "openai") {
452
- const openai = new OpenAIProvider();
453
- await openai.initialize({ apiKey: storyboardApiKey });
454
- segments = await openai.analyzeContent(options.script, options.duration, creativityOpts);
455
- }
456
- else if (storyboardProvider === "gemini") {
457
- const gemini = new GeminiProvider();
458
- await gemini.initialize({ apiKey: storyboardApiKey });
459
- segments = await gemini.analyzeContent(options.script, options.duration, creativityOpts);
460
- }
461
- else {
462
- const claude = new ClaudeProvider();
463
- await claude.initialize({ apiKey: storyboardApiKey });
464
- segments = await claude.analyzeContent(options.script, options.duration, creativityOpts);
465
- }
466
- if (segments.length === 0) {
467
- return { success: false, outputDir, scenes: 0, error: "Failed to generate storyboard" };
468
- }
469
- // Save storyboard as YAML (human-readable, easier to edit)
470
- const storyboardPath = resolve(absOutputDir, "storyboard.yaml");
471
- await writeFile(storyboardPath, yamlStringify({ scenes: segments }, { indent: 2 }), "utf-8");
472
- const result = {
473
- success: true,
474
- outputDir: absOutputDir,
475
- scenes: segments.length,
476
- storyboardPath,
477
- narrations: [],
478
- narrationEntries: [],
479
- images: [],
480
- videos: [],
481
- failedScenes: [],
482
- failedNarrations: [],
483
- };
484
- // Step 2: Generate per-scene voiceovers with ElevenLabs
485
- if (!options.noVoiceover && elevenlabsApiKey) {
486
- options.onProgress?.(`Generating ${segments.length} voiceover(s) with ElevenLabs...`);
487
- const elevenlabs = new ElevenLabsProvider();
488
- await elevenlabs.initialize({ apiKey: elevenlabsApiKey });
489
- for (let i = 0; i < segments.length; i++) {
490
- const segment = segments[i];
491
- const narrationText = segment.narration || segment.description;
492
- if (!narrationText) {
493
- // No narration text for this segment - add placeholder entry
494
- result.narrationEntries.push({
495
- path: null,
496
- duration: segment.duration,
497
- segmentIndex: i,
498
- failed: false, // Not failed, just no text
499
- });
500
- continue;
501
- }
502
- // Pre-TTS word-count heuristic (warn only; narration may still fit)
503
- const wordCount = narrationText.split(/\s+/).filter(Boolean).length;
504
- const maxWords = segment.duration > 5 ? 24 : 12;
505
- if (wordCount > maxWords * 1.3) {
506
- options.onProgress?.(`⚠ Scene ${i + 1} narration has ${wordCount} words (target ~${maxWords} for ${segment.duration}s); speech may rush.`);
507
- }
508
- options.onProgress?.(`Scene ${i + 1}/${segments.length}: generating narration...`);
509
- let ttsResult = await elevenlabs.textToSpeech(narrationText, {
510
- voiceId: options.voice,
511
- });
512
- if (ttsResult.success && ttsResult.audioBuffer) {
513
- const audioPath = resolve(absOutputDir, `narration-${i + 1}.mp3`);
514
- await writeFile(audioPath, ttsResult.audioBuffer);
515
- // Get actual audio duration
516
- let actualDuration = await getAudioDuration(audioPath);
517
- // Auto speed-adjust if narration exceeds video bracket (5s or 10s).
518
- // Pick the bracket from the pre-TTS segment.duration (the storyboard
519
- // estimate), not actualDuration — actualDuration IS the overage.
520
- const videoBracket = segment.duration > 5 ? 10 : 5;
521
- const overageRatio = actualDuration / videoBracket;
522
- if (overageRatio > 1.0 && overageRatio <= 1.35) {
523
- const adjustedSpeed = Math.min(1.35, parseFloat(overageRatio.toFixed(2)));
524
- options.onProgress?.(`Scene ${i + 1}: adjusting narration speed to ${adjustedSpeed}x...`);
525
- const speedResult = await elevenlabs.textToSpeech(narrationText, {
526
- voiceId: options.voice,
527
- speed: adjustedSpeed,
528
- });
529
- if (speedResult.success && speedResult.audioBuffer) {
530
- await writeFile(audioPath, speedResult.audioBuffer);
531
- actualDuration = await getAudioDuration(audioPath);
532
- ttsResult = speedResult;
533
- }
534
- }
535
- else if (overageRatio > 1.35) {
536
- options.onProgress?.(`⚠ Scene ${i + 1} narration is ${((overageRatio - 1) * 100).toFixed(0)}% over target (${actualDuration.toFixed(1)}s vs ${videoBracket}s bracket)`);
537
- }
538
- segment.duration = actualDuration;
539
- // Add to both arrays for backwards compatibility
540
- result.narrations.push(audioPath);
541
- result.narrationEntries.push({
542
- path: audioPath,
543
- duration: actualDuration,
544
- segmentIndex: i,
545
- failed: false,
546
- });
547
- }
548
- else {
549
- // TTS failed - add placeholder entry with error info
550
- result.narrationEntries.push({
551
- path: null,
552
- duration: segment.duration, // Keep original estimated duration
553
- segmentIndex: i,
554
- failed: true,
555
- error: ttsResult.error || "Unknown TTS error",
556
- });
557
- result.failedNarrations.push(i + 1); // 1-indexed for user display
558
- }
559
- }
560
- // Recalculate startTime for all segments
561
- let currentTime = 0;
562
- for (const segment of segments) {
563
- segment.startTime = currentTime;
564
- currentTime += segment.duration;
565
- }
566
- // Re-save storyboard with updated durations
567
- await writeFile(storyboardPath, yamlStringify({ scenes: segments }, { indent: 2 }), "utf-8");
568
- }
569
- // Step 3: Generate images
570
- options.onProgress?.(`Generating ${segments.length} scene image(s) with ${imageProvider}...`);
571
- const dalleImageSizes = {
572
- "16:9": "1536x1024",
573
- "9:16": "1024x1536",
574
- "1:1": "1024x1024",
575
- };
576
- let openaiImageInstance;
577
- let geminiInstance;
578
- let grokInstance;
579
- if (imageProvider === "openai" || imageProvider === "dalle") {
580
- openaiImageInstance = new OpenAIImageProvider();
581
- await openaiImageInstance.initialize({ apiKey: imageApiKey });
582
- }
583
- else if (imageProvider === "gemini") {
584
- geminiInstance = new GeminiProvider();
585
- await geminiInstance.initialize({ apiKey: imageApiKey });
586
- }
587
- else if (imageProvider === "grok") {
588
- grokInstance = new GrokProvider();
589
- await grokInstance.initialize({ apiKey: imageApiKey });
590
- }
591
- const imagePaths = [];
592
- for (let i = 0; i < segments.length; i++) {
593
- const segment = segments[i];
594
- const imagePrompt = segment.visualStyle
595
- ? `${segment.visuals}. Style: ${segment.visualStyle}`
596
- : segment.visuals;
597
- options.onProgress?.(`Scene ${i + 1}/${segments.length}: generating image...`);
598
- try {
599
- let imageBuffer;
600
- let imageUrl;
601
- if ((imageProvider === "openai" || imageProvider === "dalle") && openaiImageInstance) {
602
- const imageResult = await openaiImageInstance.generateImage(imagePrompt, {
603
- size: dalleImageSizes[options.aspectRatio || "16:9"] || "1536x1024",
604
- quality: "standard",
605
- });
606
- if (imageResult.success && imageResult.images && imageResult.images.length > 0) {
607
- // GPT Image 1.5 returns base64, DALL-E 3 returns URL
608
- const img = imageResult.images[0];
609
- if (img.base64) {
610
- imageBuffer = Buffer.from(img.base64, "base64");
611
- }
612
- else if (img.url) {
613
- imageUrl = img.url;
614
- }
615
- }
616
- // else: imageResult.error is available but not captured
617
- }
618
- else if (imageProvider === "gemini" && geminiInstance) {
619
- const imageResult = await geminiInstance.generateImage(imagePrompt, {
620
- aspectRatio: (options.aspectRatio || "16:9"),
621
- });
622
- if (imageResult.success && imageResult.images?.[0]?.base64) {
623
- imageBuffer = Buffer.from(imageResult.images[0].base64, "base64");
624
- }
625
- // else: imageResult.error is available but not captured
626
- }
627
- else if (imageProvider === "grok" && grokInstance) {
628
- const imageResult = await grokInstance.generateImage(imagePrompt, {
629
- aspectRatio: options.aspectRatio || "16:9",
630
- });
631
- if (imageResult.success && imageResult.images && imageResult.images.length > 0) {
632
- const img = imageResult.images[0];
633
- if (img.base64) {
634
- imageBuffer = Buffer.from(img.base64, "base64");
635
- }
636
- else if (img.url) {
637
- imageUrl = img.url;
638
- }
639
- }
640
- }
641
- const imagePath = resolve(absOutputDir, `scene-${i + 1}.png`);
642
- if (imageBuffer) {
643
- await writeFile(imagePath, imageBuffer);
644
- imagePaths.push(imagePath);
645
- result.images.push(imagePath);
646
- }
647
- else if (imageUrl) {
648
- const response = await fetch(imageUrl);
649
- const buffer = Buffer.from(await response.arrayBuffer());
650
- await writeFile(imagePath, buffer);
651
- imagePaths.push(imagePath);
652
- result.images.push(imagePath);
653
- }
654
- else {
655
- // Track failed scene - error details not captured (see provider imageResult.error)
656
- // The failedScenes array tracks which scenes failed for the caller
657
- imagePaths.push("");
658
- }
659
- }
660
- catch {
661
- imagePaths.push("");
662
- }
663
- // Rate limiting delay
664
- if (i < segments.length - 1) {
665
- await new Promise((r) => setTimeout(r, 500));
666
- }
667
- }
668
- // Step 4 (alt): branch into scene-project emit when --format scenes.
669
- // We have everything needed: storyboard, narrations, images. Video gen,
670
- // text overlays, .vibe.json assembly, and review are all skipped — the
671
- // user iterates on the resulting HTML scenes via `vibe scene lint/render`.
672
- if (options.format === "scenes") {
673
- const { executeSegmentsToScenes } = await import("./_shared/segments-to-scenes.js");
674
- options.onProgress?.("Materialising scene project...");
675
- const sceneAspect = (options.aspectRatio === "1:1" || options.aspectRatio === "16:9" || options.aspectRatio === "9:16")
676
- ? options.aspectRatio
677
- : "16:9";
678
- const scenes = await executeSegmentsToScenes({
679
- segments,
680
- narrationEntries: result.narrationEntries,
681
- imagePaths,
682
- outputDir: absOutputDir,
683
- aspectRatio: sceneAspect,
684
- scenePreset: options.scenePreset,
685
- onProgress: options.onProgress,
686
- });
687
- result.format = "scenes";
688
- result.scenePaths = scenes.scenePaths;
689
- result.sceneLint = scenes.lintResult;
690
- result.totalDuration = segments.reduce((sum, s) => sum + s.duration, 0);
691
- if (!scenes.success) {
692
- return { ...result, success: false, error: scenes.error ?? "Scene emit failed" };
693
- }
694
- return result;
695
- }
696
- // Step 4: Generate videos (if not images-only)
697
- const videoPaths = [];
698
- const maxRetries = options.retries ?? DEFAULT_VIDEO_RETRIES;
699
- if (!options.imagesOnly && videoApiKey) {
700
- options.onProgress?.(`Generating ${segments.length} scene video(s) with ${options.generator || "grok"}...`);
701
- if (options.generator === "grok") {
702
- const grok = new GrokProvider();
703
- await grok.initialize({ apiKey: videoApiKey });
704
- for (let i = 0; i < segments.length; i++) {
705
- if (!imagePaths[i]) {
706
- videoPaths.push("");
707
- continue;
708
- }
709
- const segment = segments[i];
710
- const videoDuration = Math.min(15, Math.max(1, segment.duration));
711
- options.onProgress?.(`Scene ${i + 1}/${segments.length}: generating video (grok)...`);
712
- // Image-to-video: encode image as data URI
713
- const imageBuffer = await readFile(imagePaths[i]);
714
- const ext = extname(imagePaths[i]).toLowerCase().slice(1);
715
- const mimeType = ext === "jpg" || ext === "jpeg" ? "image/jpeg" : "image/png";
716
- const referenceImage = `data:${mimeType};base64,${imageBuffer.toString("base64")}`;
717
- const taskResult = await generateVideoWithRetryGrok(grok, segment, { duration: videoDuration, aspectRatio: (options.aspectRatio || "16:9"), referenceImage }, maxRetries);
718
- if (taskResult) {
719
- try {
720
- const waitResult = await grok.waitForCompletion(taskResult.requestId, undefined, 300000);
721
- if (waitResult.status === "completed" && waitResult.videoUrl) {
722
- const videoPath = resolve(absOutputDir, `scene-${i + 1}.mp4`);
723
- const buffer = await downloadVideo(waitResult.videoUrl, videoApiKey);
724
- await writeFile(videoPath, buffer);
725
- const targetDuration = segment.duration;
726
- const actualVideoDuration = await getVideoDuration(videoPath);
727
- if (actualVideoDuration < targetDuration - 0.1) {
728
- const extendedPath = resolve(absOutputDir, `scene-${i + 1}-extended.mp4`);
729
- await extendVideoNaturally(videoPath, targetDuration, extendedPath);
730
- await unlink(videoPath);
731
- await rename(extendedPath, videoPath);
732
- }
733
- videoPaths.push(videoPath);
734
- result.videos.push(videoPath);
735
- }
736
- else {
737
- logSceneFailure("Grok", `scene ${i + 1}`, waitResult);
738
- videoPaths.push("");
739
- result.failedScenes.push(i + 1);
740
- }
741
- }
742
- catch (err) {
743
- logSceneFailure("Grok", `scene ${i + 1}`, err);
744
- videoPaths.push("");
745
- result.failedScenes.push(i + 1);
746
- }
747
- }
748
- else {
749
- videoPaths.push("");
750
- result.failedScenes.push(i + 1);
751
- }
752
- }
753
- }
754
- else if (options.generator === "kling") {
755
- const kling = new KlingProvider();
756
- await kling.initialize({ apiKey: videoApiKey });
757
- if (!kling.isConfigured()) {
758
- return { success: false, outputDir: absOutputDir, scenes: segments.length, error: "Invalid Kling API key format. Use ACCESS_KEY:SECRET_KEY" };
759
- }
760
- // Kling image2video requires a public image URL (not base64). When an
761
- // ImgBB key is configured, upload each scene image once and pass the
762
- // URL as referenceImage; otherwise fall through to text2video.
763
- const imgbbApiKey = (await getApiKeyFromConfig("imgbb")) || process.env.IMGBB_API_KEY;
764
- const imageUrls = new Array(segments.length);
765
- if (imgbbApiKey) {
766
- options.onProgress?.("Uploading scene images for Kling image-to-video...");
767
- for (let i = 0; i < imagePaths.length; i++) {
768
- if (imagePaths[i]) {
769
- try {
770
- const imageBuffer = await readFile(imagePaths[i]);
771
- const uploadResult = await uploadToImgbb(imageBuffer, imgbbApiKey);
772
- if (uploadResult.success && uploadResult.url) {
773
- imageUrls[i] = uploadResult.url;
774
- }
775
- }
776
- catch {
777
- imageUrls[i] = undefined;
778
- }
779
- }
780
- }
781
- }
782
- for (let i = 0; i < segments.length; i++) {
783
- if (!imagePaths[i]) {
784
- videoPaths.push("");
785
- continue;
786
- }
787
- const segment = segments[i];
788
- const videoDuration = (segment.duration > 5 ? 10 : 5);
789
- options.onProgress?.(`Scene ${i + 1}/${segments.length}: generating video (kling)...`);
790
- const taskResult = await generateVideoWithRetryKling(kling, segment, {
791
- duration: videoDuration,
792
- aspectRatio: (options.aspectRatio || "16:9"),
793
- referenceImage: imageUrls[i],
794
- }, maxRetries);
795
- if (taskResult) {
796
- try {
797
- const waitResult = await kling.waitForCompletion(taskResult.taskId, taskResult.type, undefined, 600000);
798
- if (waitResult.status === "completed" && waitResult.videoUrl) {
799
- const videoPath = resolve(absOutputDir, `scene-${i + 1}.mp4`);
800
- const buffer = await downloadVideo(waitResult.videoUrl, videoApiKey);
801
- await writeFile(videoPath, buffer);
802
- // Extend video to match narration duration. Use extendVideoToTarget
803
- // so large gaps (ratio > 1.4) can use Kling's own extend API for
804
- // natural continuation instead of freeze-frame padding.
805
- await extendVideoToTarget(videoPath, segment.duration, absOutputDir, `Scene ${i + 1}`, {
806
- kling,
807
- videoId: waitResult.videoId,
808
- onProgress: options.onProgress,
809
- });
810
- videoPaths.push(videoPath);
811
- result.videos.push(videoPath);
812
- }
813
- else {
814
- logSceneFailure("Kling", `scene ${i + 1}`, waitResult);
815
- videoPaths.push("");
816
- result.failedScenes.push(i + 1);
817
- }
818
- }
819
- catch (err) {
820
- logSceneFailure("Kling", `scene ${i + 1}`, err);
821
- videoPaths.push("");
822
- result.failedScenes.push(i + 1);
823
- }
824
- }
825
- else {
826
- videoPaths.push("");
827
- result.failedScenes.push(i + 1);
828
- }
829
- }
830
- }
831
- else if (options.generator === "veo") {
832
- // Veo (Gemini)
833
- const veo = new GeminiProvider();
834
- await veo.initialize({ apiKey: videoApiKey });
835
- for (let i = 0; i < segments.length; i++) {
836
- if (!imagePaths[i]) {
837
- videoPaths.push("");
838
- continue;
839
- }
840
- const segment = segments[i];
841
- const veoDuration = (segment.duration > 6 ? 8 : segment.duration > 4 ? 6 : 4);
842
- options.onProgress?.(`Scene ${i + 1}/${segments.length}: generating video (veo)...`);
843
- const taskResult = await generateVideoWithRetryVeo(veo, segment, { duration: veoDuration, aspectRatio: (options.aspectRatio || "16:9") }, maxRetries);
844
- if (taskResult) {
845
- try {
846
- const waitResult = await veo.waitForVideoCompletion(taskResult.operationName, undefined, 300000);
847
- if (waitResult.status === "completed" && waitResult.videoUrl) {
848
- const videoPath = resolve(absOutputDir, `scene-${i + 1}.mp4`);
849
- const buffer = await downloadVideo(waitResult.videoUrl, videoApiKey);
850
- await writeFile(videoPath, buffer);
851
- // Extend video to match narration duration if needed
852
- const targetDuration = segment.duration;
853
- const actualVideoDuration = await getVideoDuration(videoPath);
854
- if (actualVideoDuration < targetDuration - 0.1) {
855
- const extendedPath = resolve(absOutputDir, `scene-${i + 1}-extended.mp4`);
856
- await extendVideoNaturally(videoPath, targetDuration, extendedPath);
857
- await unlink(videoPath);
858
- await rename(extendedPath, videoPath);
859
- }
860
- videoPaths.push(videoPath);
861
- result.videos.push(videoPath);
862
- }
863
- else {
864
- logSceneFailure("Veo", `scene ${i + 1}`, waitResult);
865
- videoPaths.push("");
866
- result.failedScenes.push(i + 1);
867
- }
868
- }
869
- catch (err) {
870
- logSceneFailure("Veo", `scene ${i + 1}`, err);
871
- videoPaths.push("");
872
- result.failedScenes.push(i + 1);
873
- }
874
- }
875
- else {
876
- videoPaths.push("");
877
- result.failedScenes.push(i + 1);
878
- }
879
- }
880
- }
881
- else {
882
- // Runway
883
- const runway = new RunwayProvider();
884
- await runway.initialize({ apiKey: videoApiKey });
885
- for (let i = 0; i < segments.length; i++) {
886
- if (!imagePaths[i]) {
887
- videoPaths.push("");
888
- continue;
889
- }
890
- const segment = segments[i];
891
- const imageBuffer = await readFile(imagePaths[i]);
892
- const ext = extname(imagePaths[i]).toLowerCase().slice(1);
893
- const mimeType = ext === "jpg" || ext === "jpeg" ? "image/jpeg" : "image/png";
894
- const referenceImage = `data:${mimeType};base64,${imageBuffer.toString("base64")}`;
895
- const videoDuration = (segment.duration > 5 ? 10 : 5);
896
- const aspectRatio = options.aspectRatio === "1:1" ? "16:9" : (options.aspectRatio || "16:9");
897
- options.onProgress?.(`Scene ${i + 1}/${segments.length}: generating video (runway)...`);
898
- const taskResult = await generateVideoWithRetryRunway(runway, segment, referenceImage, { duration: videoDuration, aspectRatio }, maxRetries);
899
- if (taskResult) {
900
- try {
901
- const waitResult = await runway.waitForCompletion(taskResult.taskId, undefined, 300000);
902
- if (waitResult.status === "completed" && waitResult.videoUrl) {
903
- const videoPath = resolve(absOutputDir, `scene-${i + 1}.mp4`);
904
- const buffer = await downloadVideo(waitResult.videoUrl, videoApiKey);
905
- await writeFile(videoPath, buffer);
906
- // Extend video to match narration duration if needed
907
- const targetDuration = segment.duration;
908
- const actualVideoDuration = await getVideoDuration(videoPath);
909
- if (actualVideoDuration < targetDuration - 0.1) {
910
- const extendedPath = resolve(absOutputDir, `scene-${i + 1}-extended.mp4`);
911
- await extendVideoNaturally(videoPath, targetDuration, extendedPath);
912
- await unlink(videoPath);
913
- await rename(extendedPath, videoPath);
914
- }
915
- videoPaths.push(videoPath);
916
- result.videos.push(videoPath);
917
- }
918
- else {
919
- logSceneFailure("Runway", `scene ${i + 1}`, waitResult);
920
- videoPaths.push("");
921
- result.failedScenes.push(i + 1);
922
- }
923
- }
924
- catch (err) {
925
- logSceneFailure("Runway", `scene ${i + 1}`, err);
926
- videoPaths.push("");
927
- result.failedScenes.push(i + 1);
928
- }
929
- }
930
- else {
931
- videoPaths.push("");
932
- result.failedScenes.push(i + 1);
933
- }
934
- }
935
- }
936
- }
937
- // Step 4.5: Apply text overlays (if segments have textOverlays)
938
- if (!options.noTextOverlay) {
939
- const overlayCount = segments.filter((s, i) => s.textOverlays?.length && videoPaths[i]).length;
940
- if (overlayCount > 0)
941
- options.onProgress?.(`Applying text overlays to ${overlayCount} scene(s)...`);
942
- for (let i = 0; i < segments.length; i++) {
943
- const segment = segments[i];
944
- if (segment.textOverlays && segment.textOverlays.length > 0 && videoPaths[i] && videoPaths[i] !== "") {
945
- try {
946
- const overlayOutput = videoPaths[i].replace(/(\.[^.]+)$/, "-overlay$1");
947
- const overlayResult = await applyTextOverlays({
948
- videoPath: videoPaths[i],
949
- texts: segment.textOverlays,
950
- outputPath: overlayOutput,
951
- style: options.textStyle || "lower-third",
952
- });
953
- if (overlayResult.success && overlayResult.outputPath) {
954
- videoPaths[i] = overlayResult.outputPath;
955
- }
956
- // Silent fallback: keep original on failure
957
- }
958
- catch {
959
- // Silent fallback: keep original video
960
- }
961
- }
962
- }
963
- }
964
- // Step 5: Create project file
965
- options.onProgress?.("Assembling project...");
966
- const project = new Project("Script-to-Video Output");
967
- project.setAspectRatio((options.aspectRatio || "16:9"));
968
- // Clear default tracks
969
- const defaultTracks = project.getTracks();
970
- for (const track of defaultTracks) {
971
- project.removeTrack(track.id);
972
- }
973
- const videoTrack = project.addTrack({
974
- name: "Video",
975
- type: "video",
976
- order: 1,
977
- isMuted: false,
978
- isLocked: false,
979
- isVisible: true,
980
- });
981
- const audioTrack = project.addTrack({
982
- name: "Audio",
983
- type: "audio",
984
- order: 0,
985
- isMuted: false,
986
- isLocked: false,
987
- isVisible: true,
988
- });
989
- // Add narration clips - use narrationEntries for proper segment alignment
990
- if (result.narrationEntries && result.narrationEntries.length > 0) {
991
- for (const entry of result.narrationEntries) {
992
- // Skip failed or missing narrations
993
- if (entry.failed || !entry.path)
994
- continue;
995
- const segment = segments[entry.segmentIndex];
996
- const narrationDuration = await getAudioDuration(entry.path);
997
- const audioSource = project.addSource({
998
- name: `Narration ${entry.segmentIndex + 1}`,
999
- url: entry.path,
1000
- type: "audio",
1001
- duration: narrationDuration,
1002
- });
1003
- project.addClip({
1004
- sourceId: audioSource.id,
1005
- trackId: audioTrack.id,
1006
- startTime: segment.startTime,
1007
- duration: narrationDuration,
1008
- sourceStartOffset: 0,
1009
- sourceEndOffset: narrationDuration,
1010
- });
1011
- }
1012
- }
1013
- // Add video/image clips
1014
- let currentTime = 0;
1015
- for (let i = 0; i < segments.length; i++) {
1016
- const segment = segments[i];
1017
- const hasVideo = videoPaths[i] && videoPaths[i] !== "";
1018
- const hasImage = imagePaths[i] && imagePaths[i] !== "";
1019
- if (!hasVideo && !hasImage) {
1020
- currentTime += segment.duration;
1021
- continue;
1022
- }
1023
- const assetPath = hasVideo ? videoPaths[i] : imagePaths[i];
1024
- const mediaType = hasVideo ? "video" : "image";
1025
- // Use actual video duration (after extension) instead of segment.duration
1026
- const actualDuration = hasVideo
1027
- ? await getVideoDuration(assetPath)
1028
- : segment.duration;
1029
- const source = project.addSource({
1030
- name: `Scene ${i + 1}`,
1031
- url: assetPath,
1032
- type: mediaType,
1033
- duration: actualDuration,
1034
- });
1035
- project.addClip({
1036
- sourceId: source.id,
1037
- trackId: videoTrack.id,
1038
- startTime: currentTime,
1039
- duration: actualDuration,
1040
- sourceStartOffset: 0,
1041
- sourceEndOffset: actualDuration,
1042
- });
1043
- currentTime += actualDuration;
1044
- }
1045
- // Save project file. Default is `{outputDir}/project.vibe.json`; callers
1046
- // can override via projectFilePath (used by the CLI's `-o` handling).
1047
- const projectPath = options.projectFilePath
1048
- ? resolve(process.cwd(), options.projectFilePath)
1049
- : resolve(absOutputDir, "project.vibe.json");
1050
- const projectParentDir = dirname(projectPath);
1051
- if (!existsSync(projectParentDir)) {
1052
- await mkdir(projectParentDir, { recursive: true });
1053
- }
1054
- await writeFile(projectPath, JSON.stringify(project.toJSON(), null, 2), "utf-8");
1055
- result.projectPath = projectPath;
1056
- result.totalDuration = currentTime;
1057
- // Step 6: AI Review & Auto-fix (optional, --review flag)
1058
- if (options.review) {
1059
- options.onProgress?.("Reviewing video with Gemini AI...");
1060
- try {
1061
- // Look for storyboard file (YAML preferred, JSON fallback for backward compat)
1062
- const storyboardYaml = resolve(absOutputDir, "storyboard.yaml");
1063
- const storyboardJson = resolve(absOutputDir, "storyboard.json");
1064
- const storyboardFile = existsSync(storyboardYaml) ? storyboardYaml : existsSync(storyboardJson) ? storyboardJson : undefined;
1065
- // Export project to temp MP4 for review (use first valid video as proxy)
1066
- const reviewTarget = videoPaths.find((p) => p && p !== "") || imagePaths.find((p) => p && p !== "");
1067
- if (reviewTarget) {
1068
- const reviewResult = await executeReview({
1069
- videoPath: reviewTarget,
1070
- storyboardPath: storyboardFile,
1071
- autoApply: options.reviewAutoApply,
1072
- model: "flash",
1073
- });
1074
- if (reviewResult.success) {
1075
- result.reviewFeedback = reviewResult.feedback;
1076
- result.appliedFixes = reviewResult.appliedFixes;
1077
- result.reviewedVideoPath = reviewResult.outputPath;
1078
- }
1079
- }
1080
- }
1081
- catch {
1082
- // Review is non-critical, continue with result
1083
- }
1084
- }
1085
- return result;
1086
- }
1087
- catch (error) {
1088
- return {
1089
- success: false,
1090
- outputDir,
1091
- scenes: 0,
1092
- error: error instanceof Error ? error.message : String(error),
1093
- };
1094
- }
1095
- }
1096
- /**
1097
- * Regenerate specific scene(s) in an existing script-to-video project.
1098
- *
1099
- * Reads the storyboard.json from the project directory, then regenerates
1100
- * the requested scenes using the specified video/image provider. Supports
1101
- * image-to-video via ImgBB URL upload for Kling.
1102
- *
1103
- * @param options - Scene regeneration configuration
1104
- * @returns Result with lists of regenerated and failed scene numbers
1105
- */
1106
- export async function executeRegenerateScene(options) {
1107
- const result = {
1108
- success: false,
1109
- regeneratedScenes: [],
1110
- failedScenes: [],
1111
- };
1112
- try {
1113
- const outputDir = resolve(process.cwd(), options.projectDir);
1114
- if (!existsSync(outputDir)) {
1115
- return { ...result, error: `Project directory not found: ${outputDir}` };
1116
- }
1117
- // Load storyboard (YAML preferred, JSON fallback for backward compat)
1118
- const yamlPath = resolve(outputDir, "storyboard.yaml");
1119
- const jsonPath = resolve(outputDir, "storyboard.json");
1120
- const storyboardPath = existsSync(yamlPath) ? yamlPath : existsSync(jsonPath) ? jsonPath : null;
1121
- if (!storyboardPath) {
1122
- return { ...result, error: `Storyboard not found in: ${outputDir} (expected storyboard.yaml or storyboard.json)` };
1123
- }
1124
- const storyboardContent = await readFile(storyboardPath, "utf-8");
1125
- const segments = storyboardPath.endsWith(".yaml")
1126
- ? yamlParse(storyboardContent).scenes
1127
- : JSON.parse(storyboardContent);
1128
- // Validate scenes
1129
- for (const sceneNum of options.scenes) {
1130
- if (sceneNum < 1 || sceneNum > segments.length) {
1131
- return { ...result, error: `Scene ${sceneNum} does not exist. Storyboard has ${segments.length} scenes.` };
1132
- }
1133
- }
1134
- const regenerateVideo = options.videoOnly || (!options.narrationOnly && !options.imageOnly);
1135
- const regenerateNarration = options.narrationOnly || (!options.videoOnly && !options.imageOnly);
1136
- const regenerateImage = options.imageOnly || (!options.videoOnly && !options.narrationOnly);
1137
- // Get API keys
1138
- let videoApiKey;
1139
- if (regenerateVideo) {
1140
- const generatorKeyMap = {
1141
- grok: { envVar: "XAI_API_KEY", name: "xAI (Grok)" },
1142
- kling: { envVar: "KLING_API_KEY", name: "Kling" },
1143
- runway: { envVar: "RUNWAY_API_SECRET", name: "Runway" },
1144
- veo: { envVar: "GOOGLE_API_KEY", name: "Google (Veo)" },
1145
- };
1146
- const generator = options.generator || "grok";
1147
- const genInfo = generatorKeyMap[generator];
1148
- if (!genInfo) {
1149
- return { ...result, error: `Invalid generator: ${generator}. Available: ${Object.keys(generatorKeyMap).join(", ")}` };
1150
- }
1151
- videoApiKey = (await getApiKey(genInfo.envVar, genInfo.name)) ?? undefined;
1152
- if (!videoApiKey) {
1153
- return { ...result, error: `${genInfo.name} API key required. Run 'vibe setup' or set ${genInfo.envVar} in .env` };
1154
- }
1155
- }
1156
- let imageApiKey;
1157
- if (regenerateImage) {
1158
- const imageProvider = options.imageProvider || "openai";
1159
- const imageKeyMap = {
1160
- openai: { envVar: "OPENAI_API_KEY", name: "OpenAI" },
1161
- gemini: { envVar: "GOOGLE_API_KEY", name: "Google" },
1162
- grok: { envVar: "XAI_API_KEY", name: "xAI" },
1163
- };
1164
- const info = imageKeyMap[imageProvider];
1165
- if (!info) {
1166
- return { ...result, error: `Invalid imageProvider: ${imageProvider}` };
1167
- }
1168
- imageApiKey = (await getApiKey(info.envVar, info.name)) ?? undefined;
1169
- if (!imageApiKey) {
1170
- return { ...result, error: `${info.name} API key required. Run 'vibe setup' or set ${info.envVar} in .env` };
1171
- }
1172
- }
1173
- let elevenlabsApiKey;
1174
- if (regenerateNarration) {
1175
- elevenlabsApiKey = (await getApiKey("ELEVENLABS_API_KEY", "ElevenLabs")) ?? undefined;
1176
- if (!elevenlabsApiKey) {
1177
- return { ...result, error: "ElevenLabs API key required. Run 'vibe setup' or set ELEVENLABS_API_KEY in .env" };
1178
- }
1179
- }
1180
- // Track storyboard mutations so we only rewrite when segment durations change.
1181
- let storyboardMutated = false;
1182
- // Process each scene
1183
- for (const sceneNum of options.scenes) {
1184
- const segment = segments[sceneNum - 1];
1185
- const narrationPath = resolve(outputDir, `narration-${sceneNum}.mp3`);
1186
- const imagePath = resolve(outputDir, `scene-${sceneNum}.png`);
1187
- const videoPath = resolve(outputDir, `scene-${sceneNum}.mp4`);
1188
- let sceneFailed = false;
1189
- // Narration regeneration
1190
- if (regenerateNarration && elevenlabsApiKey) {
1191
- options.onProgress?.(`Scene ${sceneNum}: regenerating narration...`);
1192
- const elevenlabs = new ElevenLabsProvider();
1193
- await elevenlabs.initialize({ apiKey: elevenlabsApiKey });
1194
- const narrationText = segment.narration || segment.description;
1195
- const ttsResult = await elevenlabs.textToSpeech(narrationText, {
1196
- voiceId: options.voice,
1197
- });
1198
- if (ttsResult.success && ttsResult.audioBuffer) {
1199
- await writeFile(narrationPath, ttsResult.audioBuffer);
1200
- segment.duration = await getAudioDuration(narrationPath);
1201
- storyboardMutated = true;
1202
- }
1203
- else {
1204
- sceneFailed = true;
1205
- }
1206
- }
1207
- // Image regeneration (with character-consistency reference when available)
1208
- if (!sceneFailed && regenerateImage && imageApiKey) {
1209
- options.onProgress?.(`Scene ${sceneNum}: regenerating image...`);
1210
- const imageProvider = options.imageProvider || "openai";
1211
- const characterDesc = segment.characterDescription || segments[0]?.characterDescription;
1212
- let imagePrompt = segment.visualStyle
1213
- ? `${segment.visuals}. Style: ${segment.visualStyle}`
1214
- : segment.visuals;
1215
- if (characterDesc) {
1216
- imagePrompt = `${imagePrompt}\n\nIMPORTANT - Character appearance must match exactly: ${characterDesc}`;
1217
- }
1218
- // Pick a reference image for character consistency: caller-specified,
1219
- // else auto-detect the first existing scene image that isn't this scene.
1220
- let referenceImageBuffer;
1221
- const refSceneNum = options.referenceScene;
1222
- if (refSceneNum && refSceneNum >= 1 && refSceneNum <= segments.length && refSceneNum !== sceneNum) {
1223
- const refImagePath = resolve(outputDir, `scene-${refSceneNum}.png`);
1224
- if (existsSync(refImagePath)) {
1225
- referenceImageBuffer = await readFile(refImagePath);
1226
- }
1227
- }
1228
- else if (!refSceneNum) {
1229
- for (let i = 1; i <= segments.length; i++) {
1230
- if (i !== sceneNum) {
1231
- const otherImagePath = resolve(outputDir, `scene-${i}.png`);
1232
- if (existsSync(otherImagePath)) {
1233
- referenceImageBuffer = await readFile(otherImagePath);
1234
- break;
1235
- }
1236
- }
1237
- }
1238
- }
1239
- const dalleImageSizes = {
1240
- "16:9": "1536x1024",
1241
- "9:16": "1024x1536",
1242
- "1:1": "1024x1024",
1243
- };
1244
- let imageBuffer;
1245
- let imageUrl;
1246
- if (imageProvider === "openai") {
1247
- const openaiImage = new OpenAIImageProvider();
1248
- await openaiImage.initialize({ apiKey: imageApiKey });
1249
- const imageResult = await openaiImage.generateImage(imagePrompt, {
1250
- size: dalleImageSizes[options.aspectRatio || "16:9"] || "1536x1024",
1251
- quality: "standard",
1252
- });
1253
- if (imageResult.success && imageResult.images?.[0]) {
1254
- const img = imageResult.images[0];
1255
- if (img.base64)
1256
- imageBuffer = Buffer.from(img.base64, "base64");
1257
- else if (img.url)
1258
- imageUrl = img.url;
1259
- }
1260
- }
1261
- else if (imageProvider === "gemini") {
1262
- const gemini = new GeminiProvider();
1263
- await gemini.initialize({ apiKey: imageApiKey });
1264
- if (referenceImageBuffer) {
1265
- // Gemini edit-with-reference path — pulls the character forward
1266
- // while describing the new action. Simplified visuals pick the
1267
- // first recognised action verb to reduce prompt bleed.
1268
- const simplifiedVisuals = segment.visuals.split(/[,.]/).find((part) => part.includes("standing") || part.includes("sitting") || part.includes("walking") ||
1269
- part.includes("lying") || part.includes("reaching") || part.includes("looking") ||
1270
- part.includes("working") || part.includes("coding") || part.includes("typing")) || segment.visuals.split(".")[0];
1271
- const editPrompt = `Generate a new image showing the SAME SINGLE person from the reference image in a new scene.
1272
-
1273
- REFERENCE: Look at the person in the reference image - their face, hair, build, and overall appearance.
1274
-
1275
- NEW SCENE: ${simplifiedVisuals}
1276
-
1277
- CRITICAL RULES:
1278
- 1. Show ONLY ONE person - the exact same individual from the reference image
1279
- 2. The person must have the IDENTICAL face, hair style, and body type
1280
- 3. Do NOT show multiple people or duplicate the character
1281
- 4. Create a single moment in time, one pose, one action
1282
- 5. Match the art style and quality of the reference image
1283
-
1284
- Generate the single-person scene image now.`;
1285
- const imageResult = await gemini.editImage([referenceImageBuffer], editPrompt, {
1286
- aspectRatio: (options.aspectRatio || "16:9"),
1287
- });
1288
- if (imageResult.success && imageResult.images?.[0]?.base64) {
1289
- imageBuffer = Buffer.from(imageResult.images[0].base64, "base64");
1290
- }
1291
- }
1292
- else {
1293
- const imageResult = await gemini.generateImage(imagePrompt, {
1294
- aspectRatio: (options.aspectRatio || "16:9"),
1295
- });
1296
- if (imageResult.success && imageResult.images?.[0]?.base64) {
1297
- imageBuffer = Buffer.from(imageResult.images[0].base64, "base64");
1298
- }
1299
- }
1300
- }
1301
- else if (imageProvider === "grok") {
1302
- const grok = new GrokProvider();
1303
- await grok.initialize({ apiKey: imageApiKey });
1304
- const imageResult = await grok.generateImage(imagePrompt, {
1305
- aspectRatio: options.aspectRatio || "16:9",
1306
- });
1307
- if (imageResult.success && imageResult.images?.[0]) {
1308
- const img = imageResult.images[0];
1309
- if (img.base64)
1310
- imageBuffer = Buffer.from(img.base64, "base64");
1311
- else if (img.url)
1312
- imageUrl = img.url;
1313
- }
1314
- }
1315
- if (imageBuffer) {
1316
- await writeFile(imagePath, imageBuffer);
1317
- }
1318
- else if (imageUrl) {
1319
- const response = await fetch(imageUrl);
1320
- const buffer = Buffer.from(await response.arrayBuffer());
1321
- await writeFile(imagePath, buffer);
1322
- }
1323
- else {
1324
- sceneFailed = true;
1325
- }
1326
- }
1327
- if (sceneFailed) {
1328
- result.failedScenes.push(sceneNum);
1329
- continue;
1330
- }
1331
- if (regenerateVideo && videoApiKey) {
1332
- options.onProgress?.(`Scene ${sceneNum}: regenerating video (${options.generator || "grok"})...`);
1333
- if (!existsSync(imagePath)) {
1334
- result.failedScenes.push(sceneNum);
1335
- continue;
1336
- }
1337
- const imageBuffer = await readFile(imagePath);
1338
- const videoDuration = (segment.duration > 5 ? 10 : 5);
1339
- const maxRetries = options.retries ?? DEFAULT_VIDEO_RETRIES;
1340
- if (options.generator === "grok") {
1341
- // Grok (xAI) — image-to-video with data URI reference
1342
- const grok = new GrokProvider();
1343
- await grok.initialize({ apiKey: videoApiKey });
1344
- const ext = extname(imagePath).toLowerCase().slice(1);
1345
- const mimeType = ext === "jpg" || ext === "jpeg" ? "image/jpeg" : "image/png";
1346
- const referenceImage = `data:${mimeType};base64,${imageBuffer.toString("base64")}`;
1347
- const grokDuration = Math.min(15, Math.max(1, segment.duration));
1348
- const taskResult = await generateVideoWithRetryGrok(grok, segment, {
1349
- duration: grokDuration,
1350
- aspectRatio: (options.aspectRatio || "16:9"),
1351
- referenceImage,
1352
- }, maxRetries);
1353
- if (taskResult) {
1354
- try {
1355
- const waitResult = await grok.waitForCompletion(taskResult.requestId, undefined, 300000);
1356
- if (waitResult.status === "completed" && waitResult.videoUrl) {
1357
- const buffer = await downloadVideo(waitResult.videoUrl, videoApiKey);
1358
- await writeFile(videoPath, buffer);
1359
- const targetDuration = segment.duration;
1360
- const actualVideoDuration = await getVideoDuration(videoPath);
1361
- if (actualVideoDuration < targetDuration - 0.1) {
1362
- const extendedPath = resolve(outputDir, `scene-${sceneNum}-extended.mp4`);
1363
- await extendVideoNaturally(videoPath, targetDuration, extendedPath);
1364
- await unlink(videoPath);
1365
- await rename(extendedPath, videoPath);
1366
- }
1367
- result.regeneratedScenes.push(sceneNum);
1368
- }
1369
- else {
1370
- logSceneFailure("Grok", `scene ${sceneNum}`, waitResult);
1371
- result.failedScenes.push(sceneNum);
1372
- }
1373
- }
1374
- catch (err) {
1375
- logSceneFailure("Grok", `scene ${sceneNum}`, err);
1376
- result.failedScenes.push(sceneNum);
1377
- }
1378
- }
1379
- else {
1380
- result.failedScenes.push(sceneNum);
1381
- }
1382
- }
1383
- else if (options.generator === "veo") {
1384
- // Veo (Gemini)
1385
- const veo = new GeminiProvider();
1386
- await veo.initialize({ apiKey: videoApiKey });
1387
- const ext = extname(imagePath).toLowerCase().slice(1);
1388
- const mimeType = ext === "jpg" || ext === "jpeg" ? "image/jpeg" : "image/png";
1389
- const referenceImage = `data:${mimeType};base64,${imageBuffer.toString("base64")}`;
1390
- const veoDuration = (segment.duration > 6 ? 8 : segment.duration > 4 ? 6 : 4);
1391
- const taskResult = await generateVideoWithRetryVeo(veo, segment, {
1392
- duration: veoDuration,
1393
- aspectRatio: (options.aspectRatio || "16:9"),
1394
- referenceImage,
1395
- }, maxRetries);
1396
- if (taskResult) {
1397
- try {
1398
- const waitResult = await veo.waitForVideoCompletion(taskResult.operationName, undefined, 300000);
1399
- if (waitResult.status === "completed" && waitResult.videoUrl) {
1400
- const buffer = await downloadVideo(waitResult.videoUrl, videoApiKey);
1401
- await writeFile(videoPath, buffer);
1402
- const targetDuration = segment.duration;
1403
- const actualVideoDuration = await getVideoDuration(videoPath);
1404
- if (actualVideoDuration < targetDuration - 0.1) {
1405
- const extendedPath = resolve(outputDir, `scene-${sceneNum}-extended.mp4`);
1406
- await extendVideoNaturally(videoPath, targetDuration, extendedPath);
1407
- await unlink(videoPath);
1408
- await rename(extendedPath, videoPath);
1409
- }
1410
- result.regeneratedScenes.push(sceneNum);
1411
- }
1412
- else {
1413
- logSceneFailure("Veo", `scene ${sceneNum}`, waitResult);
1414
- result.failedScenes.push(sceneNum);
1415
- }
1416
- }
1417
- catch (err) {
1418
- logSceneFailure("Veo", `scene ${sceneNum}`, err);
1419
- result.failedScenes.push(sceneNum);
1420
- }
1421
- }
1422
- else {
1423
- result.failedScenes.push(sceneNum);
1424
- }
1425
- }
1426
- else if (options.generator === "kling" || !options.generator) {
1427
- const kling = new KlingProvider();
1428
- await kling.initialize({ apiKey: videoApiKey });
1429
- if (!kling.isConfigured()) {
1430
- result.failedScenes.push(sceneNum);
1431
- continue;
1432
- }
1433
- // Try to use image-to-video if ImgBB key available
1434
- const imgbbApiKey = await getApiKeyFromConfig("imgbb") || process.env.IMGBB_API_KEY;
1435
- let imageUrl;
1436
- if (imgbbApiKey) {
1437
- const uploadResult = await uploadToImgbb(imageBuffer, imgbbApiKey);
1438
- if (uploadResult.success && uploadResult.url) {
1439
- imageUrl = uploadResult.url;
1440
- }
1441
- }
1442
- const taskResult = await generateVideoWithRetryKling(kling, segment, {
1443
- duration: videoDuration,
1444
- aspectRatio: (options.aspectRatio || "16:9"),
1445
- referenceImage: imageUrl,
1446
- }, maxRetries);
1447
- if (taskResult) {
1448
- try {
1449
- const waitResult = await kling.waitForCompletion(taskResult.taskId, taskResult.type, undefined, 600000);
1450
- if (waitResult.status === "completed" && waitResult.videoUrl) {
1451
- const buffer = await downloadVideo(waitResult.videoUrl, videoApiKey);
1452
- await writeFile(videoPath, buffer);
1453
- // Extend via extendVideoToTarget so Kling's own extend API can
1454
- // be used for large gaps (ratio > 1.4) instead of freeze frames.
1455
- await extendVideoToTarget(videoPath, segment.duration, outputDir, `Scene ${sceneNum}`, {
1456
- kling,
1457
- videoId: waitResult.videoId,
1458
- onProgress: options.onProgress,
1459
- });
1460
- result.regeneratedScenes.push(sceneNum);
1461
- }
1462
- else {
1463
- logSceneFailure("Kling", `scene ${sceneNum}`, waitResult);
1464
- result.failedScenes.push(sceneNum);
1465
- }
1466
- }
1467
- catch (err) {
1468
- logSceneFailure("Kling", `scene ${sceneNum}`, err);
1469
- result.failedScenes.push(sceneNum);
1470
- }
1471
- }
1472
- else {
1473
- result.failedScenes.push(sceneNum);
1474
- }
1475
- }
1476
- else {
1477
- // Runway
1478
- const runway = new RunwayProvider();
1479
- await runway.initialize({ apiKey: videoApiKey });
1480
- const ext = extname(imagePath).toLowerCase().slice(1);
1481
- const mimeType = ext === "jpg" || ext === "jpeg" ? "image/jpeg" : "image/png";
1482
- const referenceImage = `data:${mimeType};base64,${imageBuffer.toString("base64")}`;
1483
- const aspectRatio = options.aspectRatio === "1:1" ? "16:9" : (options.aspectRatio || "16:9");
1484
- const taskResult = await generateVideoWithRetryRunway(runway, segment, referenceImage, { duration: videoDuration, aspectRatio }, maxRetries);
1485
- if (taskResult) {
1486
- try {
1487
- const waitResult = await runway.waitForCompletion(taskResult.taskId, undefined, 300000);
1488
- if (waitResult.status === "completed" && waitResult.videoUrl) {
1489
- const buffer = await downloadVideo(waitResult.videoUrl, videoApiKey);
1490
- await writeFile(videoPath, buffer);
1491
- // Extend video to match narration duration if needed
1492
- const targetDuration = segment.duration;
1493
- const actualVideoDuration = await getVideoDuration(videoPath);
1494
- if (actualVideoDuration < targetDuration - 0.1) {
1495
- const extendedPath = resolve(outputDir, `scene-${sceneNum}-extended.mp4`);
1496
- await extendVideoNaturally(videoPath, targetDuration, extendedPath);
1497
- await unlink(videoPath);
1498
- await rename(extendedPath, videoPath);
1499
- }
1500
- result.regeneratedScenes.push(sceneNum);
1501
- }
1502
- else {
1503
- logSceneFailure("Runway", `scene ${sceneNum}`, waitResult);
1504
- result.failedScenes.push(sceneNum);
1505
- }
1506
- }
1507
- catch (err) {
1508
- logSceneFailure("Runway", `scene ${sceneNum}`, err);
1509
- result.failedScenes.push(sceneNum);
1510
- }
1511
- }
1512
- else {
1513
- result.failedScenes.push(sceneNum);
1514
- }
1515
- }
1516
- }
1517
- else if (!sceneFailed) {
1518
- // narration-only / image-only / both: no video pass, so record success here
1519
- result.regeneratedScenes.push(sceneNum);
1520
- }
1521
- }
1522
- // Persist storyboard if narration regeneration changed segment durations.
1523
- // Preserve the on-disk format (yaml vs json) so no silent downgrades.
1524
- if (storyboardMutated) {
1525
- let currentTime = 0;
1526
- for (const segment of segments) {
1527
- segment.startTime = currentTime;
1528
- currentTime += segment.duration;
1529
- }
1530
- const serialized = storyboardPath.endsWith(".yaml")
1531
- ? yamlStringify({ scenes: segments }, { indent: 2 })
1532
- : JSON.stringify(segments, null, 2);
1533
- await writeFile(storyboardPath, serialized, "utf-8");
1534
- }
1535
- result.success = result.failedScenes.length === 0;
1536
- return result;
1537
- }
1538
- catch (error) {
1539
- return {
1540
- ...result,
1541
- error: error instanceof Error ? error.message : String(error),
1542
- };
1543
- }
1544
- }
1545
- /* CLI command registration moved to ai-script-pipeline-cli.ts */
1546
- //# sourceMappingURL=ai-script-pipeline.js.map