@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,697 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Media Tools - Analyze media files (scenes, silence, beats, transcription)
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { readFile, writeFile } from "node:fs/promises";
|
|
6
|
+
import { resolve } from "node:path";
|
|
7
|
+
import type { ToolRegistry, ToolHandler } from "./index.js";
|
|
8
|
+
import type { ToolDefinition, ToolResult } from "../types.js";
|
|
9
|
+
import { getApiKeyFromConfig } from "../../config/index.js";
|
|
10
|
+
import { execSafe, ffprobeDuration } from "../../utils/exec-safe.js";
|
|
11
|
+
|
|
12
|
+
// Tool Definitions
|
|
13
|
+
const mediaInfoDef: ToolDefinition = {
|
|
14
|
+
name: "media_info",
|
|
15
|
+
description: "Get information about a media file (duration, resolution, codec, etc.)",
|
|
16
|
+
parameters: {
|
|
17
|
+
type: "object",
|
|
18
|
+
properties: {
|
|
19
|
+
path: {
|
|
20
|
+
type: "string",
|
|
21
|
+
description: "Media file path",
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
required: ["path"],
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const detectScenesDef: ToolDefinition = {
|
|
29
|
+
name: "detect_scenes",
|
|
30
|
+
description: "Detect scene changes in a video file",
|
|
31
|
+
parameters: {
|
|
32
|
+
type: "object",
|
|
33
|
+
properties: {
|
|
34
|
+
video: {
|
|
35
|
+
type: "string",
|
|
36
|
+
description: "Video file path",
|
|
37
|
+
},
|
|
38
|
+
threshold: {
|
|
39
|
+
type: "number",
|
|
40
|
+
description: "Scene change threshold (0-1, default: 0.3)",
|
|
41
|
+
},
|
|
42
|
+
output: {
|
|
43
|
+
type: "string",
|
|
44
|
+
description: "Output JSON file path for timestamps",
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
required: ["video"],
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const detectSilenceDef: ToolDefinition = {
|
|
52
|
+
name: "detect_silence",
|
|
53
|
+
description: "Detect silence periods in audio/video",
|
|
54
|
+
parameters: {
|
|
55
|
+
type: "object",
|
|
56
|
+
properties: {
|
|
57
|
+
media: {
|
|
58
|
+
type: "string",
|
|
59
|
+
description: "Media file path",
|
|
60
|
+
},
|
|
61
|
+
noise: {
|
|
62
|
+
type: "string",
|
|
63
|
+
description: "Noise threshold in dB (default: -30)",
|
|
64
|
+
},
|
|
65
|
+
duration: {
|
|
66
|
+
type: "number",
|
|
67
|
+
description: "Minimum silence duration in seconds (default: 0.5)",
|
|
68
|
+
},
|
|
69
|
+
output: {
|
|
70
|
+
type: "string",
|
|
71
|
+
description: "Output JSON file path",
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
required: ["media"],
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const detectBeatsDef: ToolDefinition = {
|
|
79
|
+
name: "detect_beats",
|
|
80
|
+
description: "Detect beats in audio for music sync",
|
|
81
|
+
parameters: {
|
|
82
|
+
type: "object",
|
|
83
|
+
properties: {
|
|
84
|
+
audio: {
|
|
85
|
+
type: "string",
|
|
86
|
+
description: "Audio file path",
|
|
87
|
+
},
|
|
88
|
+
output: {
|
|
89
|
+
type: "string",
|
|
90
|
+
description: "Output JSON file path",
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
required: ["audio"],
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const transcribeDef: ToolDefinition = {
|
|
98
|
+
name: "audio_transcribe",
|
|
99
|
+
description: "Transcribe audio using Whisper AI",
|
|
100
|
+
parameters: {
|
|
101
|
+
type: "object",
|
|
102
|
+
properties: {
|
|
103
|
+
audio: {
|
|
104
|
+
type: "string",
|
|
105
|
+
description: "Audio file path",
|
|
106
|
+
},
|
|
107
|
+
language: {
|
|
108
|
+
type: "string",
|
|
109
|
+
description: "Language code (e.g., en, ko)",
|
|
110
|
+
},
|
|
111
|
+
output: {
|
|
112
|
+
type: "string",
|
|
113
|
+
description: "Output file path (supports .json, .srt, .vtt)",
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
required: ["audio"],
|
|
117
|
+
},
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
// Helper to format timestamp
|
|
121
|
+
function formatTimestamp(seconds: number): string {
|
|
122
|
+
const mins = Math.floor(seconds / 60);
|
|
123
|
+
const secs = (seconds % 60).toFixed(2);
|
|
124
|
+
return `${mins.toString().padStart(2, "0")}:${secs.padStart(5, "0")}`;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Tool Handlers
|
|
128
|
+
const mediaInfo: ToolHandler = async (args, context): Promise<ToolResult> => {
|
|
129
|
+
const mediaPath = args.path as string;
|
|
130
|
+
|
|
131
|
+
try {
|
|
132
|
+
const absPath = resolve(context.workingDirectory, mediaPath);
|
|
133
|
+
|
|
134
|
+
// Get detailed info using ffprobe
|
|
135
|
+
const { stdout } = await execSafe("ffprobe", ["-v", "quiet", "-print_format", "json", "-show_format", "-show_streams", absPath], { maxBuffer: 10 * 1024 * 1024 });
|
|
136
|
+
|
|
137
|
+
const info = JSON.parse(stdout);
|
|
138
|
+
const format = info.format || {};
|
|
139
|
+
const streams = info.streams || [];
|
|
140
|
+
|
|
141
|
+
const output: string[] = [`File: ${mediaPath}`];
|
|
142
|
+
|
|
143
|
+
// General info
|
|
144
|
+
if (format.duration) {
|
|
145
|
+
output.push(`Duration: ${parseFloat(format.duration).toFixed(2)}s`);
|
|
146
|
+
}
|
|
147
|
+
if (format.size) {
|
|
148
|
+
const sizeMB = (parseInt(format.size) / (1024 * 1024)).toFixed(2);
|
|
149
|
+
output.push(`Size: ${sizeMB} MB`);
|
|
150
|
+
}
|
|
151
|
+
if (format.bit_rate) {
|
|
152
|
+
const bitrateMbps = (parseInt(format.bit_rate) / 1000000).toFixed(2);
|
|
153
|
+
output.push(`Bitrate: ${bitrateMbps} Mbps`);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Stream info
|
|
157
|
+
for (const stream of streams) {
|
|
158
|
+
if (stream.codec_type === "video") {
|
|
159
|
+
output.push(`\nVideo:`);
|
|
160
|
+
output.push(` Resolution: ${stream.width}x${stream.height}`);
|
|
161
|
+
output.push(` Codec: ${stream.codec_name}`);
|
|
162
|
+
if (stream.r_frame_rate) {
|
|
163
|
+
const [num, den] = stream.r_frame_rate.split("/");
|
|
164
|
+
const fps = (parseInt(num) / parseInt(den)).toFixed(2);
|
|
165
|
+
output.push(` Frame Rate: ${fps} fps`);
|
|
166
|
+
}
|
|
167
|
+
} else if (stream.codec_type === "audio") {
|
|
168
|
+
output.push(`\nAudio:`);
|
|
169
|
+
output.push(` Codec: ${stream.codec_name}`);
|
|
170
|
+
output.push(` Sample Rate: ${stream.sample_rate} Hz`);
|
|
171
|
+
output.push(` Channels: ${stream.channels}`);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return {
|
|
176
|
+
toolCallId: "",
|
|
177
|
+
success: true,
|
|
178
|
+
output: output.join("\n"),
|
|
179
|
+
};
|
|
180
|
+
} catch (error) {
|
|
181
|
+
return {
|
|
182
|
+
toolCallId: "",
|
|
183
|
+
success: false,
|
|
184
|
+
output: "",
|
|
185
|
+
error: `Failed to get media info: ${error instanceof Error ? error.message : String(error)}`,
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
const detectScenes: ToolHandler = async (args, context): Promise<ToolResult> => {
|
|
191
|
+
const videoPath = args.video as string;
|
|
192
|
+
const threshold = (args.threshold as number) || 0.3;
|
|
193
|
+
|
|
194
|
+
try {
|
|
195
|
+
const absPath = resolve(context.workingDirectory, videoPath);
|
|
196
|
+
|
|
197
|
+
// Detect scenes using FFmpeg
|
|
198
|
+
const { stdout, stderr } = await execSafe("ffmpeg", ["-i", absPath, "-filter:v", `select='gt(scene,${threshold})',showinfo`, "-f", "null", "-"], { maxBuffer: 50 * 1024 * 1024 });
|
|
199
|
+
const output = stdout + stderr;
|
|
200
|
+
|
|
201
|
+
// Parse scene timestamps
|
|
202
|
+
const scenes: { timestamp: number; score: number }[] = [];
|
|
203
|
+
const regex = /pts_time:(\d+\.?\d*)/g;
|
|
204
|
+
let match;
|
|
205
|
+
|
|
206
|
+
scenes.push({ timestamp: 0, score: 1 });
|
|
207
|
+
while ((match = regex.exec(output)) !== null) {
|
|
208
|
+
scenes.push({ timestamp: parseFloat(match[1]), score: threshold });
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Get duration
|
|
212
|
+
const totalDuration = await ffprobeDuration(absPath);
|
|
213
|
+
|
|
214
|
+
// Format output
|
|
215
|
+
const sceneList = scenes.map((s, i) => {
|
|
216
|
+
const end = i < scenes.length - 1 ? scenes[i + 1].timestamp : totalDuration;
|
|
217
|
+
return `[${i + 1}] ${formatTimestamp(s.timestamp)} - ${formatTimestamp(end)} (${(end - s.timestamp).toFixed(1)}s)`;
|
|
218
|
+
}).join("\n");
|
|
219
|
+
|
|
220
|
+
// Save to file if requested
|
|
221
|
+
if (args.output) {
|
|
222
|
+
const outputPath = resolve(context.workingDirectory, args.output as string);
|
|
223
|
+
const result = {
|
|
224
|
+
source: absPath,
|
|
225
|
+
totalDuration,
|
|
226
|
+
threshold,
|
|
227
|
+
scenes: scenes.map((s, i) => ({
|
|
228
|
+
index: i,
|
|
229
|
+
startTime: s.timestamp,
|
|
230
|
+
endTime: i < scenes.length - 1 ? scenes[i + 1].timestamp : totalDuration,
|
|
231
|
+
duration: (i < scenes.length - 1 ? scenes[i + 1].timestamp : totalDuration) - s.timestamp,
|
|
232
|
+
})),
|
|
233
|
+
};
|
|
234
|
+
await writeFile(outputPath, JSON.stringify(result, null, 2), "utf-8");
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return {
|
|
238
|
+
toolCallId: "",
|
|
239
|
+
success: true,
|
|
240
|
+
output: `Detected ${scenes.length} scenes:\n${sceneList}`,
|
|
241
|
+
};
|
|
242
|
+
} catch (error) {
|
|
243
|
+
return {
|
|
244
|
+
toolCallId: "",
|
|
245
|
+
success: false,
|
|
246
|
+
output: "",
|
|
247
|
+
error: `Failed to detect scenes: ${error instanceof Error ? error.message : String(error)}`,
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
const detectSilence: ToolHandler = async (args, context): Promise<ToolResult> => {
|
|
253
|
+
const mediaPath = args.media as string;
|
|
254
|
+
const noise = (args.noise as string) || "-30";
|
|
255
|
+
const duration = (args.duration as number) || 0.5;
|
|
256
|
+
|
|
257
|
+
try {
|
|
258
|
+
const absPath = resolve(context.workingDirectory, mediaPath);
|
|
259
|
+
|
|
260
|
+
const { stdout, stderr } = await execSafe("ffmpeg", ["-i", absPath, "-af", `silencedetect=noise=${noise}dB:d=${duration}`, "-f", "null", "-"], { maxBuffer: 50 * 1024 * 1024 });
|
|
261
|
+
const output = stdout + stderr;
|
|
262
|
+
|
|
263
|
+
// Parse silence periods
|
|
264
|
+
const silences: { start: number; end: number; duration: number }[] = [];
|
|
265
|
+
const startRegex = /silence_start: (\d+\.?\d*)/g;
|
|
266
|
+
const endRegex = /silence_end: (\d+\.?\d*) \| silence_duration: (\d+\.?\d*)/g;
|
|
267
|
+
|
|
268
|
+
const starts: number[] = [];
|
|
269
|
+
let match;
|
|
270
|
+
|
|
271
|
+
while ((match = startRegex.exec(output)) !== null) {
|
|
272
|
+
starts.push(parseFloat(match[1]));
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
let i = 0;
|
|
276
|
+
while ((match = endRegex.exec(output)) !== null) {
|
|
277
|
+
if (i < starts.length) {
|
|
278
|
+
silences.push({
|
|
279
|
+
start: starts[i],
|
|
280
|
+
end: parseFloat(match[1]),
|
|
281
|
+
duration: parseFloat(match[2]),
|
|
282
|
+
});
|
|
283
|
+
i++;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Format output
|
|
288
|
+
const silenceList = silences.map((s, idx) =>
|
|
289
|
+
`[${idx + 1}] ${formatTimestamp(s.start)} - ${formatTimestamp(s.end)} (${s.duration.toFixed(1)}s)`
|
|
290
|
+
).join("\n");
|
|
291
|
+
|
|
292
|
+
// Save to file if requested
|
|
293
|
+
if (args.output) {
|
|
294
|
+
const outputPath = resolve(context.workingDirectory, args.output as string);
|
|
295
|
+
await writeFile(
|
|
296
|
+
outputPath,
|
|
297
|
+
JSON.stringify({ source: absPath, silences }, null, 2),
|
|
298
|
+
"utf-8"
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
return {
|
|
303
|
+
toolCallId: "",
|
|
304
|
+
success: true,
|
|
305
|
+
output: silences.length > 0
|
|
306
|
+
? `Detected ${silences.length} silence periods:\n${silenceList}`
|
|
307
|
+
: "No silence periods detected.",
|
|
308
|
+
};
|
|
309
|
+
} catch (error) {
|
|
310
|
+
return {
|
|
311
|
+
toolCallId: "",
|
|
312
|
+
success: false,
|
|
313
|
+
output: "",
|
|
314
|
+
error: `Failed to detect silence: ${error instanceof Error ? error.message : String(error)}`,
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
};
|
|
318
|
+
|
|
319
|
+
const detectBeats: ToolHandler = async (args, context): Promise<ToolResult> => {
|
|
320
|
+
const audioPath = args.audio as string;
|
|
321
|
+
|
|
322
|
+
try {
|
|
323
|
+
const absPath = resolve(context.workingDirectory, audioPath);
|
|
324
|
+
|
|
325
|
+
// Get duration
|
|
326
|
+
const totalDuration = await ffprobeDuration(absPath);
|
|
327
|
+
|
|
328
|
+
// Use interval-based detection (120 BPM default)
|
|
329
|
+
const estimatedBPM = 120;
|
|
330
|
+
const beatInterval = 60 / estimatedBPM;
|
|
331
|
+
const beats: number[] = [];
|
|
332
|
+
|
|
333
|
+
for (let t = 0; t < totalDuration; t += beatInterval) {
|
|
334
|
+
beats.push(t);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// Show first 20 beats
|
|
338
|
+
const beatList = beats.slice(0, 20).map((t, i) =>
|
|
339
|
+
`[${i + 1}] ${formatTimestamp(t)}`
|
|
340
|
+
).join("\n");
|
|
341
|
+
|
|
342
|
+
// Save to file if requested
|
|
343
|
+
if (args.output) {
|
|
344
|
+
const outputPath = resolve(context.workingDirectory, args.output as string);
|
|
345
|
+
await writeFile(
|
|
346
|
+
outputPath,
|
|
347
|
+
JSON.stringify({ source: absPath, beatCount: beats.length, beats }, null, 2),
|
|
348
|
+
"utf-8"
|
|
349
|
+
);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
return {
|
|
353
|
+
toolCallId: "",
|
|
354
|
+
success: true,
|
|
355
|
+
output: `Detected ${beats.length} beats (${estimatedBPM} BPM):\n${beatList}${beats.length > 20 ? `\n... and ${beats.length - 20} more` : ""}`,
|
|
356
|
+
};
|
|
357
|
+
} catch (error) {
|
|
358
|
+
return {
|
|
359
|
+
toolCallId: "",
|
|
360
|
+
success: false,
|
|
361
|
+
output: "",
|
|
362
|
+
error: `Failed to detect beats: ${error instanceof Error ? error.message : String(error)}`,
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
};
|
|
366
|
+
|
|
367
|
+
const transcribe: ToolHandler = async (args, context): Promise<ToolResult> => {
|
|
368
|
+
const audioPath = args.audio as string;
|
|
369
|
+
const language = args.language as string | undefined;
|
|
370
|
+
|
|
371
|
+
try {
|
|
372
|
+
const apiKey = await getApiKeyFromConfig("openai");
|
|
373
|
+
if (!apiKey) {
|
|
374
|
+
return {
|
|
375
|
+
toolCallId: "",
|
|
376
|
+
success: false,
|
|
377
|
+
output: "",
|
|
378
|
+
error: "OpenAI API key required for transcription. Set OPENAI_API_KEY or configure via 'vibe setup'.",
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
const absPath = resolve(context.workingDirectory, audioPath);
|
|
383
|
+
const audioBuffer = await readFile(absPath);
|
|
384
|
+
|
|
385
|
+
// Dynamic import of WhisperProvider
|
|
386
|
+
const { WhisperProvider } = await import("@vibeframe/ai-providers");
|
|
387
|
+
const whisper = new WhisperProvider();
|
|
388
|
+
await whisper.initialize({ apiKey });
|
|
389
|
+
|
|
390
|
+
const audioBlob = new Blob([audioBuffer]);
|
|
391
|
+
const result = await whisper.transcribe(audioBlob, language);
|
|
392
|
+
|
|
393
|
+
if (result.status === "failed") {
|
|
394
|
+
return {
|
|
395
|
+
toolCallId: "",
|
|
396
|
+
success: false,
|
|
397
|
+
output: "",
|
|
398
|
+
error: `Transcription failed: ${result.error}`,
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// Format output
|
|
403
|
+
let output = `Transcript:\n${result.fullText}`;
|
|
404
|
+
|
|
405
|
+
if (result.segments && result.segments.length > 0) {
|
|
406
|
+
output += "\n\nSegments:";
|
|
407
|
+
for (const seg of result.segments.slice(0, 10)) {
|
|
408
|
+
output += `\n[${formatTimestamp(seg.startTime)} - ${formatTimestamp(seg.endTime)}] ${seg.text}`;
|
|
409
|
+
}
|
|
410
|
+
if (result.segments.length > 10) {
|
|
411
|
+
output += `\n... and ${result.segments.length - 10} more segments`;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// Save to file if requested
|
|
416
|
+
if (args.output) {
|
|
417
|
+
const outputPath = resolve(context.workingDirectory, args.output as string);
|
|
418
|
+
const { detectFormat, formatTranscript } = await import("../../utils/subtitle.js");
|
|
419
|
+
const format = detectFormat(args.output as string, undefined);
|
|
420
|
+
const content = formatTranscript(result, format);
|
|
421
|
+
await writeFile(outputPath, content, "utf-8");
|
|
422
|
+
output += `\n\nSaved to: ${args.output}`;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
return {
|
|
426
|
+
toolCallId: "",
|
|
427
|
+
success: true,
|
|
428
|
+
output,
|
|
429
|
+
};
|
|
430
|
+
} catch (error) {
|
|
431
|
+
return {
|
|
432
|
+
toolCallId: "",
|
|
433
|
+
success: false,
|
|
434
|
+
output: "",
|
|
435
|
+
error: `Failed to transcribe: ${error instanceof Error ? error.message : String(error)}`,
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
};
|
|
439
|
+
|
|
440
|
+
// Media Manipulation Tools
|
|
441
|
+
|
|
442
|
+
const compressDef: ToolDefinition = {
|
|
443
|
+
name: "media_compress",
|
|
444
|
+
description: "Compress a video or audio file using FFmpeg",
|
|
445
|
+
parameters: {
|
|
446
|
+
type: "object",
|
|
447
|
+
properties: {
|
|
448
|
+
input: {
|
|
449
|
+
type: "string",
|
|
450
|
+
description: "Input media file path",
|
|
451
|
+
},
|
|
452
|
+
output: {
|
|
453
|
+
type: "string",
|
|
454
|
+
description: "Output file path (default: input-compressed.ext)",
|
|
455
|
+
},
|
|
456
|
+
quality: {
|
|
457
|
+
type: "string",
|
|
458
|
+
description: "Quality preset: low, medium (default), high",
|
|
459
|
+
enum: ["low", "medium", "high"],
|
|
460
|
+
},
|
|
461
|
+
maxSize: {
|
|
462
|
+
type: "string",
|
|
463
|
+
description: "Target max file size (e.g., '10M', '100M')",
|
|
464
|
+
},
|
|
465
|
+
},
|
|
466
|
+
required: ["input"],
|
|
467
|
+
},
|
|
468
|
+
};
|
|
469
|
+
|
|
470
|
+
const convertDef: ToolDefinition = {
|
|
471
|
+
name: "media_convert",
|
|
472
|
+
description: "Convert media file to a different format using FFmpeg",
|
|
473
|
+
parameters: {
|
|
474
|
+
type: "object",
|
|
475
|
+
properties: {
|
|
476
|
+
input: {
|
|
477
|
+
type: "string",
|
|
478
|
+
description: "Input media file path",
|
|
479
|
+
},
|
|
480
|
+
output: {
|
|
481
|
+
type: "string",
|
|
482
|
+
description: "Output file path with desired extension (e.g., 'video.webm')",
|
|
483
|
+
},
|
|
484
|
+
codec: {
|
|
485
|
+
type: "string",
|
|
486
|
+
description: "Video codec (h264, h265, vp9, av1)",
|
|
487
|
+
},
|
|
488
|
+
audioCodec: {
|
|
489
|
+
type: "string",
|
|
490
|
+
description: "Audio codec (aac, mp3, opus)",
|
|
491
|
+
},
|
|
492
|
+
},
|
|
493
|
+
required: ["input", "output"],
|
|
494
|
+
},
|
|
495
|
+
};
|
|
496
|
+
|
|
497
|
+
const concatDef: ToolDefinition = {
|
|
498
|
+
name: "media_concat",
|
|
499
|
+
description: "Concatenate multiple media files into one using FFmpeg",
|
|
500
|
+
parameters: {
|
|
501
|
+
type: "object",
|
|
502
|
+
properties: {
|
|
503
|
+
inputs: {
|
|
504
|
+
type: "array",
|
|
505
|
+
items: { type: "string", description: "Input file path" },
|
|
506
|
+
description: "Array of input file paths to concatenate",
|
|
507
|
+
},
|
|
508
|
+
output: {
|
|
509
|
+
type: "string",
|
|
510
|
+
description: "Output file path",
|
|
511
|
+
},
|
|
512
|
+
reencode: {
|
|
513
|
+
type: "boolean",
|
|
514
|
+
description: "Re-encode files (slower but works with different codecs)",
|
|
515
|
+
},
|
|
516
|
+
},
|
|
517
|
+
required: ["inputs", "output"],
|
|
518
|
+
},
|
|
519
|
+
};
|
|
520
|
+
|
|
521
|
+
const compress: ToolHandler = async (args, context): Promise<ToolResult> => {
|
|
522
|
+
const inputPath = args.input as string;
|
|
523
|
+
const quality = (args.quality as string) || "medium";
|
|
524
|
+
// maxSize is accepted as a parameter but not yet implemented
|
|
525
|
+
// const maxSize = args.maxSize as string | undefined;
|
|
526
|
+
|
|
527
|
+
try {
|
|
528
|
+
const absInput = resolve(context.workingDirectory, inputPath);
|
|
529
|
+
const ext = inputPath.split(".").pop() || "mp4";
|
|
530
|
+
const baseName = inputPath.replace(/\.[^/.]+$/, "");
|
|
531
|
+
const outputPath = args.output
|
|
532
|
+
? resolve(context.workingDirectory, args.output as string)
|
|
533
|
+
: resolve(context.workingDirectory, `${baseName}-compressed.${ext}`);
|
|
534
|
+
|
|
535
|
+
// Quality presets (CRF values - lower = better quality, larger file)
|
|
536
|
+
const crfValues: Record<string, number> = {
|
|
537
|
+
low: 28,
|
|
538
|
+
medium: 23,
|
|
539
|
+
high: 18,
|
|
540
|
+
};
|
|
541
|
+
const crf = crfValues[quality] || 23;
|
|
542
|
+
|
|
543
|
+
await execSafe("ffmpeg", ["-i", absInput, "-c:v", "libx264", "-crf", String(crf), "-preset", "medium", "-c:a", "aac", "-b:a", "128k", outputPath, "-y"], { maxBuffer: 50 * 1024 * 1024 });
|
|
544
|
+
|
|
545
|
+
// Get file sizes for comparison
|
|
546
|
+
const inputStats = await readFile(absInput);
|
|
547
|
+
const outputStats = await readFile(outputPath);
|
|
548
|
+
const inputSize = inputStats.length;
|
|
549
|
+
const outputSize = outputStats.length;
|
|
550
|
+
const reduction = (((inputSize - outputSize) / inputSize) * 100).toFixed(1);
|
|
551
|
+
|
|
552
|
+
return {
|
|
553
|
+
toolCallId: "",
|
|
554
|
+
success: true,
|
|
555
|
+
output: `Compressed: ${inputPath} → ${outputPath}\nSize: ${formatSize(inputSize)} → ${formatSize(outputSize)} (${reduction}% reduction)`,
|
|
556
|
+
};
|
|
557
|
+
} catch (error) {
|
|
558
|
+
return {
|
|
559
|
+
toolCallId: "",
|
|
560
|
+
success: false,
|
|
561
|
+
output: "",
|
|
562
|
+
error: `Failed to compress: ${error instanceof Error ? error.message : String(error)}`,
|
|
563
|
+
};
|
|
564
|
+
}
|
|
565
|
+
};
|
|
566
|
+
|
|
567
|
+
const convert: ToolHandler = async (args, context): Promise<ToolResult> => {
|
|
568
|
+
const inputPath = args.input as string;
|
|
569
|
+
const outputPath = args.output as string;
|
|
570
|
+
const codec = args.codec as string | undefined;
|
|
571
|
+
const audioCodec = args.audioCodec as string | undefined;
|
|
572
|
+
|
|
573
|
+
try {
|
|
574
|
+
const absInput = resolve(context.workingDirectory, inputPath);
|
|
575
|
+
const absOutput = resolve(context.workingDirectory, outputPath);
|
|
576
|
+
|
|
577
|
+
// Build codec options
|
|
578
|
+
let videoCodecName = "copy";
|
|
579
|
+
let audioCodecName = "copy";
|
|
580
|
+
|
|
581
|
+
if (codec) {
|
|
582
|
+
const codecMap: Record<string, string> = {
|
|
583
|
+
h264: "libx264",
|
|
584
|
+
h265: "libx265",
|
|
585
|
+
vp9: "libvpx-vp9",
|
|
586
|
+
av1: "libaom-av1",
|
|
587
|
+
};
|
|
588
|
+
videoCodecName = codecMap[codec] || codec;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
if (audioCodec) {
|
|
592
|
+
const audioCodecMap: Record<string, string> = {
|
|
593
|
+
aac: "aac",
|
|
594
|
+
mp3: "libmp3lame",
|
|
595
|
+
opus: "libopus",
|
|
596
|
+
};
|
|
597
|
+
audioCodecName = audioCodecMap[audioCodec] || audioCodec;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
await execSafe("ffmpeg", ["-i", absInput, "-c:v", videoCodecName, "-c:a", audioCodecName, absOutput, "-y"], { maxBuffer: 50 * 1024 * 1024 });
|
|
601
|
+
|
|
602
|
+
return {
|
|
603
|
+
toolCallId: "",
|
|
604
|
+
success: true,
|
|
605
|
+
output: `Converted: ${inputPath} → ${outputPath}`,
|
|
606
|
+
};
|
|
607
|
+
} catch (error) {
|
|
608
|
+
return {
|
|
609
|
+
toolCallId: "",
|
|
610
|
+
success: false,
|
|
611
|
+
output: "",
|
|
612
|
+
error: `Failed to convert: ${error instanceof Error ? error.message : String(error)}`,
|
|
613
|
+
};
|
|
614
|
+
}
|
|
615
|
+
};
|
|
616
|
+
|
|
617
|
+
const concat: ToolHandler = async (args, context): Promise<ToolResult> => {
|
|
618
|
+
const inputs = args.inputs as string[];
|
|
619
|
+
const outputPath = args.output as string;
|
|
620
|
+
const reencode = args.reencode as boolean || false;
|
|
621
|
+
|
|
622
|
+
if (!inputs || inputs.length < 2) {
|
|
623
|
+
return {
|
|
624
|
+
toolCallId: "",
|
|
625
|
+
success: false,
|
|
626
|
+
output: "",
|
|
627
|
+
error: "At least 2 input files required for concatenation",
|
|
628
|
+
};
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
try {
|
|
632
|
+
const absOutput = resolve(context.workingDirectory, outputPath);
|
|
633
|
+
|
|
634
|
+
if (reencode) {
|
|
635
|
+
// Re-encode method - works with different codecs
|
|
636
|
+
const ffmpegArgs: string[] = [];
|
|
637
|
+
for (const i of inputs) {
|
|
638
|
+
ffmpegArgs.push("-i", resolve(context.workingDirectory, i));
|
|
639
|
+
}
|
|
640
|
+
const filterComplex = inputs.map((_, i) => `[${i}:v][${i}:a]`).join("");
|
|
641
|
+
ffmpegArgs.push("-filter_complex", `${filterComplex}concat=n=${inputs.length}:v=1:a=1[outv][outa]`, "-map", "[outv]", "-map", "[outa]", absOutput, "-y");
|
|
642
|
+
await execSafe("ffmpeg", ffmpegArgs, { maxBuffer: 100 * 1024 * 1024 });
|
|
643
|
+
} else {
|
|
644
|
+
// Concat demuxer method - fast but requires same codec
|
|
645
|
+
const tempList = resolve(context.workingDirectory, `concat-list-${Date.now()}.txt`);
|
|
646
|
+
const listContent = inputs
|
|
647
|
+
.map((i) => `file '${resolve(context.workingDirectory, i)}'`)
|
|
648
|
+
.join("\n");
|
|
649
|
+
await writeFile(tempList, listContent, "utf-8");
|
|
650
|
+
|
|
651
|
+
await execSafe("ffmpeg", ["-f", "concat", "-safe", "0", "-i", tempList, "-c", "copy", absOutput, "-y"], { maxBuffer: 100 * 1024 * 1024 });
|
|
652
|
+
|
|
653
|
+
// Clean up temp file
|
|
654
|
+
const { unlink } = await import("node:fs/promises");
|
|
655
|
+
await unlink(tempList).catch(() => {});
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
return {
|
|
659
|
+
toolCallId: "",
|
|
660
|
+
success: true,
|
|
661
|
+
output: `Concatenated ${inputs.length} files → ${outputPath}`,
|
|
662
|
+
};
|
|
663
|
+
} catch (error) {
|
|
664
|
+
return {
|
|
665
|
+
toolCallId: "",
|
|
666
|
+
success: false,
|
|
667
|
+
output: "",
|
|
668
|
+
error: `Failed to concatenate: ${error instanceof Error ? error.message : String(error)}`,
|
|
669
|
+
};
|
|
670
|
+
}
|
|
671
|
+
};
|
|
672
|
+
|
|
673
|
+
// Helper function
|
|
674
|
+
function formatSize(bytes: number): string {
|
|
675
|
+
if (bytes >= 1024 * 1024 * 1024) {
|
|
676
|
+
return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`;
|
|
677
|
+
}
|
|
678
|
+
if (bytes >= 1024 * 1024) {
|
|
679
|
+
return `${(bytes / (1024 * 1024)).toFixed(2)} MB`;
|
|
680
|
+
}
|
|
681
|
+
if (bytes >= 1024) {
|
|
682
|
+
return `${(bytes / 1024).toFixed(2)} KB`;
|
|
683
|
+
}
|
|
684
|
+
return `${bytes} B`;
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
// Registration function
|
|
688
|
+
export function registerMediaTools(registry: ToolRegistry): void {
|
|
689
|
+
registry.register(mediaInfoDef, mediaInfo);
|
|
690
|
+
registry.register(detectScenesDef, detectScenes);
|
|
691
|
+
registry.register(detectSilenceDef, detectSilence);
|
|
692
|
+
registry.register(detectBeatsDef, detectBeats);
|
|
693
|
+
registry.register(transcribeDef, transcribe);
|
|
694
|
+
registry.register(compressDef, compress);
|
|
695
|
+
registry.register(convertDef, convert);
|
|
696
|
+
registry.register(concatDef, concat);
|
|
697
|
+
}
|