@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.
- package/.turbo/turbo-build.log +4 -0
- package/.turbo/turbo-lint.log +21 -0
- package/.turbo/turbo-test.log +689 -0
- package/dist/agent/adapters/claude.d.ts +15 -0
- package/dist/agent/adapters/claude.d.ts.map +1 -0
- package/dist/agent/adapters/claude.js +119 -0
- package/dist/agent/adapters/claude.js.map +1 -0
- package/dist/agent/adapters/gemini.d.ts +15 -0
- package/dist/agent/adapters/gemini.d.ts.map +1 -0
- package/dist/agent/adapters/gemini.js +132 -0
- package/dist/agent/adapters/gemini.js.map +1 -0
- package/dist/agent/adapters/index.d.ts +27 -0
- package/dist/agent/adapters/index.d.ts.map +1 -0
- package/dist/agent/adapters/index.js +38 -0
- package/dist/agent/adapters/index.js.map +1 -0
- package/dist/agent/adapters/ollama.d.ts +20 -0
- package/dist/agent/adapters/ollama.d.ts.map +1 -0
- package/dist/agent/adapters/ollama.js +186 -0
- package/dist/agent/adapters/ollama.js.map +1 -0
- package/dist/agent/adapters/openai.d.ts +15 -0
- package/dist/agent/adapters/openai.d.ts.map +1 -0
- package/dist/agent/adapters/openai.js +92 -0
- package/dist/agent/adapters/openai.js.map +1 -0
- package/dist/agent/adapters/xai.d.ts +15 -0
- package/dist/agent/adapters/xai.d.ts.map +1 -0
- package/dist/agent/adapters/xai.js +95 -0
- package/dist/agent/adapters/xai.js.map +1 -0
- package/dist/agent/index.d.ts +69 -0
- package/dist/agent/index.d.ts.map +1 -0
- package/dist/agent/index.js +180 -0
- package/dist/agent/index.js.map +1 -0
- package/dist/agent/memory/index.d.ts +70 -0
- package/dist/agent/memory/index.d.ts.map +1 -0
- package/dist/agent/memory/index.js +132 -0
- package/dist/agent/memory/index.js.map +1 -0
- package/dist/agent/prompts/system.d.ts +6 -0
- package/dist/agent/prompts/system.d.ts.map +1 -0
- package/dist/agent/prompts/system.js +103 -0
- package/dist/agent/prompts/system.js.map +1 -0
- package/dist/agent/tools/ai-editing.d.ts +15 -0
- package/dist/agent/tools/ai-editing.d.ts.map +1 -0
- package/dist/agent/tools/ai-editing.js +763 -0
- package/dist/agent/tools/ai-editing.js.map +1 -0
- package/dist/agent/tools/ai-generation.d.ts +13 -0
- package/dist/agent/tools/ai-generation.d.ts.map +1 -0
- package/dist/agent/tools/ai-generation.js +973 -0
- package/dist/agent/tools/ai-generation.js.map +1 -0
- package/dist/agent/tools/ai-pipeline.d.ts +14 -0
- package/dist/agent/tools/ai-pipeline.d.ts.map +1 -0
- package/dist/agent/tools/ai-pipeline.js +961 -0
- package/dist/agent/tools/ai-pipeline.js.map +1 -0
- package/dist/agent/tools/ai.d.ts +13 -0
- package/dist/agent/tools/ai.d.ts.map +1 -0
- package/dist/agent/tools/ai.js +19 -0
- package/dist/agent/tools/ai.js.map +1 -0
- package/dist/agent/tools/batch.d.ts +6 -0
- package/dist/agent/tools/batch.d.ts.map +1 -0
- package/dist/agent/tools/batch.js +383 -0
- package/dist/agent/tools/batch.js.map +1 -0
- package/dist/agent/tools/e2e.test.d.ts +26 -0
- package/dist/agent/tools/e2e.test.d.ts.map +1 -0
- package/dist/agent/tools/e2e.test.js +397 -0
- package/dist/agent/tools/e2e.test.js.map +1 -0
- package/dist/agent/tools/export.d.ts +6 -0
- package/dist/agent/tools/export.d.ts.map +1 -0
- package/dist/agent/tools/export.js +171 -0
- package/dist/agent/tools/export.js.map +1 -0
- package/dist/agent/tools/filesystem.d.ts +6 -0
- package/dist/agent/tools/filesystem.d.ts.map +1 -0
- package/dist/agent/tools/filesystem.js +212 -0
- package/dist/agent/tools/filesystem.js.map +1 -0
- package/dist/agent/tools/index.d.ts +65 -0
- package/dist/agent/tools/index.d.ts.map +1 -0
- package/dist/agent/tools/index.js +120 -0
- package/dist/agent/tools/index.js.map +1 -0
- package/dist/agent/tools/integration.test.d.ts +11 -0
- package/dist/agent/tools/integration.test.d.ts.map +1 -0
- package/dist/agent/tools/integration.test.js +659 -0
- package/dist/agent/tools/integration.test.js.map +1 -0
- package/dist/agent/tools/media.d.ts +6 -0
- package/dist/agent/tools/media.d.ts.map +1 -0
- package/dist/agent/tools/media.js +616 -0
- package/dist/agent/tools/media.js.map +1 -0
- package/dist/agent/tools/project.d.ts +6 -0
- package/dist/agent/tools/project.d.ts.map +1 -0
- package/dist/agent/tools/project.js +284 -0
- package/dist/agent/tools/project.js.map +1 -0
- package/dist/agent/tools/timeline.d.ts +6 -0
- package/dist/agent/tools/timeline.d.ts.map +1 -0
- package/dist/agent/tools/timeline.js +873 -0
- package/dist/agent/tools/timeline.js.map +1 -0
- package/dist/agent/types.d.ts +59 -0
- package/dist/agent/types.d.ts.map +1 -0
- package/dist/agent/types.js +5 -0
- package/dist/agent/types.js.map +1 -0
- package/dist/commands/agent.d.ts +21 -0
- package/dist/commands/agent.d.ts.map +1 -0
- package/dist/commands/agent.js +290 -0
- package/dist/commands/agent.js.map +1 -0
- package/dist/commands/ai-analyze.d.ts +106 -0
- package/dist/commands/ai-analyze.d.ts.map +1 -0
- package/dist/commands/ai-analyze.js +327 -0
- package/dist/commands/ai-analyze.js.map +1 -0
- package/dist/commands/ai-animated-caption.d.ts +64 -0
- package/dist/commands/ai-animated-caption.d.ts.map +1 -0
- package/dist/commands/ai-animated-caption.js +272 -0
- package/dist/commands/ai-animated-caption.js.map +1 -0
- package/dist/commands/ai-audio.d.ts +20 -0
- package/dist/commands/ai-audio.d.ts.map +1 -0
- package/dist/commands/ai-audio.js +808 -0
- package/dist/commands/ai-audio.js.map +1 -0
- package/dist/commands/ai-broll.d.ts +15 -0
- package/dist/commands/ai-broll.d.ts.map +1 -0
- package/dist/commands/ai-broll.js +406 -0
- package/dist/commands/ai-broll.js.map +1 -0
- package/dist/commands/ai-edit-cli.d.ts +14 -0
- package/dist/commands/ai-edit-cli.d.ts.map +1 -0
- package/dist/commands/ai-edit-cli.js +579 -0
- package/dist/commands/ai-edit-cli.js.map +1 -0
- package/dist/commands/ai-edit.d.ts +398 -0
- package/dist/commands/ai-edit.d.ts.map +1 -0
- package/dist/commands/ai-edit.js +1019 -0
- package/dist/commands/ai-edit.js.map +1 -0
- package/dist/commands/ai-fill-gaps.d.ts +14 -0
- package/dist/commands/ai-fill-gaps.d.ts.map +1 -0
- package/dist/commands/ai-fill-gaps.js +451 -0
- package/dist/commands/ai-fill-gaps.js.map +1 -0
- package/dist/commands/ai-helpers.d.ts +20 -0
- package/dist/commands/ai-helpers.d.ts.map +1 -0
- package/dist/commands/ai-helpers.js +59 -0
- package/dist/commands/ai-helpers.js.map +1 -0
- package/dist/commands/ai-highlights.d.ts +127 -0
- package/dist/commands/ai-highlights.d.ts.map +1 -0
- package/dist/commands/ai-highlights.js +1026 -0
- package/dist/commands/ai-highlights.js.map +1 -0
- package/dist/commands/ai-image.d.ts +34 -0
- package/dist/commands/ai-image.d.ts.map +1 -0
- package/dist/commands/ai-image.js +653 -0
- package/dist/commands/ai-image.js.map +1 -0
- package/dist/commands/ai-motion.d.ts +50 -0
- package/dist/commands/ai-motion.d.ts.map +1 -0
- package/dist/commands/ai-motion.js +271 -0
- package/dist/commands/ai-motion.js.map +1 -0
- package/dist/commands/ai-narrate.d.ts +66 -0
- package/dist/commands/ai-narrate.d.ts.map +1 -0
- package/dist/commands/ai-narrate.js +329 -0
- package/dist/commands/ai-narrate.js.map +1 -0
- package/dist/commands/ai-review.d.ts +57 -0
- package/dist/commands/ai-review.d.ts.map +1 -0
- package/dist/commands/ai-review.js +251 -0
- package/dist/commands/ai-review.js.map +1 -0
- package/dist/commands/ai-script-pipeline-cli.d.ts +9 -0
- package/dist/commands/ai-script-pipeline-cli.d.ts.map +1 -0
- package/dist/commands/ai-script-pipeline-cli.js +1494 -0
- package/dist/commands/ai-script-pipeline-cli.js.map +1 -0
- package/dist/commands/ai-script-pipeline.d.ts +259 -0
- package/dist/commands/ai-script-pipeline.d.ts.map +1 -0
- package/dist/commands/ai-script-pipeline.js +1027 -0
- package/dist/commands/ai-script-pipeline.js.map +1 -0
- package/dist/commands/ai-suggest-edit.d.ts +14 -0
- package/dist/commands/ai-suggest-edit.d.ts.map +1 -0
- package/dist/commands/ai-suggest-edit.js +220 -0
- package/dist/commands/ai-suggest-edit.js.map +1 -0
- package/dist/commands/ai-video-fx.d.ts +14 -0
- package/dist/commands/ai-video-fx.d.ts.map +1 -0
- package/dist/commands/ai-video-fx.js +395 -0
- package/dist/commands/ai-video-fx.js.map +1 -0
- package/dist/commands/ai-video.d.ts +15 -0
- package/dist/commands/ai-video.d.ts.map +1 -0
- package/dist/commands/ai-video.js +785 -0
- package/dist/commands/ai-video.js.map +1 -0
- package/dist/commands/ai-viral.d.ts +15 -0
- package/dist/commands/ai-viral.d.ts.map +1 -0
- package/dist/commands/ai-viral.js +519 -0
- package/dist/commands/ai-viral.js.map +1 -0
- package/dist/commands/ai-visual-fx.d.ts +14 -0
- package/dist/commands/ai-visual-fx.d.ts.map +1 -0
- package/dist/commands/ai-visual-fx.js +505 -0
- package/dist/commands/ai-visual-fx.js.map +1 -0
- package/dist/commands/ai.d.ts +38 -0
- package/dist/commands/ai.d.ts.map +1 -0
- package/dist/commands/ai.js +225 -0
- package/dist/commands/ai.js.map +1 -0
- package/dist/commands/ai.test.d.ts +2 -0
- package/dist/commands/ai.test.d.ts.map +1 -0
- package/dist/commands/ai.test.js +554 -0
- package/dist/commands/ai.test.js.map +1 -0
- package/dist/commands/analyze.d.ts +16 -0
- package/dist/commands/analyze.d.ts.map +1 -0
- package/dist/commands/analyze.js +247 -0
- package/dist/commands/analyze.js.map +1 -0
- package/dist/commands/audio.d.ts +18 -0
- package/dist/commands/audio.d.ts.map +1 -0
- package/dist/commands/audio.js +539 -0
- package/dist/commands/audio.js.map +1 -0
- package/dist/commands/batch.d.ts +3 -0
- package/dist/commands/batch.d.ts.map +1 -0
- package/dist/commands/batch.js +366 -0
- package/dist/commands/batch.js.map +1 -0
- package/dist/commands/batch.test.d.ts +2 -0
- package/dist/commands/batch.test.d.ts.map +1 -0
- package/dist/commands/batch.test.js +203 -0
- package/dist/commands/batch.test.js.map +1 -0
- package/dist/commands/detect.d.ts +3 -0
- package/dist/commands/detect.d.ts.map +1 -0
- package/dist/commands/detect.js +273 -0
- package/dist/commands/detect.js.map +1 -0
- package/dist/commands/doctor.d.ts +6 -0
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/doctor.js +191 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/edit-cmd.d.ts +26 -0
- package/dist/commands/edit-cmd.d.ts.map +1 -0
- package/dist/commands/edit-cmd.js +870 -0
- package/dist/commands/edit-cmd.js.map +1 -0
- package/dist/commands/export.d.ts +39 -0
- package/dist/commands/export.d.ts.map +1 -0
- package/dist/commands/export.js +730 -0
- package/dist/commands/export.js.map +1 -0
- package/dist/commands/generate.d.ts +25 -0
- package/dist/commands/generate.d.ts.map +1 -0
- package/dist/commands/generate.js +1885 -0
- package/dist/commands/generate.js.map +1 -0
- package/dist/commands/media.d.ts +3 -0
- package/dist/commands/media.d.ts.map +1 -0
- package/dist/commands/media.js +165 -0
- package/dist/commands/media.js.map +1 -0
- package/dist/commands/output.d.ts +45 -0
- package/dist/commands/output.d.ts.map +1 -0
- package/dist/commands/output.js +122 -0
- package/dist/commands/output.js.map +1 -0
- package/dist/commands/pipeline.d.ts +19 -0
- package/dist/commands/pipeline.d.ts.map +1 -0
- package/dist/commands/pipeline.js +345 -0
- package/dist/commands/pipeline.js.map +1 -0
- package/dist/commands/project.d.ts +3 -0
- package/dist/commands/project.d.ts.map +1 -0
- package/dist/commands/project.js +139 -0
- package/dist/commands/project.js.map +1 -0
- package/dist/commands/project.test.d.ts +2 -0
- package/dist/commands/project.test.d.ts.map +1 -0
- package/dist/commands/project.test.js +105 -0
- package/dist/commands/project.test.js.map +1 -0
- package/dist/commands/sanitize.d.ts +21 -0
- package/dist/commands/sanitize.d.ts.map +1 -0
- package/dist/commands/sanitize.js +56 -0
- package/dist/commands/sanitize.js.map +1 -0
- package/dist/commands/schema.d.ts +11 -0
- package/dist/commands/schema.d.ts.map +1 -0
- package/dist/commands/schema.js +101 -0
- package/dist/commands/schema.js.map +1 -0
- package/dist/commands/setup.d.ts +6 -0
- package/dist/commands/setup.d.ts.map +1 -0
- package/dist/commands/setup.js +440 -0
- package/dist/commands/setup.js.map +1 -0
- package/dist/commands/timeline.d.ts +3 -0
- package/dist/commands/timeline.d.ts.map +1 -0
- package/dist/commands/timeline.js +469 -0
- package/dist/commands/timeline.js.map +1 -0
- package/dist/commands/timeline.test.d.ts +2 -0
- package/dist/commands/timeline.test.d.ts.map +1 -0
- package/dist/commands/timeline.test.js +320 -0
- package/dist/commands/timeline.test.js.map +1 -0
- package/dist/commands/validate.d.ts +32 -0
- package/dist/commands/validate.d.ts.map +1 -0
- package/dist/commands/validate.js +63 -0
- package/dist/commands/validate.js.map +1 -0
- package/dist/config/config.test.d.ts +2 -0
- package/dist/config/config.test.d.ts.map +1 -0
- package/dist/config/config.test.js +164 -0
- package/dist/config/config.test.js.map +1 -0
- package/dist/config/index.d.ts +35 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +101 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/schema.d.ts +43 -0
- package/dist/config/schema.d.ts.map +1 -0
- package/dist/config/schema.js +42 -0
- package/dist/config/schema.js.map +1 -0
- package/dist/engine/index.d.ts +3 -0
- package/dist/engine/index.d.ts.map +1 -0
- package/dist/engine/index.js +2 -0
- package/dist/engine/index.js.map +1 -0
- package/dist/engine/project.d.ts +84 -0
- package/dist/engine/project.d.ts.map +1 -0
- package/dist/engine/project.js +355 -0
- package/dist/engine/project.js.map +1 -0
- package/dist/engine/project.test.d.ts +2 -0
- package/dist/engine/project.test.d.ts.map +1 -0
- package/dist/engine/project.test.js +599 -0
- package/dist/engine/project.test.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +131 -0
- package/dist/index.js.map +1 -0
- package/dist/utils/api-key.d.ts +36 -0
- package/dist/utils/api-key.d.ts.map +1 -0
- package/dist/utils/api-key.js +211 -0
- package/dist/utils/api-key.js.map +1 -0
- package/dist/utils/api-key.test.d.ts +2 -0
- package/dist/utils/api-key.test.d.ts.map +1 -0
- package/dist/utils/api-key.test.js +35 -0
- package/dist/utils/api-key.test.js.map +1 -0
- package/dist/utils/audio.d.ts +23 -0
- package/dist/utils/audio.d.ts.map +1 -0
- package/dist/utils/audio.js +79 -0
- package/dist/utils/audio.js.map +1 -0
- package/dist/utils/exec-safe.d.ts +22 -0
- package/dist/utils/exec-safe.d.ts.map +1 -0
- package/dist/utils/exec-safe.js +62 -0
- package/dist/utils/exec-safe.js.map +1 -0
- package/dist/utils/first-run.d.ts +13 -0
- package/dist/utils/first-run.d.ts.map +1 -0
- package/dist/utils/first-run.js +48 -0
- package/dist/utils/first-run.js.map +1 -0
- package/dist/utils/provider-resolver.d.ts +15 -0
- package/dist/utils/provider-resolver.d.ts.map +1 -0
- package/dist/utils/provider-resolver.js +42 -0
- package/dist/utils/provider-resolver.js.map +1 -0
- package/dist/utils/remotion.d.ts +210 -0
- package/dist/utils/remotion.d.ts.map +1 -0
- package/dist/utils/remotion.js +731 -0
- package/dist/utils/remotion.js.map +1 -0
- package/dist/utils/subtitle.d.ts +65 -0
- package/dist/utils/subtitle.d.ts.map +1 -0
- package/dist/utils/subtitle.js +135 -0
- package/dist/utils/subtitle.js.map +1 -0
- package/dist/utils/subtitle.test.d.ts +2 -0
- package/dist/utils/subtitle.test.d.ts.map +1 -0
- package/dist/utils/subtitle.test.js +175 -0
- package/dist/utils/subtitle.test.js.map +1 -0
- package/dist/utils/tty.d.ts +45 -0
- package/dist/utils/tty.d.ts.map +1 -0
- package/dist/utils/tty.js +172 -0
- package/dist/utils/tty.js.map +1 -0
- package/package.json +102 -0
- package/src/agent/adapters/claude.ts +143 -0
- package/src/agent/adapters/gemini.ts +159 -0
- package/src/agent/adapters/index.ts +61 -0
- package/src/agent/adapters/ollama.ts +231 -0
- package/src/agent/adapters/openai.ts +116 -0
- package/src/agent/adapters/xai.ts +119 -0
- package/src/agent/index.ts +251 -0
- package/src/agent/memory/index.ts +151 -0
- package/src/agent/prompts/system.ts +106 -0
- package/src/agent/tools/ai-editing.ts +845 -0
- package/src/agent/tools/ai-generation.ts +1073 -0
- package/src/agent/tools/ai-pipeline.ts +1055 -0
- package/src/agent/tools/ai.ts +21 -0
- package/src/agent/tools/batch.ts +429 -0
- package/src/agent/tools/e2e.test.ts +545 -0
- package/src/agent/tools/export.ts +184 -0
- package/src/agent/tools/filesystem.ts +237 -0
- package/src/agent/tools/index.ts +150 -0
- package/src/agent/tools/integration.test.ts +775 -0
- package/src/agent/tools/media.ts +697 -0
- package/src/agent/tools/project.ts +313 -0
- package/src/agent/tools/timeline.ts +951 -0
- package/src/agent/types.ts +68 -0
- package/src/commands/agent.ts +340 -0
- package/src/commands/ai-analyze.ts +429 -0
- package/src/commands/ai-animated-caption.ts +390 -0
- package/src/commands/ai-audio.ts +941 -0
- package/src/commands/ai-broll.ts +490 -0
- package/src/commands/ai-edit-cli.ts +658 -0
- package/src/commands/ai-edit.ts +1542 -0
- package/src/commands/ai-fill-gaps.ts +566 -0
- package/src/commands/ai-helpers.ts +65 -0
- package/src/commands/ai-highlights.ts +1303 -0
- package/src/commands/ai-image.ts +761 -0
- package/src/commands/ai-motion.ts +347 -0
- package/src/commands/ai-narrate.ts +451 -0
- package/src/commands/ai-review.ts +309 -0
- package/src/commands/ai-script-pipeline-cli.ts +1710 -0
- package/src/commands/ai-script-pipeline.ts +1365 -0
- package/src/commands/ai-suggest-edit.ts +264 -0
- package/src/commands/ai-video-fx.ts +445 -0
- package/src/commands/ai-video.ts +915 -0
- package/src/commands/ai-viral.ts +595 -0
- package/src/commands/ai-visual-fx.ts +601 -0
- package/src/commands/ai.test.ts +627 -0
- package/src/commands/ai.ts +307 -0
- package/src/commands/analyze.ts +282 -0
- package/src/commands/audio.ts +644 -0
- package/src/commands/batch.test.ts +279 -0
- package/src/commands/batch.ts +440 -0
- package/src/commands/detect.ts +329 -0
- package/src/commands/doctor.ts +237 -0
- package/src/commands/edit-cmd.ts +1014 -0
- package/src/commands/export.ts +918 -0
- package/src/commands/generate.ts +2146 -0
- package/src/commands/media.ts +177 -0
- package/src/commands/output.ts +142 -0
- package/src/commands/pipeline.ts +398 -0
- package/src/commands/project.test.ts +127 -0
- package/src/commands/project.ts +149 -0
- package/src/commands/sanitize.ts +60 -0
- package/src/commands/schema.ts +130 -0
- package/src/commands/setup.ts +509 -0
- package/src/commands/timeline.test.ts +499 -0
- package/src/commands/timeline.ts +529 -0
- package/src/commands/validate.ts +77 -0
- package/src/config/config.test.ts +197 -0
- package/src/config/index.ts +125 -0
- package/src/config/schema.ts +82 -0
- package/src/engine/index.ts +2 -0
- package/src/engine/project.test.ts +702 -0
- package/src/engine/project.ts +439 -0
- package/src/index.ts +146 -0
- package/src/utils/api-key.test.ts +41 -0
- package/src/utils/api-key.ts +247 -0
- package/src/utils/audio.ts +83 -0
- package/src/utils/exec-safe.ts +75 -0
- package/src/utils/first-run.ts +52 -0
- package/src/utils/provider-resolver.ts +56 -0
- package/src/utils/remotion.ts +951 -0
- package/src/utils/subtitle.test.ts +227 -0
- package/src/utils/subtitle.ts +169 -0
- package/src/utils/tty.ts +196 -0
- package/tsconfig.json +20 -0
|
@@ -0,0 +1,595 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module ai-viral
|
|
3
|
+
* @description AI Viral Optimizer. Optimizes video for viral potential across
|
|
4
|
+
* platforms (YouTube, TikTok, Instagram, Twitter). Analyzes content with
|
|
5
|
+
* Whisper + Claude, generates platform-specific cuts and captions.
|
|
6
|
+
*
|
|
7
|
+
* ## Commands: vibe ai viral
|
|
8
|
+
* ## Dependencies: Whisper, Claude, FFmpeg
|
|
9
|
+
* @see MODELS.md for AI model configuration
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { Command } from "commander";
|
|
13
|
+
import { readFile, writeFile, mkdir } from "node:fs/promises";
|
|
14
|
+
import { resolve, dirname, basename, relative } from "node:path";
|
|
15
|
+
import { existsSync } from "node:fs";
|
|
16
|
+
import chalk from "chalk";
|
|
17
|
+
import ora from "ora";
|
|
18
|
+
import {
|
|
19
|
+
WhisperProvider,
|
|
20
|
+
ClaudeProvider,
|
|
21
|
+
type PlatformSpec,
|
|
22
|
+
type ViralOptimizationResult,
|
|
23
|
+
} from "@vibeframe/ai-providers";
|
|
24
|
+
import { Project, type ProjectFile } from "../engine/index.js";
|
|
25
|
+
import { getApiKey } from "../utils/api-key.js";
|
|
26
|
+
import { execSafe, commandExists } from "../utils/exec-safe.js";
|
|
27
|
+
import { formatTime } from "./ai-helpers.js";
|
|
28
|
+
import { autoNarrate } from "./ai-narrate.js";
|
|
29
|
+
|
|
30
|
+
// Platform specifications for viral optimization
|
|
31
|
+
export const PLATFORM_SPECS: Record<string, PlatformSpec> = {
|
|
32
|
+
youtube: {
|
|
33
|
+
id: "youtube",
|
|
34
|
+
name: "YouTube",
|
|
35
|
+
aspectRatio: "16:9",
|
|
36
|
+
maxDuration: 600,
|
|
37
|
+
idealDuration: { min: 60, max: 480 },
|
|
38
|
+
features: { captions: true, hook: true },
|
|
39
|
+
},
|
|
40
|
+
"youtube-shorts": {
|
|
41
|
+
id: "youtube-shorts",
|
|
42
|
+
name: "YouTube Shorts",
|
|
43
|
+
aspectRatio: "9:16",
|
|
44
|
+
maxDuration: 60,
|
|
45
|
+
idealDuration: { min: 15, max: 60 },
|
|
46
|
+
features: { captions: true, hook: true },
|
|
47
|
+
},
|
|
48
|
+
tiktok: {
|
|
49
|
+
id: "tiktok",
|
|
50
|
+
name: "TikTok",
|
|
51
|
+
aspectRatio: "9:16",
|
|
52
|
+
maxDuration: 180,
|
|
53
|
+
idealDuration: { min: 15, max: 60 },
|
|
54
|
+
features: { captions: true, hook: true },
|
|
55
|
+
},
|
|
56
|
+
"instagram-reels": {
|
|
57
|
+
id: "instagram-reels",
|
|
58
|
+
name: "Instagram Reels",
|
|
59
|
+
aspectRatio: "9:16",
|
|
60
|
+
maxDuration: 90,
|
|
61
|
+
idealDuration: { min: 15, max: 60 },
|
|
62
|
+
features: { captions: true, hook: true },
|
|
63
|
+
},
|
|
64
|
+
"instagram-feed": {
|
|
65
|
+
id: "instagram-feed",
|
|
66
|
+
name: "Instagram Feed",
|
|
67
|
+
aspectRatio: "1:1",
|
|
68
|
+
maxDuration: 60,
|
|
69
|
+
idealDuration: { min: 15, max: 60 },
|
|
70
|
+
features: { captions: true, hook: false },
|
|
71
|
+
},
|
|
72
|
+
twitter: {
|
|
73
|
+
id: "twitter",
|
|
74
|
+
name: "Twitter",
|
|
75
|
+
aspectRatio: "16:9",
|
|
76
|
+
maxDuration: 140,
|
|
77
|
+
idealDuration: { min: 15, max: 60 },
|
|
78
|
+
features: { captions: true, hook: true },
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
export function registerViralCommand(ai: Command): void {
|
|
83
|
+
// Viral Optimizer command
|
|
84
|
+
ai
|
|
85
|
+
.command("viral")
|
|
86
|
+
.description("Optimize video for viral potential across platforms (deprecated)")
|
|
87
|
+
.argument("<project>", "Source project file")
|
|
88
|
+
.option("--platforms <list>", "Target platforms (comma-separated): youtube, youtube-shorts, tiktok, instagram-reels, instagram-feed, twitter", "all")
|
|
89
|
+
.option("-o, --output-dir <dir>", "Output directory for platform variants", "viral-output")
|
|
90
|
+
.option("--analyze-only", "Only analyze, don't generate variants")
|
|
91
|
+
.option("--skip-captions", "Skip caption generation")
|
|
92
|
+
.option("--caption-style <style>", "Caption style: minimal, bold, animated", "bold")
|
|
93
|
+
.option("--hook-duration <sec>", "Hook duration in seconds", "3")
|
|
94
|
+
.option("-l, --language <lang>", "Language code for transcription")
|
|
95
|
+
.option("--auto-narrate", "Auto-generate narration if no audio source found")
|
|
96
|
+
.option("--narrate-voice <voice>", "Voice for auto-narration (default: rachel)", "rachel")
|
|
97
|
+
.option("--narrate-style <style>", "Style for auto-narration: informative, energetic, calm, dramatic", "informative")
|
|
98
|
+
.action(async (projectPath: string, options) => {
|
|
99
|
+
console.warn(chalk.yellow("Warning: 'pipeline viral' is deprecated. Use individual commands instead:"));
|
|
100
|
+
console.warn(chalk.dim(" vibe edit reframe <video> -a 9:16 → vibe edit caption <video> -s bold"));
|
|
101
|
+
console.warn();
|
|
102
|
+
try {
|
|
103
|
+
// Validate API keys
|
|
104
|
+
const openaiApiKey = await getApiKey("OPENAI_API_KEY", "OpenAI");
|
|
105
|
+
if (!openaiApiKey) {
|
|
106
|
+
console.error(chalk.red("OpenAI API key required for Whisper transcription. Set OPENAI_API_KEY in .env or run: vibe setup"));
|
|
107
|
+
console.error(chalk.dim("Set OPENAI_API_KEY environment variable"));
|
|
108
|
+
process.exit(1);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const claudeApiKey = await getApiKey("ANTHROPIC_API_KEY", "Anthropic");
|
|
112
|
+
if (!claudeApiKey) {
|
|
113
|
+
console.error(chalk.red("Anthropic API key required for viral analysis. Set ANTHROPIC_API_KEY in .env or run: vibe setup"));
|
|
114
|
+
console.error(chalk.dim("Set ANTHROPIC_API_KEY environment variable"));
|
|
115
|
+
process.exit(1);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Load project
|
|
119
|
+
let filePath = resolve(process.cwd(), projectPath);
|
|
120
|
+
// If directory, look for project.vibe.json inside
|
|
121
|
+
const { statSync } = await import("node:fs");
|
|
122
|
+
try {
|
|
123
|
+
if (statSync(filePath).isDirectory()) {
|
|
124
|
+
const candidates = ["project.vibe.json", ".vibe.json"];
|
|
125
|
+
let found = false;
|
|
126
|
+
for (const candidate of candidates) {
|
|
127
|
+
const candidatePath = resolve(filePath, candidate);
|
|
128
|
+
if (existsSync(candidatePath)) {
|
|
129
|
+
filePath = candidatePath;
|
|
130
|
+
found = true;
|
|
131
|
+
break;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
if (!found) {
|
|
135
|
+
// Try any .vibe.json file in the directory
|
|
136
|
+
const { readdirSync } = await import("node:fs");
|
|
137
|
+
const files = readdirSync(filePath).filter((f: string) => f.endsWith(".vibe.json"));
|
|
138
|
+
if (files.length > 0) {
|
|
139
|
+
filePath = resolve(filePath, files[0]);
|
|
140
|
+
} else {
|
|
141
|
+
console.error(chalk.red(`No .vibe.json project file found in: ${filePath}`));
|
|
142
|
+
process.exit(1);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
} catch { /* not a directory, treat as file */ }
|
|
147
|
+
|
|
148
|
+
if (!existsSync(filePath)) {
|
|
149
|
+
console.error(chalk.red(`Project file not found: ${filePath}`));
|
|
150
|
+
process.exit(1);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const content = await readFile(filePath, "utf-8");
|
|
154
|
+
const data: ProjectFile = JSON.parse(content);
|
|
155
|
+
const project = Project.fromJSON(data);
|
|
156
|
+
|
|
157
|
+
// Parse target platforms
|
|
158
|
+
let targetPlatforms: string[];
|
|
159
|
+
if (options.platforms === "all") {
|
|
160
|
+
targetPlatforms = Object.keys(PLATFORM_SPECS);
|
|
161
|
+
} else {
|
|
162
|
+
targetPlatforms = options.platforms.split(",").map((p: string) => p.trim().toLowerCase());
|
|
163
|
+
// Validate platforms
|
|
164
|
+
for (const platform of targetPlatforms) {
|
|
165
|
+
if (!PLATFORM_SPECS[platform]) {
|
|
166
|
+
console.error(chalk.red(`Unknown platform: ${platform}`));
|
|
167
|
+
console.error(chalk.dim(`Available: ${Object.keys(PLATFORM_SPECS).join(", ")}`));
|
|
168
|
+
process.exit(1);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
console.log();
|
|
174
|
+
console.log(chalk.bold.cyan("🚀 Viral Optimizer Pipeline"));
|
|
175
|
+
console.log(chalk.dim("─".repeat(60)));
|
|
176
|
+
console.log();
|
|
177
|
+
|
|
178
|
+
// Get project info
|
|
179
|
+
const clips = project.getClips();
|
|
180
|
+
const sources = project.getSources();
|
|
181
|
+
|
|
182
|
+
// Calculate total duration from clips
|
|
183
|
+
let totalDuration = 0;
|
|
184
|
+
for (const clip of clips) {
|
|
185
|
+
const endTime = clip.startTime + clip.duration;
|
|
186
|
+
if (endTime > totalDuration) {
|
|
187
|
+
totalDuration = endTime;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const projectInfo = `${project.getMeta().name} (${formatTime(totalDuration)}, ${clips.length} clips)`;
|
|
192
|
+
console.log(`✔ Loaded project: ${chalk.bold(projectInfo)}`);
|
|
193
|
+
|
|
194
|
+
// Step 1: Extract audio and transcribe
|
|
195
|
+
// Find audio source first (narration), fall back to video
|
|
196
|
+
let audioSource = sources.find((s) => s.type === "audio");
|
|
197
|
+
const videoSource = sources.find((s) => s.type === "video");
|
|
198
|
+
|
|
199
|
+
// Check if auto-narrate is needed
|
|
200
|
+
if (!audioSource && videoSource && options.autoNarrate) {
|
|
201
|
+
console.log();
|
|
202
|
+
console.log(chalk.yellow("📝 No narration found, generating with AI..."));
|
|
203
|
+
|
|
204
|
+
const outputDir = resolve(process.cwd(), options.outputDir);
|
|
205
|
+
const videoPath = resolve(dirname(filePath), videoSource.url);
|
|
206
|
+
|
|
207
|
+
const narrateResult = await autoNarrate({
|
|
208
|
+
videoPath,
|
|
209
|
+
duration: totalDuration,
|
|
210
|
+
outputDir,
|
|
211
|
+
voice: options.narrateVoice,
|
|
212
|
+
style: options.narrateStyle as "informative" | "energetic" | "calm" | "dramatic",
|
|
213
|
+
language: options.language || "en",
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
if (!narrateResult.success) {
|
|
217
|
+
console.error(chalk.red(`Auto-narrate failed: ${narrateResult.error}`));
|
|
218
|
+
process.exit(1);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
console.log(chalk.green(`✔ Generated narration: ${narrateResult.audioPath}`));
|
|
222
|
+
|
|
223
|
+
// Add the generated narration as a source
|
|
224
|
+
// Use relative path from project directory to audio file
|
|
225
|
+
const projectDir = dirname(filePath);
|
|
226
|
+
const relativeAudioPath = relative(projectDir, narrateResult.audioPath!);
|
|
227
|
+
const newAudioSource = project.addSource({
|
|
228
|
+
name: "Auto-generated narration",
|
|
229
|
+
url: relativeAudioPath,
|
|
230
|
+
type: "audio",
|
|
231
|
+
duration: totalDuration,
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
// Add audio clip to timeline
|
|
235
|
+
const audioTrack = project.getTracks().find((t) => t.type === "audio");
|
|
236
|
+
if (audioTrack) {
|
|
237
|
+
project.addClip({
|
|
238
|
+
sourceId: newAudioSource.id,
|
|
239
|
+
trackId: audioTrack.id,
|
|
240
|
+
startTime: 0,
|
|
241
|
+
duration: totalDuration,
|
|
242
|
+
sourceStartOffset: 0,
|
|
243
|
+
sourceEndOffset: totalDuration,
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Save updated project
|
|
248
|
+
await writeFile(filePath, JSON.stringify(project.toJSON(), null, 2), "utf-8");
|
|
249
|
+
|
|
250
|
+
// Use the generated segments as transcript
|
|
251
|
+
if (narrateResult.segments && narrateResult.segments.length > 0) {
|
|
252
|
+
// Continue with viral analysis using auto-narrate segments
|
|
253
|
+
audioSource = newAudioSource;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const mediaSource = audioSource || videoSource;
|
|
258
|
+
if (!mediaSource) {
|
|
259
|
+
console.error(chalk.red("No video or audio source found in project"));
|
|
260
|
+
process.exit(1);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Check FFmpeg availability
|
|
264
|
+
if (!commandExists("ffmpeg")) {
|
|
265
|
+
console.error(chalk.red("FFmpeg not found. Please install FFmpeg."));
|
|
266
|
+
process.exit(1);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const transcribeSpinner = ora("📝 Transcribing content with Whisper...").start();
|
|
270
|
+
|
|
271
|
+
let audioPath = resolve(dirname(filePath), mediaSource.url);
|
|
272
|
+
let tempAudioPath: string | null = null;
|
|
273
|
+
|
|
274
|
+
// Extract audio if video
|
|
275
|
+
if (mediaSource.type === "video") {
|
|
276
|
+
transcribeSpinner.text = "🎵 Extracting audio from video...";
|
|
277
|
+
tempAudioPath = `/tmp/vibe_viral_audio_${Date.now()}.wav`;
|
|
278
|
+
await execSafe("ffmpeg", ["-i", audioPath, "-vn", "-acodec", "pcm_s16le", "-ar", "16000", "-ac", "1", tempAudioPath, "-y"], { maxBuffer: 50 * 1024 * 1024 });
|
|
279
|
+
audioPath = tempAudioPath;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Transcribe with Whisper
|
|
283
|
+
const whisper = new WhisperProvider();
|
|
284
|
+
await whisper.initialize({ apiKey: openaiApiKey });
|
|
285
|
+
|
|
286
|
+
const audioBuffer = await readFile(audioPath);
|
|
287
|
+
const audioBlob = new Blob([audioBuffer]);
|
|
288
|
+
|
|
289
|
+
transcribeSpinner.text = "📝 Transcribing with Whisper...";
|
|
290
|
+
const transcriptResult = await whisper.transcribe(audioBlob, options.language);
|
|
291
|
+
|
|
292
|
+
// Cleanup temp file
|
|
293
|
+
if (tempAudioPath && existsSync(tempAudioPath)) {
|
|
294
|
+
const { unlink } = await import("node:fs/promises");
|
|
295
|
+
await unlink(tempAudioPath).catch(() => {});
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
if (transcriptResult.status === "failed" || !transcriptResult.segments) {
|
|
299
|
+
transcribeSpinner.fail(chalk.red(`Transcription failed: ${transcriptResult.error}`));
|
|
300
|
+
process.exit(1);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
transcribeSpinner.succeed(chalk.green(`Transcribed ${transcriptResult.segments.length} segments`));
|
|
304
|
+
|
|
305
|
+
// Step 2: Analyze viral potential with Claude
|
|
306
|
+
const analyzeSpinner = ora("📊 Analyzing viral potential...").start();
|
|
307
|
+
|
|
308
|
+
const claude = new ClaudeProvider();
|
|
309
|
+
await claude.initialize({ apiKey: claudeApiKey });
|
|
310
|
+
|
|
311
|
+
const viralAnalysis = await claude.analyzeViralPotential(
|
|
312
|
+
transcriptResult.segments,
|
|
313
|
+
{ duration: totalDuration, clipCount: clips.length },
|
|
314
|
+
targetPlatforms
|
|
315
|
+
);
|
|
316
|
+
|
|
317
|
+
analyzeSpinner.succeed(chalk.green("Analysis complete"));
|
|
318
|
+
|
|
319
|
+
// Display analysis summary
|
|
320
|
+
console.log();
|
|
321
|
+
console.log(chalk.bold.cyan("Viral Potential Summary"));
|
|
322
|
+
console.log(chalk.dim("─".repeat(60)));
|
|
323
|
+
console.log(` Overall Score: ${chalk.bold(viralAnalysis.overallScore + "%")}`);
|
|
324
|
+
console.log(` Hook Strength: ${chalk.bold(viralAnalysis.hookStrength + "%")}`);
|
|
325
|
+
console.log(` Pacing: ${chalk.bold(viralAnalysis.pacing)}`);
|
|
326
|
+
console.log();
|
|
327
|
+
|
|
328
|
+
// Platform suitability bars
|
|
329
|
+
console.log(" Platform Suitability:");
|
|
330
|
+
for (const platform of targetPlatforms) {
|
|
331
|
+
const platformData = viralAnalysis.platforms[platform];
|
|
332
|
+
if (platformData) {
|
|
333
|
+
const score = Math.round(platformData.suitability * 100);
|
|
334
|
+
const filledBars = Math.round(score / 10);
|
|
335
|
+
const emptyBars = 10 - filledBars;
|
|
336
|
+
const bar = "█".repeat(filledBars) + "░".repeat(emptyBars);
|
|
337
|
+
const platformName = PLATFORM_SPECS[platform].name.padEnd(16);
|
|
338
|
+
console.log(` ${platformName} ${bar} ${score}%`);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
console.log();
|
|
342
|
+
|
|
343
|
+
// Emotional peaks
|
|
344
|
+
if (viralAnalysis.emotionalPeaks.length > 0) {
|
|
345
|
+
console.log(" Emotional Peaks:");
|
|
346
|
+
for (const peak of viralAnalysis.emotionalPeaks.slice(0, 5)) {
|
|
347
|
+
console.log(` ${formatTime(peak.time)} - ${peak.emotion} (${(peak.intensity * 100).toFixed(0)}%)`);
|
|
348
|
+
}
|
|
349
|
+
console.log();
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// Hook recommendation
|
|
353
|
+
if (viralAnalysis.hookRecommendation.suggestedStartTime > 0) {
|
|
354
|
+
console.log(` ${chalk.yellow("💡 Hook Tip:")} Consider starting at ${formatTime(viralAnalysis.hookRecommendation.suggestedStartTime)}`);
|
|
355
|
+
console.log(` ${chalk.dim(viralAnalysis.hookRecommendation.reason)}`);
|
|
356
|
+
console.log();
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// If analyze-only, stop here
|
|
360
|
+
if (options.analyzeOnly) {
|
|
361
|
+
// Save analysis JSON
|
|
362
|
+
const outputDir = resolve(process.cwd(), options.outputDir);
|
|
363
|
+
if (!existsSync(outputDir)) {
|
|
364
|
+
await mkdir(outputDir, { recursive: true });
|
|
365
|
+
}
|
|
366
|
+
const analysisPath = resolve(outputDir, "analysis.json");
|
|
367
|
+
await writeFile(analysisPath, JSON.stringify(viralAnalysis, null, 2), "utf-8");
|
|
368
|
+
|
|
369
|
+
console.log(chalk.green(`💾 Analysis saved to: ${analysisPath}`));
|
|
370
|
+
console.log();
|
|
371
|
+
console.log(chalk.bold.green("✅ Analysis complete!"));
|
|
372
|
+
console.log();
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// Step 3: Generate platform variants
|
|
377
|
+
console.log(chalk.bold.cyan("🎬 Generating platform variants..."));
|
|
378
|
+
|
|
379
|
+
const outputDir = resolve(process.cwd(), options.outputDir);
|
|
380
|
+
if (!existsSync(outputDir)) {
|
|
381
|
+
await mkdir(outputDir, { recursive: true });
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
const generatedProjects: Array<{ platform: string; path: string; duration: number; aspectRatio: string }> = [];
|
|
385
|
+
|
|
386
|
+
for (const platformId of targetPlatforms) {
|
|
387
|
+
const platform = PLATFORM_SPECS[platformId];
|
|
388
|
+
const variantSpinner = ora(` Generating ${platform.name}...`).start();
|
|
389
|
+
|
|
390
|
+
try {
|
|
391
|
+
// Get platform-specific cuts from Claude
|
|
392
|
+
const clipsInfo = clips.map((c) => ({
|
|
393
|
+
id: c.id,
|
|
394
|
+
startTime: c.startTime,
|
|
395
|
+
duration: c.duration,
|
|
396
|
+
}));
|
|
397
|
+
|
|
398
|
+
const platformCut = await claude.suggestPlatformCuts(
|
|
399
|
+
transcriptResult.segments,
|
|
400
|
+
viralAnalysis,
|
|
401
|
+
platform,
|
|
402
|
+
clipsInfo
|
|
403
|
+
);
|
|
404
|
+
|
|
405
|
+
// Create platform-specific project
|
|
406
|
+
const platformProject = new Project(`${project.getMeta().name} - ${platform.name}`);
|
|
407
|
+
platformProject.setAspectRatio(platform.aspectRatio as "16:9" | "9:16" | "1:1");
|
|
408
|
+
|
|
409
|
+
// Copy sources
|
|
410
|
+
const sourceMap = new Map<string, string>();
|
|
411
|
+
for (const source of sources) {
|
|
412
|
+
const newSource = platformProject.addSource({
|
|
413
|
+
name: source.name,
|
|
414
|
+
url: source.url,
|
|
415
|
+
type: source.type,
|
|
416
|
+
duration: source.duration,
|
|
417
|
+
});
|
|
418
|
+
sourceMap.set(source.id, newSource.id);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// Get video track
|
|
422
|
+
const videoTrack = platformProject.getTracks().find((t) => t.type === "video");
|
|
423
|
+
if (!videoTrack) {
|
|
424
|
+
variantSpinner.fail(chalk.red(`Failed to create ${platform.name} variant`));
|
|
425
|
+
continue;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// Add clips based on platform cuts
|
|
429
|
+
let currentTime = 0;
|
|
430
|
+
let platformDuration = 0;
|
|
431
|
+
let audioStartOffset = 0; // Track where in original timeline the cut starts
|
|
432
|
+
|
|
433
|
+
if (platformCut.segments.length > 0) {
|
|
434
|
+
// Use AI-suggested segments
|
|
435
|
+
// Determine audio start offset from first segment's original timeline position
|
|
436
|
+
const firstSegment = platformCut.segments[0];
|
|
437
|
+
const firstOriginalClip = clips.find((c) => c.id === firstSegment.sourceClipId);
|
|
438
|
+
if (firstOriginalClip) {
|
|
439
|
+
// Calculate timeline position: clip start + offset within source
|
|
440
|
+
audioStartOffset = firstOriginalClip.startTime + (firstSegment.startTime - firstOriginalClip.sourceStartOffset);
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
for (const segment of platformCut.segments) {
|
|
444
|
+
// Find the original clip
|
|
445
|
+
const originalClip = clips.find((c) => c.id === segment.sourceClipId);
|
|
446
|
+
if (!originalClip) continue;
|
|
447
|
+
|
|
448
|
+
const sourceId = sourceMap.get(originalClip.sourceId);
|
|
449
|
+
if (!sourceId) continue;
|
|
450
|
+
|
|
451
|
+
const segmentDuration = segment.endTime - segment.startTime;
|
|
452
|
+
platformProject.addClip({
|
|
453
|
+
sourceId,
|
|
454
|
+
trackId: videoTrack.id,
|
|
455
|
+
startTime: currentTime,
|
|
456
|
+
duration: segmentDuration,
|
|
457
|
+
sourceStartOffset: segment.startTime,
|
|
458
|
+
sourceEndOffset: segment.endTime,
|
|
459
|
+
});
|
|
460
|
+
currentTime += segmentDuration;
|
|
461
|
+
platformDuration += segmentDuration;
|
|
462
|
+
}
|
|
463
|
+
} else {
|
|
464
|
+
// Fallback: use original clips, trimmed to fit duration
|
|
465
|
+
// Audio starts from first clip's timeline position
|
|
466
|
+
if (clips.length > 0) {
|
|
467
|
+
audioStartOffset = clips[0].startTime;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
for (const clip of clips) {
|
|
471
|
+
const sourceId = sourceMap.get(clip.sourceId);
|
|
472
|
+
if (!sourceId) continue;
|
|
473
|
+
|
|
474
|
+
if (currentTime + clip.duration <= platform.maxDuration) {
|
|
475
|
+
platformProject.addClip({
|
|
476
|
+
sourceId,
|
|
477
|
+
trackId: videoTrack.id,
|
|
478
|
+
startTime: currentTime,
|
|
479
|
+
duration: clip.duration,
|
|
480
|
+
sourceStartOffset: clip.sourceStartOffset,
|
|
481
|
+
sourceEndOffset: clip.sourceEndOffset,
|
|
482
|
+
});
|
|
483
|
+
currentTime += clip.duration;
|
|
484
|
+
platformDuration += clip.duration;
|
|
485
|
+
} else {
|
|
486
|
+
// Trim the last clip to fit
|
|
487
|
+
const remainingDuration = platform.maxDuration - currentTime;
|
|
488
|
+
if (remainingDuration > 0) {
|
|
489
|
+
platformProject.addClip({
|
|
490
|
+
sourceId,
|
|
491
|
+
trackId: videoTrack.id,
|
|
492
|
+
startTime: currentTime,
|
|
493
|
+
duration: remainingDuration,
|
|
494
|
+
sourceStartOffset: clip.sourceStartOffset,
|
|
495
|
+
sourceEndOffset: clip.sourceStartOffset + remainingDuration,
|
|
496
|
+
});
|
|
497
|
+
platformDuration += remainingDuration;
|
|
498
|
+
}
|
|
499
|
+
break;
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
// Add audio clip if original project has audio
|
|
505
|
+
const originalAudioSource = sources.find((s) => s.type === "audio");
|
|
506
|
+
const audioTrack = platformProject.getTracks().find((t) => t.type === "audio");
|
|
507
|
+
if (originalAudioSource && audioTrack && platformDuration > 0) {
|
|
508
|
+
const audioSourceId = sourceMap.get(originalAudioSource.id);
|
|
509
|
+
if (audioSourceId) {
|
|
510
|
+
// Add audio clip synced with the video cut
|
|
511
|
+
platformProject.addClip({
|
|
512
|
+
sourceId: audioSourceId,
|
|
513
|
+
trackId: audioTrack.id,
|
|
514
|
+
startTime: 0,
|
|
515
|
+
duration: platformDuration,
|
|
516
|
+
sourceStartOffset: audioStartOffset,
|
|
517
|
+
sourceEndOffset: audioStartOffset + platformDuration,
|
|
518
|
+
});
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
// Generate captions if not skipped
|
|
523
|
+
if (!options.skipCaptions) {
|
|
524
|
+
const captionStyle = options.captionStyle as "minimal" | "bold" | "animated";
|
|
525
|
+
const captions = await claude.generateViralCaptions(
|
|
526
|
+
transcriptResult.segments.filter(
|
|
527
|
+
(s) => s.endTime <= platformDuration
|
|
528
|
+
),
|
|
529
|
+
captionStyle
|
|
530
|
+
);
|
|
531
|
+
|
|
532
|
+
// Store captions as project metadata (for future caption track support)
|
|
533
|
+
// For now, save as separate file
|
|
534
|
+
if (captions.length > 0) {
|
|
535
|
+
const captionsPath = resolve(outputDir, `${platformId}-captions.json`);
|
|
536
|
+
await writeFile(captionsPath, JSON.stringify(captions, null, 2), "utf-8");
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
// Save platform project
|
|
541
|
+
const projectPath = resolve(outputDir, `${platformId}.vibe.json`);
|
|
542
|
+
await writeFile(projectPath, JSON.stringify(platformProject.toJSON(), null, 2), "utf-8");
|
|
543
|
+
|
|
544
|
+
generatedProjects.push({
|
|
545
|
+
platform: platform.name,
|
|
546
|
+
path: projectPath,
|
|
547
|
+
duration: platformDuration,
|
|
548
|
+
aspectRatio: platform.aspectRatio,
|
|
549
|
+
});
|
|
550
|
+
|
|
551
|
+
variantSpinner.succeed(chalk.green(` ✔ ${platformId}.vibe.json (${formatTime(platformDuration)}, ${platform.aspectRatio})`));
|
|
552
|
+
} catch (error) {
|
|
553
|
+
variantSpinner.fail(chalk.red(` ✘ Failed to generate ${platform.name}: ${error}`));
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
// Save analysis JSON
|
|
558
|
+
const analysisPath = resolve(outputDir, "analysis.json");
|
|
559
|
+
const result: ViralOptimizationResult = {
|
|
560
|
+
sourceProject: filePath,
|
|
561
|
+
analysis: viralAnalysis,
|
|
562
|
+
platformCuts: [],
|
|
563
|
+
platformProjects: generatedProjects.map((p) => ({
|
|
564
|
+
platform: p.platform,
|
|
565
|
+
projectPath: p.path,
|
|
566
|
+
duration: p.duration,
|
|
567
|
+
aspectRatio: p.aspectRatio,
|
|
568
|
+
})),
|
|
569
|
+
};
|
|
570
|
+
await writeFile(analysisPath, JSON.stringify(result, null, 2), "utf-8");
|
|
571
|
+
|
|
572
|
+
// Final summary
|
|
573
|
+
console.log();
|
|
574
|
+
console.log(chalk.dim("─".repeat(60)));
|
|
575
|
+
console.log(chalk.bold.green(`✅ Viral optimization complete!`));
|
|
576
|
+
console.log(` ${chalk.bold(generatedProjects.length)} platform variants generated`);
|
|
577
|
+
console.log();
|
|
578
|
+
console.log(`💾 Saved to: ${chalk.cyan(outputDir)}/`);
|
|
579
|
+
console.log();
|
|
580
|
+
console.log(chalk.dim("Next steps:"));
|
|
581
|
+
for (const proj of generatedProjects.slice(0, 3)) {
|
|
582
|
+
const filename = basename(proj.path);
|
|
583
|
+
console.log(chalk.dim(` vibe export ${options.outputDir}/${filename} -o ${proj.platform.toLowerCase().replace(/\s+/g, "-")}.mp4`));
|
|
584
|
+
}
|
|
585
|
+
if (generatedProjects.length > 3) {
|
|
586
|
+
console.log(chalk.dim(` ... and ${generatedProjects.length - 3} more`));
|
|
587
|
+
}
|
|
588
|
+
console.log();
|
|
589
|
+
} catch (error) {
|
|
590
|
+
console.error(chalk.red("Viral optimization failed"));
|
|
591
|
+
console.error(error);
|
|
592
|
+
process.exit(1);
|
|
593
|
+
}
|
|
594
|
+
});
|
|
595
|
+
}
|