@vibeframe/cli 0.27.0

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 (420) hide show
  1. package/.turbo/turbo-build.log +4 -0
  2. package/.turbo/turbo-lint.log +21 -0
  3. package/.turbo/turbo-test.log +689 -0
  4. package/dist/agent/adapters/claude.d.ts +15 -0
  5. package/dist/agent/adapters/claude.d.ts.map +1 -0
  6. package/dist/agent/adapters/claude.js +119 -0
  7. package/dist/agent/adapters/claude.js.map +1 -0
  8. package/dist/agent/adapters/gemini.d.ts +15 -0
  9. package/dist/agent/adapters/gemini.d.ts.map +1 -0
  10. package/dist/agent/adapters/gemini.js +132 -0
  11. package/dist/agent/adapters/gemini.js.map +1 -0
  12. package/dist/agent/adapters/index.d.ts +27 -0
  13. package/dist/agent/adapters/index.d.ts.map +1 -0
  14. package/dist/agent/adapters/index.js +38 -0
  15. package/dist/agent/adapters/index.js.map +1 -0
  16. package/dist/agent/adapters/ollama.d.ts +20 -0
  17. package/dist/agent/adapters/ollama.d.ts.map +1 -0
  18. package/dist/agent/adapters/ollama.js +186 -0
  19. package/dist/agent/adapters/ollama.js.map +1 -0
  20. package/dist/agent/adapters/openai.d.ts +15 -0
  21. package/dist/agent/adapters/openai.d.ts.map +1 -0
  22. package/dist/agent/adapters/openai.js +92 -0
  23. package/dist/agent/adapters/openai.js.map +1 -0
  24. package/dist/agent/adapters/xai.d.ts +15 -0
  25. package/dist/agent/adapters/xai.d.ts.map +1 -0
  26. package/dist/agent/adapters/xai.js +95 -0
  27. package/dist/agent/adapters/xai.js.map +1 -0
  28. package/dist/agent/index.d.ts +69 -0
  29. package/dist/agent/index.d.ts.map +1 -0
  30. package/dist/agent/index.js +180 -0
  31. package/dist/agent/index.js.map +1 -0
  32. package/dist/agent/memory/index.d.ts +70 -0
  33. package/dist/agent/memory/index.d.ts.map +1 -0
  34. package/dist/agent/memory/index.js +132 -0
  35. package/dist/agent/memory/index.js.map +1 -0
  36. package/dist/agent/prompts/system.d.ts +6 -0
  37. package/dist/agent/prompts/system.d.ts.map +1 -0
  38. package/dist/agent/prompts/system.js +103 -0
  39. package/dist/agent/prompts/system.js.map +1 -0
  40. package/dist/agent/tools/ai-editing.d.ts +15 -0
  41. package/dist/agent/tools/ai-editing.d.ts.map +1 -0
  42. package/dist/agent/tools/ai-editing.js +763 -0
  43. package/dist/agent/tools/ai-editing.js.map +1 -0
  44. package/dist/agent/tools/ai-generation.d.ts +13 -0
  45. package/dist/agent/tools/ai-generation.d.ts.map +1 -0
  46. package/dist/agent/tools/ai-generation.js +973 -0
  47. package/dist/agent/tools/ai-generation.js.map +1 -0
  48. package/dist/agent/tools/ai-pipeline.d.ts +14 -0
  49. package/dist/agent/tools/ai-pipeline.d.ts.map +1 -0
  50. package/dist/agent/tools/ai-pipeline.js +961 -0
  51. package/dist/agent/tools/ai-pipeline.js.map +1 -0
  52. package/dist/agent/tools/ai.d.ts +13 -0
  53. package/dist/agent/tools/ai.d.ts.map +1 -0
  54. package/dist/agent/tools/ai.js +19 -0
  55. package/dist/agent/tools/ai.js.map +1 -0
  56. package/dist/agent/tools/batch.d.ts +6 -0
  57. package/dist/agent/tools/batch.d.ts.map +1 -0
  58. package/dist/agent/tools/batch.js +383 -0
  59. package/dist/agent/tools/batch.js.map +1 -0
  60. package/dist/agent/tools/e2e.test.d.ts +26 -0
  61. package/dist/agent/tools/e2e.test.d.ts.map +1 -0
  62. package/dist/agent/tools/e2e.test.js +397 -0
  63. package/dist/agent/tools/e2e.test.js.map +1 -0
  64. package/dist/agent/tools/export.d.ts +6 -0
  65. package/dist/agent/tools/export.d.ts.map +1 -0
  66. package/dist/agent/tools/export.js +171 -0
  67. package/dist/agent/tools/export.js.map +1 -0
  68. package/dist/agent/tools/filesystem.d.ts +6 -0
  69. package/dist/agent/tools/filesystem.d.ts.map +1 -0
  70. package/dist/agent/tools/filesystem.js +212 -0
  71. package/dist/agent/tools/filesystem.js.map +1 -0
  72. package/dist/agent/tools/index.d.ts +65 -0
  73. package/dist/agent/tools/index.d.ts.map +1 -0
  74. package/dist/agent/tools/index.js +120 -0
  75. package/dist/agent/tools/index.js.map +1 -0
  76. package/dist/agent/tools/integration.test.d.ts +11 -0
  77. package/dist/agent/tools/integration.test.d.ts.map +1 -0
  78. package/dist/agent/tools/integration.test.js +659 -0
  79. package/dist/agent/tools/integration.test.js.map +1 -0
  80. package/dist/agent/tools/media.d.ts +6 -0
  81. package/dist/agent/tools/media.d.ts.map +1 -0
  82. package/dist/agent/tools/media.js +616 -0
  83. package/dist/agent/tools/media.js.map +1 -0
  84. package/dist/agent/tools/project.d.ts +6 -0
  85. package/dist/agent/tools/project.d.ts.map +1 -0
  86. package/dist/agent/tools/project.js +284 -0
  87. package/dist/agent/tools/project.js.map +1 -0
  88. package/dist/agent/tools/timeline.d.ts +6 -0
  89. package/dist/agent/tools/timeline.d.ts.map +1 -0
  90. package/dist/agent/tools/timeline.js +873 -0
  91. package/dist/agent/tools/timeline.js.map +1 -0
  92. package/dist/agent/types.d.ts +59 -0
  93. package/dist/agent/types.d.ts.map +1 -0
  94. package/dist/agent/types.js +5 -0
  95. package/dist/agent/types.js.map +1 -0
  96. package/dist/commands/agent.d.ts +21 -0
  97. package/dist/commands/agent.d.ts.map +1 -0
  98. package/dist/commands/agent.js +290 -0
  99. package/dist/commands/agent.js.map +1 -0
  100. package/dist/commands/ai-analyze.d.ts +106 -0
  101. package/dist/commands/ai-analyze.d.ts.map +1 -0
  102. package/dist/commands/ai-analyze.js +327 -0
  103. package/dist/commands/ai-analyze.js.map +1 -0
  104. package/dist/commands/ai-animated-caption.d.ts +64 -0
  105. package/dist/commands/ai-animated-caption.d.ts.map +1 -0
  106. package/dist/commands/ai-animated-caption.js +272 -0
  107. package/dist/commands/ai-animated-caption.js.map +1 -0
  108. package/dist/commands/ai-audio.d.ts +20 -0
  109. package/dist/commands/ai-audio.d.ts.map +1 -0
  110. package/dist/commands/ai-audio.js +808 -0
  111. package/dist/commands/ai-audio.js.map +1 -0
  112. package/dist/commands/ai-broll.d.ts +15 -0
  113. package/dist/commands/ai-broll.d.ts.map +1 -0
  114. package/dist/commands/ai-broll.js +406 -0
  115. package/dist/commands/ai-broll.js.map +1 -0
  116. package/dist/commands/ai-edit-cli.d.ts +14 -0
  117. package/dist/commands/ai-edit-cli.d.ts.map +1 -0
  118. package/dist/commands/ai-edit-cli.js +579 -0
  119. package/dist/commands/ai-edit-cli.js.map +1 -0
  120. package/dist/commands/ai-edit.d.ts +398 -0
  121. package/dist/commands/ai-edit.d.ts.map +1 -0
  122. package/dist/commands/ai-edit.js +1019 -0
  123. package/dist/commands/ai-edit.js.map +1 -0
  124. package/dist/commands/ai-fill-gaps.d.ts +14 -0
  125. package/dist/commands/ai-fill-gaps.d.ts.map +1 -0
  126. package/dist/commands/ai-fill-gaps.js +451 -0
  127. package/dist/commands/ai-fill-gaps.js.map +1 -0
  128. package/dist/commands/ai-helpers.d.ts +20 -0
  129. package/dist/commands/ai-helpers.d.ts.map +1 -0
  130. package/dist/commands/ai-helpers.js +59 -0
  131. package/dist/commands/ai-helpers.js.map +1 -0
  132. package/dist/commands/ai-highlights.d.ts +127 -0
  133. package/dist/commands/ai-highlights.d.ts.map +1 -0
  134. package/dist/commands/ai-highlights.js +1026 -0
  135. package/dist/commands/ai-highlights.js.map +1 -0
  136. package/dist/commands/ai-image.d.ts +34 -0
  137. package/dist/commands/ai-image.d.ts.map +1 -0
  138. package/dist/commands/ai-image.js +653 -0
  139. package/dist/commands/ai-image.js.map +1 -0
  140. package/dist/commands/ai-motion.d.ts +50 -0
  141. package/dist/commands/ai-motion.d.ts.map +1 -0
  142. package/dist/commands/ai-motion.js +271 -0
  143. package/dist/commands/ai-motion.js.map +1 -0
  144. package/dist/commands/ai-narrate.d.ts +66 -0
  145. package/dist/commands/ai-narrate.d.ts.map +1 -0
  146. package/dist/commands/ai-narrate.js +329 -0
  147. package/dist/commands/ai-narrate.js.map +1 -0
  148. package/dist/commands/ai-review.d.ts +57 -0
  149. package/dist/commands/ai-review.d.ts.map +1 -0
  150. package/dist/commands/ai-review.js +251 -0
  151. package/dist/commands/ai-review.js.map +1 -0
  152. package/dist/commands/ai-script-pipeline-cli.d.ts +9 -0
  153. package/dist/commands/ai-script-pipeline-cli.d.ts.map +1 -0
  154. package/dist/commands/ai-script-pipeline-cli.js +1494 -0
  155. package/dist/commands/ai-script-pipeline-cli.js.map +1 -0
  156. package/dist/commands/ai-script-pipeline.d.ts +259 -0
  157. package/dist/commands/ai-script-pipeline.d.ts.map +1 -0
  158. package/dist/commands/ai-script-pipeline.js +1027 -0
  159. package/dist/commands/ai-script-pipeline.js.map +1 -0
  160. package/dist/commands/ai-suggest-edit.d.ts +14 -0
  161. package/dist/commands/ai-suggest-edit.d.ts.map +1 -0
  162. package/dist/commands/ai-suggest-edit.js +220 -0
  163. package/dist/commands/ai-suggest-edit.js.map +1 -0
  164. package/dist/commands/ai-video-fx.d.ts +14 -0
  165. package/dist/commands/ai-video-fx.d.ts.map +1 -0
  166. package/dist/commands/ai-video-fx.js +395 -0
  167. package/dist/commands/ai-video-fx.js.map +1 -0
  168. package/dist/commands/ai-video.d.ts +15 -0
  169. package/dist/commands/ai-video.d.ts.map +1 -0
  170. package/dist/commands/ai-video.js +785 -0
  171. package/dist/commands/ai-video.js.map +1 -0
  172. package/dist/commands/ai-viral.d.ts +15 -0
  173. package/dist/commands/ai-viral.d.ts.map +1 -0
  174. package/dist/commands/ai-viral.js +519 -0
  175. package/dist/commands/ai-viral.js.map +1 -0
  176. package/dist/commands/ai-visual-fx.d.ts +14 -0
  177. package/dist/commands/ai-visual-fx.d.ts.map +1 -0
  178. package/dist/commands/ai-visual-fx.js +505 -0
  179. package/dist/commands/ai-visual-fx.js.map +1 -0
  180. package/dist/commands/ai.d.ts +38 -0
  181. package/dist/commands/ai.d.ts.map +1 -0
  182. package/dist/commands/ai.js +225 -0
  183. package/dist/commands/ai.js.map +1 -0
  184. package/dist/commands/ai.test.d.ts +2 -0
  185. package/dist/commands/ai.test.d.ts.map +1 -0
  186. package/dist/commands/ai.test.js +554 -0
  187. package/dist/commands/ai.test.js.map +1 -0
  188. package/dist/commands/analyze.d.ts +16 -0
  189. package/dist/commands/analyze.d.ts.map +1 -0
  190. package/dist/commands/analyze.js +247 -0
  191. package/dist/commands/analyze.js.map +1 -0
  192. package/dist/commands/audio.d.ts +18 -0
  193. package/dist/commands/audio.d.ts.map +1 -0
  194. package/dist/commands/audio.js +539 -0
  195. package/dist/commands/audio.js.map +1 -0
  196. package/dist/commands/batch.d.ts +3 -0
  197. package/dist/commands/batch.d.ts.map +1 -0
  198. package/dist/commands/batch.js +366 -0
  199. package/dist/commands/batch.js.map +1 -0
  200. package/dist/commands/batch.test.d.ts +2 -0
  201. package/dist/commands/batch.test.d.ts.map +1 -0
  202. package/dist/commands/batch.test.js +203 -0
  203. package/dist/commands/batch.test.js.map +1 -0
  204. package/dist/commands/detect.d.ts +3 -0
  205. package/dist/commands/detect.d.ts.map +1 -0
  206. package/dist/commands/detect.js +273 -0
  207. package/dist/commands/detect.js.map +1 -0
  208. package/dist/commands/doctor.d.ts +6 -0
  209. package/dist/commands/doctor.d.ts.map +1 -0
  210. package/dist/commands/doctor.js +191 -0
  211. package/dist/commands/doctor.js.map +1 -0
  212. package/dist/commands/edit-cmd.d.ts +26 -0
  213. package/dist/commands/edit-cmd.d.ts.map +1 -0
  214. package/dist/commands/edit-cmd.js +870 -0
  215. package/dist/commands/edit-cmd.js.map +1 -0
  216. package/dist/commands/export.d.ts +39 -0
  217. package/dist/commands/export.d.ts.map +1 -0
  218. package/dist/commands/export.js +730 -0
  219. package/dist/commands/export.js.map +1 -0
  220. package/dist/commands/generate.d.ts +25 -0
  221. package/dist/commands/generate.d.ts.map +1 -0
  222. package/dist/commands/generate.js +1885 -0
  223. package/dist/commands/generate.js.map +1 -0
  224. package/dist/commands/media.d.ts +3 -0
  225. package/dist/commands/media.d.ts.map +1 -0
  226. package/dist/commands/media.js +165 -0
  227. package/dist/commands/media.js.map +1 -0
  228. package/dist/commands/output.d.ts +45 -0
  229. package/dist/commands/output.d.ts.map +1 -0
  230. package/dist/commands/output.js +122 -0
  231. package/dist/commands/output.js.map +1 -0
  232. package/dist/commands/pipeline.d.ts +19 -0
  233. package/dist/commands/pipeline.d.ts.map +1 -0
  234. package/dist/commands/pipeline.js +345 -0
  235. package/dist/commands/pipeline.js.map +1 -0
  236. package/dist/commands/project.d.ts +3 -0
  237. package/dist/commands/project.d.ts.map +1 -0
  238. package/dist/commands/project.js +139 -0
  239. package/dist/commands/project.js.map +1 -0
  240. package/dist/commands/project.test.d.ts +2 -0
  241. package/dist/commands/project.test.d.ts.map +1 -0
  242. package/dist/commands/project.test.js +105 -0
  243. package/dist/commands/project.test.js.map +1 -0
  244. package/dist/commands/sanitize.d.ts +21 -0
  245. package/dist/commands/sanitize.d.ts.map +1 -0
  246. package/dist/commands/sanitize.js +56 -0
  247. package/dist/commands/sanitize.js.map +1 -0
  248. package/dist/commands/schema.d.ts +11 -0
  249. package/dist/commands/schema.d.ts.map +1 -0
  250. package/dist/commands/schema.js +101 -0
  251. package/dist/commands/schema.js.map +1 -0
  252. package/dist/commands/setup.d.ts +6 -0
  253. package/dist/commands/setup.d.ts.map +1 -0
  254. package/dist/commands/setup.js +440 -0
  255. package/dist/commands/setup.js.map +1 -0
  256. package/dist/commands/timeline.d.ts +3 -0
  257. package/dist/commands/timeline.d.ts.map +1 -0
  258. package/dist/commands/timeline.js +469 -0
  259. package/dist/commands/timeline.js.map +1 -0
  260. package/dist/commands/timeline.test.d.ts +2 -0
  261. package/dist/commands/timeline.test.d.ts.map +1 -0
  262. package/dist/commands/timeline.test.js +320 -0
  263. package/dist/commands/timeline.test.js.map +1 -0
  264. package/dist/commands/validate.d.ts +32 -0
  265. package/dist/commands/validate.d.ts.map +1 -0
  266. package/dist/commands/validate.js +63 -0
  267. package/dist/commands/validate.js.map +1 -0
  268. package/dist/config/config.test.d.ts +2 -0
  269. package/dist/config/config.test.d.ts.map +1 -0
  270. package/dist/config/config.test.js +164 -0
  271. package/dist/config/config.test.js.map +1 -0
  272. package/dist/config/index.d.ts +35 -0
  273. package/dist/config/index.d.ts.map +1 -0
  274. package/dist/config/index.js +101 -0
  275. package/dist/config/index.js.map +1 -0
  276. package/dist/config/schema.d.ts +43 -0
  277. package/dist/config/schema.d.ts.map +1 -0
  278. package/dist/config/schema.js +42 -0
  279. package/dist/config/schema.js.map +1 -0
  280. package/dist/engine/index.d.ts +3 -0
  281. package/dist/engine/index.d.ts.map +1 -0
  282. package/dist/engine/index.js +2 -0
  283. package/dist/engine/index.js.map +1 -0
  284. package/dist/engine/project.d.ts +84 -0
  285. package/dist/engine/project.d.ts.map +1 -0
  286. package/dist/engine/project.js +355 -0
  287. package/dist/engine/project.js.map +1 -0
  288. package/dist/engine/project.test.d.ts +2 -0
  289. package/dist/engine/project.test.d.ts.map +1 -0
  290. package/dist/engine/project.test.js +599 -0
  291. package/dist/engine/project.test.js.map +1 -0
  292. package/dist/index.d.ts +7 -0
  293. package/dist/index.d.ts.map +1 -0
  294. package/dist/index.js +131 -0
  295. package/dist/index.js.map +1 -0
  296. package/dist/utils/api-key.d.ts +36 -0
  297. package/dist/utils/api-key.d.ts.map +1 -0
  298. package/dist/utils/api-key.js +211 -0
  299. package/dist/utils/api-key.js.map +1 -0
  300. package/dist/utils/api-key.test.d.ts +2 -0
  301. package/dist/utils/api-key.test.d.ts.map +1 -0
  302. package/dist/utils/api-key.test.js +35 -0
  303. package/dist/utils/api-key.test.js.map +1 -0
  304. package/dist/utils/audio.d.ts +23 -0
  305. package/dist/utils/audio.d.ts.map +1 -0
  306. package/dist/utils/audio.js +79 -0
  307. package/dist/utils/audio.js.map +1 -0
  308. package/dist/utils/exec-safe.d.ts +22 -0
  309. package/dist/utils/exec-safe.d.ts.map +1 -0
  310. package/dist/utils/exec-safe.js +62 -0
  311. package/dist/utils/exec-safe.js.map +1 -0
  312. package/dist/utils/first-run.d.ts +13 -0
  313. package/dist/utils/first-run.d.ts.map +1 -0
  314. package/dist/utils/first-run.js +48 -0
  315. package/dist/utils/first-run.js.map +1 -0
  316. package/dist/utils/provider-resolver.d.ts +15 -0
  317. package/dist/utils/provider-resolver.d.ts.map +1 -0
  318. package/dist/utils/provider-resolver.js +42 -0
  319. package/dist/utils/provider-resolver.js.map +1 -0
  320. package/dist/utils/remotion.d.ts +210 -0
  321. package/dist/utils/remotion.d.ts.map +1 -0
  322. package/dist/utils/remotion.js +731 -0
  323. package/dist/utils/remotion.js.map +1 -0
  324. package/dist/utils/subtitle.d.ts +65 -0
  325. package/dist/utils/subtitle.d.ts.map +1 -0
  326. package/dist/utils/subtitle.js +135 -0
  327. package/dist/utils/subtitle.js.map +1 -0
  328. package/dist/utils/subtitle.test.d.ts +2 -0
  329. package/dist/utils/subtitle.test.d.ts.map +1 -0
  330. package/dist/utils/subtitle.test.js +175 -0
  331. package/dist/utils/subtitle.test.js.map +1 -0
  332. package/dist/utils/tty.d.ts +45 -0
  333. package/dist/utils/tty.d.ts.map +1 -0
  334. package/dist/utils/tty.js +172 -0
  335. package/dist/utils/tty.js.map +1 -0
  336. package/package.json +102 -0
  337. package/src/agent/adapters/claude.ts +143 -0
  338. package/src/agent/adapters/gemini.ts +159 -0
  339. package/src/agent/adapters/index.ts +61 -0
  340. package/src/agent/adapters/ollama.ts +231 -0
  341. package/src/agent/adapters/openai.ts +116 -0
  342. package/src/agent/adapters/xai.ts +119 -0
  343. package/src/agent/index.ts +251 -0
  344. package/src/agent/memory/index.ts +151 -0
  345. package/src/agent/prompts/system.ts +106 -0
  346. package/src/agent/tools/ai-editing.ts +845 -0
  347. package/src/agent/tools/ai-generation.ts +1073 -0
  348. package/src/agent/tools/ai-pipeline.ts +1055 -0
  349. package/src/agent/tools/ai.ts +21 -0
  350. package/src/agent/tools/batch.ts +429 -0
  351. package/src/agent/tools/e2e.test.ts +545 -0
  352. package/src/agent/tools/export.ts +184 -0
  353. package/src/agent/tools/filesystem.ts +237 -0
  354. package/src/agent/tools/index.ts +150 -0
  355. package/src/agent/tools/integration.test.ts +775 -0
  356. package/src/agent/tools/media.ts +697 -0
  357. package/src/agent/tools/project.ts +313 -0
  358. package/src/agent/tools/timeline.ts +951 -0
  359. package/src/agent/types.ts +68 -0
  360. package/src/commands/agent.ts +340 -0
  361. package/src/commands/ai-analyze.ts +429 -0
  362. package/src/commands/ai-animated-caption.ts +390 -0
  363. package/src/commands/ai-audio.ts +941 -0
  364. package/src/commands/ai-broll.ts +490 -0
  365. package/src/commands/ai-edit-cli.ts +658 -0
  366. package/src/commands/ai-edit.ts +1542 -0
  367. package/src/commands/ai-fill-gaps.ts +566 -0
  368. package/src/commands/ai-helpers.ts +65 -0
  369. package/src/commands/ai-highlights.ts +1303 -0
  370. package/src/commands/ai-image.ts +761 -0
  371. package/src/commands/ai-motion.ts +347 -0
  372. package/src/commands/ai-narrate.ts +451 -0
  373. package/src/commands/ai-review.ts +309 -0
  374. package/src/commands/ai-script-pipeline-cli.ts +1710 -0
  375. package/src/commands/ai-script-pipeline.ts +1365 -0
  376. package/src/commands/ai-suggest-edit.ts +264 -0
  377. package/src/commands/ai-video-fx.ts +445 -0
  378. package/src/commands/ai-video.ts +915 -0
  379. package/src/commands/ai-viral.ts +595 -0
  380. package/src/commands/ai-visual-fx.ts +601 -0
  381. package/src/commands/ai.test.ts +627 -0
  382. package/src/commands/ai.ts +307 -0
  383. package/src/commands/analyze.ts +282 -0
  384. package/src/commands/audio.ts +644 -0
  385. package/src/commands/batch.test.ts +279 -0
  386. package/src/commands/batch.ts +440 -0
  387. package/src/commands/detect.ts +329 -0
  388. package/src/commands/doctor.ts +237 -0
  389. package/src/commands/edit-cmd.ts +1014 -0
  390. package/src/commands/export.ts +918 -0
  391. package/src/commands/generate.ts +2146 -0
  392. package/src/commands/media.ts +177 -0
  393. package/src/commands/output.ts +142 -0
  394. package/src/commands/pipeline.ts +398 -0
  395. package/src/commands/project.test.ts +127 -0
  396. package/src/commands/project.ts +149 -0
  397. package/src/commands/sanitize.ts +60 -0
  398. package/src/commands/schema.ts +130 -0
  399. package/src/commands/setup.ts +509 -0
  400. package/src/commands/timeline.test.ts +499 -0
  401. package/src/commands/timeline.ts +529 -0
  402. package/src/commands/validate.ts +77 -0
  403. package/src/config/config.test.ts +197 -0
  404. package/src/config/index.ts +125 -0
  405. package/src/config/schema.ts +82 -0
  406. package/src/engine/index.ts +2 -0
  407. package/src/engine/project.test.ts +702 -0
  408. package/src/engine/project.ts +439 -0
  409. package/src/index.ts +146 -0
  410. package/src/utils/api-key.test.ts +41 -0
  411. package/src/utils/api-key.ts +247 -0
  412. package/src/utils/audio.ts +83 -0
  413. package/src/utils/exec-safe.ts +75 -0
  414. package/src/utils/first-run.ts +52 -0
  415. package/src/utils/provider-resolver.ts +56 -0
  416. package/src/utils/remotion.ts +951 -0
  417. package/src/utils/subtitle.test.ts +227 -0
  418. package/src/utils/subtitle.ts +169 -0
  419. package/src/utils/tty.ts +196 -0
  420. package/tsconfig.json +20 -0
@@ -0,0 +1,731 @@
1
+ /**
2
+ * Remotion rendering and compositing utilities.
3
+ *
4
+ * Uses `npx remotion` on-demand — Remotion is NOT a package dependency.
5
+ * Scaffolds a temporary project, renders H264 MP4, and muxes audio separately.
6
+ *
7
+ * Strategy:
8
+ * - Images/Videos are embedded NATIVELY inside the Remotion component using
9
+ * <Img> / <Video> from Remotion (copied to public/).
10
+ * - No transparent WebM rendering. No FFmpeg overlay compositing.
11
+ * - Final output is always a standard H264 MP4.
12
+ */
13
+ import { writeFile, mkdir, rm, copyFile } from "node:fs/promises";
14
+ import { existsSync } from "node:fs";
15
+ import { join } from "node:path";
16
+ import { tmpdir } from "node:os";
17
+ import { execSafe } from "./exec-safe.js";
18
+ // ── Helpers ────────────────────────────────────────────────────────────────
19
+ /**
20
+ * Check that `npx remotion` is available. Returns an error message if not.
21
+ */
22
+ export async function ensureRemotionInstalled() {
23
+ try {
24
+ await execSafe("npx", ["remotion", "--help"], { timeout: 30_000 });
25
+ return null;
26
+ }
27
+ catch {
28
+ return [
29
+ "Remotion CLI not found. Install it with:",
30
+ " npm install -g @remotion/cli",
31
+ "Or ensure npx is available and can download @remotion/cli on demand.",
32
+ ].join("\n");
33
+ }
34
+ }
35
+ /**
36
+ * Create a minimal Remotion project in a temp directory.
37
+ * Returns the directory path.
38
+ *
39
+ * @param useMediaPackage - Include @remotion/media for <Video> support (default: false)
40
+ */
41
+ export async function scaffoldRemotionProject(componentCode, componentName, opts) {
42
+ const dir = join(tmpdir(), `vibe_motion_${Date.now()}`);
43
+ await mkdir(dir, { recursive: true });
44
+ // package.json — remotion + react deps
45
+ const deps = {
46
+ remotion: "^4.0.0",
47
+ "@remotion/cli": "^4.0.0",
48
+ react: "^18.0.0",
49
+ "react-dom": "^18.0.0",
50
+ "@types/react": "^18.0.0",
51
+ };
52
+ // @remotion/media is needed for the <Video> component (per Remotion docs)
53
+ if (opts.useMediaPackage) {
54
+ deps["@remotion/media"] = "^4.0.0";
55
+ }
56
+ const packageJson = {
57
+ name: "vibe-motion-render",
58
+ version: "1.0.0",
59
+ private: true,
60
+ dependencies: deps,
61
+ };
62
+ await writeFile(join(dir, "package.json"), JSON.stringify(packageJson, null, 2));
63
+ // tsconfig.json — minimal config for TSX
64
+ const tsconfig = {
65
+ compilerOptions: {
66
+ target: "ES2020",
67
+ module: "ESNext",
68
+ moduleResolution: "bundler",
69
+ jsx: "react-jsx",
70
+ strict: false,
71
+ esModuleInterop: true,
72
+ skipLibCheck: true,
73
+ },
74
+ };
75
+ await writeFile(join(dir, "tsconfig.json"), JSON.stringify(tsconfig, null, 2));
76
+ // Component.tsx — the AI-generated (and optionally wrapped) component
77
+ await writeFile(join(dir, "Component.tsx"), componentCode);
78
+ // Root.tsx — Remotion entry point
79
+ const rootCode = `import { registerRoot, Composition } from "remotion";
80
+ import { ${componentName} } from "./Component";
81
+
82
+ const Root = () => {
83
+ return (
84
+ <Composition
85
+ id="${componentName}"
86
+ component={${componentName}}
87
+ durationInFrames={${opts.durationInFrames}}
88
+ fps={${opts.fps}}
89
+ width={${opts.width}}
90
+ height={${opts.height}}
91
+ />
92
+ );
93
+ };
94
+
95
+ registerRoot(Root);
96
+ `;
97
+ await writeFile(join(dir, "Root.tsx"), rootCode);
98
+ // Install deps
99
+ if (!existsSync(join(dir, "node_modules"))) {
100
+ // npm install needs to run in the scaffolded directory
101
+ const { execFile } = await import("node:child_process");
102
+ const { promisify } = await import("node:util");
103
+ const execFileAsync = promisify(execFile);
104
+ await execFileAsync("npm", ["install", "--prefer-offline", "--no-audit", "--no-fund"], {
105
+ cwd: dir,
106
+ timeout: 180_000,
107
+ });
108
+ }
109
+ return dir;
110
+ }
111
+ // ── Standalone Motion Render ───────────────────────────────────────────────
112
+ /**
113
+ * Render a standalone Remotion composition to video (no base media).
114
+ * When transparent: tries VP8 then VP9.
115
+ * When opaque: renders H264 MP4.
116
+ */
117
+ export async function renderMotion(options) {
118
+ const transparent = options.transparent !== false;
119
+ const dir = await scaffoldRemotionProject(options.componentCode, options.componentName, {
120
+ width: options.width,
121
+ height: options.height,
122
+ fps: options.fps,
123
+ durationInFrames: options.durationInFrames,
124
+ });
125
+ try {
126
+ const entryPoint = join(dir, "Root.tsx");
127
+ const { execFile } = await import("node:child_process");
128
+ const { promisify } = await import("node:util");
129
+ const execFileAsync = promisify(execFile);
130
+ if (transparent) {
131
+ const webmOut = options.outputPath.replace(/\.\w+$/, ".webm");
132
+ try {
133
+ await execFileAsync("npx", [
134
+ "remotion", "render", entryPoint, options.componentName, webmOut,
135
+ "--codec", "vp8", "--image-format", "png", "--pixel-format", "yuva420p",
136
+ ], { cwd: dir, timeout: 300_000 });
137
+ return { success: true, outputPath: webmOut };
138
+ }
139
+ catch {
140
+ // VP8 failed, try VP9
141
+ }
142
+ try {
143
+ await execFileAsync("npx", [
144
+ "remotion", "render", entryPoint, options.componentName, webmOut,
145
+ "--codec", "vp9", "--image-format", "png", "--pixel-format", "yuva420p",
146
+ ], { cwd: dir, timeout: 300_000 });
147
+ return { success: true, outputPath: webmOut };
148
+ }
149
+ catch (error) {
150
+ const msg = error instanceof Error ? error.message : String(error);
151
+ return { success: false, error: `Transparent render failed (VP8 & VP9): ${msg}` };
152
+ }
153
+ }
154
+ // Non-transparent: H264 MP4
155
+ const mp4Out = options.outputPath.replace(/\.\w+$/, ".mp4");
156
+ await execFileAsync("npx", [
157
+ "remotion", "render", entryPoint, options.componentName, mp4Out,
158
+ "--codec", "h264", "--crf", "18",
159
+ ], { cwd: dir, timeout: 300_000 });
160
+ return { success: true, outputPath: mp4Out };
161
+ }
162
+ catch (error) {
163
+ const msg = error instanceof Error ? error.message : String(error);
164
+ return { success: false, error: `Remotion render failed: ${msg}` };
165
+ }
166
+ finally {
167
+ await rm(dir, { recursive: true, force: true }).catch(() => { });
168
+ }
169
+ }
170
+ // ── Pre-render code validator & auto-fixer ────────────────────────────────
171
+ /**
172
+ * Validates and auto-fixes common LLM-generated Remotion code bugs before
173
+ * attempting to render. Returns fixed code and a list of applied fixes.
174
+ *
175
+ * Known patterns fixed:
176
+ * 1. interpolate(x, [a,b], scalar, num) → interpolate(x, [a,b], [scalar, num])
177
+ * Cause: outputRange must be an array, not a bare scalar.
178
+ * 2. interpolate(x, [a,b], scalar) where scalar is a variable name
179
+ * Cause: same — LLM passes a single number variable instead of [from, to].
180
+ */
181
+ export function validateAndFixMotionCode(code) {
182
+ const fixes = [];
183
+ let fixed = code;
184
+ // Pattern 1: interpolate(expr, [a, b], varName, numericLiteral)
185
+ // where varName is a JS identifier and numericLiteral is a number
186
+ // This is the exact bug seen in practice: interpolate(exitEase, [0, 1], barH, 0)
187
+ const pattern1 = /interpolate\(([^,]+),\s*(\[[^\]]+\]),\s*([a-zA-Z_$][a-zA-Z0-9_$.]*),\s*(-?[\d.]+)\s*\)/g;
188
+ fixed = fixed.replace(pattern1, (_match, val, inputRange, outVar, outNum) => {
189
+ const fix = `interpolate(${val}, ${inputRange}, [${outVar}, ${outNum}])`;
190
+ fixes.push(`Fixed scalar outputRange: interpolate(..., ${outVar}, ${outNum}) → [..., [${outVar}, ${outNum}]]`);
191
+ return fix;
192
+ });
193
+ // Pattern 2: interpolate(expr, [a, b], singleIdentifier) — no options arg
194
+ // e.g. interpolate(frame, [0, 30], progress) where progress is not an array
195
+ // Heuristic: if the third arg is a plain identifier (not starting with [) and
196
+ // there's no fourth arg, we can't safely auto-fix without knowing the intent,
197
+ // so just log a warning in the fixes list for visibility.
198
+ const pattern2 = /interpolate\(([^,]+),\s*(\[[^\]]+\]),\s*([a-zA-Z_$][a-zA-Z0-9_$.]*)\s*\)/g;
199
+ let p2match;
200
+ while ((p2match = pattern2.exec(fixed)) !== null) {
201
+ // Only warn if the identifier doesn't look like an array variable name
202
+ const varName = p2match[3];
203
+ if (!varName.includes("[")) {
204
+ fixes.push(`Warning: interpolate third arg "${varName}" may not be an array — verify outputRange is [from, to]`);
205
+ }
206
+ }
207
+ return { code: fixed, fixes };
208
+ }
209
+ // ── Import injection helper ────────────────────────────────────────────────
210
+ /**
211
+ * Inject additional named imports into the existing `from 'remotion'`
212
+ * import statement in the component code.
213
+ * Avoids duplicate identifier errors when the component already imports
214
+ * some of the same names (e.g. AbsoluteFill).
215
+ */
216
+ function injectRemotionImports(code, additions) {
217
+ return code.replace(/import\s*\{([^}]+)\}\s*from\s*['"]remotion['"]/, (match, imports) => {
218
+ const existing = imports
219
+ .split(",")
220
+ .map((s) => s.trim())
221
+ .filter(Boolean);
222
+ const toAdd = additions.filter((a) => !existing.includes(a));
223
+ if (toAdd.length === 0)
224
+ return match;
225
+ return `import { ${[...existing, ...toAdd].join(", ")} } from "remotion"`;
226
+ });
227
+ }
228
+ // ── Native Image Embed ─────────────────────────────────────────────────────
229
+ /**
230
+ * Wrap an overlay component to embed a static image as background.
231
+ * Uses Remotion's <Img> component (required per Remotion docs — ensures
232
+ * image is fully loaded before each frame renders).
233
+ *
234
+ * Injects Img and staticFile into the component's existing remotion import
235
+ * to avoid duplicate identifier errors.
236
+ */
237
+ export function wrapComponentWithImage(componentCode, componentName, imageFileName) {
238
+ const wrappedName = "ImageComposite";
239
+ // Inject Img and staticFile into the existing remotion import
240
+ const modifiedCode = injectRemotionImports(componentCode, ["Img", "staticFile"]);
241
+ const code = `${modifiedCode}
242
+
243
+ export const ${wrappedName}: React.FC = () => {
244
+ return (
245
+ <AbsoluteFill>
246
+ <Img
247
+ src={staticFile("${imageFileName}")}
248
+ style={{ width: "100%", height: "100%", objectFit: "cover" }}
249
+ />
250
+ <AbsoluteFill>
251
+ <${componentName} />
252
+ </AbsoluteFill>
253
+ </AbsoluteFill>
254
+ );
255
+ };
256
+ `;
257
+ return { code, name: wrappedName };
258
+ }
259
+ /**
260
+ * Render a Remotion component that embeds a static image as background.
261
+ * Copies image to public/, renders H264 MP4 directly — no transparency needed.
262
+ */
263
+ export async function renderWithEmbeddedImage(options) {
264
+ const dir = await scaffoldRemotionProject(options.componentCode, options.componentName, {
265
+ width: options.width,
266
+ height: options.height,
267
+ fps: options.fps,
268
+ durationInFrames: options.durationInFrames,
269
+ useMediaPackage: false,
270
+ });
271
+ try {
272
+ // Copy image to public/ so staticFile() can access it
273
+ const publicDir = join(dir, "public");
274
+ await mkdir(publicDir, { recursive: true });
275
+ await copyFile(options.imagePath, join(publicDir, options.imageFileName));
276
+ const entryPoint = join(dir, "Root.tsx");
277
+ const mp4Out = options.outputPath.replace(/\.\w+$/, ".mp4");
278
+ const { execFile: execFileImg } = await import("node:child_process");
279
+ const { promisify: promisifyImg } = await import("node:util");
280
+ const execFileAsyncImg = promisifyImg(execFileImg);
281
+ await execFileAsyncImg("npx", [
282
+ "remotion", "render", entryPoint, options.componentName, mp4Out,
283
+ "--codec", "h264", "--crf", "18",
284
+ ], { cwd: dir, timeout: 600_000, maxBuffer: 50 * 1024 * 1024 });
285
+ return { success: true, outputPath: mp4Out };
286
+ }
287
+ catch (error) {
288
+ const msg = error instanceof Error ? error.message : String(error);
289
+ return { success: false, error: `Remotion image embed render failed: ${msg}` };
290
+ }
291
+ finally {
292
+ await rm(dir, { recursive: true, force: true }).catch(() => { });
293
+ }
294
+ }
295
+ // ── Native Video Embed ─────────────────────────────────────────────────────
296
+ /**
297
+ * Wrap an overlay component to embed a video as background.
298
+ * Uses <Video> from @remotion/media (required per Remotion docs).
299
+ * Video is muted — audio is muxed back via FFmpeg after rendering.
300
+ *
301
+ * Injects staticFile into the component's existing remotion import to avoid
302
+ * duplicate identifier errors. Video is imported from @remotion/media
303
+ * (different module — no conflict).
304
+ */
305
+ export function wrapComponentWithVideo(componentCode, componentName, videoFileName) {
306
+ const wrappedName = "VideoComposite";
307
+ // Inject staticFile into the existing remotion import
308
+ const modifiedCode = injectRemotionImports(componentCode, ["staticFile"]);
309
+ // Prepend @remotion/media import (different module, no conflict)
310
+ const code = `import { Video } from "@remotion/media";
311
+ ${modifiedCode}
312
+
313
+ export const ${wrappedName}: React.FC = () => {
314
+ return (
315
+ <AbsoluteFill>
316
+ <Video
317
+ src={staticFile("${videoFileName}")}
318
+ style={{ width: "100%", height: "100%" }}
319
+ muted
320
+ />
321
+ <AbsoluteFill>
322
+ <${componentName} />
323
+ </AbsoluteFill>
324
+ </AbsoluteFill>
325
+ );
326
+ };
327
+ `;
328
+ return { code, name: wrappedName };
329
+ }
330
+ /**
331
+ * Render a Remotion component that embeds the video directly.
332
+ * Uses @remotion/media's <Video> component (official Remotion approach).
333
+ * After rendering, muxes audio from the original video back into the output.
334
+ */
335
+ export async function renderWithEmbeddedVideo(options) {
336
+ const dir = await scaffoldRemotionProject(options.componentCode, options.componentName, {
337
+ width: options.width,
338
+ height: options.height,
339
+ fps: options.fps,
340
+ durationInFrames: options.durationInFrames,
341
+ useMediaPackage: true,
342
+ });
343
+ try {
344
+ // Copy video to public/ so staticFile() can access it
345
+ const publicDir = join(dir, "public");
346
+ await mkdir(publicDir, { recursive: true });
347
+ await copyFile(options.videoPath, join(publicDir, options.videoFileName));
348
+ const entryPoint = join(dir, "Root.tsx");
349
+ const mp4VideoOnly = options.outputPath.replace(/\.\w+$/, "_video_only.mp4");
350
+ // Render H264 (video-only, audio muted inside component)
351
+ const { execFile: execFileVid } = await import("node:child_process");
352
+ const { promisify: promisifyVid } = await import("node:util");
353
+ const execFileAsyncVid = promisifyVid(execFileVid);
354
+ await execFileAsyncVid("npx", [
355
+ "remotion", "render", entryPoint, options.componentName, mp4VideoOnly,
356
+ "--codec", "h264", "--crf", "18",
357
+ ], { cwd: dir, timeout: 600_000, maxBuffer: 50 * 1024 * 1024 });
358
+ // Mux: rendered video + original audio
359
+ const mp4Out = options.outputPath.replace(/\.\w+$/, ".mp4");
360
+ await execSafe("ffmpeg", [
361
+ "-y", "-i", mp4VideoOnly, "-i", options.videoPath,
362
+ "-map", "0:v:0", "-map", "1:a:0?", "-c:v", "copy", "-c:a", "copy", "-shortest", mp4Out,
363
+ ], { timeout: 120_000 });
364
+ await rm(mp4VideoOnly, { force: true }).catch(() => { });
365
+ return { success: true, outputPath: mp4Out };
366
+ }
367
+ catch (error) {
368
+ const msg = error instanceof Error ? error.message : String(error);
369
+ return { success: false, error: `Remotion video embed render failed: ${msg}` };
370
+ }
371
+ finally {
372
+ await rm(dir, { recursive: true, force: true }).catch(() => { });
373
+ }
374
+ }
375
+ /**
376
+ * Generate a Remotion TSX component that renders styled captions.
377
+ * No LLM call — purely programmatic from SRT segments + style config.
378
+ */
379
+ export function generateCaptionComponent(options) {
380
+ const { segments, style, fontSize, fontColor, position, width, videoFileName } = options;
381
+ const name = videoFileName ? "VideoCaptioned" : "CaptionOverlay";
382
+ const segmentsJSON = JSON.stringify(segments.map((s) => ({ start: s.start, end: s.end, text: s.text })));
383
+ const styleMap = {
384
+ bold: `
385
+ fontWeight: "bold" as const,
386
+ color: "${fontColor === "yellow" ? "#FFFF00" : "#FFFFFF"}",
387
+ textShadow: "3px 3px 6px rgba(0,0,0,0.9), -1px -1px 3px rgba(0,0,0,0.7)",
388
+ WebkitTextStroke: "1px rgba(0,0,0,0.5)",
389
+ `,
390
+ minimal: `
391
+ fontWeight: "normal" as const,
392
+ color: "#FFFFFF",
393
+ textShadow: "1px 1px 3px rgba(0,0,0,0.5)",
394
+ `,
395
+ outline: `
396
+ fontWeight: "bold" as const,
397
+ color: "#FFFFFF",
398
+ WebkitTextStroke: "2px #FF0000",
399
+ textShadow: "none",
400
+ `,
401
+ karaoke: `
402
+ fontWeight: "bold" as const,
403
+ color: "#00FFFF",
404
+ textShadow: "2px 2px 4px rgba(0,0,0,0.8), -1px -1px 2px rgba(0,0,0,0.6)",
405
+ `,
406
+ };
407
+ const justifyContent = position === "top" ? "flex-start" : position === "center" ? "center" : "flex-end";
408
+ const paddingDir = position === "top" ? "paddingTop" : position === "bottom" ? "paddingBottom" : "";
409
+ const paddingVal = position === "center" ? "" : `${paddingDir}: 40,`;
410
+ const videoImport = videoFileName ? `, staticFile` : "";
411
+ const videoElement = videoFileName
412
+ ? `<Video src={staticFile("${videoFileName}")} style={{ width: "100%", height: "100%" }} muted />`
413
+ : "";
414
+ const videoMediaImport = videoFileName
415
+ ? `import { Video } from "@remotion/media";\n`
416
+ : "";
417
+ const code = `import { AbsoluteFill, useCurrentFrame, useVideoConfig${videoImport} } from "remotion";
418
+ ${videoMediaImport}
419
+ interface Segment {
420
+ start: number;
421
+ end: number;
422
+ text: string;
423
+ }
424
+
425
+ const segments: Segment[] = ${segmentsJSON};
426
+
427
+ export const ${name} = () => {
428
+ const frame = useCurrentFrame();
429
+ const { fps } = useVideoConfig();
430
+ const currentTime = frame / fps;
431
+
432
+ const activeSegment = segments.find(
433
+ (s) => currentTime >= s.start && currentTime < s.end
434
+ );
435
+
436
+ return (
437
+ <AbsoluteFill>
438
+ ${videoElement}
439
+ {activeSegment && (
440
+ <AbsoluteFill
441
+ style={{
442
+ display: "flex",
443
+ justifyContent: "${justifyContent}",
444
+ alignItems: "center",
445
+ ${paddingVal}
446
+ }}
447
+ >
448
+ <div
449
+ style={{
450
+ fontSize: ${fontSize},
451
+ fontFamily: "Arial, Helvetica, sans-serif",
452
+ textAlign: "center" as const,
453
+ maxWidth: "${Math.round(width * 0.9)}px",
454
+ lineHeight: 1.3,
455
+ padding: "8px 16px",
456
+ ${styleMap[style]}
457
+ }}
458
+ >
459
+ {activeSegment.text}
460
+ </div>
461
+ </AbsoluteFill>
462
+ )}
463
+ </AbsoluteFill>
464
+ );
465
+ };
466
+ `;
467
+ return { code, name };
468
+ }
469
+ /**
470
+ * Generate a Remotion TSX component for word-level animated captions.
471
+ * Each style creates different visual effects per word.
472
+ */
473
+ export function generateAnimatedCaptionComponent(options) {
474
+ const { groups, style, highlightColor, fontSize, position, width, fps, videoFileName } = options;
475
+ const name = videoFileName ? "VideoAnimatedCaption" : "AnimatedCaptionOverlay";
476
+ const groupsJSON = JSON.stringify(groups.map((g) => ({
477
+ words: g.words.map((w) => ({ word: w.word, start: w.start, end: w.end })),
478
+ startTime: g.startTime,
479
+ endTime: g.endTime,
480
+ text: g.text,
481
+ })));
482
+ const justifyContent = position === "top" ? "flex-start" : position === "center" ? "center" : "flex-end";
483
+ const paddingDir = position === "top" ? "paddingTop" : position === "bottom" ? "paddingBottom" : "";
484
+ const paddingVal = position === "center" ? "" : `${paddingDir}: 40,`;
485
+ const videoImport = videoFileName ? `, staticFile` : "";
486
+ const videoElement = videoFileName
487
+ ? `<Video src={staticFile("${videoFileName}")} style={{ width: "100%", height: "100%" }} muted />`
488
+ : "";
489
+ const videoMediaImport = videoFileName
490
+ ? `import { Video } from "@remotion/media";\n`
491
+ : "";
492
+ // Style-specific word rendering
493
+ let wordRenderer;
494
+ switch (style) {
495
+ case "highlight":
496
+ wordRenderer = `
497
+ const isActive = currentTime >= w.start && currentTime < w.end;
498
+ const bgOpacity = isActive ? 1 : 0;
499
+ return (
500
+ <span
501
+ key={wi}
502
+ style={{
503
+ display: "inline-block",
504
+ padding: "2px 6px",
505
+ margin: "0 2px",
506
+ borderRadius: 4,
507
+ backgroundColor: isActive ? "${highlightColor}" : "transparent",
508
+ color: isActive ? "#000000" : "#FFFFFF",
509
+ transition: "background-color 0.1s",
510
+ fontWeight: "bold",
511
+ textShadow: isActive ? "none" : "2px 2px 4px rgba(0,0,0,0.8)",
512
+ }}
513
+ >
514
+ {w.word}
515
+ </span>
516
+ );`;
517
+ break;
518
+ case "bounce":
519
+ wordRenderer = `
520
+ const isActive = currentTime >= w.start && currentTime < w.end;
521
+ const entryFrame = w.start * ${fps};
522
+ const progress = Math.min(1, Math.max(0, (frame - entryFrame) / 5));
523
+ const springVal = isActive
524
+ ? 1 + Math.sin(progress * Math.PI) * 0.15
525
+ : 1;
526
+ const translateY = isActive
527
+ ? -Math.sin(progress * Math.PI) * 8
528
+ : 0;
529
+ return (
530
+ <span
531
+ key={wi}
532
+ style={{
533
+ display: "inline-block",
534
+ margin: "0 3px",
535
+ transform: \`scale(\${springVal}) translateY(\${translateY}px)\`,
536
+ color: isActive ? "${highlightColor}" : "#FFFFFF",
537
+ fontWeight: "bold",
538
+ textShadow: "2px 2px 4px rgba(0,0,0,0.8)",
539
+ }}
540
+ >
541
+ {w.word}
542
+ </span>
543
+ );`;
544
+ break;
545
+ case "pop-in":
546
+ wordRenderer = `
547
+ const entryFrame = w.start * ${fps};
548
+ const scale = frame >= entryFrame
549
+ ? Math.min(1, (frame - entryFrame) / 5)
550
+ : 0;
551
+ const isActive = currentTime >= w.start && currentTime < w.end;
552
+ return (
553
+ <span
554
+ key={wi}
555
+ style={{
556
+ display: "inline-block",
557
+ margin: "0 3px",
558
+ transform: \`scale(\${scale})\`,
559
+ opacity: scale,
560
+ color: isActive ? "${highlightColor}" : "#FFFFFF",
561
+ fontWeight: "bold",
562
+ textShadow: "2px 2px 4px rgba(0,0,0,0.8)",
563
+ }}
564
+ >
565
+ {w.word}
566
+ </span>
567
+ );`;
568
+ break;
569
+ case "neon":
570
+ wordRenderer = `
571
+ const isActive = currentTime >= w.start && currentTime < w.end;
572
+ const pulse = isActive ? 0.8 + Math.sin(frame * 0.3) * 0.2 : 0.5;
573
+ const glowSize = isActive ? 15 : 0;
574
+ return (
575
+ <span
576
+ key={wi}
577
+ style={{
578
+ display: "inline-block",
579
+ margin: "0 3px",
580
+ color: isActive ? "${highlightColor}" : "#FFFFFF",
581
+ fontWeight: "bold",
582
+ opacity: isActive ? 1 : pulse,
583
+ textShadow: isActive
584
+ ? \`0 0 \${glowSize}px ${highlightColor}, 0 0 \${glowSize * 2}px ${highlightColor}, 0 0 \${glowSize * 3}px ${highlightColor}\`
585
+ : "2px 2px 4px rgba(0,0,0,0.8)",
586
+ }}
587
+ >
588
+ {w.word}
589
+ </span>
590
+ );`;
591
+ break;
592
+ }
593
+ const code = `import { AbsoluteFill, useCurrentFrame, useVideoConfig${videoImport} } from "remotion";
594
+ ${videoMediaImport}
595
+ interface Word {
596
+ word: string;
597
+ start: number;
598
+ end: number;
599
+ }
600
+
601
+ interface WordGroup {
602
+ words: Word[];
603
+ startTime: number;
604
+ endTime: number;
605
+ text: string;
606
+ }
607
+
608
+ const groups: WordGroup[] = ${groupsJSON};
609
+
610
+ export const ${name} = () => {
611
+ const frame = useCurrentFrame();
612
+ const { fps } = useVideoConfig();
613
+ const currentTime = frame / fps;
614
+
615
+ const activeGroup = groups.find(
616
+ (g) => currentTime >= g.startTime && currentTime < g.endTime
617
+ );
618
+
619
+ const renderWord = (w: Word, wi: number) => {
620
+ ${wordRenderer}
621
+ };
622
+
623
+ return (
624
+ <AbsoluteFill>
625
+ ${videoElement}
626
+ {activeGroup && (
627
+ <AbsoluteFill
628
+ style={{
629
+ display: "flex",
630
+ justifyContent: "${justifyContent}",
631
+ alignItems: "center",
632
+ ${paddingVal}
633
+ }}
634
+ >
635
+ <div
636
+ style={{
637
+ fontSize: ${fontSize},
638
+ fontFamily: "Arial, Helvetica, sans-serif",
639
+ textAlign: "center" as const,
640
+ maxWidth: "${Math.round(width * 0.9)}px",
641
+ lineHeight: 1.5,
642
+ padding: "8px 16px",
643
+ display: "flex",
644
+ flexWrap: "wrap" as const,
645
+ justifyContent: "center",
646
+ gap: "0px",
647
+ }}
648
+ >
649
+ {activeGroup.words.map((w, wi) => renderWord(w, wi))}
650
+ </div>
651
+ </AbsoluteFill>
652
+ )}
653
+ </AbsoluteFill>
654
+ );
655
+ };
656
+ `;
657
+ return { code, name };
658
+ }
659
+ // ── Legacy composite helpers (kept for backward compat) ───────────────────
660
+ /**
661
+ * Composite a transparent overlay on top of a base video using FFmpeg.
662
+ * @deprecated Use renderWithEmbeddedVideo() for new code.
663
+ */
664
+ export async function compositeOverlay(options) {
665
+ try {
666
+ await execSafe("ffmpeg", [
667
+ "-y", "-i", options.baseVideo, "-i", options.overlayPath,
668
+ "-filter_complex", "[0:v][1:v]overlay=0:0:shortest=1[out]",
669
+ "-map", "[out]", "-map", "0:a?", "-c:a", "copy",
670
+ "-c:v", "libx264", "-crf", "18", "-pix_fmt", "yuv420p",
671
+ options.outputPath,
672
+ ], { timeout: 300_000 });
673
+ return { success: true, outputPath: options.outputPath };
674
+ }
675
+ catch (error) {
676
+ const msg = error instanceof Error ? error.message : String(error);
677
+ return { success: false, error: `FFmpeg composite failed: ${msg}` };
678
+ }
679
+ }
680
+ /**
681
+ * Composite a transparent overlay on top of a static image using FFmpeg.
682
+ * @deprecated Use renderWithEmbeddedImage() for new code.
683
+ */
684
+ export async function compositeWithImage(options) {
685
+ try {
686
+ const fps = options.fps || 30;
687
+ await execSafe("ffmpeg", [
688
+ "-y", "-loop", "1", "-framerate", String(fps), "-i", options.baseImage,
689
+ "-i", options.overlayPath,
690
+ "-filter_complex", "[0:v]scale=iw:ih[base];[base][1:v]overlay=0:0:shortest=1[out]",
691
+ "-map", "[out]",
692
+ "-c:v", "libx264", "-crf", "18", "-pix_fmt", "yuv420p",
693
+ "-t", String(options.durationSeconds),
694
+ options.outputPath,
695
+ ], { timeout: 300_000 });
696
+ return { success: true, outputPath: options.outputPath };
697
+ }
698
+ catch (error) {
699
+ const msg = error instanceof Error ? error.message : String(error);
700
+ return { success: false, error: `FFmpeg image composite failed: ${msg}` };
701
+ }
702
+ }
703
+ /**
704
+ * Full pipeline: render motion graphic → composite onto base video.
705
+ * @deprecated Use renderWithEmbeddedVideo() for new code.
706
+ */
707
+ export async function renderAndComposite(motionOpts, baseVideo, finalOutput) {
708
+ const renderOpts = {
709
+ ...motionOpts,
710
+ transparent: !!baseVideo,
711
+ outputPath: baseVideo
712
+ ? motionOpts.outputPath.replace(/\.\w+$/, "_overlay.webm")
713
+ : motionOpts.outputPath,
714
+ };
715
+ const renderResult = await renderMotion(renderOpts);
716
+ if (!renderResult.success || !renderResult.outputPath) {
717
+ return renderResult;
718
+ }
719
+ if (!baseVideo) {
720
+ return renderResult;
721
+ }
722
+ const output = finalOutput || motionOpts.outputPath;
723
+ const compositeResult = await compositeOverlay({
724
+ baseVideo,
725
+ overlayPath: renderResult.outputPath,
726
+ outputPath: output,
727
+ });
728
+ await rm(renderResult.outputPath, { force: true }).catch(() => { });
729
+ return compositeResult;
730
+ }
731
+ //# sourceMappingURL=remotion.js.map