@agimon-ai/video-editor-mcp 0.2.2 → 0.2.4

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/dist/cli.cjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- const e=require(`./stdio-DRNt1yxK.cjs`);let t=require(`commander`),n=require(`node:child_process`),r=require(`node:path`);r=e.m(r);let i=require(`chalk`);i=e.m(i);let a=require(`node:fs`);a=e.m(a);var o=`0.2.1`;const s=new t.Command(`http-serve`).description(`Start Remotion Studio for video editing`).option(`-p, --port <port>`,`Port to run Remotion Studio on`,`3001`).action(async e=>{try{let t=parseInt(e.port,10),a=r.default.resolve(__dirname,`..`);console.error(i.default.green(`✓ Starting Remotion Studio on port ${t}...`)),console.error(i.default.gray(` Working directory: ${a}`));let o=(0,n.spawn)(r.default.join(a,`node_modules`,`.bin`,`remotion`),[`studio`,`--port`,String(t)],{stdio:`inherit`,cwd:a});o.on(`error`,e=>{console.error(i.default.red(`Failed to start Remotion Studio: ${e.message}`)),process.exit(1)}),process.on(`SIGINT`,()=>{o.kill(`SIGINT`),process.exit(0)}),process.on(`SIGTERM`,()=>{o.kill(`SIGTERM`),process.exit(0)})}catch(e){console.error(`Error executing http-serve:`,e),process.exit(1)}});async function c(e){await e.start();let t=async t=>{console.error(`\nReceived ${t}, shutting down gracefully...`);try{await e.stop(),process.exit(0)}catch(e){console.error(`Error during shutdown:`,e),process.exit(1)}};process.on(`SIGINT`,()=>t(`SIGINT`)),process.on(`SIGTERM`,()=>t(`SIGTERM`))}const l=new t.Command(`mcp-serve`).description(`Start MCP server with specified transport`).option(`-t, --type <type>`,`Transport type: stdio`,`stdio`).action(async t=>{try{let n=t.type.toLowerCase();n===`stdio`?await c(new e.t(e.n(e.r()))):(console.error(`Unknown transport type: ${n}. Use: stdio`),process.exit(1))}catch(e){console.error(`Failed to start MCP server:`,e),process.exit(1)}}),u=new t.Command(`render`).description(`Render a video from JSON props file`).requiredOption(`-i, --input <path>`,`Path to JSON props file`).requiredOption(`-o, --output <path>`,`Output video file path`).option(`-c, --composition <id>`,`Composition ID`,`Main`).option(`--codec <codec>`,`Video codec (h264, h265, vp8, vp9)`,`h264`).action(async t=>{try{process.stderr.write(i.default.blue(`🎬 Starting video render...
2
+ const e=require(`./stdio-D8Z9ArjP.cjs`);let t=require(`commander`),n=require(`node:child_process`),r=require(`node:path`);r=e.m(r);let i=require(`chalk`);i=e.m(i);let a=require(`node:fs`);a=e.m(a);var o=`0.2.3`;const s=new t.Command(`http-serve`).description(`Start Remotion Studio for video editing`).option(`-p, --port <port>`,`Port to run Remotion Studio on`,`3001`).action(async e=>{try{let t=parseInt(e.port,10),a=r.default.resolve(__dirname,`..`);console.error(i.default.green(`✓ Starting Remotion Studio on port ${t}...`)),console.error(i.default.gray(` Working directory: ${a}`));let o=(0,n.spawn)(r.default.join(a,`node_modules`,`.bin`,`remotion`),[`studio`,`--port`,String(t)],{stdio:`inherit`,cwd:a});o.on(`error`,e=>{console.error(i.default.red(`Failed to start Remotion Studio: ${e.message}`)),process.exit(1)}),process.on(`SIGINT`,()=>{o.kill(`SIGINT`),process.exit(0)}),process.on(`SIGTERM`,()=>{o.kill(`SIGTERM`),process.exit(0)})}catch(e){console.error(`Error executing http-serve:`,e),process.exit(1)}});async function c(e){await e.start();let t=async t=>{console.error(`\nReceived ${t}, shutting down gracefully...`);try{await e.stop(),process.exit(0)}catch(e){console.error(`Error during shutdown:`,e),process.exit(1)}};process.on(`SIGINT`,()=>t(`SIGINT`)),process.on(`SIGTERM`,()=>t(`SIGTERM`))}const l=new t.Command(`mcp-serve`).description(`Start MCP server with specified transport`).option(`-t, --type <type>`,`Transport type: stdio`,`stdio`).action(async t=>{try{let n=t.type.toLowerCase();n===`stdio`?await c(new e.t(e.n(e.r()))):(console.error(`Unknown transport type: ${n}. Use: stdio`),process.exit(1))}catch(e){console.error(`Failed to start MCP server:`,e),process.exit(1)}}),u=new t.Command(`render`).description(`Render a video from JSON props file`).requiredOption(`-i, --input <path>`,`Path to JSON props file`).requiredOption(`-o, --output <path>`,`Output video file path`).option(`-c, --composition <id>`,`Composition ID`,`Main`).option(`--codec <codec>`,`Video codec (h264, h265, vp8, vp9)`,`h264`).action(async t=>{try{process.stderr.write(i.default.blue(`🎬 Starting video render...
3
3
  `)),process.stderr.write(i.default.gray(` Input: ${t.input}\n`)),process.stderr.write(i.default.gray(` Output: ${t.output}\n`)),process.stderr.write(i.default.gray(` Composition: ${t.composition}\n`)),process.stderr.write(i.default.gray(` Codec: ${t.codec}\n`)),a.default.existsSync(t.input)||(process.stderr.write(i.default.red(`Error: Input file not found: ${t.input}\n`)),process.exit(1));let n=a.default.readFileSync(t.input,`utf-8`),r=JSON.parse(n),o=e.r().get(e.d.RenderService);process.stderr.write(i.default.blue(`📦 Bundling Remotion project...
4
4
  `));let s=await o.render({compositionId:t.composition,inputProps:r,outputPath:t.output,codec:t.codec});process.stderr.write(i.default.green(`✓ Video rendered successfully: ${s.outputPath}\n`))}catch(e){process.stderr.write(i.default.red(`Error rendering video: ${e}\n`)),process.exit(1)}});async function d(){let e=new t.Command;e.name(`video-editor-mcp`).description(`MCP server for video editing with Remotion`).version(o),e.addCommand(l),e.addCommand(s),e.addCommand(u),await e.parseAsync(process.argv)}d();
5
5
  //# sourceMappingURL=cli.cjs.map
package/dist/cli.cjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.cjs","names":["Command","path","Command","StdioTransportHandler","createServer","createContainer","Command","fs","createContainer","TYPES","Command","packageJson.version"],"sources":["../package.json","../src/commands/http-serve.ts","../src/commands/mcp-serve.ts","../src/commands/render.ts","../src/cli.ts"],"sourcesContent":["{\n \"name\": \"@agimon-ai/video-editor-mcp\",\n \"description\": \"MCP server for video editing with Remotion\",\n \"version\": \"0.2.1\",\n \"license\": \"AGPL-3.0\",\n \"keywords\": [\n \"mcp\",\n \"model-context-protocol\",\n \"remotion\",\n \"video-editor\",\n \"typescript\"\n ],\n \"bin\": {\n \"video-editor-mcp\": \"./dist/cli.cjs\"\n },\n \"main\": \"./dist/index.cjs\",\n \"types\": \"./dist/index.d.cts\",\n \"module\": \"./dist/index.mjs\",\n \"files\": [\n \"dist\",\n \"README.md\"\n ],\n \"scripts\": {\n \"dev\": \"node --loader ts-node/esm src/cli.ts mcp-serve\",\n \"build\": \"tsdown\",\n \"test\": \"vitest --run\",\n \"lint\": \"eslint src\",\n \"typecheck\": \"tsc --noEmit\"\n },\n \"dependencies\": {\n \"@modelcontextprotocol/sdk\": \"1.19.1\",\n \"@remotion/bundler\": \"^4.0.0\",\n \"@remotion/captions\": \"^4.0.0\",\n \"@remotion/cli\": \"^4.0.0\",\n \"@remotion/fonts\": \"^4.0.0\",\n \"@remotion/gif\": \"^4.0.0\",\n \"@remotion/google-fonts\": \"^4.0.0\",\n \"@remotion/lottie\": \"^4.0.0\",\n \"@remotion/media-utils\": \"^4.0.0\",\n \"@remotion/renderer\": \"^4.0.0\",\n \"@remotion/transitions\": \"^4.0.0\",\n \"chalk\": \"5.4.1\",\n \"commander\": \"14.0.0\",\n \"inversify\": \"7.1.0\",\n \"react\": \"^19.0.0\",\n \"react-dom\": \"^19.0.0\",\n \"reflect-metadata\": \"0.2.2\",\n \"remotion\": \"^4.0.0\",\n \"zod\": \"4.3.6\"\n },\n \"devDependencies\": {\n \"@types/node\": \"^22.0.0\",\n \"@types/react\": \"^19.0.0\",\n \"tsdown\": \"0.16.1\",\n \"typescript\": \"5.9.3\",\n \"vitest\": \"4.0.18\"\n },\n \"type\": \"module\",\n \"exports\": {\n \".\": {\n \"import\": \"./dist/index.mjs\",\n \"require\": \"./dist/index.cjs\"\n },\n \"./cli\": {\n \"import\": \"./dist/cli.mjs\",\n \"require\": \"./dist/cli.cjs\"\n },\n \"./package.json\": \"./package.json\"\n }\n}\n","/**\n * HTTP Serve Command\n *\n * Starts Remotion Studio for interactive video editing.\n */\n\nimport { spawn } from 'node:child_process';\nimport path from 'node:path';\nimport chalk from 'chalk';\nimport { Command } from 'commander';\n\nexport const httpServeCommand = new Command('http-serve')\n .description('Start Remotion Studio for video editing')\n .option('-p, --port <port>', 'Port to run Remotion Studio on', '3001')\n .action(async (options: { port: string }) => {\n try {\n const port = parseInt(options.port, 10);\n\n // Get the package directory (where remotion entry point is)\n // In compiled output, files are flat in dist/, so go up one level\n const packageDir = path.resolve(import.meta.dirname, '..');\n\n console.error(chalk.green(`✓ Starting Remotion Studio on port ${port}...`));\n console.error(chalk.gray(` Working directory: ${packageDir}`));\n\n // Use the locally installed remotion CLI\n const remotionBin = path.join(packageDir, 'node_modules', '.bin', 'remotion');\n\n const studio = spawn(remotionBin, ['studio', '--port', String(port)], {\n stdio: 'inherit',\n cwd: packageDir,\n });\n\n studio.on('error', (error) => {\n console.error(chalk.red(`Failed to start Remotion Studio: ${error.message}`));\n process.exit(1);\n });\n\n process.on('SIGINT', () => {\n studio.kill('SIGINT');\n process.exit(0);\n });\n\n process.on('SIGTERM', () => {\n studio.kill('SIGTERM');\n process.exit(0);\n });\n } catch (error) {\n console.error('Error executing http-serve:', error);\n process.exit(1);\n }\n });\n","/**\n * MCP Serve Command\n *\n * DESIGN PATTERNS:\n * - Command pattern with Commander for CLI argument parsing\n * - Transport abstraction pattern for flexible deployment (stdio, HTTP, SSE)\n * - Factory pattern for creating transport handlers\n * - Graceful shutdown pattern with signal handling\n *\n * CODING STANDARDS:\n * - Use async/await for asynchronous operations\n * - Implement proper error handling with try-catch blocks\n * - Handle process signals for graceful shutdown\n * - Provide clear CLI options and help messages\n *\n * AVOID:\n * - Hardcoded configuration values (use CLI options or environment variables)\n * - Missing error handling for transport startup\n * - Not cleaning up resources on shutdown\n */\n\nimport { Command } from 'commander';\nimport { createContainer } from '../container/index.js';\nimport { createServer } from '../server/index.js';\nimport { StdioTransportHandler } from '../transports/stdio.js';\n\n/**\n * Start MCP server with given transport handler\n */\nasync function startServer(handler: any) {\n await handler.start();\n\n // Handle graceful shutdown\n const shutdown = async (signal: string) => {\n console.error(`\\nReceived ${signal}, shutting down gracefully...`);\n try {\n await handler.stop();\n process.exit(0);\n } catch (error) {\n console.error('Error during shutdown:', error);\n process.exit(1);\n }\n };\n\n process.on('SIGINT', () => shutdown('SIGINT'));\n process.on('SIGTERM', () => shutdown('SIGTERM'));\n}\n\n/**\n * MCP Serve command\n */\nexport const mcpServeCommand = new Command('mcp-serve')\n .description('Start MCP server with specified transport')\n .option('-t, --type <type>', 'Transport type: stdio', 'stdio')\n .action(async (options) => {\n try {\n const transportType = options.type.toLowerCase();\n\n if (transportType === 'stdio') {\n const container = createContainer();\n const server = createServer(container);\n const handler = new StdioTransportHandler(server);\n await startServer(handler);\n } else {\n console.error(`Unknown transport type: ${transportType}. Use: stdio`);\n process.exit(1);\n }\n } catch (error) {\n console.error('Failed to start MCP server:', error);\n process.exit(1);\n }\n });\n","/**\n * Render Command\n *\n * Renders a video from JSON props using Remotion.\n */\n\nimport fs from 'node:fs';\nimport chalk from 'chalk';\nimport { Command } from 'commander';\nimport { createContainer } from '../container/index.js';\nimport { RenderService } from '../services/RenderService.js';\nimport { TYPES } from '../types/index.js';\n\ninterface RenderOptions {\n input: string;\n output: string;\n composition: string;\n codec: 'h264' | 'h265' | 'vp8' | 'vp9';\n}\n\nexport const renderCommand = new Command('render')\n .description('Render a video from JSON props file')\n .requiredOption('-i, --input <path>', 'Path to JSON props file')\n .requiredOption('-o, --output <path>', 'Output video file path')\n .option('-c, --composition <id>', 'Composition ID', 'Main')\n .option('--codec <codec>', 'Video codec (h264, h265, vp8, vp9)', 'h264')\n .action(async (options: RenderOptions) => {\n try {\n process.stderr.write(chalk.blue('🎬 Starting video render...\\n'));\n process.stderr.write(chalk.gray(` Input: ${options.input}\\n`));\n process.stderr.write(chalk.gray(` Output: ${options.output}\\n`));\n process.stderr.write(chalk.gray(` Composition: ${options.composition}\\n`));\n process.stderr.write(chalk.gray(` Codec: ${options.codec}\\n`));\n\n // Read JSON props file\n if (!fs.existsSync(options.input)) {\n process.stderr.write(chalk.red(`Error: Input file not found: ${options.input}\\n`));\n process.exit(1);\n }\n\n const propsContent = fs.readFileSync(options.input, 'utf-8');\n const inputProps = JSON.parse(propsContent) as Record<string, unknown>;\n\n // Create container and get RenderService\n const container = createContainer();\n const renderService = container.get<RenderService>(TYPES.RenderService);\n\n process.stderr.write(chalk.blue('📦 Bundling Remotion project...\\n'));\n\n const result = await renderService.render({\n compositionId: options.composition,\n inputProps,\n outputPath: options.output,\n codec: options.codec,\n });\n\n process.stderr.write(chalk.green(`✓ Video rendered successfully: ${result.outputPath}\\n`));\n } catch (error) {\n process.stderr.write(chalk.red(`Error rendering video: ${error}\\n`));\n process.exit(1);\n }\n });\n","#!/usr/bin/env node\n/**\n * MCP Server Entry Point\n *\n * DESIGN PATTERNS:\n * - CLI pattern with Commander for argument parsing\n * - Command pattern for organizing CLI commands\n * - Transport abstraction for multiple communication methods\n *\n * CODING STANDARDS:\n * - Use async/await for asynchronous operations\n * - Handle errors gracefully with try-catch\n * - Log important events for debugging\n * - Register all commands in main entry point\n *\n * AVOID:\n * - Hardcoding command logic in index.ts (use separate command files)\n * - Missing error handling for command execution\n */\nimport { Command } from 'commander';\nimport packageJson from '../package.json' with { type: 'json' };\nimport { httpServeCommand } from './commands/http-serve.js';\nimport { mcpServeCommand } from './commands/mcp-serve.js';\nimport { renderCommand } from './commands/render.js';\n\n/**\n * Main entry point\n */\nasync function main() {\n const program = new Command();\n\n program\n .name('video-editor-mcp')\n .description('MCP server for video editing with Remotion')\n .version(packageJson.version);\n\n // Add all commands\n program.addCommand(mcpServeCommand);\n program.addCommand(httpServeCommand);\n program.addCommand(renderCommand);\n\n // Parse arguments\n await program.parseAsync(process.argv);\n}\n\nmain();\n"],"mappings":"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"}
1
+ {"version":3,"file":"cli.cjs","names":["Command","path","Command","StdioTransportHandler","createServer","createContainer","Command","fs","createContainer","TYPES","Command","packageJson.version"],"sources":["../package.json","../src/commands/http-serve.ts","../src/commands/mcp-serve.ts","../src/commands/render.ts","../src/cli.ts"],"sourcesContent":["{\n \"name\": \"@agimon-ai/video-editor-mcp\",\n \"description\": \"MCP server for video editing with Remotion\",\n \"version\": \"0.2.3\",\n \"license\": \"AGPL-3.0\",\n \"keywords\": [\n \"mcp\",\n \"model-context-protocol\",\n \"remotion\",\n \"video-editor\",\n \"typescript\"\n ],\n \"bin\": {\n \"video-editor-mcp\": \"./dist/cli.cjs\"\n },\n \"main\": \"./dist/index.cjs\",\n \"types\": \"./dist/index.d.cts\",\n \"module\": \"./dist/index.mjs\",\n \"files\": [\n \"dist\",\n \"README.md\"\n ],\n \"scripts\": {\n \"dev\": \"node --loader ts-node/esm src/cli.ts mcp-serve\",\n \"build\": \"tsdown\",\n \"test\": \"vitest --run\",\n \"lint\": \"eslint src\",\n \"typecheck\": \"tsc --noEmit\"\n },\n \"dependencies\": {\n \"@modelcontextprotocol/sdk\": \"1.19.1\",\n \"@remotion/bundler\": \"^4.0.0\",\n \"@remotion/captions\": \"^4.0.0\",\n \"@remotion/cli\": \"^4.0.0\",\n \"@remotion/fonts\": \"^4.0.0\",\n \"@remotion/gif\": \"^4.0.0\",\n \"@remotion/google-fonts\": \"^4.0.0\",\n \"@remotion/lottie\": \"^4.0.0\",\n \"@remotion/media-utils\": \"^4.0.0\",\n \"@remotion/renderer\": \"^4.0.0\",\n \"@remotion/transitions\": \"^4.0.0\",\n \"chalk\": \"5.4.1\",\n \"commander\": \"14.0.0\",\n \"inversify\": \"7.1.0\",\n \"react\": \"^19.0.0\",\n \"react-dom\": \"^19.0.0\",\n \"reflect-metadata\": \"0.2.2\",\n \"remotion\": \"^4.0.0\",\n \"zod\": \"4.3.6\"\n },\n \"devDependencies\": {\n \"@types/node\": \"^22.0.0\",\n \"@types/react\": \"^19.0.0\",\n \"tsdown\": \"0.16.1\",\n \"typescript\": \"5.9.3\",\n \"vitest\": \"4.0.18\"\n },\n \"type\": \"module\",\n \"exports\": {\n \".\": {\n \"import\": \"./dist/index.mjs\",\n \"require\": \"./dist/index.cjs\"\n },\n \"./cli\": {\n \"import\": \"./dist/cli.mjs\",\n \"require\": \"./dist/cli.cjs\"\n },\n \"./package.json\": \"./package.json\"\n }\n}\n","/**\n * HTTP Serve Command\n *\n * Starts Remotion Studio for interactive video editing.\n */\n\nimport { spawn } from 'node:child_process';\nimport path from 'node:path';\nimport chalk from 'chalk';\nimport { Command } from 'commander';\n\nexport const httpServeCommand = new Command('http-serve')\n .description('Start Remotion Studio for video editing')\n .option('-p, --port <port>', 'Port to run Remotion Studio on', '3001')\n .action(async (options: { port: string }) => {\n try {\n const port = parseInt(options.port, 10);\n\n // Get the package directory (where remotion entry point is)\n // In compiled output, files are flat in dist/, so go up one level\n const packageDir = path.resolve(import.meta.dirname, '..');\n\n console.error(chalk.green(`✓ Starting Remotion Studio on port ${port}...`));\n console.error(chalk.gray(` Working directory: ${packageDir}`));\n\n // Use the locally installed remotion CLI\n const remotionBin = path.join(packageDir, 'node_modules', '.bin', 'remotion');\n\n const studio = spawn(remotionBin, ['studio', '--port', String(port)], {\n stdio: 'inherit',\n cwd: packageDir,\n });\n\n studio.on('error', (error) => {\n console.error(chalk.red(`Failed to start Remotion Studio: ${error.message}`));\n process.exit(1);\n });\n\n process.on('SIGINT', () => {\n studio.kill('SIGINT');\n process.exit(0);\n });\n\n process.on('SIGTERM', () => {\n studio.kill('SIGTERM');\n process.exit(0);\n });\n } catch (error) {\n console.error('Error executing http-serve:', error);\n process.exit(1);\n }\n });\n","/**\n * MCP Serve Command\n *\n * DESIGN PATTERNS:\n * - Command pattern with Commander for CLI argument parsing\n * - Transport abstraction pattern for flexible deployment (stdio, HTTP, SSE)\n * - Factory pattern for creating transport handlers\n * - Graceful shutdown pattern with signal handling\n *\n * CODING STANDARDS:\n * - Use async/await for asynchronous operations\n * - Implement proper error handling with try-catch blocks\n * - Handle process signals for graceful shutdown\n * - Provide clear CLI options and help messages\n *\n * AVOID:\n * - Hardcoded configuration values (use CLI options or environment variables)\n * - Missing error handling for transport startup\n * - Not cleaning up resources on shutdown\n */\n\nimport { Command } from 'commander';\nimport { createContainer } from '../container/index.js';\nimport { createServer } from '../server/index.js';\nimport { StdioTransportHandler } from '../transports/stdio.js';\n\ninterface LifecycleHandler {\n start(): Promise<void>;\n stop(): Promise<void>;\n}\n\n/**\n * Start MCP server with given transport handler\n */\nasync function startServer(handler: LifecycleHandler) {\n await handler.start();\n\n // Handle graceful shutdown\n const shutdown = async (signal: string) => {\n console.error(`\\nReceived ${signal}, shutting down gracefully...`);\n try {\n await handler.stop();\n process.exit(0);\n } catch (error) {\n console.error('Error during shutdown:', error);\n process.exit(1);\n }\n };\n\n process.on('SIGINT', () => shutdown('SIGINT'));\n process.on('SIGTERM', () => shutdown('SIGTERM'));\n}\n\n/**\n * MCP Serve command\n */\nexport const mcpServeCommand = new Command('mcp-serve')\n .description('Start MCP server with specified transport')\n .option('-t, --type <type>', 'Transport type: stdio', 'stdio')\n .action(async (options) => {\n try {\n const transportType = options.type.toLowerCase();\n\n if (transportType === 'stdio') {\n const container = createContainer();\n const server = createServer(container);\n const handler = new StdioTransportHandler(server);\n await startServer(handler);\n } else {\n console.error(`Unknown transport type: ${transportType}. Use: stdio`);\n process.exit(1);\n }\n } catch (error) {\n console.error('Failed to start MCP server:', error);\n process.exit(1);\n }\n });\n","/**\n * Render Command\n *\n * Renders a video from JSON props using Remotion.\n */\n\nimport fs from 'node:fs';\nimport chalk from 'chalk';\nimport { Command } from 'commander';\nimport { createContainer } from '../container/index.js';\nimport { RenderService } from '../services/RenderService.js';\nimport { TYPES } from '../types/index.js';\n\ninterface RenderOptions {\n input: string;\n output: string;\n composition: string;\n codec: 'h264' | 'h265' | 'vp8' | 'vp9';\n}\n\nexport const renderCommand = new Command('render')\n .description('Render a video from JSON props file')\n .requiredOption('-i, --input <path>', 'Path to JSON props file')\n .requiredOption('-o, --output <path>', 'Output video file path')\n .option('-c, --composition <id>', 'Composition ID', 'Main')\n .option('--codec <codec>', 'Video codec (h264, h265, vp8, vp9)', 'h264')\n .action(async (options: RenderOptions) => {\n try {\n process.stderr.write(chalk.blue('🎬 Starting video render...\\n'));\n process.stderr.write(chalk.gray(` Input: ${options.input}\\n`));\n process.stderr.write(chalk.gray(` Output: ${options.output}\\n`));\n process.stderr.write(chalk.gray(` Composition: ${options.composition}\\n`));\n process.stderr.write(chalk.gray(` Codec: ${options.codec}\\n`));\n\n // Read JSON props file\n if (!fs.existsSync(options.input)) {\n process.stderr.write(chalk.red(`Error: Input file not found: ${options.input}\\n`));\n process.exit(1);\n }\n\n const propsContent = fs.readFileSync(options.input, 'utf-8');\n const inputProps = JSON.parse(propsContent) as Record<string, unknown>;\n\n // Create container and get RenderService\n const container = createContainer();\n const renderService = container.get<RenderService>(TYPES.RenderService);\n\n process.stderr.write(chalk.blue('📦 Bundling Remotion project...\\n'));\n\n const result = await renderService.render({\n compositionId: options.composition,\n inputProps,\n outputPath: options.output,\n codec: options.codec,\n });\n\n process.stderr.write(chalk.green(`✓ Video rendered successfully: ${result.outputPath}\\n`));\n } catch (error) {\n process.stderr.write(chalk.red(`Error rendering video: ${error}\\n`));\n process.exit(1);\n }\n });\n","#!/usr/bin/env node\n/**\n * MCP Server Entry Point\n *\n * DESIGN PATTERNS:\n * - CLI pattern with Commander for argument parsing\n * - Command pattern for organizing CLI commands\n * - Transport abstraction for multiple communication methods\n *\n * CODING STANDARDS:\n * - Use async/await for asynchronous operations\n * - Handle errors gracefully with try-catch\n * - Log important events for debugging\n * - Register all commands in main entry point\n *\n * AVOID:\n * - Hardcoding command logic in index.ts (use separate command files)\n * - Missing error handling for command execution\n */\nimport { Command } from 'commander';\nimport packageJson from '../package.json' with { type: 'json' };\nimport { httpServeCommand } from './commands/http-serve.js';\nimport { mcpServeCommand } from './commands/mcp-serve.js';\nimport { renderCommand } from './commands/render.js';\n\n/**\n * Main entry point\n */\nasync function main() {\n const program = new Command();\n\n program\n .name('video-editor-mcp')\n .description('MCP server for video editing with Remotion')\n .version(packageJson.version);\n\n // Add all commands\n program.addCommand(mcpServeCommand);\n program.addCommand(httpServeCommand);\n program.addCommand(renderCommand);\n\n // Parse arguments\n await program.parseAsync(process.argv);\n}\n\nmain();\n"],"mappings":"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"}
package/dist/cli.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import{d as e,n as t,r as n,t as r}from"./stdio-C7h0m1FD.mjs";import{Command as i}from"commander";import{spawn as a}from"node:child_process";import o from"node:path";import s from"chalk";import c from"node:fs";var l=`0.2.1`;const u=new i(`http-serve`).description(`Start Remotion Studio for video editing`).option(`-p, --port <port>`,`Port to run Remotion Studio on`,`3001`).action(async e=>{try{let t=parseInt(e.port,10),n=o.resolve(import.meta.dirname,`..`);console.error(s.green(`✓ Starting Remotion Studio on port ${t}...`)),console.error(s.gray(` Working directory: ${n}`));let r=a(o.join(n,`node_modules`,`.bin`,`remotion`),[`studio`,`--port`,String(t)],{stdio:`inherit`,cwd:n});r.on(`error`,e=>{console.error(s.red(`Failed to start Remotion Studio: ${e.message}`)),process.exit(1)}),process.on(`SIGINT`,()=>{r.kill(`SIGINT`),process.exit(0)}),process.on(`SIGTERM`,()=>{r.kill(`SIGTERM`),process.exit(0)})}catch(e){console.error(`Error executing http-serve:`,e),process.exit(1)}});async function d(e){await e.start();let t=async t=>{console.error(`\nReceived ${t}, shutting down gracefully...`);try{await e.stop(),process.exit(0)}catch(e){console.error(`Error during shutdown:`,e),process.exit(1)}};process.on(`SIGINT`,()=>t(`SIGINT`)),process.on(`SIGTERM`,()=>t(`SIGTERM`))}const f=new i(`mcp-serve`).description(`Start MCP server with specified transport`).option(`-t, --type <type>`,`Transport type: stdio`,`stdio`).action(async e=>{try{let i=e.type.toLowerCase();i===`stdio`?await d(new r(t(n()))):(console.error(`Unknown transport type: ${i}. Use: stdio`),process.exit(1))}catch(e){console.error(`Failed to start MCP server:`,e),process.exit(1)}}),p=new i(`render`).description(`Render a video from JSON props file`).requiredOption(`-i, --input <path>`,`Path to JSON props file`).requiredOption(`-o, --output <path>`,`Output video file path`).option(`-c, --composition <id>`,`Composition ID`,`Main`).option(`--codec <codec>`,`Video codec (h264, h265, vp8, vp9)`,`h264`).action(async t=>{try{process.stderr.write(s.blue(`🎬 Starting video render...
2
+ import{d as e,n as t,r as n,t as r}from"./stdio-CB5Y_bMX.mjs";import{Command as i}from"commander";import{spawn as a}from"node:child_process";import o from"node:path";import s from"chalk";import c from"node:fs";var l=`0.2.3`;const u=new i(`http-serve`).description(`Start Remotion Studio for video editing`).option(`-p, --port <port>`,`Port to run Remotion Studio on`,`3001`).action(async e=>{try{let t=parseInt(e.port,10),n=o.resolve(import.meta.dirname,`..`);console.error(s.green(`✓ Starting Remotion Studio on port ${t}...`)),console.error(s.gray(` Working directory: ${n}`));let r=a(o.join(n,`node_modules`,`.bin`,`remotion`),[`studio`,`--port`,String(t)],{stdio:`inherit`,cwd:n});r.on(`error`,e=>{console.error(s.red(`Failed to start Remotion Studio: ${e.message}`)),process.exit(1)}),process.on(`SIGINT`,()=>{r.kill(`SIGINT`),process.exit(0)}),process.on(`SIGTERM`,()=>{r.kill(`SIGTERM`),process.exit(0)})}catch(e){console.error(`Error executing http-serve:`,e),process.exit(1)}});async function d(e){await e.start();let t=async t=>{console.error(`\nReceived ${t}, shutting down gracefully...`);try{await e.stop(),process.exit(0)}catch(e){console.error(`Error during shutdown:`,e),process.exit(1)}};process.on(`SIGINT`,()=>t(`SIGINT`)),process.on(`SIGTERM`,()=>t(`SIGTERM`))}const f=new i(`mcp-serve`).description(`Start MCP server with specified transport`).option(`-t, --type <type>`,`Transport type: stdio`,`stdio`).action(async e=>{try{let i=e.type.toLowerCase();i===`stdio`?await d(new r(t(n()))):(console.error(`Unknown transport type: ${i}. Use: stdio`),process.exit(1))}catch(e){console.error(`Failed to start MCP server:`,e),process.exit(1)}}),p=new i(`render`).description(`Render a video from JSON props file`).requiredOption(`-i, --input <path>`,`Path to JSON props file`).requiredOption(`-o, --output <path>`,`Output video file path`).option(`-c, --composition <id>`,`Composition ID`,`Main`).option(`--codec <codec>`,`Video codec (h264, h265, vp8, vp9)`,`h264`).action(async t=>{try{process.stderr.write(s.blue(`🎬 Starting video render...
3
3
  `)),process.stderr.write(s.gray(` Input: ${t.input}\n`)),process.stderr.write(s.gray(` Output: ${t.output}\n`)),process.stderr.write(s.gray(` Composition: ${t.composition}\n`)),process.stderr.write(s.gray(` Codec: ${t.codec}\n`)),c.existsSync(t.input)||(process.stderr.write(s.red(`Error: Input file not found: ${t.input}\n`)),process.exit(1));let r=c.readFileSync(t.input,`utf-8`),i=JSON.parse(r),a=n().get(e.RenderService);process.stderr.write(s.blue(`📦 Bundling Remotion project...
4
4
  `));let o=await a.render({compositionId:t.composition,inputProps:i,outputPath:t.output,codec:t.codec});process.stderr.write(s.green(`✓ Video rendered successfully: ${o.outputPath}\n`))}catch(e){process.stderr.write(s.red(`Error rendering video: ${e}\n`)),process.exit(1)}});async function m(){let e=new i;e.name(`video-editor-mcp`).description(`MCP server for video editing with Remotion`).version(l),e.addCommand(f),e.addCommand(u),e.addCommand(p),await e.parseAsync(process.argv)}m();export{};
5
5
  //# sourceMappingURL=cli.mjs.map
package/dist/cli.mjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.mjs","names":["packageJson.version"],"sources":["../package.json","../src/commands/http-serve.ts","../src/commands/mcp-serve.ts","../src/commands/render.ts","../src/cli.ts"],"sourcesContent":["{\n \"name\": \"@agimon-ai/video-editor-mcp\",\n \"description\": \"MCP server for video editing with Remotion\",\n \"version\": \"0.2.1\",\n \"license\": \"AGPL-3.0\",\n \"keywords\": [\n \"mcp\",\n \"model-context-protocol\",\n \"remotion\",\n \"video-editor\",\n \"typescript\"\n ],\n \"bin\": {\n \"video-editor-mcp\": \"./dist/cli.cjs\"\n },\n \"main\": \"./dist/index.cjs\",\n \"types\": \"./dist/index.d.cts\",\n \"module\": \"./dist/index.mjs\",\n \"files\": [\n \"dist\",\n \"README.md\"\n ],\n \"scripts\": {\n \"dev\": \"node --loader ts-node/esm src/cli.ts mcp-serve\",\n \"build\": \"tsdown\",\n \"test\": \"vitest --run\",\n \"lint\": \"eslint src\",\n \"typecheck\": \"tsc --noEmit\"\n },\n \"dependencies\": {\n \"@modelcontextprotocol/sdk\": \"1.19.1\",\n \"@remotion/bundler\": \"^4.0.0\",\n \"@remotion/captions\": \"^4.0.0\",\n \"@remotion/cli\": \"^4.0.0\",\n \"@remotion/fonts\": \"^4.0.0\",\n \"@remotion/gif\": \"^4.0.0\",\n \"@remotion/google-fonts\": \"^4.0.0\",\n \"@remotion/lottie\": \"^4.0.0\",\n \"@remotion/media-utils\": \"^4.0.0\",\n \"@remotion/renderer\": \"^4.0.0\",\n \"@remotion/transitions\": \"^4.0.0\",\n \"chalk\": \"5.4.1\",\n \"commander\": \"14.0.0\",\n \"inversify\": \"7.1.0\",\n \"react\": \"^19.0.0\",\n \"react-dom\": \"^19.0.0\",\n \"reflect-metadata\": \"0.2.2\",\n \"remotion\": \"^4.0.0\",\n \"zod\": \"4.3.6\"\n },\n \"devDependencies\": {\n \"@types/node\": \"^22.0.0\",\n \"@types/react\": \"^19.0.0\",\n \"tsdown\": \"0.16.1\",\n \"typescript\": \"5.9.3\",\n \"vitest\": \"4.0.18\"\n },\n \"type\": \"module\",\n \"exports\": {\n \".\": {\n \"import\": \"./dist/index.mjs\",\n \"require\": \"./dist/index.cjs\"\n },\n \"./cli\": {\n \"import\": \"./dist/cli.mjs\",\n \"require\": \"./dist/cli.cjs\"\n },\n \"./package.json\": \"./package.json\"\n }\n}\n","/**\n * HTTP Serve Command\n *\n * Starts Remotion Studio for interactive video editing.\n */\n\nimport { spawn } from 'node:child_process';\nimport path from 'node:path';\nimport chalk from 'chalk';\nimport { Command } from 'commander';\n\nexport const httpServeCommand = new Command('http-serve')\n .description('Start Remotion Studio for video editing')\n .option('-p, --port <port>', 'Port to run Remotion Studio on', '3001')\n .action(async (options: { port: string }) => {\n try {\n const port = parseInt(options.port, 10);\n\n // Get the package directory (where remotion entry point is)\n // In compiled output, files are flat in dist/, so go up one level\n const packageDir = path.resolve(import.meta.dirname, '..');\n\n console.error(chalk.green(`✓ Starting Remotion Studio on port ${port}...`));\n console.error(chalk.gray(` Working directory: ${packageDir}`));\n\n // Use the locally installed remotion CLI\n const remotionBin = path.join(packageDir, 'node_modules', '.bin', 'remotion');\n\n const studio = spawn(remotionBin, ['studio', '--port', String(port)], {\n stdio: 'inherit',\n cwd: packageDir,\n });\n\n studio.on('error', (error) => {\n console.error(chalk.red(`Failed to start Remotion Studio: ${error.message}`));\n process.exit(1);\n });\n\n process.on('SIGINT', () => {\n studio.kill('SIGINT');\n process.exit(0);\n });\n\n process.on('SIGTERM', () => {\n studio.kill('SIGTERM');\n process.exit(0);\n });\n } catch (error) {\n console.error('Error executing http-serve:', error);\n process.exit(1);\n }\n });\n","/**\n * MCP Serve Command\n *\n * DESIGN PATTERNS:\n * - Command pattern with Commander for CLI argument parsing\n * - Transport abstraction pattern for flexible deployment (stdio, HTTP, SSE)\n * - Factory pattern for creating transport handlers\n * - Graceful shutdown pattern with signal handling\n *\n * CODING STANDARDS:\n * - Use async/await for asynchronous operations\n * - Implement proper error handling with try-catch blocks\n * - Handle process signals for graceful shutdown\n * - Provide clear CLI options and help messages\n *\n * AVOID:\n * - Hardcoded configuration values (use CLI options or environment variables)\n * - Missing error handling for transport startup\n * - Not cleaning up resources on shutdown\n */\n\nimport { Command } from 'commander';\nimport { createContainer } from '../container/index.js';\nimport { createServer } from '../server/index.js';\nimport { StdioTransportHandler } from '../transports/stdio.js';\n\n/**\n * Start MCP server with given transport handler\n */\nasync function startServer(handler: any) {\n await handler.start();\n\n // Handle graceful shutdown\n const shutdown = async (signal: string) => {\n console.error(`\\nReceived ${signal}, shutting down gracefully...`);\n try {\n await handler.stop();\n process.exit(0);\n } catch (error) {\n console.error('Error during shutdown:', error);\n process.exit(1);\n }\n };\n\n process.on('SIGINT', () => shutdown('SIGINT'));\n process.on('SIGTERM', () => shutdown('SIGTERM'));\n}\n\n/**\n * MCP Serve command\n */\nexport const mcpServeCommand = new Command('mcp-serve')\n .description('Start MCP server with specified transport')\n .option('-t, --type <type>', 'Transport type: stdio', 'stdio')\n .action(async (options) => {\n try {\n const transportType = options.type.toLowerCase();\n\n if (transportType === 'stdio') {\n const container = createContainer();\n const server = createServer(container);\n const handler = new StdioTransportHandler(server);\n await startServer(handler);\n } else {\n console.error(`Unknown transport type: ${transportType}. Use: stdio`);\n process.exit(1);\n }\n } catch (error) {\n console.error('Failed to start MCP server:', error);\n process.exit(1);\n }\n });\n","/**\n * Render Command\n *\n * Renders a video from JSON props using Remotion.\n */\n\nimport fs from 'node:fs';\nimport chalk from 'chalk';\nimport { Command } from 'commander';\nimport { createContainer } from '../container/index.js';\nimport { RenderService } from '../services/RenderService.js';\nimport { TYPES } from '../types/index.js';\n\ninterface RenderOptions {\n input: string;\n output: string;\n composition: string;\n codec: 'h264' | 'h265' | 'vp8' | 'vp9';\n}\n\nexport const renderCommand = new Command('render')\n .description('Render a video from JSON props file')\n .requiredOption('-i, --input <path>', 'Path to JSON props file')\n .requiredOption('-o, --output <path>', 'Output video file path')\n .option('-c, --composition <id>', 'Composition ID', 'Main')\n .option('--codec <codec>', 'Video codec (h264, h265, vp8, vp9)', 'h264')\n .action(async (options: RenderOptions) => {\n try {\n process.stderr.write(chalk.blue('🎬 Starting video render...\\n'));\n process.stderr.write(chalk.gray(` Input: ${options.input}\\n`));\n process.stderr.write(chalk.gray(` Output: ${options.output}\\n`));\n process.stderr.write(chalk.gray(` Composition: ${options.composition}\\n`));\n process.stderr.write(chalk.gray(` Codec: ${options.codec}\\n`));\n\n // Read JSON props file\n if (!fs.existsSync(options.input)) {\n process.stderr.write(chalk.red(`Error: Input file not found: ${options.input}\\n`));\n process.exit(1);\n }\n\n const propsContent = fs.readFileSync(options.input, 'utf-8');\n const inputProps = JSON.parse(propsContent) as Record<string, unknown>;\n\n // Create container and get RenderService\n const container = createContainer();\n const renderService = container.get<RenderService>(TYPES.RenderService);\n\n process.stderr.write(chalk.blue('📦 Bundling Remotion project...\\n'));\n\n const result = await renderService.render({\n compositionId: options.composition,\n inputProps,\n outputPath: options.output,\n codec: options.codec,\n });\n\n process.stderr.write(chalk.green(`✓ Video rendered successfully: ${result.outputPath}\\n`));\n } catch (error) {\n process.stderr.write(chalk.red(`Error rendering video: ${error}\\n`));\n process.exit(1);\n }\n });\n","#!/usr/bin/env node\n/**\n * MCP Server Entry Point\n *\n * DESIGN PATTERNS:\n * - CLI pattern with Commander for argument parsing\n * - Command pattern for organizing CLI commands\n * - Transport abstraction for multiple communication methods\n *\n * CODING STANDARDS:\n * - Use async/await for asynchronous operations\n * - Handle errors gracefully with try-catch\n * - Log important events for debugging\n * - Register all commands in main entry point\n *\n * AVOID:\n * - Hardcoding command logic in index.ts (use separate command files)\n * - Missing error handling for command execution\n */\nimport { Command } from 'commander';\nimport packageJson from '../package.json' with { type: 'json' };\nimport { httpServeCommand } from './commands/http-serve.js';\nimport { mcpServeCommand } from './commands/mcp-serve.js';\nimport { renderCommand } from './commands/render.js';\n\n/**\n * Main entry point\n */\nasync function main() {\n const program = new Command();\n\n program\n .name('video-editor-mcp')\n .description('MCP server for video editing with Remotion')\n .version(packageJson.version);\n\n // Add all commands\n program.addCommand(mcpServeCommand);\n program.addCommand(httpServeCommand);\n program.addCommand(renderCommand);\n\n // Parse arguments\n await program.parseAsync(process.argv);\n}\n\nmain();\n"],"mappings":"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"}
1
+ {"version":3,"file":"cli.mjs","names":["packageJson.version"],"sources":["../package.json","../src/commands/http-serve.ts","../src/commands/mcp-serve.ts","../src/commands/render.ts","../src/cli.ts"],"sourcesContent":["{\n \"name\": \"@agimon-ai/video-editor-mcp\",\n \"description\": \"MCP server for video editing with Remotion\",\n \"version\": \"0.2.3\",\n \"license\": \"AGPL-3.0\",\n \"keywords\": [\n \"mcp\",\n \"model-context-protocol\",\n \"remotion\",\n \"video-editor\",\n \"typescript\"\n ],\n \"bin\": {\n \"video-editor-mcp\": \"./dist/cli.cjs\"\n },\n \"main\": \"./dist/index.cjs\",\n \"types\": \"./dist/index.d.cts\",\n \"module\": \"./dist/index.mjs\",\n \"files\": [\n \"dist\",\n \"README.md\"\n ],\n \"scripts\": {\n \"dev\": \"node --loader ts-node/esm src/cli.ts mcp-serve\",\n \"build\": \"tsdown\",\n \"test\": \"vitest --run\",\n \"lint\": \"eslint src\",\n \"typecheck\": \"tsc --noEmit\"\n },\n \"dependencies\": {\n \"@modelcontextprotocol/sdk\": \"1.19.1\",\n \"@remotion/bundler\": \"^4.0.0\",\n \"@remotion/captions\": \"^4.0.0\",\n \"@remotion/cli\": \"^4.0.0\",\n \"@remotion/fonts\": \"^4.0.0\",\n \"@remotion/gif\": \"^4.0.0\",\n \"@remotion/google-fonts\": \"^4.0.0\",\n \"@remotion/lottie\": \"^4.0.0\",\n \"@remotion/media-utils\": \"^4.0.0\",\n \"@remotion/renderer\": \"^4.0.0\",\n \"@remotion/transitions\": \"^4.0.0\",\n \"chalk\": \"5.4.1\",\n \"commander\": \"14.0.0\",\n \"inversify\": \"7.1.0\",\n \"react\": \"^19.0.0\",\n \"react-dom\": \"^19.0.0\",\n \"reflect-metadata\": \"0.2.2\",\n \"remotion\": \"^4.0.0\",\n \"zod\": \"4.3.6\"\n },\n \"devDependencies\": {\n \"@types/node\": \"^22.0.0\",\n \"@types/react\": \"^19.0.0\",\n \"tsdown\": \"0.16.1\",\n \"typescript\": \"5.9.3\",\n \"vitest\": \"4.0.18\"\n },\n \"type\": \"module\",\n \"exports\": {\n \".\": {\n \"import\": \"./dist/index.mjs\",\n \"require\": \"./dist/index.cjs\"\n },\n \"./cli\": {\n \"import\": \"./dist/cli.mjs\",\n \"require\": \"./dist/cli.cjs\"\n },\n \"./package.json\": \"./package.json\"\n }\n}\n","/**\n * HTTP Serve Command\n *\n * Starts Remotion Studio for interactive video editing.\n */\n\nimport { spawn } from 'node:child_process';\nimport path from 'node:path';\nimport chalk from 'chalk';\nimport { Command } from 'commander';\n\nexport const httpServeCommand = new Command('http-serve')\n .description('Start Remotion Studio for video editing')\n .option('-p, --port <port>', 'Port to run Remotion Studio on', '3001')\n .action(async (options: { port: string }) => {\n try {\n const port = parseInt(options.port, 10);\n\n // Get the package directory (where remotion entry point is)\n // In compiled output, files are flat in dist/, so go up one level\n const packageDir = path.resolve(import.meta.dirname, '..');\n\n console.error(chalk.green(`✓ Starting Remotion Studio on port ${port}...`));\n console.error(chalk.gray(` Working directory: ${packageDir}`));\n\n // Use the locally installed remotion CLI\n const remotionBin = path.join(packageDir, 'node_modules', '.bin', 'remotion');\n\n const studio = spawn(remotionBin, ['studio', '--port', String(port)], {\n stdio: 'inherit',\n cwd: packageDir,\n });\n\n studio.on('error', (error) => {\n console.error(chalk.red(`Failed to start Remotion Studio: ${error.message}`));\n process.exit(1);\n });\n\n process.on('SIGINT', () => {\n studio.kill('SIGINT');\n process.exit(0);\n });\n\n process.on('SIGTERM', () => {\n studio.kill('SIGTERM');\n process.exit(0);\n });\n } catch (error) {\n console.error('Error executing http-serve:', error);\n process.exit(1);\n }\n });\n","/**\n * MCP Serve Command\n *\n * DESIGN PATTERNS:\n * - Command pattern with Commander for CLI argument parsing\n * - Transport abstraction pattern for flexible deployment (stdio, HTTP, SSE)\n * - Factory pattern for creating transport handlers\n * - Graceful shutdown pattern with signal handling\n *\n * CODING STANDARDS:\n * - Use async/await for asynchronous operations\n * - Implement proper error handling with try-catch blocks\n * - Handle process signals for graceful shutdown\n * - Provide clear CLI options and help messages\n *\n * AVOID:\n * - Hardcoded configuration values (use CLI options or environment variables)\n * - Missing error handling for transport startup\n * - Not cleaning up resources on shutdown\n */\n\nimport { Command } from 'commander';\nimport { createContainer } from '../container/index.js';\nimport { createServer } from '../server/index.js';\nimport { StdioTransportHandler } from '../transports/stdio.js';\n\ninterface LifecycleHandler {\n start(): Promise<void>;\n stop(): Promise<void>;\n}\n\n/**\n * Start MCP server with given transport handler\n */\nasync function startServer(handler: LifecycleHandler) {\n await handler.start();\n\n // Handle graceful shutdown\n const shutdown = async (signal: string) => {\n console.error(`\\nReceived ${signal}, shutting down gracefully...`);\n try {\n await handler.stop();\n process.exit(0);\n } catch (error) {\n console.error('Error during shutdown:', error);\n process.exit(1);\n }\n };\n\n process.on('SIGINT', () => shutdown('SIGINT'));\n process.on('SIGTERM', () => shutdown('SIGTERM'));\n}\n\n/**\n * MCP Serve command\n */\nexport const mcpServeCommand = new Command('mcp-serve')\n .description('Start MCP server with specified transport')\n .option('-t, --type <type>', 'Transport type: stdio', 'stdio')\n .action(async (options) => {\n try {\n const transportType = options.type.toLowerCase();\n\n if (transportType === 'stdio') {\n const container = createContainer();\n const server = createServer(container);\n const handler = new StdioTransportHandler(server);\n await startServer(handler);\n } else {\n console.error(`Unknown transport type: ${transportType}. Use: stdio`);\n process.exit(1);\n }\n } catch (error) {\n console.error('Failed to start MCP server:', error);\n process.exit(1);\n }\n });\n","/**\n * Render Command\n *\n * Renders a video from JSON props using Remotion.\n */\n\nimport fs from 'node:fs';\nimport chalk from 'chalk';\nimport { Command } from 'commander';\nimport { createContainer } from '../container/index.js';\nimport { RenderService } from '../services/RenderService.js';\nimport { TYPES } from '../types/index.js';\n\ninterface RenderOptions {\n input: string;\n output: string;\n composition: string;\n codec: 'h264' | 'h265' | 'vp8' | 'vp9';\n}\n\nexport const renderCommand = new Command('render')\n .description('Render a video from JSON props file')\n .requiredOption('-i, --input <path>', 'Path to JSON props file')\n .requiredOption('-o, --output <path>', 'Output video file path')\n .option('-c, --composition <id>', 'Composition ID', 'Main')\n .option('--codec <codec>', 'Video codec (h264, h265, vp8, vp9)', 'h264')\n .action(async (options: RenderOptions) => {\n try {\n process.stderr.write(chalk.blue('🎬 Starting video render...\\n'));\n process.stderr.write(chalk.gray(` Input: ${options.input}\\n`));\n process.stderr.write(chalk.gray(` Output: ${options.output}\\n`));\n process.stderr.write(chalk.gray(` Composition: ${options.composition}\\n`));\n process.stderr.write(chalk.gray(` Codec: ${options.codec}\\n`));\n\n // Read JSON props file\n if (!fs.existsSync(options.input)) {\n process.stderr.write(chalk.red(`Error: Input file not found: ${options.input}\\n`));\n process.exit(1);\n }\n\n const propsContent = fs.readFileSync(options.input, 'utf-8');\n const inputProps = JSON.parse(propsContent) as Record<string, unknown>;\n\n // Create container and get RenderService\n const container = createContainer();\n const renderService = container.get<RenderService>(TYPES.RenderService);\n\n process.stderr.write(chalk.blue('📦 Bundling Remotion project...\\n'));\n\n const result = await renderService.render({\n compositionId: options.composition,\n inputProps,\n outputPath: options.output,\n codec: options.codec,\n });\n\n process.stderr.write(chalk.green(`✓ Video rendered successfully: ${result.outputPath}\\n`));\n } catch (error) {\n process.stderr.write(chalk.red(`Error rendering video: ${error}\\n`));\n process.exit(1);\n }\n });\n","#!/usr/bin/env node\n/**\n * MCP Server Entry Point\n *\n * DESIGN PATTERNS:\n * - CLI pattern with Commander for argument parsing\n * - Command pattern for organizing CLI commands\n * - Transport abstraction for multiple communication methods\n *\n * CODING STANDARDS:\n * - Use async/await for asynchronous operations\n * - Handle errors gracefully with try-catch\n * - Log important events for debugging\n * - Register all commands in main entry point\n *\n * AVOID:\n * - Hardcoding command logic in index.ts (use separate command files)\n * - Missing error handling for command execution\n */\nimport { Command } from 'commander';\nimport packageJson from '../package.json' with { type: 'json' };\nimport { httpServeCommand } from './commands/http-serve.js';\nimport { mcpServeCommand } from './commands/mcp-serve.js';\nimport { renderCommand } from './commands/render.js';\n\n/**\n * Main entry point\n */\nasync function main() {\n const program = new Command();\n\n program\n .name('video-editor-mcp')\n .description('MCP server for video editing with Remotion')\n .version(packageJson.version);\n\n // Add all commands\n program.addCommand(mcpServeCommand);\n program.addCommand(httpServeCommand);\n program.addCommand(renderCommand);\n\n // Parse arguments\n await program.parseAsync(process.argv);\n}\n\nmain();\n"],"mappings":"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"}
package/dist/index.cjs CHANGED
@@ -1,2 +1,2 @@
1
- const e=require(`./stdio-DRNt1yxK.cjs`);let t=require(`zod`);const n=t.z.object({type:t.z.enum([`interpolate`,`spring`]).default(`interpolate`),easing:t.z.enum([`linear`,`ease-in`,`ease-out`,`ease-in-out`]).optional(),springConfig:t.z.object({mass:t.z.number().positive().default(1),damping:t.z.number().positive().default(10),stiffness:t.z.number().positive().default(100)}).optional(),delay:t.z.number().int().nonnegative().default(0)}),r=t.z.object({type:t.z.enum([`fade`,`slide`,`wipe`,`flip`,`clockWipe`,`none`]).default(`fade`),durationInFrames:t.z.number().int().positive().default(15),direction:t.z.enum([`from-left`,`from-right`,`from-top`,`from-bottom`]).optional()}),i={startFrame:t.z.number().int().nonnegative(),durationInFrames:t.z.number().int().positive(),premountFor:t.z.number().int().nonnegative().default(30),entrance:n.optional(),exit:n.optional(),transition:r.optional()},a=t.z.object({type:t.z.literal(`video`),...i,src:t.z.string(),volume:t.z.number().min(0).max(1).optional(),playbackRate:t.z.number().positive().optional(),muted:t.z.boolean().optional(),loop:t.z.boolean().optional(),trimBefore:t.z.number().nonnegative().optional(),trimAfter:t.z.number().nonnegative().optional(),transparent:t.z.boolean().optional(),style:t.z.record(t.z.string(),t.z.unknown()).optional()}),o=t.z.object({type:t.z.literal(`image`),...i,src:t.z.string(),fit:t.z.enum([`contain`,`cover`,`fill`]).optional(),style:t.z.record(t.z.string(),t.z.unknown()).optional()}),s=t.z.object({type:t.z.literal(`text`),...i,text:t.z.string(),fontSize:t.z.number().positive().default(80),fontFamily:t.z.string().default(`sans-serif`),fontWeight:t.z.union([t.z.string(),t.z.number()]).optional(),color:t.z.string().default(`#ffffff`),backgroundColor:t.z.string().optional(),position:t.z.enum([`top`,`center`,`bottom`]).default(`center`),animation:t.z.enum([`none`,`fade`,`slide`,`typewriter`,`highlight`]).default(`none`),highlightColor:t.z.string().optional(),highlightWord:t.z.string().optional(),style:t.z.record(t.z.string(),t.z.unknown()).optional()}),c=t.z.object({type:t.z.literal(`audio`),...i,src:t.z.string(),volume:t.z.number().min(0).max(1).optional(),playbackRate:t.z.number().positive().optional(),muted:t.z.boolean().optional(),loop:t.z.boolean().optional(),trimBefore:t.z.number().nonnegative().optional(),trimAfter:t.z.number().nonnegative().optional(),toneFrequency:t.z.number().positive().optional()}),l=t.z.object({type:t.z.literal(`subtitle`),...i,text:t.z.string(),position:t.z.enum([`top`,`center`,`bottom`]).default(`bottom`),animation:t.z.enum([`none`,`fade`,`slide`]).default(`fade`),fontSize:t.z.number().positive().default(36),fontFamily:t.z.string().default(`Arial, sans-serif`),color:t.z.string().default(`#ffffff`),backgroundColor:t.z.string().default(`rgba(0, 0, 0, 0.7)`),style:t.z.record(t.z.string(),t.z.unknown()).optional()}),u=t.z.object({type:t.z.literal(`gif`),...i,src:t.z.string(),width:t.z.number().positive().optional(),height:t.z.number().positive().optional(),fit:t.z.enum([`contain`,`cover`,`fill`]).optional(),playbackRate:t.z.number().positive().optional(),loopBehavior:t.z.enum([`loop`,`pause-after-finish`]).optional(),style:t.z.record(t.z.string(),t.z.unknown()).optional()}),d=t.z.object({type:t.z.literal(`lottie`),...i,src:t.z.string(),width:t.z.number().positive().optional(),height:t.z.number().positive().optional(),loop:t.z.boolean().optional(),style:t.z.record(t.z.string(),t.z.unknown()).optional()}),f=t.z.discriminatedUnion(`type`,[a,o,s,c,l,u,d]),p=t.z.object({text:t.z.string(),startMs:t.z.number().nonnegative(),endMs:t.z.number().nonnegative(),timestampMs:t.z.number().nonnegative().optional(),confidence:t.z.number().min(0).max(1).optional()}),m=t.z.object({captions:t.z.array(p),style:t.z.enum([`tiktok`,`standard`,`karaoke`]).default(`tiktok`),combineTokensWithinMilliseconds:t.z.number().nonnegative().default(800),fontSize:t.z.number().positive().default(60),fontFamily:t.z.string().default(`Arial, sans-serif`),color:t.z.string().default(`#ffffff`),highlightColor:t.z.string().default(`#FFD700`),textColor:t.z.string().default(`#ffffff`),backgroundColor:t.z.string().default(`rgba(0, 0, 0, 0.8)`),position:t.z.enum([`top`,`center`,`bottom`]).default(`bottom`)}),h=t.z.object({family:t.z.string(),source:t.z.enum([`google`,`local`]).default(`google`),weights:t.z.array(t.z.number().int().positive()).optional(),url:t.z.string().optional()}),g=t.z.object({clips:t.z.array(f).default([]),backgroundColor:t.z.string().default(`#000000`),fps:t.z.number().int().positive().default(30),width:t.z.number().int().positive().default(1920),height:t.z.number().int().positive().default(1080),captions:m.optional(),globalTransition:r.optional(),fonts:t.z.array(h).optional()});exports.AnimationConfigSchema=n,exports.AudioClipSchema=c,Object.defineProperty(exports,`BaseTool`,{enumerable:!0,get:function(){return e.f}}),exports.CaptionConfigSchema=m,exports.CaptionSchema=p,exports.ClipSchema=f,exports.FontConfigSchema=h,Object.defineProperty(exports,`GenerateCaptionsTool`,{enumerable:!0,get:function(){return e.u}}),Object.defineProperty(exports,`GenerateVoiceoverTool`,{enumerable:!0,get:function(){return e.l}}),Object.defineProperty(exports,`GetMediaInfoTool`,{enumerable:!0,get:function(){return e.c}}),exports.GifClipSchema=u,exports.ImageClipSchema=o,Object.defineProperty(exports,`ImportSrtTool`,{enumerable:!0,get:function(){return e.s}}),Object.defineProperty(exports,`ListCompositionsTool`,{enumerable:!0,get:function(){return e.o}}),exports.LottieClipSchema=d,exports.MainCompositionSchema=g,Object.defineProperty(exports,`PreviewFrameTool`,{enumerable:!0,get:function(){return e.a}}),Object.defineProperty(exports,`RenderService`,{enumerable:!0,get:function(){return e.p}}),Object.defineProperty(exports,`RenderVideoTool`,{enumerable:!0,get:function(){return e.i}}),exports.StdioTransportHandler=e.t,exports.SubtitleClipSchema=l,exports.TYPES=e.d,exports.TextClipSchema=s,exports.TransitionConfigSchema=r,exports.VideoClipSchema=a,exports.createContainer=e.r,exports.createServer=e.n;
1
+ const e=require(`./stdio-D8Z9ArjP.cjs`);let t=require(`zod`);const n=t.z.object({type:t.z.enum([`interpolate`,`spring`]).default(`interpolate`),easing:t.z.enum([`linear`,`ease-in`,`ease-out`,`ease-in-out`]).optional(),springConfig:t.z.object({mass:t.z.number().positive().default(1),damping:t.z.number().positive().default(10),stiffness:t.z.number().positive().default(100)}).optional(),delay:t.z.number().int().nonnegative().default(0)}),r=t.z.object({type:t.z.enum([`fade`,`slide`,`wipe`,`flip`,`clockWipe`,`none`]).default(`fade`),durationInFrames:t.z.number().int().positive().default(15),direction:t.z.enum([`from-left`,`from-right`,`from-top`,`from-bottom`]).optional()}),i={startFrame:t.z.number().int().nonnegative(),durationInFrames:t.z.number().int().positive(),premountFor:t.z.number().int().nonnegative().default(30),entrance:n.optional(),exit:n.optional(),transition:r.optional()},a=t.z.object({type:t.z.literal(`video`),...i,src:t.z.string(),volume:t.z.number().min(0).max(1).optional(),playbackRate:t.z.number().positive().optional(),muted:t.z.boolean().optional(),loop:t.z.boolean().optional(),trimBefore:t.z.number().nonnegative().optional(),trimAfter:t.z.number().nonnegative().optional(),transparent:t.z.boolean().optional(),style:t.z.record(t.z.string(),t.z.unknown()).optional()}),o=t.z.object({type:t.z.literal(`image`),...i,src:t.z.string(),fit:t.z.enum([`contain`,`cover`,`fill`]).optional(),style:t.z.record(t.z.string(),t.z.unknown()).optional()}),s=t.z.object({type:t.z.literal(`text`),...i,text:t.z.string(),fontSize:t.z.number().positive().default(80),fontFamily:t.z.string().default(`sans-serif`),fontWeight:t.z.union([t.z.string(),t.z.number()]).optional(),color:t.z.string().default(`#ffffff`),backgroundColor:t.z.string().optional(),position:t.z.enum([`top`,`center`,`bottom`]).default(`center`),animation:t.z.enum([`none`,`fade`,`slide`,`typewriter`,`highlight`]).default(`none`),highlightColor:t.z.string().optional(),highlightWord:t.z.string().optional(),style:t.z.record(t.z.string(),t.z.unknown()).optional()}),c=t.z.object({type:t.z.literal(`audio`),...i,src:t.z.string(),volume:t.z.number().min(0).max(1).optional(),playbackRate:t.z.number().positive().optional(),muted:t.z.boolean().optional(),loop:t.z.boolean().optional(),trimBefore:t.z.number().nonnegative().optional(),trimAfter:t.z.number().nonnegative().optional(),toneFrequency:t.z.number().positive().optional()}),l=t.z.object({type:t.z.literal(`subtitle`),...i,text:t.z.string(),position:t.z.enum([`top`,`center`,`bottom`]).default(`bottom`),animation:t.z.enum([`none`,`fade`,`slide`]).default(`fade`),fontSize:t.z.number().positive().default(36),fontFamily:t.z.string().default(`Arial, sans-serif`),color:t.z.string().default(`#ffffff`),backgroundColor:t.z.string().default(`rgba(0, 0, 0, 0.7)`),style:t.z.record(t.z.string(),t.z.unknown()).optional()}),u=t.z.object({type:t.z.literal(`gif`),...i,src:t.z.string(),width:t.z.number().positive().optional(),height:t.z.number().positive().optional(),fit:t.z.enum([`contain`,`cover`,`fill`]).optional(),playbackRate:t.z.number().positive().optional(),loopBehavior:t.z.enum([`loop`,`pause-after-finish`]).optional(),style:t.z.record(t.z.string(),t.z.unknown()).optional()}),d=t.z.object({type:t.z.literal(`lottie`),...i,src:t.z.string(),width:t.z.number().positive().optional(),height:t.z.number().positive().optional(),loop:t.z.boolean().optional(),style:t.z.record(t.z.string(),t.z.unknown()).optional()}),f=t.z.discriminatedUnion(`type`,[a,o,s,c,l,u,d]),p=t.z.object({text:t.z.string(),startMs:t.z.number().nonnegative(),endMs:t.z.number().nonnegative(),timestampMs:t.z.number().nonnegative().optional(),confidence:t.z.number().min(0).max(1).optional()}),m=t.z.object({captions:t.z.array(p),style:t.z.enum([`tiktok`,`standard`,`karaoke`]).default(`tiktok`),combineTokensWithinMilliseconds:t.z.number().nonnegative().default(800),fontSize:t.z.number().positive().default(60),fontFamily:t.z.string().default(`Arial, sans-serif`),color:t.z.string().default(`#ffffff`),highlightColor:t.z.string().default(`#FFD700`),textColor:t.z.string().default(`#ffffff`),backgroundColor:t.z.string().default(`rgba(0, 0, 0, 0.8)`),position:t.z.enum([`top`,`center`,`bottom`]).default(`bottom`)}),h=t.z.object({family:t.z.string(),source:t.z.enum([`google`,`local`]).default(`google`),weights:t.z.array(t.z.number().int().positive()).optional(),url:t.z.string().optional()}),g=t.z.object({clips:t.z.array(f).default([]),backgroundColor:t.z.string().default(`#000000`),fps:t.z.number().int().positive().default(30),width:t.z.number().int().positive().default(1920),height:t.z.number().int().positive().default(1080),captions:m.optional(),globalTransition:r.optional(),fonts:t.z.array(h).optional()});exports.AnimationConfigSchema=n,exports.AudioClipSchema=c,Object.defineProperty(exports,`BaseTool`,{enumerable:!0,get:function(){return e.f}}),exports.CaptionConfigSchema=m,exports.CaptionSchema=p,exports.ClipSchema=f,exports.FontConfigSchema=h,Object.defineProperty(exports,`GenerateCaptionsTool`,{enumerable:!0,get:function(){return e.u}}),Object.defineProperty(exports,`GenerateVoiceoverTool`,{enumerable:!0,get:function(){return e.l}}),Object.defineProperty(exports,`GetMediaInfoTool`,{enumerable:!0,get:function(){return e.c}}),exports.GifClipSchema=u,exports.ImageClipSchema=o,Object.defineProperty(exports,`ImportSrtTool`,{enumerable:!0,get:function(){return e.s}}),Object.defineProperty(exports,`ListCompositionsTool`,{enumerable:!0,get:function(){return e.o}}),exports.LottieClipSchema=d,exports.MainCompositionSchema=g,Object.defineProperty(exports,`PreviewFrameTool`,{enumerable:!0,get:function(){return e.a}}),Object.defineProperty(exports,`RenderService`,{enumerable:!0,get:function(){return e.p}}),Object.defineProperty(exports,`RenderVideoTool`,{enumerable:!0,get:function(){return e.i}}),exports.StdioTransportHandler=e.t,exports.SubtitleClipSchema=l,exports.TYPES=e.d,exports.TextClipSchema=s,exports.TransitionConfigSchema=r,exports.VideoClipSchema=a,exports.createContainer=e.r,exports.createServer=e.n;
2
2
  //# sourceMappingURL=index.cjs.map
package/dist/index.mjs CHANGED
@@ -1,2 +1,2 @@
1
- import{a as e,c as t,d as n,f as r,i,l as a,n as o,o as s,p as c,r as l,s as u,t as d,u as f}from"./stdio-C7h0m1FD.mjs";import{z as p}from"zod";const m=p.object({type:p.enum([`interpolate`,`spring`]).default(`interpolate`),easing:p.enum([`linear`,`ease-in`,`ease-out`,`ease-in-out`]).optional(),springConfig:p.object({mass:p.number().positive().default(1),damping:p.number().positive().default(10),stiffness:p.number().positive().default(100)}).optional(),delay:p.number().int().nonnegative().default(0)}),h=p.object({type:p.enum([`fade`,`slide`,`wipe`,`flip`,`clockWipe`,`none`]).default(`fade`),durationInFrames:p.number().int().positive().default(15),direction:p.enum([`from-left`,`from-right`,`from-top`,`from-bottom`]).optional()}),g={startFrame:p.number().int().nonnegative(),durationInFrames:p.number().int().positive(),premountFor:p.number().int().nonnegative().default(30),entrance:m.optional(),exit:m.optional(),transition:h.optional()},_=p.object({type:p.literal(`video`),...g,src:p.string(),volume:p.number().min(0).max(1).optional(),playbackRate:p.number().positive().optional(),muted:p.boolean().optional(),loop:p.boolean().optional(),trimBefore:p.number().nonnegative().optional(),trimAfter:p.number().nonnegative().optional(),transparent:p.boolean().optional(),style:p.record(p.string(),p.unknown()).optional()}),v=p.object({type:p.literal(`image`),...g,src:p.string(),fit:p.enum([`contain`,`cover`,`fill`]).optional(),style:p.record(p.string(),p.unknown()).optional()}),y=p.object({type:p.literal(`text`),...g,text:p.string(),fontSize:p.number().positive().default(80),fontFamily:p.string().default(`sans-serif`),fontWeight:p.union([p.string(),p.number()]).optional(),color:p.string().default(`#ffffff`),backgroundColor:p.string().optional(),position:p.enum([`top`,`center`,`bottom`]).default(`center`),animation:p.enum([`none`,`fade`,`slide`,`typewriter`,`highlight`]).default(`none`),highlightColor:p.string().optional(),highlightWord:p.string().optional(),style:p.record(p.string(),p.unknown()).optional()}),b=p.object({type:p.literal(`audio`),...g,src:p.string(),volume:p.number().min(0).max(1).optional(),playbackRate:p.number().positive().optional(),muted:p.boolean().optional(),loop:p.boolean().optional(),trimBefore:p.number().nonnegative().optional(),trimAfter:p.number().nonnegative().optional(),toneFrequency:p.number().positive().optional()}),x=p.object({type:p.literal(`subtitle`),...g,text:p.string(),position:p.enum([`top`,`center`,`bottom`]).default(`bottom`),animation:p.enum([`none`,`fade`,`slide`]).default(`fade`),fontSize:p.number().positive().default(36),fontFamily:p.string().default(`Arial, sans-serif`),color:p.string().default(`#ffffff`),backgroundColor:p.string().default(`rgba(0, 0, 0, 0.7)`),style:p.record(p.string(),p.unknown()).optional()}),S=p.object({type:p.literal(`gif`),...g,src:p.string(),width:p.number().positive().optional(),height:p.number().positive().optional(),fit:p.enum([`contain`,`cover`,`fill`]).optional(),playbackRate:p.number().positive().optional(),loopBehavior:p.enum([`loop`,`pause-after-finish`]).optional(),style:p.record(p.string(),p.unknown()).optional()}),C=p.object({type:p.literal(`lottie`),...g,src:p.string(),width:p.number().positive().optional(),height:p.number().positive().optional(),loop:p.boolean().optional(),style:p.record(p.string(),p.unknown()).optional()}),w=p.discriminatedUnion(`type`,[_,v,y,b,x,S,C]),T=p.object({text:p.string(),startMs:p.number().nonnegative(),endMs:p.number().nonnegative(),timestampMs:p.number().nonnegative().optional(),confidence:p.number().min(0).max(1).optional()}),E=p.object({captions:p.array(T),style:p.enum([`tiktok`,`standard`,`karaoke`]).default(`tiktok`),combineTokensWithinMilliseconds:p.number().nonnegative().default(800),fontSize:p.number().positive().default(60),fontFamily:p.string().default(`Arial, sans-serif`),color:p.string().default(`#ffffff`),highlightColor:p.string().default(`#FFD700`),textColor:p.string().default(`#ffffff`),backgroundColor:p.string().default(`rgba(0, 0, 0, 0.8)`),position:p.enum([`top`,`center`,`bottom`]).default(`bottom`)}),D=p.object({family:p.string(),source:p.enum([`google`,`local`]).default(`google`),weights:p.array(p.number().int().positive()).optional(),url:p.string().optional()}),O=p.object({clips:p.array(w).default([]),backgroundColor:p.string().default(`#000000`),fps:p.number().int().positive().default(30),width:p.number().int().positive().default(1920),height:p.number().int().positive().default(1080),captions:E.optional(),globalTransition:h.optional(),fonts:p.array(D).optional()});export{m as AnimationConfigSchema,b as AudioClipSchema,r as BaseTool,E as CaptionConfigSchema,T as CaptionSchema,w as ClipSchema,D as FontConfigSchema,f as GenerateCaptionsTool,a as GenerateVoiceoverTool,t as GetMediaInfoTool,S as GifClipSchema,v as ImageClipSchema,u as ImportSrtTool,s as ListCompositionsTool,C as LottieClipSchema,O as MainCompositionSchema,e as PreviewFrameTool,c as RenderService,i as RenderVideoTool,d as StdioTransportHandler,x as SubtitleClipSchema,n as TYPES,y as TextClipSchema,h as TransitionConfigSchema,_ as VideoClipSchema,l as createContainer,o as createServer};
1
+ import{a as e,c as t,d as n,f as r,i,l as a,n as o,o as s,p as c,r as l,s as u,t as d,u as f}from"./stdio-CB5Y_bMX.mjs";import{z as p}from"zod";const m=p.object({type:p.enum([`interpolate`,`spring`]).default(`interpolate`),easing:p.enum([`linear`,`ease-in`,`ease-out`,`ease-in-out`]).optional(),springConfig:p.object({mass:p.number().positive().default(1),damping:p.number().positive().default(10),stiffness:p.number().positive().default(100)}).optional(),delay:p.number().int().nonnegative().default(0)}),h=p.object({type:p.enum([`fade`,`slide`,`wipe`,`flip`,`clockWipe`,`none`]).default(`fade`),durationInFrames:p.number().int().positive().default(15),direction:p.enum([`from-left`,`from-right`,`from-top`,`from-bottom`]).optional()}),g={startFrame:p.number().int().nonnegative(),durationInFrames:p.number().int().positive(),premountFor:p.number().int().nonnegative().default(30),entrance:m.optional(),exit:m.optional(),transition:h.optional()},_=p.object({type:p.literal(`video`),...g,src:p.string(),volume:p.number().min(0).max(1).optional(),playbackRate:p.number().positive().optional(),muted:p.boolean().optional(),loop:p.boolean().optional(),trimBefore:p.number().nonnegative().optional(),trimAfter:p.number().nonnegative().optional(),transparent:p.boolean().optional(),style:p.record(p.string(),p.unknown()).optional()}),v=p.object({type:p.literal(`image`),...g,src:p.string(),fit:p.enum([`contain`,`cover`,`fill`]).optional(),style:p.record(p.string(),p.unknown()).optional()}),y=p.object({type:p.literal(`text`),...g,text:p.string(),fontSize:p.number().positive().default(80),fontFamily:p.string().default(`sans-serif`),fontWeight:p.union([p.string(),p.number()]).optional(),color:p.string().default(`#ffffff`),backgroundColor:p.string().optional(),position:p.enum([`top`,`center`,`bottom`]).default(`center`),animation:p.enum([`none`,`fade`,`slide`,`typewriter`,`highlight`]).default(`none`),highlightColor:p.string().optional(),highlightWord:p.string().optional(),style:p.record(p.string(),p.unknown()).optional()}),b=p.object({type:p.literal(`audio`),...g,src:p.string(),volume:p.number().min(0).max(1).optional(),playbackRate:p.number().positive().optional(),muted:p.boolean().optional(),loop:p.boolean().optional(),trimBefore:p.number().nonnegative().optional(),trimAfter:p.number().nonnegative().optional(),toneFrequency:p.number().positive().optional()}),x=p.object({type:p.literal(`subtitle`),...g,text:p.string(),position:p.enum([`top`,`center`,`bottom`]).default(`bottom`),animation:p.enum([`none`,`fade`,`slide`]).default(`fade`),fontSize:p.number().positive().default(36),fontFamily:p.string().default(`Arial, sans-serif`),color:p.string().default(`#ffffff`),backgroundColor:p.string().default(`rgba(0, 0, 0, 0.7)`),style:p.record(p.string(),p.unknown()).optional()}),S=p.object({type:p.literal(`gif`),...g,src:p.string(),width:p.number().positive().optional(),height:p.number().positive().optional(),fit:p.enum([`contain`,`cover`,`fill`]).optional(),playbackRate:p.number().positive().optional(),loopBehavior:p.enum([`loop`,`pause-after-finish`]).optional(),style:p.record(p.string(),p.unknown()).optional()}),C=p.object({type:p.literal(`lottie`),...g,src:p.string(),width:p.number().positive().optional(),height:p.number().positive().optional(),loop:p.boolean().optional(),style:p.record(p.string(),p.unknown()).optional()}),w=p.discriminatedUnion(`type`,[_,v,y,b,x,S,C]),T=p.object({text:p.string(),startMs:p.number().nonnegative(),endMs:p.number().nonnegative(),timestampMs:p.number().nonnegative().optional(),confidence:p.number().min(0).max(1).optional()}),E=p.object({captions:p.array(T),style:p.enum([`tiktok`,`standard`,`karaoke`]).default(`tiktok`),combineTokensWithinMilliseconds:p.number().nonnegative().default(800),fontSize:p.number().positive().default(60),fontFamily:p.string().default(`Arial, sans-serif`),color:p.string().default(`#ffffff`),highlightColor:p.string().default(`#FFD700`),textColor:p.string().default(`#ffffff`),backgroundColor:p.string().default(`rgba(0, 0, 0, 0.8)`),position:p.enum([`top`,`center`,`bottom`]).default(`bottom`)}),D=p.object({family:p.string(),source:p.enum([`google`,`local`]).default(`google`),weights:p.array(p.number().int().positive()).optional(),url:p.string().optional()}),O=p.object({clips:p.array(w).default([]),backgroundColor:p.string().default(`#000000`),fps:p.number().int().positive().default(30),width:p.number().int().positive().default(1920),height:p.number().int().positive().default(1080),captions:E.optional(),globalTransition:h.optional(),fonts:p.array(D).optional()});export{m as AnimationConfigSchema,b as AudioClipSchema,r as BaseTool,E as CaptionConfigSchema,T as CaptionSchema,w as ClipSchema,D as FontConfigSchema,f as GenerateCaptionsTool,a as GenerateVoiceoverTool,t as GetMediaInfoTool,S as GifClipSchema,v as ImageClipSchema,u as ImportSrtTool,s as ListCompositionsTool,C as LottieClipSchema,O as MainCompositionSchema,e as PreviewFrameTool,c as RenderService,i as RenderVideoTool,d as StdioTransportHandler,x as SubtitleClipSchema,n as TYPES,y as TextClipSchema,h as TransitionConfigSchema,_ as VideoClipSchema,l as createContainer,o as createServer};
2
2
  //# sourceMappingURL=index.mjs.map
@@ -0,0 +1,4 @@
1
+ import{execFile as e}from"node:child_process";import t from"node:path";import"reflect-metadata";import{Container as n,ContainerModule as r,inject as i,injectable as a}from"inversify";import{promisify as o}from"node:util";import{bundle as s}from"@remotion/bundler";import{renderMedia as c,renderStill as l,selectComposition as u}from"@remotion/renderer";import d from"node:fs/promises";import f from"node:os";import{Server as p}from"@modelcontextprotocol/sdk/server/index.js";import{CallToolRequestSchema as m,ListToolsRequestSchema as h}from"@modelcontextprotocol/sdk/types.js";import{StdioServerTransport as g}from"@modelcontextprotocol/sdk/server/stdio.js";function _(e,t,n,r){var i=arguments.length,a=i<3?t:r===null?r=Object.getOwnPropertyDescriptor(t,n):r,o;if(typeof Reflect==`object`&&typeof Reflect.decorate==`function`)a=Reflect.decorate(e,t,n,r);else for(var s=e.length-1;s>=0;s--)(o=e[s])&&(a=(i<3?o(a):i>3?o(t,n,a):o(t,n))||a);return i>3&&a&&Object.defineProperty(t,n,a),a}let v=class{parseSrt(e){let t=[],n=e.split(`
2
+ `),r=0;for(;r<n.length;){if(!n[r]?.trim()){r++;continue}r++;let e=n[r];if(!e?.includes(`-->`)){r++;continue}let[i,a]=e.split(`-->`).map(e=>e.trim());if(!i||!a){r++;continue}let o=this.parseTimestamp(i),s=this.parseTimestamp(a);r++;let c=[];for(;r<n.length&&n[r]?.trim();)c.push(n[r]??``),r++;let l=c.join(`
3
+ `);l&&t.push({text:l,startMs:o,endMs:s})}return t}parseTimestamp(e){let[t,n]=e.split(`,`);if(!t||!n)throw Error(`Invalid timestamp format: ${e}`);let[r,i,a]=t.split(`:`).map(Number);if(r===void 0||i===void 0||a===void 0)throw Error(`Invalid time format: ${t}`);return(r*3600+i*60+a)*1e3+Number(n)}createTikTokCaptions(e,t){let n=[];if(e.length===0)return{pages:n};let r={startMs:e[0]?.startMs??0,endMs:e[0]?.endMs??0,tokens:[e[0]]};for(let i=1;i<e.length;i++){let a=e[i];a&&(a.startMs-r.endMs<=t?(r.tokens.push(a),r.endMs=a.endMs):(n.push(r),r={startMs:a.startMs,endMs:a.endMs,tokens:[a]}))}return r.tokens.length>0&&n.push(r),{pages:n}}};v=_([a()],v);const y=[`Inter`,`Roboto`,`Open Sans`,`Montserrat`,`Lato`,`Poppins`,`Oswald`,`Raleway`,`Playfair Display`,`Merriweather`,`Source Sans Pro`,`PT Sans`,`Ubuntu`,`Nunito`,`Rubik`,`Work Sans`,`Karla`,`Noto Sans`,`Fira Sans`,`DM Sans`];let b=class{getAvailableGoogleFonts(){return[...y]}validateFontFamily(e){let t=e.toLowerCase();return y.some(e=>e.toLowerCase()===t)}};b=_([a()],b);const x=o(e);let S=class{async getVideoDuration(e){try{let{stdout:t}=await x(`ffprobe`,[`-v`,`error`,`-show_entries`,`format=duration`,`-of`,`json`,e]),n=JSON.parse(t),r=Number.parseFloat(n.format?.duration??`0`);if(Number.isNaN(r)||r<=0)throw Error(`Invalid duration for video: ${e}`);return r}catch(t){throw Error(`Failed to get video duration for ${e}: ${t instanceof Error?t.message:String(t)}`,{cause:t})}}async getAudioDuration(e){try{let{stdout:t}=await x(`ffprobe`,[`-v`,`error`,`-show_entries`,`format=duration`,`-of`,`json`,e]),n=JSON.parse(t),r=Number.parseFloat(n.format?.duration??`0`);if(Number.isNaN(r)||r<=0)throw Error(`Invalid duration for audio: ${e}`);return r}catch(t){throw Error(`Failed to get audio duration for ${e}: ${t instanceof Error?t.message:String(t)}`,{cause:t})}}async getVideoDimensions(e){try{let{stdout:t}=await x(`ffprobe`,[`-v`,`error`,`-select_streams`,`v:0`,`-show_entries`,`stream=width,height`,`-of`,`json`,e]),n=JSON.parse(t).streams?.[0];if(!n?.width||!n?.height)throw Error(`No video stream found in: ${e}`);return{width:Number(n.width),height:Number(n.height)}}catch(t){throw Error(`Failed to get video dimensions for ${e}: ${t instanceof Error?t.message:String(t)}`,{cause:t})}}async canDecode(e){try{return await x(`ffprobe`,[`-v`,`error`,`-show_entries`,`format=duration`,`-of`,`json`,e]),!0}catch{return!1}}};S=_([a()],S);let C=class{bundlePromise=null;async getServeUrl(){if(!this.bundlePromise){let e=t.resolve(import.meta.dirname,`../..`);this.bundlePromise=s({entryPoint:t.resolve(e,`src/remotion/index.ts`),publicDir:t.resolve(e,`public`),webpackOverride:e=>({...e,resolve:{...e.resolve,extensionAlias:{".js":[`.ts`,`.tsx`,`.js`,`.jsx`]}}})})}return this.bundlePromise}async render(e){let t=await this.getServeUrl();return await c({composition:await u({serveUrl:t,id:e.compositionId,inputProps:e.inputProps}),serveUrl:t,codec:e.codec??`h264`,outputLocation:e.outputPath,inputProps:e.inputProps}),{outputPath:e.outputPath}}async renderStill(e){let t=await this.getServeUrl();return await l({composition:await u({serveUrl:t,id:e.compositionId,inputProps:e.inputProps}),serveUrl:t,output:e.outputPath,frame:e.frame??0,imageFormat:e.imageFormat??`png`,inputProps:e.inputProps}),{outputPath:e.outputPath}}};C=_([a()],C);const w=o(e),T=[`af_sarah`,`af_nicole`,`af_bella`,`af_sky`,`am_adam`,`am_michael`,`bf_emma`,`bf_isabella`,`bm_george`,`bm_lewis`];let E=class{async isAvailable(){try{return await w(`kokoro-tts`,[`--version`]),!0}catch{return!1}}async generateVoiceover(e){let{text:n,voice:r=`af_sarah`,speed:i=1,outputPath:a}=e,o=await d.mkdtemp(t.join(f.tmpdir(),`kokoro-`)),s=t.join(o,`input.txt`);try{return await d.writeFile(s,n,`utf-8`),await w(`kokoro-tts`,[s,a,`--voice`,r,`--speed`,i.toString()]),{outputPath:a}}catch(e){throw Error(`Failed to generate voiceover: ${e instanceof Error?e.message:String(e)}`,{cause:e})}finally{try{await d.rm(o,{recursive:!0,force:!0})}catch{}}}listVoices(){return[...T]}};E=_([a()],E);let D=class{success(e){return{content:[{type:`text`,text:e}]}}successJson(e){return{content:[{type:`text`,text:JSON.stringify(e,null,2)}]}}error(e){return{content:[{type:`text`,text:`Error: ${e}`}],isError:!0}}async safeExecute(e){try{return await e()}catch(e){return this.error(e instanceof Error?e.message:`Unknown error`)}}};D=_([a()],D);const O={RenderService:Symbol.for(`RenderService`),MediaInfoService:Symbol.for(`MediaInfoService`),CaptionService:Symbol.for(`CaptionService`),FontService:Symbol.for(`FontService`),VoiceoverService:Symbol.for(`VoiceoverService`),Tool:Symbol.for(`Tool`)};function k(e,t){if(typeof Reflect==`object`&&typeof Reflect.metadata==`function`)return Reflect.metadata(e,t)}function A(e,t){return function(n,r){t(n,r,e)}}var j,M;let N=class extends D{static{M=this}static TOOL_NAME=`generate_captions`;constructor(e){super(),this.captionService=e}getDefinition(){return{name:M.TOOL_NAME,description:`Create TikTok-style caption pages from a caption array for video overlay`,inputSchema:{type:`object`,properties:{captions:{type:`array`,description:`Array of caption objects with text, startMs, and endMs`,items:{type:`object`,properties:{text:{type:`string`},startMs:{type:`number`},endMs:{type:`number`}},required:[`text`,`startMs`,`endMs`]}},combineMs:{type:`number`,description:`Milliseconds threshold for combining captions into pages (default: 100)`}},required:[`captions`],additionalProperties:!1}}}async execute(e){try{let{captions:t,combineMs:n=100}=e,r=t.map(e=>({text:e.text,startMs:e.startMs,endMs:e.endMs})),i=this.captionService.createTikTokCaptions(r,n);return this.successJson(i)}catch(e){return this.error(e instanceof Error?e.message:`Unknown error`)}}};N=M=_([a(),A(0,i(O.CaptionService)),k(`design:paramtypes`,[typeof(j=v!==void 0&&v)==`function`?j:Object])],N);var P,F;let I=class extends D{static{F=this}static TOOL_NAME=`generate_voiceover`;constructor(e){super(),this.voiceoverService=e}getDefinition(){return{name:F.TOOL_NAME,description:`Generate voiceover audio from text using Kokoro TTS local engine`,inputSchema:{type:`object`,properties:{text:{type:`string`,description:`Text to convert to speech`},voice:{type:`string`,description:`Voice ID (default: af_sarah). Options: af_sarah, af_nicole, af_bella, af_sky, am_adam, am_michael, bf_emma, bf_isabella, bm_george, bm_lewis`},speed:{type:`number`,description:`Speech speed multiplier (default: 1.0)`},outputPath:{type:`string`,description:`Output file path for the generated audio`}},required:[`text`,`outputPath`],additionalProperties:!1}}}async execute(e){try{if(!await this.voiceoverService.isAvailable())return this.error(`Kokoro TTS is not installed. Install it with: pip install kokoro-tts`);let t=await this.voiceoverService.generateVoiceover({text:e.text,voice:e.voice,speed:e.speed,outputPath:e.outputPath});return this.successJson(t)}catch(e){return this.error(e instanceof Error?e.message:`Unknown error`)}}};I=F=_([a(),A(0,i(O.VoiceoverService)),k(`design:paramtypes`,[typeof(P=E!==void 0&&E)==`function`?P:Object])],I);var L,R;let z=class extends D{static{R=this}static TOOL_NAME=`get_media_info`;constructor(e){super(),this.mediaInfoService=e}getDefinition(){return{name:R.TOOL_NAME,description:`Get duration, dimensions, and decodability info for video/audio files`,inputSchema:{type:`object`,properties:{src:{type:`string`,description:`Path to the media file`},type:{type:`string`,enum:[`video`,`audio`],description:`Media type (default: video)`}},required:[`src`],additionalProperties:!1}}}async execute(e){try{let{src:t,type:n=`video`}=e,r=await this.mediaInfoService.canDecode(t);if(n===`audio`){let e=await this.mediaInfoService.getAudioDuration(t);return this.successJson({src:t,type:`audio`,duration:e,canDecode:r})}let[i,a]=await Promise.all([this.mediaInfoService.getVideoDuration(t),this.mediaInfoService.getVideoDimensions(t)]);return this.successJson({src:t,type:`video`,duration:i,width:a.width,height:a.height,canDecode:r})}catch(e){return this.error(e instanceof Error?e.message:`Unknown error`)}}};z=R=_([a(),A(0,i(O.MediaInfoService)),k(`design:paramtypes`,[typeof(L=S!==void 0&&S)==`function`?L:Object])],z);var B,V;let H=class extends D{static{V=this}static TOOL_NAME=`import_srt`;constructor(e){super(),this.captionService=e}getDefinition(){return{name:V.TOOL_NAME,description:`Parse SRT subtitle file content into structured Caption format for video overlay`,inputSchema:{type:`object`,properties:{srtContent:{type:`string`,description:`SRT file content as a string`}},required:[`srtContent`],additionalProperties:!1}}}async execute(e){try{let t=this.captionService.parseSrt(e.srtContent);return this.successJson({captionCount:t.length,captions:t})}catch(e){return this.error(e instanceof Error?e.message:`Unknown error`)}}};H=V=_([a(),A(0,i(O.CaptionService)),k(`design:paramtypes`,[typeof(B=v!==void 0&&v)==`function`?B:Object])],H);var U;let W=class extends D{static{U=this}static TOOL_NAME=`list_compositions`;getDefinition(){return{name:U.TOOL_NAME,description:`List all available Remotion compositions with their schemas, dimensions, and format info`,inputSchema:{type:`object`,properties:{},required:[],additionalProperties:!1}}}async execute(){let e=[{id:`Main`,description:`Main 16:9 landscape composition`,width:1920,height:1080,fps:30,durationInFrames:null,defaultProps:{clips:[],audioVolume:1,backgroundColor:`#000000`}},{id:`Vertical`,description:`Vertical 9:16 portrait composition (TikTok, Instagram Stories)`,width:1080,height:1920,fps:30,durationInFrames:null,defaultProps:{clips:[],audioVolume:1,backgroundColor:`#000000`}},{id:`Square`,description:`Square 1:1 composition (Instagram posts)`,width:1080,height:1080,fps:30,durationInFrames:null,defaultProps:{clips:[],audioVolume:1,backgroundColor:`#000000`}}];return this.successJson({compositionCount:e.length,compositions:e})}};W=U=_([a()],W);var G,K;let q=class extends D{static{K=this}static TOOL_NAME=`preview_frame`;constructor(e){super(),this.renderService=e}getDefinition(){return{name:K.TOOL_NAME,description:`Render a single frame from a composition as a PNG or JPEG image for preview`,inputSchema:{type:`object`,properties:{compositionId:{type:`string`,description:`Remotion composition ID (e.g., "Main", "Vertical", "Square")`},inputProps:{type:`object`,description:`JSON props to pass to the composition`},outputPath:{type:`string`,description:`Output file path for the rendered image`},frame:{type:`number`,description:`Frame number to render (default: 0)`},imageFormat:{type:`string`,enum:[`png`,`jpeg`],description:`Image format (default: png)`}},required:[`compositionId`,`inputProps`,`outputPath`],additionalProperties:!1}}}async execute(e){try{let t={compositionId:e.compositionId,inputProps:e.inputProps,outputPath:e.outputPath,frame:e.frame,imageFormat:e.imageFormat},n=await this.renderService.renderStill(t);return this.successJson(n)}catch(e){return this.error(e instanceof Error?e.message:`Unknown error`)}}};q=K=_([a(),A(0,i(O.RenderService)),k(`design:paramtypes`,[typeof(G=C!==void 0&&C)==`function`?G:Object])],q);var J,Y;let X=class extends D{static{Y=this}static TOOL_NAME=`render_video`;constructor(e){super(),this.renderService=e}getDefinition(){return{name:Y.TOOL_NAME,description:`Render a video from JSON props using Remotion`,inputSchema:{type:`object`,properties:{compositionId:{type:`string`,description:`Remotion composition ID (e.g., "Main", "Vertical", "Square")`},inputProps:{type:`object`,description:`JSON props to pass to the composition`},outputPath:{type:`string`,description:`Output file path for the rendered video`},codec:{type:`string`,enum:[`h264`,`h265`,`vp8`,`vp9`],description:`Video codec (default: h264)`}},required:[`compositionId`,`inputProps`,`outputPath`],additionalProperties:!1}}}async execute(e){try{let t={compositionId:e.compositionId,inputProps:e.inputProps,outputPath:e.outputPath,codec:e.codec},n=await this.renderService.render(t);return this.successJson(n)}catch(e){return this.error(e instanceof Error?e.message:`Unknown error`)}}};X=Y=_([a(),A(0,i(O.RenderService)),k(`design:paramtypes`,[typeof(J=C!==void 0&&C)==`function`?J:Object])],X);const Z=new r(e=>{e.bind(O.RenderService).to(C).inSingletonScope(),e.bind(O.MediaInfoService).to(S).inSingletonScope(),e.bind(O.CaptionService).to(v).inSingletonScope(),e.bind(O.FontService).to(b).inSingletonScope(),e.bind(O.VoiceoverService).to(E).inSingletonScope()}),Q=new r(e=>{e.bind(O.Tool).to(X).inSingletonScope(),e.bind(O.Tool).to(W).inSingletonScope(),e.bind(O.Tool).to(z).inSingletonScope(),e.bind(O.Tool).to(q).inSingletonScope(),e.bind(O.Tool).to(N).inSingletonScope(),e.bind(O.Tool).to(H).inSingletonScope(),e.bind(O.Tool).to(I).inSingletonScope()});function $(){let e=new n;return e.load(Z,Q),e}function ee(e){let t=new p({name:`video-editor-mcp`,version:`0.1.0`},{capabilities:{tools:{}}}),n=e.getAll(O.Tool),r=new Map;for(let e of n){let t=e.getDefinition();r.set(t.name,e)}return t.setRequestHandler(h,async()=>({tools:n.map(e=>e.getDefinition())})),t.setRequestHandler(m,async e=>{let{name:t,arguments:n}=e.params,i=r.get(t);if(!i)throw Error(`Unknown tool: ${t}`);return await i.execute(n)}),t}var te=class{server;transport=null;constructor(e){this.server=e}async start(){this.transport=new g,await this.server.connect(this.transport),console.error(`video-editor MCP server started on stdio`)}async stop(){this.transport&&=(await this.transport.close(),null)}};export{q as a,z as c,O as d,D as f,X as i,I as l,ee as n,W as o,C as p,$ as r,H as s,te as t,N as u};
4
+ //# sourceMappingURL=stdio-CB5Y_bMX.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stdio-CB5Y_bMX.mjs","names":["CaptionService","captions: Caption[]","textLines: string[]","pages: CaptionPage[]","currentPage: CaptionPage","FontService","execFileAsync","MediaInfoService","RenderService","VoiceoverService","BaseTool","GenerateCaptionsTool","captionService: CaptionService","captionArray: Caption[]","GenerateVoiceoverTool","voiceoverService: VoiceoverService","GetMediaInfoTool","mediaInfoService: MediaInfoService","duration","ImportSrtTool","captionService: CaptionService","ListCompositionsTool","PreviewFrameTool","renderService: RenderService","options: RenderStillOptions","RenderVideoTool","renderService: RenderService","options: RenderOptions"],"sources":["../src/services/CaptionService.ts","../src/services/FontService.ts","../src/services/MediaInfoService.ts","../src/services/RenderService.ts","../src/services/VoiceoverService.ts","../src/tools/BaseTool.ts","../src/types/index.ts","../src/tools/GenerateCaptionsTool.ts","../src/tools/GenerateVoiceoverTool.ts","../src/tools/GetMediaInfoTool.ts","../src/tools/ImportSrtTool.ts","../src/tools/ListCompositionsTool.ts","../src/tools/PreviewFrameTool.ts","../src/tools/RenderVideoTool.ts","../src/container/index.ts","../src/server/index.ts","../src/transports/stdio.ts"],"sourcesContent":["/**\n * CaptionService\n *\n * DESIGN PATTERNS:\n * - Service pattern for business logic encapsulation\n * - Dependency injection via InversifyJS\n * - Single responsibility principle\n *\n * CODING STANDARDS:\n * - Use async/await for asynchronous operations\n * - Throw descriptive errors for error cases\n * - Keep methods focused and well-named\n */\n\nimport { injectable } from 'inversify';\nimport type { Caption } from '../types/index.js';\n\n/**\n * TikTok-style caption page with grouped tokens\n */\nexport interface CaptionPage {\n startMs: number;\n endMs: number;\n tokens: Caption[];\n}\n\n@injectable()\nexport class CaptionService {\n /**\n * Parse SRT format content into Caption array\n *\n * SRT format:\n * 1\n * 00:00:01,000 --> 00:00:04,000\n * Welcome to the video\n *\n * 2\n * 00:00:04,500 --> 00:00:08,000\n * This is the second caption\n */\n parseSrt(srtContent: string): Caption[] {\n const captions: Caption[] = [];\n const lines = srtContent.split('\\n');\n let i = 0;\n\n while (i < lines.length) {\n // Skip empty lines\n if (!lines[i]?.trim()) {\n i++;\n continue;\n }\n\n // Skip sequence number\n i++;\n\n // Parse timestamp line\n const timestampLine = lines[i];\n if (!timestampLine?.includes('-->')) {\n i++;\n continue;\n }\n\n const [startStr, endStr] = timestampLine.split('-->').map((s) => s.trim());\n if (!startStr || !endStr) {\n i++;\n continue;\n }\n\n const startMs = this.parseTimestamp(startStr);\n const endMs = this.parseTimestamp(endStr);\n\n // Collect text lines until empty line or end\n i++;\n const textLines: string[] = [];\n while (i < lines.length && lines[i]?.trim()) {\n textLines.push(lines[i] ?? '');\n i++;\n }\n\n const text = textLines.join('\\n');\n if (text) {\n captions.push({ text, startMs, endMs });\n }\n }\n\n return captions;\n }\n\n /**\n * Parse SRT timestamp to milliseconds\n * Format: HH:MM:SS,mmm\n */\n private parseTimestamp(timestamp: string): number {\n const [time, ms] = timestamp.split(',');\n if (!time || !ms) {\n throw new Error(`Invalid timestamp format: ${timestamp}`);\n }\n\n const [hours, minutes, seconds] = time.split(':').map(Number);\n if (hours === undefined || minutes === undefined || seconds === undefined) {\n throw new Error(`Invalid time format: ${time}`);\n }\n\n const totalSeconds = hours * 3600 + minutes * 60 + seconds;\n return totalSeconds * 1000 + Number(ms);\n }\n\n /**\n * Create TikTok-style caption pages by grouping captions\n * Combines tokens that appear within combineMs milliseconds of each other\n */\n createTikTokCaptions(captions: Caption[], combineMs: number): { pages: CaptionPage[] } {\n const pages: CaptionPage[] = [];\n\n if (captions.length === 0) {\n return { pages };\n }\n\n let currentPage: CaptionPage = {\n startMs: captions[0]?.startMs ?? 0,\n endMs: captions[0]?.endMs ?? 0,\n tokens: [captions[0] as Caption],\n };\n\n for (let i = 1; i < captions.length; i++) {\n const caption = captions[i];\n if (!caption) continue;\n\n const gap = caption.startMs - currentPage.endMs;\n\n if (gap <= combineMs) {\n // Combine into current page\n currentPage.tokens.push(caption);\n currentPage.endMs = caption.endMs;\n } else {\n // Start new page\n pages.push(currentPage);\n currentPage = {\n startMs: caption.startMs,\n endMs: caption.endMs,\n tokens: [caption],\n };\n }\n }\n\n // Add final page\n if (currentPage.tokens.length > 0) {\n pages.push(currentPage);\n }\n\n return { pages };\n }\n}\n","/**\n * FontService\n *\n * DESIGN PATTERNS:\n * - Service pattern for business logic encapsulation\n * - Dependency injection via InversifyJS\n * - Single responsibility principle\n *\n * CODING STANDARDS:\n * - Use async/await for asynchronous operations\n * - Throw descriptive errors for error cases\n * - Keep methods focused and well-named\n */\n\nimport { injectable } from 'inversify';\n\n/**\n * Curated list of popular Google Fonts\n * These fonts are widely used and well-supported\n */\nconst AVAILABLE_GOOGLE_FONTS = [\n 'Inter',\n 'Roboto',\n 'Open Sans',\n 'Montserrat',\n 'Lato',\n 'Poppins',\n 'Oswald',\n 'Raleway',\n 'Playfair Display',\n 'Merriweather',\n 'Source Sans Pro',\n 'PT Sans',\n 'Ubuntu',\n 'Nunito',\n 'Rubik',\n 'Work Sans',\n 'Karla',\n 'Noto Sans',\n 'Fira Sans',\n 'DM Sans',\n] as const;\n\n@injectable()\nexport class FontService {\n /**\n * Get list of available Google Fonts\n */\n getAvailableGoogleFonts(): string[] {\n return [...AVAILABLE_GOOGLE_FONTS];\n }\n\n /**\n * Validate if a font family is in the available list\n * Case-insensitive comparison\n */\n validateFontFamily(family: string): boolean {\n const normalizedFamily = family.toLowerCase();\n return AVAILABLE_GOOGLE_FONTS.some((font) => font.toLowerCase() === normalizedFamily);\n }\n}\n","/**\n * MediaInfoService\n *\n * DESIGN PATTERNS:\n * - Service pattern for business logic encapsulation\n * - Dependency injection via InversifyJS\n * - Single responsibility principle\n *\n * CODING STANDARDS:\n * - Use async/await for asynchronous operations\n * - Throw descriptive errors for error cases\n * - Keep methods focused and well-named\n */\n\nimport { execFile } from 'node:child_process';\nimport { promisify } from 'node:util';\nimport { injectable } from 'inversify';\n\nconst execFileAsync = promisify(execFile);\n\n@injectable()\nexport class MediaInfoService {\n /**\n * Get video duration in seconds using ffprobe\n */\n async getVideoDuration(src: string): Promise<number> {\n try {\n const { stdout } = await execFileAsync('ffprobe', [\n '-v',\n 'error',\n '-show_entries',\n 'format=duration',\n '-of',\n 'json',\n src,\n ]);\n\n const result = JSON.parse(stdout);\n const duration = Number.parseFloat(result.format?.duration ?? '0');\n\n if (Number.isNaN(duration) || duration <= 0) {\n throw new Error(`Invalid duration for video: ${src}`);\n }\n\n return duration;\n } catch (error) {\n throw new Error(`Failed to get video duration for ${src}: ${error instanceof Error ? error.message : String(error)}`, {\n cause: error,\n });\n }\n }\n\n /**\n * Get audio duration in seconds using ffprobe\n */\n async getAudioDuration(src: string): Promise<number> {\n try {\n const { stdout } = await execFileAsync('ffprobe', [\n '-v',\n 'error',\n '-show_entries',\n 'format=duration',\n '-of',\n 'json',\n src,\n ]);\n\n const result = JSON.parse(stdout);\n const duration = Number.parseFloat(result.format?.duration ?? '0');\n\n if (Number.isNaN(duration) || duration <= 0) {\n throw new Error(`Invalid duration for audio: ${src}`);\n }\n\n return duration;\n } catch (error) {\n throw new Error(`Failed to get audio duration for ${src}: ${error instanceof Error ? error.message : String(error)}`, {\n cause: error,\n });\n }\n }\n\n /**\n * Get video dimensions (width and height) using ffprobe\n */\n async getVideoDimensions(src: string): Promise<{ width: number; height: number }> {\n try {\n const { stdout } = await execFileAsync('ffprobe', [\n '-v',\n 'error',\n '-select_streams',\n 'v:0',\n '-show_entries',\n 'stream=width,height',\n '-of',\n 'json',\n src,\n ]);\n\n const result = JSON.parse(stdout);\n const stream = result.streams?.[0];\n\n if (!stream?.width || !stream?.height) {\n throw new Error(`No video stream found in: ${src}`);\n }\n\n return {\n width: Number(stream.width),\n height: Number(stream.height),\n };\n } catch (error) {\n throw new Error(\n `Failed to get video dimensions for ${src}: ${error instanceof Error ? error.message : String(error)}`,\n { cause: error },\n );\n }\n }\n\n /**\n * Check if a media file can be decoded using ffprobe\n */\n async canDecode(src: string): Promise<boolean> {\n try {\n await execFileAsync('ffprobe', ['-v', 'error', '-show_entries', 'format=duration', '-of', 'json', src]);\n return true;\n } catch {\n return false;\n }\n }\n}\n","/**\n * RenderService\n *\n * Uses @remotion/renderer to render videos from JSON props.\n */\n\nimport path from 'node:path';\nimport { bundle } from '@remotion/bundler';\nimport { renderMedia, renderStill, selectComposition } from '@remotion/renderer';\nimport { injectable } from 'inversify';\nimport type { RenderOptions, RenderResult, RenderStillOptions } from '../types/index.js';\n\n@injectable()\nexport class RenderService {\n private bundlePromise: Promise<string> | null = null;\n\n private async getServeUrl(): Promise<string> {\n if (!this.bundlePromise) {\n const projectRoot = path.resolve(import.meta.dirname, '../..');\n this.bundlePromise = bundle({\n entryPoint: path.resolve(projectRoot, 'src/remotion/index.ts'),\n publicDir: path.resolve(projectRoot, 'public'),\n webpackOverride: (config) => ({\n ...config,\n resolve: {\n ...config.resolve,\n extensionAlias: {\n '.js': ['.ts', '.tsx', '.js', '.jsx'],\n },\n },\n }),\n });\n }\n return this.bundlePromise;\n }\n\n async render(options: RenderOptions): Promise<RenderResult> {\n const serveUrl = await this.getServeUrl();\n\n const composition = await selectComposition({\n serveUrl,\n id: options.compositionId,\n inputProps: options.inputProps,\n });\n\n await renderMedia({\n composition,\n serveUrl,\n codec: options.codec ?? 'h264',\n outputLocation: options.outputPath,\n inputProps: options.inputProps,\n });\n\n return { outputPath: options.outputPath };\n }\n\n async renderStill(options: RenderStillOptions): Promise<RenderResult> {\n const serveUrl = await this.getServeUrl();\n\n const composition = await selectComposition({\n serveUrl,\n id: options.compositionId,\n inputProps: options.inputProps,\n });\n\n await renderStill({\n composition,\n serveUrl,\n output: options.outputPath,\n frame: options.frame ?? 0,\n imageFormat: options.imageFormat ?? 'png',\n inputProps: options.inputProps,\n });\n\n return { outputPath: options.outputPath };\n }\n}\n","/**\n * VoiceoverService\n *\n * DESIGN PATTERNS:\n * - Service pattern for business logic encapsulation\n * - Dependency injection via InversifyJS\n * - Single responsibility principle\n *\n * CODING STANDARDS:\n * - Use async/await for asynchronous operations\n * - Throw descriptive errors for error cases\n * - Keep methods focused and well-named\n * - Use execFile for command execution (prevents command injection)\n */\n\nimport { execFile } from 'node:child_process';\nimport fs from 'node:fs/promises';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { promisify } from 'node:util';\nimport { injectable } from 'inversify';\n\nconst execFileAsync = promisify(execFile);\n\n/**\n * Available Kokoro TTS voices\n */\nconst AVAILABLE_VOICES = [\n 'af_sarah',\n 'af_nicole',\n 'af_bella',\n 'af_sky',\n 'am_adam',\n 'am_michael',\n 'bf_emma',\n 'bf_isabella',\n 'bm_george',\n 'bm_lewis',\n] as const;\n\nexport interface VoiceoverOptions {\n text: string;\n voice?: string;\n speed?: number;\n outputPath: string;\n}\n\nexport interface VoiceoverResult {\n outputPath: string;\n duration?: number;\n}\n\n@injectable()\nexport class VoiceoverService {\n /**\n * Check if kokoro-tts CLI is installed\n */\n async isAvailable(): Promise<boolean> {\n try {\n await execFileAsync('kokoro-tts', ['--version']);\n return true;\n } catch {\n return false;\n }\n }\n\n /**\n * Generate voiceover using Kokoro TTS\n */\n async generateVoiceover(options: VoiceoverOptions): Promise<VoiceoverResult> {\n const { text, voice = 'af_sarah', speed = 1.0, outputPath } = options;\n\n // Create temp file for text input\n const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'kokoro-'));\n const tempTextFile = path.join(tempDir, 'input.txt');\n\n try {\n // Write text to temp file\n await fs.writeFile(tempTextFile, text, 'utf-8');\n\n // Run kokoro-tts CLI\n const args = [tempTextFile, outputPath, '--voice', voice, '--speed', speed.toString()];\n\n await execFileAsync('kokoro-tts', args);\n\n return { outputPath };\n } catch (error) {\n throw new Error(`Failed to generate voiceover: ${error instanceof Error ? error.message : String(error)}`, {\n cause: error,\n });\n } finally {\n // Cleanup temp file\n try {\n await fs.rm(tempDir, { recursive: true, force: true });\n } catch {\n // Ignore cleanup errors\n }\n }\n }\n\n /**\n * Get list of available Kokoro voice IDs\n */\n listVoices(): string[] {\n return [...AVAILABLE_VOICES];\n }\n}\n","/**\n * BaseTool - Abstract base class for video editor MCP tools\n *\n * DESIGN PATTERNS:\n * - Template Method pattern for consistent tool interface\n * - Dependency Injection via InversifyJS for services\n * - Helper methods for response formatting\n *\n * CODING STANDARDS:\n * - Use async/await for asynchronous operations\n * - Return CallToolResult with content array\n * - Handle errors gracefully with isError flag\n *\n * AVOID:\n * - Business logic in base class (delegate to services)\n * - Exposing internal errors to users\n */\n\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport { injectable } from 'inversify';\n\n@injectable()\nexport abstract class BaseTool {\n /**\n * Creates a success response with text content.\n * @param text - The success message or content.\n * @returns A CallToolResult with the content.\n */\n protected success(text: string): CallToolResult {\n return {\n content: [{ type: 'text', text }],\n };\n }\n\n /**\n * Creates a success response with JSON content.\n * @param data - The data to serialize as JSON.\n * @returns A CallToolResult with the JSON content.\n */\n protected successJson(data: unknown): CallToolResult {\n return {\n content: [{ type: 'text', text: JSON.stringify(data, null, 2) }],\n };\n }\n\n /**\n * Creates an error response.\n * @param message - The error message.\n * @returns A CallToolResult with isError flag set.\n */\n protected error(message: string): CallToolResult {\n return {\n content: [{ type: 'text', text: `Error: ${message}` }],\n isError: true,\n };\n }\n\n /**\n * Wraps execution with standard error handling.\n * @param fn - The async function to execute.\n * @returns The result of the function or an error response.\n */\n protected async safeExecute<T>(fn: () => Promise<T>): Promise<T | CallToolResult> {\n try {\n return await fn();\n } catch (err) {\n return this.error(err instanceof Error ? err.message : 'Unknown error');\n }\n }\n}\n","/**\n * Shared TypeScript Types\n *\n * DESIGN PATTERNS:\n * - Type-first development with Zod schema inference\n * - Interface segregation\n *\n * CODING STANDARDS:\n * - Export all shared types from this file\n * - Derive types from Zod schemas via z.infer<>\n */\n\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport type { z } from 'zod';\nimport type {\n AudioClipSchema,\n ClipSchema,\n GifClipSchema,\n ImageClipSchema,\n LottieClipSchema,\n SubtitleClipSchema,\n TextClipSchema,\n VideoClipSchema,\n} from '../schemas/clips.js';\nimport type {\n CaptionConfigSchema,\n CaptionSchema,\n FontConfigSchema,\n MainCompositionSchema,\n} from '../schemas/compositions.js';\n\n/**\n * DI Container Symbols\n */\nexport const TYPES = {\n RenderService: Symbol.for('RenderService'),\n MediaInfoService: Symbol.for('MediaInfoService'),\n CaptionService: Symbol.for('CaptionService'),\n FontService: Symbol.for('FontService'),\n VoiceoverService: Symbol.for('VoiceoverService'),\n Tool: Symbol.for('Tool'),\n} as const;\n\n/**\n * Tool definition for MCP\n */\nexport interface ToolDefinition {\n name: string;\n description: string;\n inputSchema: {\n type: string;\n properties: Record<string, unknown>;\n required?: string[];\n additionalProperties?: boolean;\n };\n}\n\n/**\n * Base tool interface following MCP SDK patterns\n */\nexport interface Tool<TInput = unknown> {\n getDefinition(): ToolDefinition;\n execute(input: TInput): Promise<CallToolResult>;\n}\n\n/**\n * Render options for Remotion\n */\nexport interface RenderOptions {\n compositionId: string;\n inputProps: Record<string, unknown>;\n outputPath: string;\n codec?: 'h264' | 'h265' | 'vp8' | 'vp9';\n}\n\n/**\n * Render still options\n */\nexport interface RenderStillOptions {\n compositionId: string;\n inputProps: Record<string, unknown>;\n outputPath: string;\n frame?: number;\n imageFormat?: 'png' | 'jpeg';\n}\n\n/**\n * Render result from Remotion\n */\nexport interface RenderResult {\n outputPath: string;\n}\n\n// Clip types derived from Zod schemas\nexport type VideoClip = z.infer<typeof VideoClipSchema>;\nexport type ImageClip = z.infer<typeof ImageClipSchema>;\nexport type TextClip = z.infer<typeof TextClipSchema>;\nexport type AudioClip = z.infer<typeof AudioClipSchema>;\nexport type SubtitleClip = z.infer<typeof SubtitleClipSchema>;\nexport type GifClip = z.infer<typeof GifClipSchema>;\nexport type LottieClip = z.infer<typeof LottieClipSchema>;\nexport type Clip = z.infer<typeof ClipSchema>;\n\n// Composition types derived from Zod schemas\nexport type Caption = z.infer<typeof CaptionSchema>;\nexport type CaptionConfig = z.infer<typeof CaptionConfigSchema>;\nexport type FontConfig = z.infer<typeof FontConfigSchema>;\nexport type MainCompositionProps = z.infer<typeof MainCompositionSchema>;\n","/**\n * GenerateCaptionsTool\n *\n * Creates TikTok-style caption pages from a caption array for video overlay.\n */\n\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport { inject, injectable } from 'inversify';\nimport { CaptionService } from '../services/CaptionService.js';\nimport type { Caption, Tool, ToolDefinition } from '../types/index.js';\nimport { TYPES } from '../types/index.js';\nimport { BaseTool } from './BaseTool.js';\n\ninterface GenerateCaptionsToolInput {\n captions: Array<{ text: string; startMs: number; endMs: number }>;\n combineMs?: number;\n}\n\n@injectable()\nexport class GenerateCaptionsTool extends BaseTool implements Tool<GenerateCaptionsToolInput> {\n static readonly TOOL_NAME = 'generate_captions';\n\n constructor(@inject(TYPES.CaptionService) private captionService: CaptionService) {\n super();\n }\n\n getDefinition(): ToolDefinition {\n return {\n name: GenerateCaptionsTool.TOOL_NAME,\n description: 'Create TikTok-style caption pages from a caption array for video overlay',\n inputSchema: {\n type: 'object',\n properties: {\n captions: {\n type: 'array',\n description: 'Array of caption objects with text, startMs, and endMs',\n items: {\n type: 'object',\n properties: {\n text: { type: 'string' },\n startMs: { type: 'number' },\n endMs: { type: 'number' },\n },\n required: ['text', 'startMs', 'endMs'],\n },\n },\n combineMs: {\n type: 'number',\n description: 'Milliseconds threshold for combining captions into pages (default: 100)',\n },\n },\n required: ['captions'],\n additionalProperties: false,\n },\n };\n }\n\n async execute(input: GenerateCaptionsToolInput): Promise<CallToolResult> {\n try {\n const { captions, combineMs = 100 } = input;\n const captionArray: Caption[] = captions.map((c) => ({\n text: c.text,\n startMs: c.startMs,\n endMs: c.endMs,\n }));\n const result = this.captionService.createTikTokCaptions(captionArray, combineMs);\n return this.successJson(result);\n } catch (error) {\n return this.error(error instanceof Error ? error.message : 'Unknown error');\n }\n }\n}\n","/**\n * GenerateVoiceoverTool\n *\n * Generates voiceover audio from text using Kokoro TTS local engine.\n */\n\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport { inject, injectable } from 'inversify';\nimport { VoiceoverService } from '../services/VoiceoverService.js';\nimport type { Tool, ToolDefinition } from '../types/index.js';\nimport { TYPES } from '../types/index.js';\nimport { BaseTool } from './BaseTool.js';\n\ninterface GenerateVoiceoverToolInput {\n text: string;\n voice?: string;\n speed?: number;\n outputPath: string;\n}\n\n@injectable()\nexport class GenerateVoiceoverTool extends BaseTool implements Tool<GenerateVoiceoverToolInput> {\n static readonly TOOL_NAME = 'generate_voiceover';\n\n constructor(@inject(TYPES.VoiceoverService) private voiceoverService: VoiceoverService) {\n super();\n }\n\n getDefinition(): ToolDefinition {\n return {\n name: GenerateVoiceoverTool.TOOL_NAME,\n description: 'Generate voiceover audio from text using Kokoro TTS local engine',\n inputSchema: {\n type: 'object',\n properties: {\n text: {\n type: 'string',\n description: 'Text to convert to speech',\n },\n voice: {\n type: 'string',\n description:\n 'Voice ID (default: af_sarah). Options: af_sarah, af_nicole, af_bella, af_sky, am_adam, am_michael, bf_emma, bf_isabella, bm_george, bm_lewis',\n },\n speed: {\n type: 'number',\n description: 'Speech speed multiplier (default: 1.0)',\n },\n outputPath: {\n type: 'string',\n description: 'Output file path for the generated audio',\n },\n },\n required: ['text', 'outputPath'],\n additionalProperties: false,\n },\n };\n }\n\n async execute(input: GenerateVoiceoverToolInput): Promise<CallToolResult> {\n try {\n const isAvailable = await this.voiceoverService.isAvailable();\n if (!isAvailable) {\n return this.error('Kokoro TTS is not installed. Install it with: pip install kokoro-tts');\n }\n\n const result = await this.voiceoverService.generateVoiceover({\n text: input.text,\n voice: input.voice,\n speed: input.speed,\n outputPath: input.outputPath,\n });\n\n return this.successJson(result);\n } catch (error) {\n return this.error(error instanceof Error ? error.message : 'Unknown error');\n }\n }\n}\n","/**\n * GetMediaInfoTool\n *\n * Gets duration, dimensions, and decodability info for video/audio files.\n */\n\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport { inject, injectable } from 'inversify';\nimport { MediaInfoService } from '../services/MediaInfoService.js';\nimport type { Tool, ToolDefinition } from '../types/index.js';\nimport { TYPES } from '../types/index.js';\nimport { BaseTool } from './BaseTool.js';\n\ninterface GetMediaInfoToolInput {\n src: string;\n type?: 'video' | 'audio';\n}\n\n@injectable()\nexport class GetMediaInfoTool extends BaseTool implements Tool<GetMediaInfoToolInput> {\n static readonly TOOL_NAME = 'get_media_info';\n\n constructor(@inject(TYPES.MediaInfoService) private mediaInfoService: MediaInfoService) {\n super();\n }\n\n getDefinition(): ToolDefinition {\n return {\n name: GetMediaInfoTool.TOOL_NAME,\n description: 'Get duration, dimensions, and decodability info for video/audio files',\n inputSchema: {\n type: 'object',\n properties: {\n src: {\n type: 'string',\n description: 'Path to the media file',\n },\n type: {\n type: 'string',\n enum: ['video', 'audio'],\n description: 'Media type (default: video)',\n },\n },\n required: ['src'],\n additionalProperties: false,\n },\n };\n }\n\n async execute(input: GetMediaInfoToolInput): Promise<CallToolResult> {\n try {\n const { src, type = 'video' } = input;\n const canDecode = await this.mediaInfoService.canDecode(src);\n\n if (type === 'audio') {\n const duration = await this.mediaInfoService.getAudioDuration(src);\n return this.successJson({\n src,\n type: 'audio',\n duration,\n canDecode,\n });\n }\n\n const [duration, dimensions] = await Promise.all([\n this.mediaInfoService.getVideoDuration(src),\n this.mediaInfoService.getVideoDimensions(src),\n ]);\n\n return this.successJson({\n src,\n type: 'video',\n duration,\n width: dimensions.width,\n height: dimensions.height,\n canDecode,\n });\n } catch (error) {\n return this.error(error instanceof Error ? error.message : 'Unknown error');\n }\n }\n}\n","/**\n * ImportSrtTool\n *\n * Parses SRT subtitle file content into structured Caption format for video overlay.\n */\n\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport { inject, injectable } from 'inversify';\nimport { CaptionService } from '../services/CaptionService.js';\nimport type { Tool, ToolDefinition } from '../types/index.js';\nimport { TYPES } from '../types/index.js';\nimport { BaseTool } from './BaseTool.js';\n\ninterface ImportSrtToolInput {\n srtContent: string;\n}\n\n@injectable()\nexport class ImportSrtTool extends BaseTool implements Tool<ImportSrtToolInput> {\n static readonly TOOL_NAME = 'import_srt';\n\n constructor(@inject(TYPES.CaptionService) private captionService: CaptionService) {\n super();\n }\n\n getDefinition(): ToolDefinition {\n return {\n name: ImportSrtTool.TOOL_NAME,\n description: 'Parse SRT subtitle file content into structured Caption format for video overlay',\n inputSchema: {\n type: 'object',\n properties: {\n srtContent: {\n type: 'string',\n description: 'SRT file content as a string',\n },\n },\n required: ['srtContent'],\n additionalProperties: false,\n },\n };\n }\n\n async execute(input: ImportSrtToolInput): Promise<CallToolResult> {\n try {\n const captions = this.captionService.parseSrt(input.srtContent);\n return this.successJson({\n captionCount: captions.length,\n captions,\n });\n } catch (error) {\n return this.error(error instanceof Error ? error.message : 'Unknown error');\n }\n }\n}\n","/**\n * ListCompositionsTool\n *\n * Lists all available Remotion compositions with their metadata.\n */\n\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport { injectable } from 'inversify';\nimport type { Tool, ToolDefinition } from '../types/index.js';\nimport { BaseTool } from './BaseTool.js';\n\n// eslint-disable-next-line @typescript-eslint/no-empty-object-type\ntype ListCompositionsToolInput = {};\n\n@injectable()\nexport class ListCompositionsTool extends BaseTool implements Tool<ListCompositionsToolInput> {\n static readonly TOOL_NAME = 'list_compositions';\n\n getDefinition(): ToolDefinition {\n return {\n name: ListCompositionsTool.TOOL_NAME,\n description: 'List all available Remotion compositions with their schemas, dimensions, and format info',\n inputSchema: {\n type: 'object',\n properties: {},\n required: [],\n additionalProperties: false,\n },\n };\n }\n\n async execute(): Promise<CallToolResult> {\n const compositions = [\n {\n id: 'Main',\n description: 'Main 16:9 landscape composition',\n width: 1920,\n height: 1080,\n fps: 30,\n durationInFrames: null,\n defaultProps: {\n clips: [],\n audioVolume: 1,\n backgroundColor: '#000000',\n },\n },\n {\n id: 'Vertical',\n description: 'Vertical 9:16 portrait composition (TikTok, Instagram Stories)',\n width: 1080,\n height: 1920,\n fps: 30,\n durationInFrames: null,\n defaultProps: {\n clips: [],\n audioVolume: 1,\n backgroundColor: '#000000',\n },\n },\n {\n id: 'Square',\n description: 'Square 1:1 composition (Instagram posts)',\n width: 1080,\n height: 1080,\n fps: 30,\n durationInFrames: null,\n defaultProps: {\n clips: [],\n audioVolume: 1,\n backgroundColor: '#000000',\n },\n },\n ];\n\n return this.successJson({\n compositionCount: compositions.length,\n compositions,\n });\n }\n}\n","/**\n * PreviewFrameTool\n *\n * Renders a single frame from a composition as a PNG or JPEG image for preview.\n */\n\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport { inject, injectable } from 'inversify';\nimport { RenderService } from '../services/RenderService.js';\nimport type { RenderStillOptions, Tool, ToolDefinition } from '../types/index.js';\nimport { TYPES } from '../types/index.js';\nimport { BaseTool } from './BaseTool.js';\n\ninterface PreviewFrameToolInput {\n compositionId: string;\n inputProps: Record<string, unknown>;\n outputPath: string;\n frame?: number;\n imageFormat?: 'png' | 'jpeg';\n}\n\n@injectable()\nexport class PreviewFrameTool extends BaseTool implements Tool<PreviewFrameToolInput> {\n static readonly TOOL_NAME = 'preview_frame';\n\n constructor(@inject(TYPES.RenderService) private renderService: RenderService) {\n super();\n }\n\n getDefinition(): ToolDefinition {\n return {\n name: PreviewFrameTool.TOOL_NAME,\n description: 'Render a single frame from a composition as a PNG or JPEG image for preview',\n inputSchema: {\n type: 'object',\n properties: {\n compositionId: {\n type: 'string',\n description: 'Remotion composition ID (e.g., \"Main\", \"Vertical\", \"Square\")',\n },\n inputProps: {\n type: 'object',\n description: 'JSON props to pass to the composition',\n },\n outputPath: {\n type: 'string',\n description: 'Output file path for the rendered image',\n },\n frame: {\n type: 'number',\n description: 'Frame number to render (default: 0)',\n },\n imageFormat: {\n type: 'string',\n enum: ['png', 'jpeg'],\n description: 'Image format (default: png)',\n },\n },\n required: ['compositionId', 'inputProps', 'outputPath'],\n additionalProperties: false,\n },\n };\n }\n\n async execute(input: PreviewFrameToolInput): Promise<CallToolResult> {\n try {\n const options: RenderStillOptions = {\n compositionId: input.compositionId,\n inputProps: input.inputProps,\n outputPath: input.outputPath,\n frame: input.frame,\n imageFormat: input.imageFormat,\n };\n const result = await this.renderService.renderStill(options);\n return this.successJson(result);\n } catch (error) {\n return this.error(error instanceof Error ? error.message : 'Unknown error');\n }\n }\n}\n","/**\n * RenderVideoTool\n *\n * Renders videos from JSON props using Remotion.\n */\n\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport { inject, injectable } from 'inversify';\nimport { RenderService } from '../services/RenderService.js';\nimport type { RenderOptions, Tool, ToolDefinition } from '../types/index.js';\nimport { TYPES } from '../types/index.js';\nimport { BaseTool } from './BaseTool.js';\n\ninterface RenderVideoToolInput {\n compositionId: string;\n inputProps: Record<string, unknown>;\n outputPath: string;\n codec?: 'h264' | 'h265' | 'vp8' | 'vp9';\n}\n\n@injectable()\nexport class RenderVideoTool extends BaseTool implements Tool<RenderVideoToolInput> {\n static readonly TOOL_NAME = 'render_video';\n\n constructor(@inject(TYPES.RenderService) private renderService: RenderService) {\n super();\n }\n\n getDefinition(): ToolDefinition {\n return {\n name: RenderVideoTool.TOOL_NAME,\n description: 'Render a video from JSON props using Remotion',\n inputSchema: {\n type: 'object',\n properties: {\n compositionId: {\n type: 'string',\n description: 'Remotion composition ID (e.g., \"Main\", \"Vertical\", \"Square\")',\n },\n inputProps: {\n type: 'object',\n description: 'JSON props to pass to the composition',\n },\n outputPath: {\n type: 'string',\n description: 'Output file path for the rendered video',\n },\n codec: {\n type: 'string',\n enum: ['h264', 'h265', 'vp8', 'vp9'],\n description: 'Video codec (default: h264)',\n },\n },\n required: ['compositionId', 'inputProps', 'outputPath'],\n additionalProperties: false,\n },\n };\n }\n\n async execute(input: RenderVideoToolInput): Promise<CallToolResult> {\n try {\n const options: RenderOptions = {\n compositionId: input.compositionId,\n inputProps: input.inputProps,\n outputPath: input.outputPath,\n codec: input.codec,\n };\n const result = await this.renderService.render(options);\n return this.successJson(result);\n } catch (error) {\n return this.error(error instanceof Error ? error.message : 'Unknown error');\n }\n }\n}\n","/**\n * DI Container Setup\n *\n * DESIGN PATTERNS:\n * - ContainerModule pattern for modular DI configuration\n * - Separation of concerns: services vs tools\n * - Singleton scope for stateful services\n *\n * CODING STANDARDS:\n * - Use ContainerModule for grouping related bindings\n * - Import reflect-metadata at top\n * - Bind services to their interface symbols from TYPES\n */\n\nimport 'reflect-metadata';\nimport { Container, ContainerModule, type ContainerModuleLoadOptions } from 'inversify';\nimport { CaptionService, FontService, MediaInfoService, RenderService, VoiceoverService } from '../services/index.js';\nimport {\n GenerateCaptionsTool,\n GenerateVoiceoverTool,\n GetMediaInfoTool,\n ImportSrtTool,\n ListCompositionsTool,\n PreviewFrameTool,\n RenderVideoTool,\n} from '../tools/index.js';\nimport { TYPES } from '../types/index.js';\n\n/**\n * Services module - binds all core services\n */\nexport const servicesModule = new ContainerModule((options: ContainerModuleLoadOptions) => {\n options.bind(TYPES.RenderService).to(RenderService).inSingletonScope();\n options.bind(TYPES.MediaInfoService).to(MediaInfoService).inSingletonScope();\n options.bind(TYPES.CaptionService).to(CaptionService).inSingletonScope();\n options.bind(TYPES.FontService).to(FontService).inSingletonScope();\n options.bind(TYPES.VoiceoverService).to(VoiceoverService).inSingletonScope();\n});\n\n/**\n * Tools module - binds MCP tools\n */\nexport const toolsModule = new ContainerModule((options: ContainerModuleLoadOptions) => {\n options.bind(TYPES.Tool).to(RenderVideoTool).inSingletonScope();\n options.bind(TYPES.Tool).to(ListCompositionsTool).inSingletonScope();\n options.bind(TYPES.Tool).to(GetMediaInfoTool).inSingletonScope();\n options.bind(TYPES.Tool).to(PreviewFrameTool).inSingletonScope();\n options.bind(TYPES.Tool).to(GenerateCaptionsTool).inSingletonScope();\n options.bind(TYPES.Tool).to(ImportSrtTool).inSingletonScope();\n options.bind(TYPES.Tool).to(GenerateVoiceoverTool).inSingletonScope();\n});\n\n/**\n * Creates and configures the DI container\n */\nexport function createContainer(): Container {\n const container = new Container();\n container.load(servicesModule, toolsModule);\n return container;\n}\n","import { Server } from '@modelcontextprotocol/sdk/server/index.js';\nimport { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';\nimport type { Container } from 'inversify';\nimport type { Tool } from '../types/index.js';\nimport { TYPES } from '../types/index.js';\n\nexport function createServer(container: Container): Server {\n const server = new Server(\n {\n name: 'video-editor-mcp',\n version: '0.1.0',\n },\n {\n capabilities: {\n tools: {},\n },\n },\n );\n\n const tools = container.getAll<Tool>(TYPES.Tool);\n const toolMap = new Map<string, Tool>();\n for (const tool of tools) {\n const def = tool.getDefinition();\n toolMap.set(def.name, tool);\n }\n\n server.setRequestHandler(ListToolsRequestSchema, async () => ({\n tools: tools.map((t) => t.getDefinition()),\n }));\n\n server.setRequestHandler(CallToolRequestSchema, async (request) => {\n const { name, arguments: args } = request.params;\n const tool = toolMap.get(name);\n if (!tool) {\n throw new Error(`Unknown tool: ${name}`);\n }\n return await tool.execute(args);\n });\n\n return server;\n}\n","/**\n * STDIO Transport\n *\n * DESIGN PATTERNS:\n * - Transport handler pattern implementing TransportHandler interface\n * - Standard I/O based communication for CLI usage\n *\n * CODING STANDARDS:\n * - Initialize server and transport properly\n * - Handle cleanup on shutdown with stop() method\n * - Use async/await for all operations\n *\n * AVOID:\n * - Forgetting to close transport on shutdown\n * - Missing error handling for connection failures\n */\n\nimport type { Server } from '@modelcontextprotocol/sdk/server/index.js';\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\n\n/**\n * Stdio transport handler for MCP server\n * Used for command-line and direct integrations\n */\nexport class StdioTransportHandler {\n private server: Server;\n private transport: StdioServerTransport | null = null;\n\n constructor(server: Server) {\n this.server = server;\n }\n\n async start(): Promise<void> {\n this.transport = new StdioServerTransport();\n await this.server.connect(this.transport);\n console.error('video-editor MCP server started on stdio');\n }\n\n async stop(): Promise<void> {\n if (this.transport) {\n await this.transport.close();\n this.transport = null;\n }\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},s=(n,r,a)=>(a=n==null?{}:e(i(n)),o(r||!n||!n.__esModule?t(a,`default`,{value:n,enumerable:!0}):a,n));let c=require(`node:child_process`),l=require(`node:path`);l=s(l),require(`reflect-metadata`);let u=require(`inversify`),d=require(`node:util`),f=require(`@remotion/bundler`),p=require(`@remotion/renderer`),m=require(`node:fs/promises`);m=s(m);let h=require(`node:os`);h=s(h);let g=require(`@modelcontextprotocol/sdk/server/index.js`),_=require(`@modelcontextprotocol/sdk/types.js`),v=require(`@modelcontextprotocol/sdk/server/stdio.js`);function y(e,t,n,r){var i=arguments.length,a=i<3?t:r===null?r=Object.getOwnPropertyDescriptor(t,n):r,o;if(typeof Reflect==`object`&&typeof Reflect.decorate==`function`)a=Reflect.decorate(e,t,n,r);else for(var s=e.length-1;s>=0;s--)(o=e[s])&&(a=(i<3?o(a):i>3?o(t,n,a):o(t,n))||a);return i>3&&a&&Object.defineProperty(t,n,a),a}let b=class{parseSrt(e){let t=[],n=e.split(`
2
+ `),r=0;for(;r<n.length;){if(!n[r]?.trim()){r++;continue}r++;let e=n[r];if(!e?.includes(`-->`)){r++;continue}let[i,a]=e.split(`-->`).map(e=>e.trim());if(!i||!a){r++;continue}let o=this.parseTimestamp(i),s=this.parseTimestamp(a);r++;let c=[];for(;r<n.length&&n[r]?.trim();)c.push(n[r]??``),r++;let l=c.join(`
3
+ `);l&&t.push({text:l,startMs:o,endMs:s})}return t}parseTimestamp(e){let[t,n]=e.split(`,`);if(!t||!n)throw Error(`Invalid timestamp format: ${e}`);let[r,i,a]=t.split(`:`).map(Number);if(r===void 0||i===void 0||a===void 0)throw Error(`Invalid time format: ${t}`);return(r*3600+i*60+a)*1e3+Number(n)}createTikTokCaptions(e,t){let n=[];if(e.length===0)return{pages:n};let r={startMs:e[0]?.startMs??0,endMs:e[0]?.endMs??0,tokens:[e[0]]};for(let i=1;i<e.length;i++){let a=e[i];a&&(a.startMs-r.endMs<=t?(r.tokens.push(a),r.endMs=a.endMs):(n.push(r),r={startMs:a.startMs,endMs:a.endMs,tokens:[a]}))}return r.tokens.length>0&&n.push(r),{pages:n}}};b=y([(0,u.injectable)()],b);const x=[`Inter`,`Roboto`,`Open Sans`,`Montserrat`,`Lato`,`Poppins`,`Oswald`,`Raleway`,`Playfair Display`,`Merriweather`,`Source Sans Pro`,`PT Sans`,`Ubuntu`,`Nunito`,`Rubik`,`Work Sans`,`Karla`,`Noto Sans`,`Fira Sans`,`DM Sans`];let S=class{getAvailableGoogleFonts(){return[...x]}validateFontFamily(e){let t=e.toLowerCase();return x.some(e=>e.toLowerCase()===t)}};S=y([(0,u.injectable)()],S);const C=(0,d.promisify)(c.execFile);let w=class{async getVideoDuration(e){try{let{stdout:t}=await C(`ffprobe`,[`-v`,`error`,`-show_entries`,`format=duration`,`-of`,`json`,e]),n=JSON.parse(t),r=Number.parseFloat(n.format?.duration??`0`);if(Number.isNaN(r)||r<=0)throw Error(`Invalid duration for video: ${e}`);return r}catch(t){throw Error(`Failed to get video duration for ${e}: ${t instanceof Error?t.message:String(t)}`,{cause:t})}}async getAudioDuration(e){try{let{stdout:t}=await C(`ffprobe`,[`-v`,`error`,`-show_entries`,`format=duration`,`-of`,`json`,e]),n=JSON.parse(t),r=Number.parseFloat(n.format?.duration??`0`);if(Number.isNaN(r)||r<=0)throw Error(`Invalid duration for audio: ${e}`);return r}catch(t){throw Error(`Failed to get audio duration for ${e}: ${t instanceof Error?t.message:String(t)}`,{cause:t})}}async getVideoDimensions(e){try{let{stdout:t}=await C(`ffprobe`,[`-v`,`error`,`-select_streams`,`v:0`,`-show_entries`,`stream=width,height`,`-of`,`json`,e]),n=JSON.parse(t).streams?.[0];if(!n?.width||!n?.height)throw Error(`No video stream found in: ${e}`);return{width:Number(n.width),height:Number(n.height)}}catch(t){throw Error(`Failed to get video dimensions for ${e}: ${t instanceof Error?t.message:String(t)}`,{cause:t})}}async canDecode(e){try{return await C(`ffprobe`,[`-v`,`error`,`-show_entries`,`format=duration`,`-of`,`json`,e]),!0}catch{return!1}}};w=y([(0,u.injectable)()],w);let T=class{bundlePromise=null;async getServeUrl(){if(!this.bundlePromise){let e=l.default.resolve(__dirname,`../..`);this.bundlePromise=(0,f.bundle)({entryPoint:l.default.resolve(e,`src/remotion/index.ts`),publicDir:l.default.resolve(e,`public`),webpackOverride:e=>({...e,resolve:{...e.resolve,extensionAlias:{".js":[`.ts`,`.tsx`,`.js`,`.jsx`]}}})})}return this.bundlePromise}async render(e){let t=await this.getServeUrl();return await(0,p.renderMedia)({composition:await(0,p.selectComposition)({serveUrl:t,id:e.compositionId,inputProps:e.inputProps}),serveUrl:t,codec:e.codec??`h264`,outputLocation:e.outputPath,inputProps:e.inputProps}),{outputPath:e.outputPath}}async renderStill(e){let t=await this.getServeUrl();return await(0,p.renderStill)({composition:await(0,p.selectComposition)({serveUrl:t,id:e.compositionId,inputProps:e.inputProps}),serveUrl:t,output:e.outputPath,frame:e.frame??0,imageFormat:e.imageFormat??`png`,inputProps:e.inputProps}),{outputPath:e.outputPath}}};T=y([(0,u.injectable)()],T);const E=(0,d.promisify)(c.execFile),D=[`af_sarah`,`af_nicole`,`af_bella`,`af_sky`,`am_adam`,`am_michael`,`bf_emma`,`bf_isabella`,`bm_george`,`bm_lewis`];let O=class{async isAvailable(){try{return await E(`kokoro-tts`,[`--version`]),!0}catch{return!1}}async generateVoiceover(e){let{text:t,voice:n=`af_sarah`,speed:r=1,outputPath:i}=e,a=await m.default.mkdtemp(l.default.join(h.default.tmpdir(),`kokoro-`)),o=l.default.join(a,`input.txt`);try{return await m.default.writeFile(o,t,`utf-8`),await E(`kokoro-tts`,[o,i,`--voice`,n,`--speed`,r.toString()]),{outputPath:i}}catch(e){throw Error(`Failed to generate voiceover: ${e instanceof Error?e.message:String(e)}`,{cause:e})}finally{try{await m.default.rm(a,{recursive:!0,force:!0})}catch{}}}listVoices(){return[...D]}};O=y([(0,u.injectable)()],O);let k=class{success(e){return{content:[{type:`text`,text:e}]}}successJson(e){return{content:[{type:`text`,text:JSON.stringify(e,null,2)}]}}error(e){return{content:[{type:`text`,text:`Error: ${e}`}],isError:!0}}async safeExecute(e){try{return await e()}catch(e){return this.error(e instanceof Error?e.message:`Unknown error`)}}};k=y([(0,u.injectable)()],k);const A={RenderService:Symbol.for(`RenderService`),MediaInfoService:Symbol.for(`MediaInfoService`),CaptionService:Symbol.for(`CaptionService`),FontService:Symbol.for(`FontService`),VoiceoverService:Symbol.for(`VoiceoverService`),Tool:Symbol.for(`Tool`)};function j(e,t){if(typeof Reflect==`object`&&typeof Reflect.metadata==`function`)return Reflect.metadata(e,t)}function M(e,t){return function(n,r){t(n,r,e)}}var N,P;let F=class extends k{static{P=this}static TOOL_NAME=`generate_captions`;constructor(e){super(),this.captionService=e}getDefinition(){return{name:P.TOOL_NAME,description:`Create TikTok-style caption pages from a caption array for video overlay`,inputSchema:{type:`object`,properties:{captions:{type:`array`,description:`Array of caption objects with text, startMs, and endMs`,items:{type:`object`,properties:{text:{type:`string`},startMs:{type:`number`},endMs:{type:`number`}},required:[`text`,`startMs`,`endMs`]}},combineMs:{type:`number`,description:`Milliseconds threshold for combining captions into pages (default: 100)`}},required:[`captions`],additionalProperties:!1}}}async execute(e){try{let{captions:t,combineMs:n=100}=e,r=t.map(e=>({text:e.text,startMs:e.startMs,endMs:e.endMs})),i=this.captionService.createTikTokCaptions(r,n);return this.successJson(i)}catch(e){return this.error(e instanceof Error?e.message:`Unknown error`)}}};F=P=y([(0,u.injectable)(),M(0,(0,u.inject)(A.CaptionService)),j(`design:paramtypes`,[typeof(N=b!==void 0&&b)==`function`?N:Object])],F);var I,L;let R=class extends k{static{L=this}static TOOL_NAME=`generate_voiceover`;constructor(e){super(),this.voiceoverService=e}getDefinition(){return{name:L.TOOL_NAME,description:`Generate voiceover audio from text using Kokoro TTS local engine`,inputSchema:{type:`object`,properties:{text:{type:`string`,description:`Text to convert to speech`},voice:{type:`string`,description:`Voice ID (default: af_sarah). Options: af_sarah, af_nicole, af_bella, af_sky, am_adam, am_michael, bf_emma, bf_isabella, bm_george, bm_lewis`},speed:{type:`number`,description:`Speech speed multiplier (default: 1.0)`},outputPath:{type:`string`,description:`Output file path for the generated audio`}},required:[`text`,`outputPath`],additionalProperties:!1}}}async execute(e){try{if(!await this.voiceoverService.isAvailable())return this.error(`Kokoro TTS is not installed. Install it with: pip install kokoro-tts`);let t=await this.voiceoverService.generateVoiceover({text:e.text,voice:e.voice,speed:e.speed,outputPath:e.outputPath});return this.successJson(t)}catch(e){return this.error(e instanceof Error?e.message:`Unknown error`)}}};R=L=y([(0,u.injectable)(),M(0,(0,u.inject)(A.VoiceoverService)),j(`design:paramtypes`,[typeof(I=O!==void 0&&O)==`function`?I:Object])],R);var z,B;let V=class extends k{static{B=this}static TOOL_NAME=`get_media_info`;constructor(e){super(),this.mediaInfoService=e}getDefinition(){return{name:B.TOOL_NAME,description:`Get duration, dimensions, and decodability info for video/audio files`,inputSchema:{type:`object`,properties:{src:{type:`string`,description:`Path to the media file`},type:{type:`string`,enum:[`video`,`audio`],description:`Media type (default: video)`}},required:[`src`],additionalProperties:!1}}}async execute(e){try{let{src:t,type:n=`video`}=e,r=await this.mediaInfoService.canDecode(t);if(n===`audio`){let e=await this.mediaInfoService.getAudioDuration(t);return this.successJson({src:t,type:`audio`,duration:e,canDecode:r})}let[i,a]=await Promise.all([this.mediaInfoService.getVideoDuration(t),this.mediaInfoService.getVideoDimensions(t)]);return this.successJson({src:t,type:`video`,duration:i,width:a.width,height:a.height,canDecode:r})}catch(e){return this.error(e instanceof Error?e.message:`Unknown error`)}}};V=B=y([(0,u.injectable)(),M(0,(0,u.inject)(A.MediaInfoService)),j(`design:paramtypes`,[typeof(z=w!==void 0&&w)==`function`?z:Object])],V);var H,U;let W=class extends k{static{U=this}static TOOL_NAME=`import_srt`;constructor(e){super(),this.captionService=e}getDefinition(){return{name:U.TOOL_NAME,description:`Parse SRT subtitle file content into structured Caption format for video overlay`,inputSchema:{type:`object`,properties:{srtContent:{type:`string`,description:`SRT file content as a string`}},required:[`srtContent`],additionalProperties:!1}}}async execute(e){try{let t=this.captionService.parseSrt(e.srtContent);return this.successJson({captionCount:t.length,captions:t})}catch(e){return this.error(e instanceof Error?e.message:`Unknown error`)}}};W=U=y([(0,u.injectable)(),M(0,(0,u.inject)(A.CaptionService)),j(`design:paramtypes`,[typeof(H=b!==void 0&&b)==`function`?H:Object])],W);var G;let K=class extends k{static{G=this}static TOOL_NAME=`list_compositions`;getDefinition(){return{name:G.TOOL_NAME,description:`List all available Remotion compositions with their schemas, dimensions, and format info`,inputSchema:{type:`object`,properties:{},required:[],additionalProperties:!1}}}async execute(){let e=[{id:`Main`,description:`Main 16:9 landscape composition`,width:1920,height:1080,fps:30,durationInFrames:null,defaultProps:{clips:[],audioVolume:1,backgroundColor:`#000000`}},{id:`Vertical`,description:`Vertical 9:16 portrait composition (TikTok, Instagram Stories)`,width:1080,height:1920,fps:30,durationInFrames:null,defaultProps:{clips:[],audioVolume:1,backgroundColor:`#000000`}},{id:`Square`,description:`Square 1:1 composition (Instagram posts)`,width:1080,height:1080,fps:30,durationInFrames:null,defaultProps:{clips:[],audioVolume:1,backgroundColor:`#000000`}}];return this.successJson({compositionCount:e.length,compositions:e})}};K=G=y([(0,u.injectable)()],K);var q,J;let Y=class extends k{static{J=this}static TOOL_NAME=`preview_frame`;constructor(e){super(),this.renderService=e}getDefinition(){return{name:J.TOOL_NAME,description:`Render a single frame from a composition as a PNG or JPEG image for preview`,inputSchema:{type:`object`,properties:{compositionId:{type:`string`,description:`Remotion composition ID (e.g., "Main", "Vertical", "Square")`},inputProps:{type:`object`,description:`JSON props to pass to the composition`},outputPath:{type:`string`,description:`Output file path for the rendered image`},frame:{type:`number`,description:`Frame number to render (default: 0)`},imageFormat:{type:`string`,enum:[`png`,`jpeg`],description:`Image format (default: png)`}},required:[`compositionId`,`inputProps`,`outputPath`],additionalProperties:!1}}}async execute(e){try{let t={compositionId:e.compositionId,inputProps:e.inputProps,outputPath:e.outputPath,frame:e.frame,imageFormat:e.imageFormat},n=await this.renderService.renderStill(t);return this.successJson(n)}catch(e){return this.error(e instanceof Error?e.message:`Unknown error`)}}};Y=J=y([(0,u.injectable)(),M(0,(0,u.inject)(A.RenderService)),j(`design:paramtypes`,[typeof(q=T!==void 0&&T)==`function`?q:Object])],Y);var X,Z;let Q=class extends k{static{Z=this}static TOOL_NAME=`render_video`;constructor(e){super(),this.renderService=e}getDefinition(){return{name:Z.TOOL_NAME,description:`Render a video from JSON props using Remotion`,inputSchema:{type:`object`,properties:{compositionId:{type:`string`,description:`Remotion composition ID (e.g., "Main", "Vertical", "Square")`},inputProps:{type:`object`,description:`JSON props to pass to the composition`},outputPath:{type:`string`,description:`Output file path for the rendered video`},codec:{type:`string`,enum:[`h264`,`h265`,`vp8`,`vp9`],description:`Video codec (default: h264)`}},required:[`compositionId`,`inputProps`,`outputPath`],additionalProperties:!1}}}async execute(e){try{let t={compositionId:e.compositionId,inputProps:e.inputProps,outputPath:e.outputPath,codec:e.codec},n=await this.renderService.render(t);return this.successJson(n)}catch(e){return this.error(e instanceof Error?e.message:`Unknown error`)}}};Q=Z=y([(0,u.injectable)(),M(0,(0,u.inject)(A.RenderService)),j(`design:paramtypes`,[typeof(X=T!==void 0&&T)==`function`?X:Object])],Q);const $=new u.ContainerModule(e=>{e.bind(A.RenderService).to(T).inSingletonScope(),e.bind(A.MediaInfoService).to(w).inSingletonScope(),e.bind(A.CaptionService).to(b).inSingletonScope(),e.bind(A.FontService).to(S).inSingletonScope(),e.bind(A.VoiceoverService).to(O).inSingletonScope()}),ee=new u.ContainerModule(e=>{e.bind(A.Tool).to(Q).inSingletonScope(),e.bind(A.Tool).to(K).inSingletonScope(),e.bind(A.Tool).to(V).inSingletonScope(),e.bind(A.Tool).to(Y).inSingletonScope(),e.bind(A.Tool).to(F).inSingletonScope(),e.bind(A.Tool).to(W).inSingletonScope(),e.bind(A.Tool).to(R).inSingletonScope()});function te(){let e=new u.Container;return e.load($,ee),e}function ne(e){let t=new g.Server({name:`video-editor-mcp`,version:`0.1.0`},{capabilities:{tools:{}}}),n=e.getAll(A.Tool),r=new Map;for(let e of n){let t=e.getDefinition();r.set(t.name,e)}return t.setRequestHandler(_.ListToolsRequestSchema,async()=>({tools:n.map(e=>e.getDefinition())})),t.setRequestHandler(_.CallToolRequestSchema,async e=>{let{name:t,arguments:n}=e.params,i=r.get(t);if(!i)throw Error(`Unknown tool: ${t}`);return await i.execute(n)}),t}var re=class{server;transport=null;constructor(e){this.server=e}async start(){this.transport=new v.StdioServerTransport,await this.server.connect(this.transport),console.error(`video-editor MCP server started on stdio`)}async stop(){this.transport&&=(await this.transport.close(),null)}};Object.defineProperty(exports,`a`,{enumerable:!0,get:function(){return Y}}),Object.defineProperty(exports,`c`,{enumerable:!0,get:function(){return V}}),Object.defineProperty(exports,`d`,{enumerable:!0,get:function(){return A}}),Object.defineProperty(exports,`f`,{enumerable:!0,get:function(){return k}}),Object.defineProperty(exports,`i`,{enumerable:!0,get:function(){return Q}}),Object.defineProperty(exports,`l`,{enumerable:!0,get:function(){return R}}),Object.defineProperty(exports,`m`,{enumerable:!0,get:function(){return s}}),Object.defineProperty(exports,`n`,{enumerable:!0,get:function(){return ne}}),Object.defineProperty(exports,`o`,{enumerable:!0,get:function(){return K}}),Object.defineProperty(exports,`p`,{enumerable:!0,get:function(){return T}}),Object.defineProperty(exports,`r`,{enumerable:!0,get:function(){return te}}),Object.defineProperty(exports,`s`,{enumerable:!0,get:function(){return W}}),Object.defineProperty(exports,`t`,{enumerable:!0,get:function(){return re}}),Object.defineProperty(exports,`u`,{enumerable:!0,get:function(){return F}});
4
+ //# sourceMappingURL=stdio-D8Z9ArjP.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stdio-D8Z9ArjP.cjs","names":["CaptionService","captions: Caption[]","textLines: string[]","pages: CaptionPage[]","currentPage: CaptionPage","FontService","execFileAsync","execFile","MediaInfoService","RenderService","path","execFile","VoiceoverService","fs","path","os","BaseTool","GenerateCaptionsTool","captionService: CaptionService","captionArray: Caption[]","GenerateVoiceoverTool","voiceoverService: VoiceoverService","GetMediaInfoTool","mediaInfoService: MediaInfoService","duration","ImportSrtTool","captionService: CaptionService","ListCompositionsTool","PreviewFrameTool","renderService: RenderService","options: RenderStillOptions","RenderVideoTool","renderService: RenderService","options: RenderOptions","ContainerModule","Container","Server","ListToolsRequestSchema","CallToolRequestSchema","StdioServerTransport"],"sources":["../src/services/CaptionService.ts","../src/services/FontService.ts","../src/services/MediaInfoService.ts","../src/services/RenderService.ts","../src/services/VoiceoverService.ts","../src/tools/BaseTool.ts","../src/types/index.ts","../src/tools/GenerateCaptionsTool.ts","../src/tools/GenerateVoiceoverTool.ts","../src/tools/GetMediaInfoTool.ts","../src/tools/ImportSrtTool.ts","../src/tools/ListCompositionsTool.ts","../src/tools/PreviewFrameTool.ts","../src/tools/RenderVideoTool.ts","../src/container/index.ts","../src/server/index.ts","../src/transports/stdio.ts"],"sourcesContent":["/**\n * CaptionService\n *\n * DESIGN PATTERNS:\n * - Service pattern for business logic encapsulation\n * - Dependency injection via InversifyJS\n * - Single responsibility principle\n *\n * CODING STANDARDS:\n * - Use async/await for asynchronous operations\n * - Throw descriptive errors for error cases\n * - Keep methods focused and well-named\n */\n\nimport { injectable } from 'inversify';\nimport type { Caption } from '../types/index.js';\n\n/**\n * TikTok-style caption page with grouped tokens\n */\nexport interface CaptionPage {\n startMs: number;\n endMs: number;\n tokens: Caption[];\n}\n\n@injectable()\nexport class CaptionService {\n /**\n * Parse SRT format content into Caption array\n *\n * SRT format:\n * 1\n * 00:00:01,000 --> 00:00:04,000\n * Welcome to the video\n *\n * 2\n * 00:00:04,500 --> 00:00:08,000\n * This is the second caption\n */\n parseSrt(srtContent: string): Caption[] {\n const captions: Caption[] = [];\n const lines = srtContent.split('\\n');\n let i = 0;\n\n while (i < lines.length) {\n // Skip empty lines\n if (!lines[i]?.trim()) {\n i++;\n continue;\n }\n\n // Skip sequence number\n i++;\n\n // Parse timestamp line\n const timestampLine = lines[i];\n if (!timestampLine?.includes('-->')) {\n i++;\n continue;\n }\n\n const [startStr, endStr] = timestampLine.split('-->').map((s) => s.trim());\n if (!startStr || !endStr) {\n i++;\n continue;\n }\n\n const startMs = this.parseTimestamp(startStr);\n const endMs = this.parseTimestamp(endStr);\n\n // Collect text lines until empty line or end\n i++;\n const textLines: string[] = [];\n while (i < lines.length && lines[i]?.trim()) {\n textLines.push(lines[i] ?? '');\n i++;\n }\n\n const text = textLines.join('\\n');\n if (text) {\n captions.push({ text, startMs, endMs });\n }\n }\n\n return captions;\n }\n\n /**\n * Parse SRT timestamp to milliseconds\n * Format: HH:MM:SS,mmm\n */\n private parseTimestamp(timestamp: string): number {\n const [time, ms] = timestamp.split(',');\n if (!time || !ms) {\n throw new Error(`Invalid timestamp format: ${timestamp}`);\n }\n\n const [hours, minutes, seconds] = time.split(':').map(Number);\n if (hours === undefined || minutes === undefined || seconds === undefined) {\n throw new Error(`Invalid time format: ${time}`);\n }\n\n const totalSeconds = hours * 3600 + minutes * 60 + seconds;\n return totalSeconds * 1000 + Number(ms);\n }\n\n /**\n * Create TikTok-style caption pages by grouping captions\n * Combines tokens that appear within combineMs milliseconds of each other\n */\n createTikTokCaptions(captions: Caption[], combineMs: number): { pages: CaptionPage[] } {\n const pages: CaptionPage[] = [];\n\n if (captions.length === 0) {\n return { pages };\n }\n\n let currentPage: CaptionPage = {\n startMs: captions[0]?.startMs ?? 0,\n endMs: captions[0]?.endMs ?? 0,\n tokens: [captions[0] as Caption],\n };\n\n for (let i = 1; i < captions.length; i++) {\n const caption = captions[i];\n if (!caption) continue;\n\n const gap = caption.startMs - currentPage.endMs;\n\n if (gap <= combineMs) {\n // Combine into current page\n currentPage.tokens.push(caption);\n currentPage.endMs = caption.endMs;\n } else {\n // Start new page\n pages.push(currentPage);\n currentPage = {\n startMs: caption.startMs,\n endMs: caption.endMs,\n tokens: [caption],\n };\n }\n }\n\n // Add final page\n if (currentPage.tokens.length > 0) {\n pages.push(currentPage);\n }\n\n return { pages };\n }\n}\n","/**\n * FontService\n *\n * DESIGN PATTERNS:\n * - Service pattern for business logic encapsulation\n * - Dependency injection via InversifyJS\n * - Single responsibility principle\n *\n * CODING STANDARDS:\n * - Use async/await for asynchronous operations\n * - Throw descriptive errors for error cases\n * - Keep methods focused and well-named\n */\n\nimport { injectable } from 'inversify';\n\n/**\n * Curated list of popular Google Fonts\n * These fonts are widely used and well-supported\n */\nconst AVAILABLE_GOOGLE_FONTS = [\n 'Inter',\n 'Roboto',\n 'Open Sans',\n 'Montserrat',\n 'Lato',\n 'Poppins',\n 'Oswald',\n 'Raleway',\n 'Playfair Display',\n 'Merriweather',\n 'Source Sans Pro',\n 'PT Sans',\n 'Ubuntu',\n 'Nunito',\n 'Rubik',\n 'Work Sans',\n 'Karla',\n 'Noto Sans',\n 'Fira Sans',\n 'DM Sans',\n] as const;\n\n@injectable()\nexport class FontService {\n /**\n * Get list of available Google Fonts\n */\n getAvailableGoogleFonts(): string[] {\n return [...AVAILABLE_GOOGLE_FONTS];\n }\n\n /**\n * Validate if a font family is in the available list\n * Case-insensitive comparison\n */\n validateFontFamily(family: string): boolean {\n const normalizedFamily = family.toLowerCase();\n return AVAILABLE_GOOGLE_FONTS.some((font) => font.toLowerCase() === normalizedFamily);\n }\n}\n","/**\n * MediaInfoService\n *\n * DESIGN PATTERNS:\n * - Service pattern for business logic encapsulation\n * - Dependency injection via InversifyJS\n * - Single responsibility principle\n *\n * CODING STANDARDS:\n * - Use async/await for asynchronous operations\n * - Throw descriptive errors for error cases\n * - Keep methods focused and well-named\n */\n\nimport { execFile } from 'node:child_process';\nimport { promisify } from 'node:util';\nimport { injectable } from 'inversify';\n\nconst execFileAsync = promisify(execFile);\n\n@injectable()\nexport class MediaInfoService {\n /**\n * Get video duration in seconds using ffprobe\n */\n async getVideoDuration(src: string): Promise<number> {\n try {\n const { stdout } = await execFileAsync('ffprobe', [\n '-v',\n 'error',\n '-show_entries',\n 'format=duration',\n '-of',\n 'json',\n src,\n ]);\n\n const result = JSON.parse(stdout);\n const duration = Number.parseFloat(result.format?.duration ?? '0');\n\n if (Number.isNaN(duration) || duration <= 0) {\n throw new Error(`Invalid duration for video: ${src}`);\n }\n\n return duration;\n } catch (error) {\n throw new Error(`Failed to get video duration for ${src}: ${error instanceof Error ? error.message : String(error)}`, {\n cause: error,\n });\n }\n }\n\n /**\n * Get audio duration in seconds using ffprobe\n */\n async getAudioDuration(src: string): Promise<number> {\n try {\n const { stdout } = await execFileAsync('ffprobe', [\n '-v',\n 'error',\n '-show_entries',\n 'format=duration',\n '-of',\n 'json',\n src,\n ]);\n\n const result = JSON.parse(stdout);\n const duration = Number.parseFloat(result.format?.duration ?? '0');\n\n if (Number.isNaN(duration) || duration <= 0) {\n throw new Error(`Invalid duration for audio: ${src}`);\n }\n\n return duration;\n } catch (error) {\n throw new Error(`Failed to get audio duration for ${src}: ${error instanceof Error ? error.message : String(error)}`, {\n cause: error,\n });\n }\n }\n\n /**\n * Get video dimensions (width and height) using ffprobe\n */\n async getVideoDimensions(src: string): Promise<{ width: number; height: number }> {\n try {\n const { stdout } = await execFileAsync('ffprobe', [\n '-v',\n 'error',\n '-select_streams',\n 'v:0',\n '-show_entries',\n 'stream=width,height',\n '-of',\n 'json',\n src,\n ]);\n\n const result = JSON.parse(stdout);\n const stream = result.streams?.[0];\n\n if (!stream?.width || !stream?.height) {\n throw new Error(`No video stream found in: ${src}`);\n }\n\n return {\n width: Number(stream.width),\n height: Number(stream.height),\n };\n } catch (error) {\n throw new Error(\n `Failed to get video dimensions for ${src}: ${error instanceof Error ? error.message : String(error)}`,\n { cause: error },\n );\n }\n }\n\n /**\n * Check if a media file can be decoded using ffprobe\n */\n async canDecode(src: string): Promise<boolean> {\n try {\n await execFileAsync('ffprobe', ['-v', 'error', '-show_entries', 'format=duration', '-of', 'json', src]);\n return true;\n } catch {\n return false;\n }\n }\n}\n","/**\n * RenderService\n *\n * Uses @remotion/renderer to render videos from JSON props.\n */\n\nimport path from 'node:path';\nimport { bundle } from '@remotion/bundler';\nimport { renderMedia, renderStill, selectComposition } from '@remotion/renderer';\nimport { injectable } from 'inversify';\nimport type { RenderOptions, RenderResult, RenderStillOptions } from '../types/index.js';\n\n@injectable()\nexport class RenderService {\n private bundlePromise: Promise<string> | null = null;\n\n private async getServeUrl(): Promise<string> {\n if (!this.bundlePromise) {\n const projectRoot = path.resolve(import.meta.dirname, '../..');\n this.bundlePromise = bundle({\n entryPoint: path.resolve(projectRoot, 'src/remotion/index.ts'),\n publicDir: path.resolve(projectRoot, 'public'),\n webpackOverride: (config) => ({\n ...config,\n resolve: {\n ...config.resolve,\n extensionAlias: {\n '.js': ['.ts', '.tsx', '.js', '.jsx'],\n },\n },\n }),\n });\n }\n return this.bundlePromise;\n }\n\n async render(options: RenderOptions): Promise<RenderResult> {\n const serveUrl = await this.getServeUrl();\n\n const composition = await selectComposition({\n serveUrl,\n id: options.compositionId,\n inputProps: options.inputProps,\n });\n\n await renderMedia({\n composition,\n serveUrl,\n codec: options.codec ?? 'h264',\n outputLocation: options.outputPath,\n inputProps: options.inputProps,\n });\n\n return { outputPath: options.outputPath };\n }\n\n async renderStill(options: RenderStillOptions): Promise<RenderResult> {\n const serveUrl = await this.getServeUrl();\n\n const composition = await selectComposition({\n serveUrl,\n id: options.compositionId,\n inputProps: options.inputProps,\n });\n\n await renderStill({\n composition,\n serveUrl,\n output: options.outputPath,\n frame: options.frame ?? 0,\n imageFormat: options.imageFormat ?? 'png',\n inputProps: options.inputProps,\n });\n\n return { outputPath: options.outputPath };\n }\n}\n","/**\n * VoiceoverService\n *\n * DESIGN PATTERNS:\n * - Service pattern for business logic encapsulation\n * - Dependency injection via InversifyJS\n * - Single responsibility principle\n *\n * CODING STANDARDS:\n * - Use async/await for asynchronous operations\n * - Throw descriptive errors for error cases\n * - Keep methods focused and well-named\n * - Use execFile for command execution (prevents command injection)\n */\n\nimport { execFile } from 'node:child_process';\nimport fs from 'node:fs/promises';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { promisify } from 'node:util';\nimport { injectable } from 'inversify';\n\nconst execFileAsync = promisify(execFile);\n\n/**\n * Available Kokoro TTS voices\n */\nconst AVAILABLE_VOICES = [\n 'af_sarah',\n 'af_nicole',\n 'af_bella',\n 'af_sky',\n 'am_adam',\n 'am_michael',\n 'bf_emma',\n 'bf_isabella',\n 'bm_george',\n 'bm_lewis',\n] as const;\n\nexport interface VoiceoverOptions {\n text: string;\n voice?: string;\n speed?: number;\n outputPath: string;\n}\n\nexport interface VoiceoverResult {\n outputPath: string;\n duration?: number;\n}\n\n@injectable()\nexport class VoiceoverService {\n /**\n * Check if kokoro-tts CLI is installed\n */\n async isAvailable(): Promise<boolean> {\n try {\n await execFileAsync('kokoro-tts', ['--version']);\n return true;\n } catch {\n return false;\n }\n }\n\n /**\n * Generate voiceover using Kokoro TTS\n */\n async generateVoiceover(options: VoiceoverOptions): Promise<VoiceoverResult> {\n const { text, voice = 'af_sarah', speed = 1.0, outputPath } = options;\n\n // Create temp file for text input\n const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'kokoro-'));\n const tempTextFile = path.join(tempDir, 'input.txt');\n\n try {\n // Write text to temp file\n await fs.writeFile(tempTextFile, text, 'utf-8');\n\n // Run kokoro-tts CLI\n const args = [tempTextFile, outputPath, '--voice', voice, '--speed', speed.toString()];\n\n await execFileAsync('kokoro-tts', args);\n\n return { outputPath };\n } catch (error) {\n throw new Error(`Failed to generate voiceover: ${error instanceof Error ? error.message : String(error)}`, {\n cause: error,\n });\n } finally {\n // Cleanup temp file\n try {\n await fs.rm(tempDir, { recursive: true, force: true });\n } catch {\n // Ignore cleanup errors\n }\n }\n }\n\n /**\n * Get list of available Kokoro voice IDs\n */\n listVoices(): string[] {\n return [...AVAILABLE_VOICES];\n }\n}\n","/**\n * BaseTool - Abstract base class for video editor MCP tools\n *\n * DESIGN PATTERNS:\n * - Template Method pattern for consistent tool interface\n * - Dependency Injection via InversifyJS for services\n * - Helper methods for response formatting\n *\n * CODING STANDARDS:\n * - Use async/await for asynchronous operations\n * - Return CallToolResult with content array\n * - Handle errors gracefully with isError flag\n *\n * AVOID:\n * - Business logic in base class (delegate to services)\n * - Exposing internal errors to users\n */\n\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport { injectable } from 'inversify';\n\n@injectable()\nexport abstract class BaseTool {\n /**\n * Creates a success response with text content.\n * @param text - The success message or content.\n * @returns A CallToolResult with the content.\n */\n protected success(text: string): CallToolResult {\n return {\n content: [{ type: 'text', text }],\n };\n }\n\n /**\n * Creates a success response with JSON content.\n * @param data - The data to serialize as JSON.\n * @returns A CallToolResult with the JSON content.\n */\n protected successJson(data: unknown): CallToolResult {\n return {\n content: [{ type: 'text', text: JSON.stringify(data, null, 2) }],\n };\n }\n\n /**\n * Creates an error response.\n * @param message - The error message.\n * @returns A CallToolResult with isError flag set.\n */\n protected error(message: string): CallToolResult {\n return {\n content: [{ type: 'text', text: `Error: ${message}` }],\n isError: true,\n };\n }\n\n /**\n * Wraps execution with standard error handling.\n * @param fn - The async function to execute.\n * @returns The result of the function or an error response.\n */\n protected async safeExecute<T>(fn: () => Promise<T>): Promise<T | CallToolResult> {\n try {\n return await fn();\n } catch (err) {\n return this.error(err instanceof Error ? err.message : 'Unknown error');\n }\n }\n}\n","/**\n * Shared TypeScript Types\n *\n * DESIGN PATTERNS:\n * - Type-first development with Zod schema inference\n * - Interface segregation\n *\n * CODING STANDARDS:\n * - Export all shared types from this file\n * - Derive types from Zod schemas via z.infer<>\n */\n\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport type { z } from 'zod';\nimport type {\n AudioClipSchema,\n ClipSchema,\n GifClipSchema,\n ImageClipSchema,\n LottieClipSchema,\n SubtitleClipSchema,\n TextClipSchema,\n VideoClipSchema,\n} from '../schemas/clips.js';\nimport type {\n CaptionConfigSchema,\n CaptionSchema,\n FontConfigSchema,\n MainCompositionSchema,\n} from '../schemas/compositions.js';\n\n/**\n * DI Container Symbols\n */\nexport const TYPES = {\n RenderService: Symbol.for('RenderService'),\n MediaInfoService: Symbol.for('MediaInfoService'),\n CaptionService: Symbol.for('CaptionService'),\n FontService: Symbol.for('FontService'),\n VoiceoverService: Symbol.for('VoiceoverService'),\n Tool: Symbol.for('Tool'),\n} as const;\n\n/**\n * Tool definition for MCP\n */\nexport interface ToolDefinition {\n name: string;\n description: string;\n inputSchema: {\n type: string;\n properties: Record<string, unknown>;\n required?: string[];\n additionalProperties?: boolean;\n };\n}\n\n/**\n * Base tool interface following MCP SDK patterns\n */\nexport interface Tool<TInput = unknown> {\n getDefinition(): ToolDefinition;\n execute(input: TInput): Promise<CallToolResult>;\n}\n\n/**\n * Render options for Remotion\n */\nexport interface RenderOptions {\n compositionId: string;\n inputProps: Record<string, unknown>;\n outputPath: string;\n codec?: 'h264' | 'h265' | 'vp8' | 'vp9';\n}\n\n/**\n * Render still options\n */\nexport interface RenderStillOptions {\n compositionId: string;\n inputProps: Record<string, unknown>;\n outputPath: string;\n frame?: number;\n imageFormat?: 'png' | 'jpeg';\n}\n\n/**\n * Render result from Remotion\n */\nexport interface RenderResult {\n outputPath: string;\n}\n\n// Clip types derived from Zod schemas\nexport type VideoClip = z.infer<typeof VideoClipSchema>;\nexport type ImageClip = z.infer<typeof ImageClipSchema>;\nexport type TextClip = z.infer<typeof TextClipSchema>;\nexport type AudioClip = z.infer<typeof AudioClipSchema>;\nexport type SubtitleClip = z.infer<typeof SubtitleClipSchema>;\nexport type GifClip = z.infer<typeof GifClipSchema>;\nexport type LottieClip = z.infer<typeof LottieClipSchema>;\nexport type Clip = z.infer<typeof ClipSchema>;\n\n// Composition types derived from Zod schemas\nexport type Caption = z.infer<typeof CaptionSchema>;\nexport type CaptionConfig = z.infer<typeof CaptionConfigSchema>;\nexport type FontConfig = z.infer<typeof FontConfigSchema>;\nexport type MainCompositionProps = z.infer<typeof MainCompositionSchema>;\n","/**\n * GenerateCaptionsTool\n *\n * Creates TikTok-style caption pages from a caption array for video overlay.\n */\n\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport { inject, injectable } from 'inversify';\nimport { CaptionService } from '../services/CaptionService.js';\nimport type { Caption, Tool, ToolDefinition } from '../types/index.js';\nimport { TYPES } from '../types/index.js';\nimport { BaseTool } from './BaseTool.js';\n\ninterface GenerateCaptionsToolInput {\n captions: Array<{ text: string; startMs: number; endMs: number }>;\n combineMs?: number;\n}\n\n@injectable()\nexport class GenerateCaptionsTool extends BaseTool implements Tool<GenerateCaptionsToolInput> {\n static readonly TOOL_NAME = 'generate_captions';\n\n constructor(@inject(TYPES.CaptionService) private captionService: CaptionService) {\n super();\n }\n\n getDefinition(): ToolDefinition {\n return {\n name: GenerateCaptionsTool.TOOL_NAME,\n description: 'Create TikTok-style caption pages from a caption array for video overlay',\n inputSchema: {\n type: 'object',\n properties: {\n captions: {\n type: 'array',\n description: 'Array of caption objects with text, startMs, and endMs',\n items: {\n type: 'object',\n properties: {\n text: { type: 'string' },\n startMs: { type: 'number' },\n endMs: { type: 'number' },\n },\n required: ['text', 'startMs', 'endMs'],\n },\n },\n combineMs: {\n type: 'number',\n description: 'Milliseconds threshold for combining captions into pages (default: 100)',\n },\n },\n required: ['captions'],\n additionalProperties: false,\n },\n };\n }\n\n async execute(input: GenerateCaptionsToolInput): Promise<CallToolResult> {\n try {\n const { captions, combineMs = 100 } = input;\n const captionArray: Caption[] = captions.map((c) => ({\n text: c.text,\n startMs: c.startMs,\n endMs: c.endMs,\n }));\n const result = this.captionService.createTikTokCaptions(captionArray, combineMs);\n return this.successJson(result);\n } catch (error) {\n return this.error(error instanceof Error ? error.message : 'Unknown error');\n }\n }\n}\n","/**\n * GenerateVoiceoverTool\n *\n * Generates voiceover audio from text using Kokoro TTS local engine.\n */\n\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport { inject, injectable } from 'inversify';\nimport { VoiceoverService } from '../services/VoiceoverService.js';\nimport type { Tool, ToolDefinition } from '../types/index.js';\nimport { TYPES } from '../types/index.js';\nimport { BaseTool } from './BaseTool.js';\n\ninterface GenerateVoiceoverToolInput {\n text: string;\n voice?: string;\n speed?: number;\n outputPath: string;\n}\n\n@injectable()\nexport class GenerateVoiceoverTool extends BaseTool implements Tool<GenerateVoiceoverToolInput> {\n static readonly TOOL_NAME = 'generate_voiceover';\n\n constructor(@inject(TYPES.VoiceoverService) private voiceoverService: VoiceoverService) {\n super();\n }\n\n getDefinition(): ToolDefinition {\n return {\n name: GenerateVoiceoverTool.TOOL_NAME,\n description: 'Generate voiceover audio from text using Kokoro TTS local engine',\n inputSchema: {\n type: 'object',\n properties: {\n text: {\n type: 'string',\n description: 'Text to convert to speech',\n },\n voice: {\n type: 'string',\n description:\n 'Voice ID (default: af_sarah). Options: af_sarah, af_nicole, af_bella, af_sky, am_adam, am_michael, bf_emma, bf_isabella, bm_george, bm_lewis',\n },\n speed: {\n type: 'number',\n description: 'Speech speed multiplier (default: 1.0)',\n },\n outputPath: {\n type: 'string',\n description: 'Output file path for the generated audio',\n },\n },\n required: ['text', 'outputPath'],\n additionalProperties: false,\n },\n };\n }\n\n async execute(input: GenerateVoiceoverToolInput): Promise<CallToolResult> {\n try {\n const isAvailable = await this.voiceoverService.isAvailable();\n if (!isAvailable) {\n return this.error('Kokoro TTS is not installed. Install it with: pip install kokoro-tts');\n }\n\n const result = await this.voiceoverService.generateVoiceover({\n text: input.text,\n voice: input.voice,\n speed: input.speed,\n outputPath: input.outputPath,\n });\n\n return this.successJson(result);\n } catch (error) {\n return this.error(error instanceof Error ? error.message : 'Unknown error');\n }\n }\n}\n","/**\n * GetMediaInfoTool\n *\n * Gets duration, dimensions, and decodability info for video/audio files.\n */\n\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport { inject, injectable } from 'inversify';\nimport { MediaInfoService } from '../services/MediaInfoService.js';\nimport type { Tool, ToolDefinition } from '../types/index.js';\nimport { TYPES } from '../types/index.js';\nimport { BaseTool } from './BaseTool.js';\n\ninterface GetMediaInfoToolInput {\n src: string;\n type?: 'video' | 'audio';\n}\n\n@injectable()\nexport class GetMediaInfoTool extends BaseTool implements Tool<GetMediaInfoToolInput> {\n static readonly TOOL_NAME = 'get_media_info';\n\n constructor(@inject(TYPES.MediaInfoService) private mediaInfoService: MediaInfoService) {\n super();\n }\n\n getDefinition(): ToolDefinition {\n return {\n name: GetMediaInfoTool.TOOL_NAME,\n description: 'Get duration, dimensions, and decodability info for video/audio files',\n inputSchema: {\n type: 'object',\n properties: {\n src: {\n type: 'string',\n description: 'Path to the media file',\n },\n type: {\n type: 'string',\n enum: ['video', 'audio'],\n description: 'Media type (default: video)',\n },\n },\n required: ['src'],\n additionalProperties: false,\n },\n };\n }\n\n async execute(input: GetMediaInfoToolInput): Promise<CallToolResult> {\n try {\n const { src, type = 'video' } = input;\n const canDecode = await this.mediaInfoService.canDecode(src);\n\n if (type === 'audio') {\n const duration = await this.mediaInfoService.getAudioDuration(src);\n return this.successJson({\n src,\n type: 'audio',\n duration,\n canDecode,\n });\n }\n\n const [duration, dimensions] = await Promise.all([\n this.mediaInfoService.getVideoDuration(src),\n this.mediaInfoService.getVideoDimensions(src),\n ]);\n\n return this.successJson({\n src,\n type: 'video',\n duration,\n width: dimensions.width,\n height: dimensions.height,\n canDecode,\n });\n } catch (error) {\n return this.error(error instanceof Error ? error.message : 'Unknown error');\n }\n }\n}\n","/**\n * ImportSrtTool\n *\n * Parses SRT subtitle file content into structured Caption format for video overlay.\n */\n\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport { inject, injectable } from 'inversify';\nimport { CaptionService } from '../services/CaptionService.js';\nimport type { Tool, ToolDefinition } from '../types/index.js';\nimport { TYPES } from '../types/index.js';\nimport { BaseTool } from './BaseTool.js';\n\ninterface ImportSrtToolInput {\n srtContent: string;\n}\n\n@injectable()\nexport class ImportSrtTool extends BaseTool implements Tool<ImportSrtToolInput> {\n static readonly TOOL_NAME = 'import_srt';\n\n constructor(@inject(TYPES.CaptionService) private captionService: CaptionService) {\n super();\n }\n\n getDefinition(): ToolDefinition {\n return {\n name: ImportSrtTool.TOOL_NAME,\n description: 'Parse SRT subtitle file content into structured Caption format for video overlay',\n inputSchema: {\n type: 'object',\n properties: {\n srtContent: {\n type: 'string',\n description: 'SRT file content as a string',\n },\n },\n required: ['srtContent'],\n additionalProperties: false,\n },\n };\n }\n\n async execute(input: ImportSrtToolInput): Promise<CallToolResult> {\n try {\n const captions = this.captionService.parseSrt(input.srtContent);\n return this.successJson({\n captionCount: captions.length,\n captions,\n });\n } catch (error) {\n return this.error(error instanceof Error ? error.message : 'Unknown error');\n }\n }\n}\n","/**\n * ListCompositionsTool\n *\n * Lists all available Remotion compositions with their metadata.\n */\n\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport { injectable } from 'inversify';\nimport type { Tool, ToolDefinition } from '../types/index.js';\nimport { BaseTool } from './BaseTool.js';\n\n// eslint-disable-next-line @typescript-eslint/no-empty-object-type\ntype ListCompositionsToolInput = {};\n\n@injectable()\nexport class ListCompositionsTool extends BaseTool implements Tool<ListCompositionsToolInput> {\n static readonly TOOL_NAME = 'list_compositions';\n\n getDefinition(): ToolDefinition {\n return {\n name: ListCompositionsTool.TOOL_NAME,\n description: 'List all available Remotion compositions with their schemas, dimensions, and format info',\n inputSchema: {\n type: 'object',\n properties: {},\n required: [],\n additionalProperties: false,\n },\n };\n }\n\n async execute(): Promise<CallToolResult> {\n const compositions = [\n {\n id: 'Main',\n description: 'Main 16:9 landscape composition',\n width: 1920,\n height: 1080,\n fps: 30,\n durationInFrames: null,\n defaultProps: {\n clips: [],\n audioVolume: 1,\n backgroundColor: '#000000',\n },\n },\n {\n id: 'Vertical',\n description: 'Vertical 9:16 portrait composition (TikTok, Instagram Stories)',\n width: 1080,\n height: 1920,\n fps: 30,\n durationInFrames: null,\n defaultProps: {\n clips: [],\n audioVolume: 1,\n backgroundColor: '#000000',\n },\n },\n {\n id: 'Square',\n description: 'Square 1:1 composition (Instagram posts)',\n width: 1080,\n height: 1080,\n fps: 30,\n durationInFrames: null,\n defaultProps: {\n clips: [],\n audioVolume: 1,\n backgroundColor: '#000000',\n },\n },\n ];\n\n return this.successJson({\n compositionCount: compositions.length,\n compositions,\n });\n }\n}\n","/**\n * PreviewFrameTool\n *\n * Renders a single frame from a composition as a PNG or JPEG image for preview.\n */\n\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport { inject, injectable } from 'inversify';\nimport { RenderService } from '../services/RenderService.js';\nimport type { RenderStillOptions, Tool, ToolDefinition } from '../types/index.js';\nimport { TYPES } from '../types/index.js';\nimport { BaseTool } from './BaseTool.js';\n\ninterface PreviewFrameToolInput {\n compositionId: string;\n inputProps: Record<string, unknown>;\n outputPath: string;\n frame?: number;\n imageFormat?: 'png' | 'jpeg';\n}\n\n@injectable()\nexport class PreviewFrameTool extends BaseTool implements Tool<PreviewFrameToolInput> {\n static readonly TOOL_NAME = 'preview_frame';\n\n constructor(@inject(TYPES.RenderService) private renderService: RenderService) {\n super();\n }\n\n getDefinition(): ToolDefinition {\n return {\n name: PreviewFrameTool.TOOL_NAME,\n description: 'Render a single frame from a composition as a PNG or JPEG image for preview',\n inputSchema: {\n type: 'object',\n properties: {\n compositionId: {\n type: 'string',\n description: 'Remotion composition ID (e.g., \"Main\", \"Vertical\", \"Square\")',\n },\n inputProps: {\n type: 'object',\n description: 'JSON props to pass to the composition',\n },\n outputPath: {\n type: 'string',\n description: 'Output file path for the rendered image',\n },\n frame: {\n type: 'number',\n description: 'Frame number to render (default: 0)',\n },\n imageFormat: {\n type: 'string',\n enum: ['png', 'jpeg'],\n description: 'Image format (default: png)',\n },\n },\n required: ['compositionId', 'inputProps', 'outputPath'],\n additionalProperties: false,\n },\n };\n }\n\n async execute(input: PreviewFrameToolInput): Promise<CallToolResult> {\n try {\n const options: RenderStillOptions = {\n compositionId: input.compositionId,\n inputProps: input.inputProps,\n outputPath: input.outputPath,\n frame: input.frame,\n imageFormat: input.imageFormat,\n };\n const result = await this.renderService.renderStill(options);\n return this.successJson(result);\n } catch (error) {\n return this.error(error instanceof Error ? error.message : 'Unknown error');\n }\n }\n}\n","/**\n * RenderVideoTool\n *\n * Renders videos from JSON props using Remotion.\n */\n\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport { inject, injectable } from 'inversify';\nimport { RenderService } from '../services/RenderService.js';\nimport type { RenderOptions, Tool, ToolDefinition } from '../types/index.js';\nimport { TYPES } from '../types/index.js';\nimport { BaseTool } from './BaseTool.js';\n\ninterface RenderVideoToolInput {\n compositionId: string;\n inputProps: Record<string, unknown>;\n outputPath: string;\n codec?: 'h264' | 'h265' | 'vp8' | 'vp9';\n}\n\n@injectable()\nexport class RenderVideoTool extends BaseTool implements Tool<RenderVideoToolInput> {\n static readonly TOOL_NAME = 'render_video';\n\n constructor(@inject(TYPES.RenderService) private renderService: RenderService) {\n super();\n }\n\n getDefinition(): ToolDefinition {\n return {\n name: RenderVideoTool.TOOL_NAME,\n description: 'Render a video from JSON props using Remotion',\n inputSchema: {\n type: 'object',\n properties: {\n compositionId: {\n type: 'string',\n description: 'Remotion composition ID (e.g., \"Main\", \"Vertical\", \"Square\")',\n },\n inputProps: {\n type: 'object',\n description: 'JSON props to pass to the composition',\n },\n outputPath: {\n type: 'string',\n description: 'Output file path for the rendered video',\n },\n codec: {\n type: 'string',\n enum: ['h264', 'h265', 'vp8', 'vp9'],\n description: 'Video codec (default: h264)',\n },\n },\n required: ['compositionId', 'inputProps', 'outputPath'],\n additionalProperties: false,\n },\n };\n }\n\n async execute(input: RenderVideoToolInput): Promise<CallToolResult> {\n try {\n const options: RenderOptions = {\n compositionId: input.compositionId,\n inputProps: input.inputProps,\n outputPath: input.outputPath,\n codec: input.codec,\n };\n const result = await this.renderService.render(options);\n return this.successJson(result);\n } catch (error) {\n return this.error(error instanceof Error ? error.message : 'Unknown error');\n }\n }\n}\n","/**\n * DI Container Setup\n *\n * DESIGN PATTERNS:\n * - ContainerModule pattern for modular DI configuration\n * - Separation of concerns: services vs tools\n * - Singleton scope for stateful services\n *\n * CODING STANDARDS:\n * - Use ContainerModule for grouping related bindings\n * - Import reflect-metadata at top\n * - Bind services to their interface symbols from TYPES\n */\n\nimport 'reflect-metadata';\nimport { Container, ContainerModule, type ContainerModuleLoadOptions } from 'inversify';\nimport { CaptionService, FontService, MediaInfoService, RenderService, VoiceoverService } from '../services/index.js';\nimport {\n GenerateCaptionsTool,\n GenerateVoiceoverTool,\n GetMediaInfoTool,\n ImportSrtTool,\n ListCompositionsTool,\n PreviewFrameTool,\n RenderVideoTool,\n} from '../tools/index.js';\nimport { TYPES } from '../types/index.js';\n\n/**\n * Services module - binds all core services\n */\nexport const servicesModule = new ContainerModule((options: ContainerModuleLoadOptions) => {\n options.bind(TYPES.RenderService).to(RenderService).inSingletonScope();\n options.bind(TYPES.MediaInfoService).to(MediaInfoService).inSingletonScope();\n options.bind(TYPES.CaptionService).to(CaptionService).inSingletonScope();\n options.bind(TYPES.FontService).to(FontService).inSingletonScope();\n options.bind(TYPES.VoiceoverService).to(VoiceoverService).inSingletonScope();\n});\n\n/**\n * Tools module - binds MCP tools\n */\nexport const toolsModule = new ContainerModule((options: ContainerModuleLoadOptions) => {\n options.bind(TYPES.Tool).to(RenderVideoTool).inSingletonScope();\n options.bind(TYPES.Tool).to(ListCompositionsTool).inSingletonScope();\n options.bind(TYPES.Tool).to(GetMediaInfoTool).inSingletonScope();\n options.bind(TYPES.Tool).to(PreviewFrameTool).inSingletonScope();\n options.bind(TYPES.Tool).to(GenerateCaptionsTool).inSingletonScope();\n options.bind(TYPES.Tool).to(ImportSrtTool).inSingletonScope();\n options.bind(TYPES.Tool).to(GenerateVoiceoverTool).inSingletonScope();\n});\n\n/**\n * Creates and configures the DI container\n */\nexport function createContainer(): Container {\n const container = new Container();\n container.load(servicesModule, toolsModule);\n return container;\n}\n","import { Server } from '@modelcontextprotocol/sdk/server/index.js';\nimport { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';\nimport type { Container } from 'inversify';\nimport type { Tool } from '../types/index.js';\nimport { TYPES } from '../types/index.js';\n\nexport function createServer(container: Container): Server {\n const server = new Server(\n {\n name: 'video-editor-mcp',\n version: '0.1.0',\n },\n {\n capabilities: {\n tools: {},\n },\n },\n );\n\n const tools = container.getAll<Tool>(TYPES.Tool);\n const toolMap = new Map<string, Tool>();\n for (const tool of tools) {\n const def = tool.getDefinition();\n toolMap.set(def.name, tool);\n }\n\n server.setRequestHandler(ListToolsRequestSchema, async () => ({\n tools: tools.map((t) => t.getDefinition()),\n }));\n\n server.setRequestHandler(CallToolRequestSchema, async (request) => {\n const { name, arguments: args } = request.params;\n const tool = toolMap.get(name);\n if (!tool) {\n throw new Error(`Unknown tool: ${name}`);\n }\n return await tool.execute(args);\n });\n\n return server;\n}\n","/**\n * STDIO Transport\n *\n * DESIGN PATTERNS:\n * - Transport handler pattern implementing TransportHandler interface\n * - Standard I/O based communication for CLI usage\n *\n * CODING STANDARDS:\n * - Initialize server and transport properly\n * - Handle cleanup on shutdown with stop() method\n * - Use async/await for all operations\n *\n * AVOID:\n * - Forgetting to close transport on shutdown\n * - Missing error handling for connection failures\n */\n\nimport type { Server } from '@modelcontextprotocol/sdk/server/index.js';\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\n\n/**\n * Stdio transport handler for MCP server\n * Used for command-line and direct integrations\n */\nexport class StdioTransportHandler {\n private server: Server;\n private transport: StdioServerTransport | null = null;\n\n constructor(server: Server) {\n this.server = server;\n }\n\n async start(): Promise<void> {\n this.transport = new StdioServerTransport();\n await this.server.connect(this.transport);\n console.error('video-editor MCP server started on stdio');\n }\n\n async stop(): Promise<void> {\n if (this.transport) {\n await this.transport.close();\n this.transport = null;\n }\n }\n}\n"],"mappings":"ytCA2BO,IAAA,EAAA,KAAqB,CAa1B,SAAS,EAA+B,CACtC,IAAMC,EAAsB,EAAE,CACxB,EAAQ,EAAW,MAAM;EAAK,CAChC,EAAI,EAER,KAAO,EAAI,EAAM,QAAQ,CAEvB,GAAI,CAAC,EAAM,IAAI,MAAM,CAAE,CACrB,IACA,SAIF,IAGA,IAAM,EAAgB,EAAM,GAC5B,GAAI,CAAC,GAAe,SAAS,MAAM,CAAE,CACnC,IACA,SAGF,GAAM,CAAC,EAAU,GAAU,EAAc,MAAM,MAAM,CAAC,IAAK,GAAM,EAAE,MAAM,CAAC,CAC1E,GAAI,CAAC,GAAY,CAAC,EAAQ,CACxB,IACA,SAGF,IAAM,EAAU,KAAK,eAAe,EAAS,CACvC,EAAQ,KAAK,eAAe,EAAO,CAGzC,IACA,IAAMC,EAAsB,EAAE,CAC9B,KAAO,EAAI,EAAM,QAAU,EAAM,IAAI,MAAM,EACzC,EAAU,KAAK,EAAM,IAAM,GAAG,CAC9B,IAGF,IAAM,EAAO,EAAU,KAAK;EAAK,CAC7B,GACF,EAAS,KAAK,CAAE,OAAM,UAAS,QAAO,CAAC,CAI3C,OAAO,EAOT,eAAuB,EAA2B,CAChD,GAAM,CAAC,EAAM,GAAM,EAAU,MAAM,IAAI,CACvC,GAAI,CAAC,GAAQ,CAAC,EACZ,MAAU,MAAM,6BAA6B,IAAY,CAG3D,GAAM,CAAC,EAAO,EAAS,GAAW,EAAK,MAAM,IAAI,CAAC,IAAI,OAAO,CAC7D,GAAI,IAAU,IAAA,IAAa,IAAY,IAAA,IAAa,IAAY,IAAA,GAC9D,MAAU,MAAM,wBAAwB,IAAO,CAIjD,OADqB,EAAQ,KAAO,EAAU,GAAK,GAC7B,IAAO,OAAO,EAAG,CAOzC,qBAAqB,EAAqB,EAA6C,CACrF,IAAMC,EAAuB,EAAE,CAE/B,GAAI,EAAS,SAAW,EACtB,MAAO,CAAE,QAAO,CAGlB,IAAIC,EAA2B,CAC7B,QAAS,EAAS,IAAI,SAAW,EACjC,MAAO,EAAS,IAAI,OAAS,EAC7B,OAAQ,CAAC,EAAS,GAAc,CACjC,CAED,IAAK,IAAI,EAAI,EAAG,EAAI,EAAS,OAAQ,IAAK,CACxC,IAAM,EAAU,EAAS,GACpB,IAEO,EAAQ,QAAU,EAAY,OAE/B,GAET,EAAY,OAAO,KAAK,EAAQ,CAChC,EAAY,MAAQ,EAAQ,QAG5B,EAAM,KAAK,EAAY,CACvB,EAAc,CACZ,QAAS,EAAQ,QACjB,MAAO,EAAQ,MACf,OAAQ,CAAC,EAAQ,CAClB,GASL,OAJI,EAAY,OAAO,OAAS,GAC9B,EAAM,KAAK,EAAY,CAGlB,CAAE,QAAO,0BA5HP,CAAA,CAAA,EAAA,CCNb,MAAM,EAAyB,CAC7B,QACA,SACA,YACA,aACA,OACA,UACA,SACA,UACA,mBACA,eACA,kBACA,UACA,SACA,SACA,QACA,YACA,QACA,YACA,YACA,UACD,CAGM,IAAA,EAAA,KAAkB,CAIvB,yBAAoC,CAClC,MAAO,CAAC,GAAG,EAAuB,CAOpC,mBAAmB,EAAyB,CAC1C,IAAM,EAAmB,EAAO,aAAa,CAC7C,OAAO,EAAuB,KAAM,GAAS,EAAK,aAAa,GAAK,EAAiB,0BAf5E,CAAA,CAAA,EAAA,CCzBb,MAAME,GAAAA,EAAAA,EAAAA,WAA0BC,EAAAA,SAAS,CAGlC,IAAA,EAAA,KAAuB,CAI5B,MAAM,iBAAiB,EAA8B,CACnD,GAAI,CACF,GAAM,CAAE,UAAW,MAAMD,EAAc,UAAW,CAChD,KACA,QACA,gBACA,kBACA,MACA,OACA,EACD,CAAC,CAEI,EAAS,KAAK,MAAM,EAAO,CAC3B,EAAW,OAAO,WAAW,EAAO,QAAQ,UAAY,IAAI,CAElE,GAAI,OAAO,MAAM,EAAS,EAAI,GAAY,EACxC,MAAU,MAAM,+BAA+B,IAAM,CAGvD,OAAO,QACA,EAAO,CACd,MAAU,MAAM,oCAAoC,EAAI,IAAI,aAAiB,MAAQ,EAAM,QAAU,OAAO,EAAM,GAAI,CACpH,MAAO,EACR,CAAC,EAON,MAAM,iBAAiB,EAA8B,CACnD,GAAI,CACF,GAAM,CAAE,UAAW,MAAMA,EAAc,UAAW,CAChD,KACA,QACA,gBACA,kBACA,MACA,OACA,EACD,CAAC,CAEI,EAAS,KAAK,MAAM,EAAO,CAC3B,EAAW,OAAO,WAAW,EAAO,QAAQ,UAAY,IAAI,CAElE,GAAI,OAAO,MAAM,EAAS,EAAI,GAAY,EACxC,MAAU,MAAM,+BAA+B,IAAM,CAGvD,OAAO,QACA,EAAO,CACd,MAAU,MAAM,oCAAoC,EAAI,IAAI,aAAiB,MAAQ,EAAM,QAAU,OAAO,EAAM,GAAI,CACpH,MAAO,EACR,CAAC,EAON,MAAM,mBAAmB,EAAyD,CAChF,GAAI,CACF,GAAM,CAAE,UAAW,MAAMA,EAAc,UAAW,CAChD,KACA,QACA,kBACA,MACA,gBACA,sBACA,MACA,OACA,EACD,CAAC,CAGI,EADS,KAAK,MAAM,EAAO,CACX,UAAU,GAEhC,GAAI,CAAC,GAAQ,OAAS,CAAC,GAAQ,OAC7B,MAAU,MAAM,6BAA6B,IAAM,CAGrD,MAAO,CACL,MAAO,OAAO,EAAO,MAAM,CAC3B,OAAQ,OAAO,EAAO,OAAO,CAC9B,OACM,EAAO,CACd,MAAU,MACR,sCAAsC,EAAI,IAAI,aAAiB,MAAQ,EAAM,QAAU,OAAO,EAAM,GACpG,CAAE,MAAO,EAAO,CACjB,EAOL,MAAM,UAAU,EAA+B,CAC7C,GAAI,CAEF,OADA,MAAMA,EAAc,UAAW,CAAC,KAAM,QAAS,gBAAiB,kBAAmB,MAAO,OAAQ,EAAI,CAAC,CAChG,QACD,CACN,MAAO,6BA1GA,CAAA,CAAA,EAAA,CCPN,IAAA,EAAA,KAAoB,CACzB,cAAgD,KAEhD,MAAc,aAA+B,CAC3C,GAAI,CAAC,KAAK,cAAe,CACvB,IAAM,EAAcI,EAAAA,QAAK,QAAA,UAA6B,QAAQ,CAC9D,KAAK,eAAA,EAAA,EAAA,QAAuB,CAC1B,WAAYA,EAAAA,QAAK,QAAQ,EAAa,wBAAwB,CAC9D,UAAWA,EAAAA,QAAK,QAAQ,EAAa,SAAS,CAC9C,gBAAkB,IAAY,CAC5B,GAAG,EACH,QAAS,CACP,GAAG,EAAO,QACV,eAAgB,CACd,MAAO,CAAC,MAAO,OAAQ,MAAO,OAAO,CACtC,CACF,CACF,EACF,CAAC,CAEJ,OAAO,KAAK,cAGd,MAAM,OAAO,EAA+C,CAC1D,IAAM,EAAW,MAAM,KAAK,aAAa,CAgBzC,OARA,MAAA,EAAA,EAAA,aAAkB,CAChB,YAPkB,MAAA,EAAA,EAAA,mBAAwB,CAC1C,WACA,GAAI,EAAQ,cACZ,WAAY,EAAQ,WACrB,CAAC,CAIA,WACA,MAAO,EAAQ,OAAS,OACxB,eAAgB,EAAQ,WACxB,WAAY,EAAQ,WACrB,CAAC,CAEK,CAAE,WAAY,EAAQ,WAAY,CAG3C,MAAM,YAAY,EAAoD,CACpE,IAAM,EAAW,MAAM,KAAK,aAAa,CAiBzC,OATA,MAAA,EAAA,EAAA,aAAkB,CAChB,YAPkB,MAAA,EAAA,EAAA,mBAAwB,CAC1C,WACA,GAAI,EAAQ,cACZ,WAAY,EAAQ,WACrB,CAAC,CAIA,WACA,OAAQ,EAAQ,WAChB,MAAO,EAAQ,OAAS,EACxB,YAAa,EAAQ,aAAe,MACpC,WAAY,EAAQ,WACrB,CAAC,CAEK,CAAE,WAAY,EAAQ,WAAY,0BA9DhC,CAAA,CAAA,EAAA,CCUb,MAAM,GAAA,EAAA,EAAA,WAA0BC,EAAAA,SAAS,CAKnC,EAAmB,CACvB,WACA,YACA,WACA,SACA,UACA,aACA,UACA,cACA,YACA,WACD,CAeM,IAAA,EAAA,KAAuB,CAI5B,MAAM,aAAgC,CACpC,GAAI,CAEF,OADA,MAAM,EAAc,aAAc,CAAC,YAAY,CAAC,CACzC,QACD,CACN,MAAO,IAOX,MAAM,kBAAkB,EAAqD,CAC3E,GAAM,CAAE,OAAM,QAAQ,WAAY,QAAQ,EAAK,cAAe,EAGxD,EAAU,MAAME,EAAAA,QAAG,QAAQC,EAAAA,QAAK,KAAKC,EAAAA,QAAG,QAAQ,CAAE,UAAU,CAAC,CAC7D,EAAeD,EAAAA,QAAK,KAAK,EAAS,YAAY,CAEpD,GAAI,CASF,OAPA,MAAMD,EAAAA,QAAG,UAAU,EAAc,EAAM,QAAQ,CAK/C,MAAM,EAAc,aAFP,CAAC,EAAc,EAAY,UAAW,EAAO,UAAW,EAAM,UAAU,CAAC,CAE/C,CAEhC,CAAE,aAAY,OACd,EAAO,CACd,MAAU,MAAM,iCAAiC,aAAiB,MAAQ,EAAM,QAAU,OAAO,EAAM,GAAI,CACzG,MAAO,EACR,CAAC,QACM,CAER,GAAI,CACF,MAAMA,EAAAA,QAAG,GAAG,EAAS,CAAE,UAAW,GAAM,MAAO,GAAM,CAAC,MAChD,IASZ,YAAuB,CACrB,MAAO,CAAC,GAAG,EAAiB,0BApDnB,CAAA,CAAA,EAAA,CC9BN,IAAA,EAAA,KAAwB,CAM7B,QAAkB,EAA8B,CAC9C,MAAO,CACL,QAAS,CAAC,CAAE,KAAM,OAAQ,OAAM,CAAC,CAClC,CAQH,YAAsB,EAA+B,CACnD,MAAO,CACL,QAAS,CAAC,CAAE,KAAM,OAAQ,KAAM,KAAK,UAAU,EAAM,KAAM,EAAE,CAAE,CAAC,CACjE,CAQH,MAAgB,EAAiC,CAC/C,MAAO,CACL,QAAS,CAAC,CAAE,KAAM,OAAQ,KAAM,UAAU,IAAW,CAAC,CACtD,QAAS,GACV,CAQH,MAAgB,YAAe,EAAmD,CAChF,GAAI,CACF,OAAO,MAAM,GAAI,OACV,EAAK,CACZ,OAAO,KAAK,MAAM,aAAe,MAAQ,EAAI,QAAU,gBAAgB,2BA7ChE,CAAA,CAAA,EAAA,CCab,MAAa,EAAQ,CACnB,cAAe,OAAO,IAAI,gBAAgB,CAC1C,iBAAkB,OAAO,IAAI,mBAAmB,CAChD,eAAgB,OAAO,IAAI,iBAAiB,CAC5C,YAAa,OAAO,IAAI,cAAc,CACtC,iBAAkB,OAAO,IAAI,mBAAmB,CAChD,KAAM,OAAO,IAAI,OAAO,CACzB,sKCtBM,IAAA,EAAA,cAAmC,CAAoD,eAC5F,OAAgB,UAAY,oBAE5B,YAAY,EAAsE,CAChF,OAAO,CADyC,KAAA,eAAA,EAIlD,eAAgC,CAC9B,MAAO,CACL,KAAA,EAA2B,UAC3B,YAAa,2EACb,YAAa,CACX,KAAM,SACN,WAAY,CACV,SAAU,CACR,KAAM,QACN,YAAa,yDACb,MAAO,CACL,KAAM,SACN,WAAY,CACV,KAAM,CAAE,KAAM,SAAU,CACxB,QAAS,CAAE,KAAM,SAAU,CAC3B,MAAO,CAAE,KAAM,SAAU,CAC1B,CACD,SAAU,CAAC,OAAQ,UAAW,QAAQ,CACvC,CACF,CACD,UAAW,CACT,KAAM,SACN,YAAa,0EACd,CACF,CACD,SAAU,CAAC,WAAW,CACtB,qBAAsB,GACvB,CACF,CAGH,MAAM,QAAQ,EAA2D,CACvE,GAAI,CACF,GAAM,CAAE,WAAU,YAAY,KAAQ,EAChCM,EAA0B,EAAS,IAAK,IAAO,CACnD,KAAM,EAAE,KACR,QAAS,EAAE,QACX,MAAO,EAAE,MACV,EAAE,CACG,EAAS,KAAK,eAAe,qBAAqB,EAAc,EAAU,CAChF,OAAO,KAAK,YAAY,EAAO,OACxB,EAAO,CACd,OAAO,KAAK,MAAM,aAAiB,MAAQ,EAAM,QAAU,gBAAgB,6BAlDpE,kBAIS,EAAM,eAAe,CAAA,mFCDpC,IAAA,EAAA,cAAoC,CAAqD,eAC9F,OAAgB,UAAY,qBAE5B,YAAY,EAA4E,CACtF,OAAO,CAD2C,KAAA,iBAAA,EAIpD,eAAgC,CAC9B,MAAO,CACL,KAAA,EAA4B,UAC5B,YAAa,mEACb,YAAa,CACX,KAAM,SACN,WAAY,CACV,KAAM,CACJ,KAAM,SACN,YAAa,4BACd,CACD,MAAO,CACL,KAAM,SACN,YACE,+IACH,CACD,MAAO,CACL,KAAM,SACN,YAAa,yCACd,CACD,WAAY,CACV,KAAM,SACN,YAAa,2CACd,CACF,CACD,SAAU,CAAC,OAAQ,aAAa,CAChC,qBAAsB,GACvB,CACF,CAGH,MAAM,QAAQ,EAA4D,CACxE,GAAI,CAEF,GAAI,CADgB,MAAM,KAAK,iBAAiB,aAAa,CAE3D,OAAO,KAAK,MAAM,uEAAuE,CAG3F,IAAM,EAAS,MAAM,KAAK,iBAAiB,kBAAkB,CAC3D,KAAM,EAAM,KACZ,MAAO,EAAM,MACb,MAAO,EAAM,MACb,WAAY,EAAM,WACnB,CAAC,CAEF,OAAO,KAAK,YAAY,EAAO,OACxB,EAAO,CACd,OAAO,KAAK,MAAM,aAAiB,MAAQ,EAAM,QAAU,gBAAgB,6BAvDpE,kBAIS,EAAM,iBAAiB,CAAA,mFCLtC,IAAA,EAAA,cAA+B,CAAgD,eACpF,OAAgB,UAAY,iBAE5B,YAAY,EAA4E,CACtF,OAAO,CAD2C,KAAA,iBAAA,EAIpD,eAAgC,CAC9B,MAAO,CACL,KAAA,EAAuB,UACvB,YAAa,wEACb,YAAa,CACX,KAAM,SACN,WAAY,CACV,IAAK,CACH,KAAM,SACN,YAAa,yBACd,CACD,KAAM,CACJ,KAAM,SACN,KAAM,CAAC,QAAS,QAAQ,CACxB,YAAa,8BACd,CACF,CACD,SAAU,CAAC,MAAM,CACjB,qBAAsB,GACvB,CACF,CAGH,MAAM,QAAQ,EAAuD,CACnE,GAAI,CACF,GAAM,CAAE,MAAK,OAAO,SAAY,EAC1B,EAAY,MAAM,KAAK,iBAAiB,UAAU,EAAI,CAE5D,GAAI,IAAS,QAAS,CACpB,IAAMK,EAAW,MAAM,KAAK,iBAAiB,iBAAiB,EAAI,CAClE,OAAO,KAAK,YAAY,CACtB,MACA,KAAM,QACN,SAAA,EACA,YACD,CAAC,CAGJ,GAAM,CAAC,EAAU,GAAc,MAAM,QAAQ,IAAI,CAC/C,KAAK,iBAAiB,iBAAiB,EAAI,CAC3C,KAAK,iBAAiB,mBAAmB,EAAI,CAC9C,CAAC,CAEF,OAAO,KAAK,YAAY,CACtB,MACA,KAAM,QACN,WACA,MAAO,EAAW,MAClB,OAAQ,EAAW,OACnB,YACD,CAAC,OACK,EAAO,CACd,OAAO,KAAK,MAAM,aAAiB,MAAQ,EAAM,QAAU,gBAAgB,6BA5DpE,kBAIS,EAAM,iBAAiB,CAAA,mFCJtC,IAAA,EAAA,cAA4B,CAA6C,eAC9E,OAAgB,UAAY,aAE5B,YAAY,EAAsE,CAChF,OAAO,CADyC,KAAA,eAAA,EAIlD,eAAgC,CAC9B,MAAO,CACL,KAAA,EAAoB,UACpB,YAAa,mFACb,YAAa,CACX,KAAM,SACN,WAAY,CACV,WAAY,CACV,KAAM,SACN,YAAa,+BACd,CACF,CACD,SAAU,CAAC,aAAa,CACxB,qBAAsB,GACvB,CACF,CAGH,MAAM,QAAQ,EAAoD,CAChE,GAAI,CACF,IAAM,EAAW,KAAK,eAAe,SAAS,EAAM,WAAW,CAC/D,OAAO,KAAK,YAAY,CACtB,aAAc,EAAS,OACvB,WACD,CAAC,OACK,EAAO,CACd,OAAO,KAAK,MAAM,aAAiB,MAAQ,EAAM,QAAU,gBAAgB,6BAlCpE,kBAIS,EAAM,eAAe,CAAA,iFCNpC,IAAA,EAAA,cAAmC,CAAoD,eAC5F,OAAgB,UAAY,oBAE5B,eAAgC,CAC9B,MAAO,CACL,KAAA,EAA2B,UAC3B,YAAa,2FACb,YAAa,CACX,KAAM,SACN,WAAY,EAAE,CACd,SAAU,EAAE,CACZ,qBAAsB,GACvB,CACF,CAGH,MAAM,SAAmC,CACvC,IAAM,EAAe,CACnB,CACE,GAAI,OACJ,YAAa,kCACb,MAAO,KACP,OAAQ,KACR,IAAK,GACL,iBAAkB,KAClB,aAAc,CACZ,MAAO,EAAE,CACT,YAAa,EACb,gBAAiB,UAClB,CACF,CACD,CACE,GAAI,WACJ,YAAa,iEACb,MAAO,KACP,OAAQ,KACR,IAAK,GACL,iBAAkB,KAClB,aAAc,CACZ,MAAO,EAAE,CACT,YAAa,EACb,gBAAiB,UAClB,CACF,CACD,CACE,GAAI,SACJ,YAAa,2CACb,MAAO,KACP,OAAQ,KACR,IAAK,GACL,iBAAkB,KAClB,aAAc,CACZ,MAAO,EAAE,CACT,YAAa,EACb,gBAAiB,UAClB,CACF,CACF,CAED,OAAO,KAAK,YAAY,CACtB,iBAAkB,EAAa,OAC/B,eACD,CAAC,4BA/DO,CAAA,CAAA,EAAA,SCQN,IAAA,EAAA,cAA+B,CAAgD,eACpF,OAAgB,UAAY,gBAE5B,YAAY,EAAmE,CAC7E,OAAO,CADwC,KAAA,cAAA,EAIjD,eAAgC,CAC9B,MAAO,CACL,KAAA,EAAuB,UACvB,YAAa,8EACb,YAAa,CACX,KAAM,SACN,WAAY,CACV,cAAe,CACb,KAAM,SACN,YAAa,+DACd,CACD,WAAY,CACV,KAAM,SACN,YAAa,wCACd,CACD,WAAY,CACV,KAAM,SACN,YAAa,0CACd,CACD,MAAO,CACL,KAAM,SACN,YAAa,sCACd,CACD,YAAa,CACX,KAAM,SACN,KAAM,CAAC,MAAO,OAAO,CACrB,YAAa,8BACd,CACF,CACD,SAAU,CAAC,gBAAiB,aAAc,aAAa,CACvD,qBAAsB,GACvB,CACF,CAGH,MAAM,QAAQ,EAAuD,CACnE,GAAI,CACF,IAAMM,EAA8B,CAClC,cAAe,EAAM,cACrB,WAAY,EAAM,WAClB,WAAY,EAAM,WAClB,MAAO,EAAM,MACb,YAAa,EAAM,YACpB,CACK,EAAS,MAAM,KAAK,cAAc,YAAY,EAAQ,CAC5D,OAAO,KAAK,YAAY,EAAO,OACxB,EAAO,CACd,OAAO,KAAK,MAAM,aAAiB,MAAQ,EAAM,QAAU,gBAAgB,6BAvDpE,kBAIS,EAAM,cAAc,CAAA,mFCJnC,IAAA,EAAA,cAA8B,CAA+C,eAClF,OAAgB,UAAY,eAE5B,YAAY,EAAmE,CAC7E,OAAO,CADwC,KAAA,cAAA,EAIjD,eAAgC,CAC9B,MAAO,CACL,KAAA,EAAsB,UACtB,YAAa,gDACb,YAAa,CACX,KAAM,SACN,WAAY,CACV,cAAe,CACb,KAAM,SACN,YAAa,+DACd,CACD,WAAY,CACV,KAAM,SACN,YAAa,wCACd,CACD,WAAY,CACV,KAAM,SACN,YAAa,0CACd,CACD,MAAO,CACL,KAAM,SACN,KAAM,CAAC,OAAQ,OAAQ,MAAO,MAAM,CACpC,YAAa,8BACd,CACF,CACD,SAAU,CAAC,gBAAiB,aAAc,aAAa,CACvD,qBAAsB,GACvB,CACF,CAGH,MAAM,QAAQ,EAAsD,CAClE,GAAI,CACF,IAAMG,EAAyB,CAC7B,cAAe,EAAM,cACrB,WAAY,EAAM,WAClB,WAAY,EAAM,WAClB,MAAO,EAAM,MACd,CACK,EAAS,MAAM,KAAK,cAAc,OAAO,EAAQ,CACvD,OAAO,KAAK,YAAY,EAAO,OACxB,EAAO,CACd,OAAO,KAAK,MAAM,aAAiB,MAAQ,EAAM,QAAU,gBAAgB,6BAlDpE,kBAIS,EAAM,cAAc,CAAA,2ECO1C,MAAa,EAAiB,IAAIC,EAAAA,gBAAiB,GAAwC,CACzF,EAAQ,KAAK,EAAM,cAAc,CAAC,GAAG,EAAc,CAAC,kBAAkB,CACtE,EAAQ,KAAK,EAAM,iBAAiB,CAAC,GAAG,EAAiB,CAAC,kBAAkB,CAC5E,EAAQ,KAAK,EAAM,eAAe,CAAC,GAAG,EAAe,CAAC,kBAAkB,CACxE,EAAQ,KAAK,EAAM,YAAY,CAAC,GAAG,EAAY,CAAC,kBAAkB,CAClE,EAAQ,KAAK,EAAM,iBAAiB,CAAC,GAAG,EAAiB,CAAC,kBAAkB,EAC5E,CAKW,GAAc,IAAIA,EAAAA,gBAAiB,GAAwC,CACtF,EAAQ,KAAK,EAAM,KAAK,CAAC,GAAG,EAAgB,CAAC,kBAAkB,CAC/D,EAAQ,KAAK,EAAM,KAAK,CAAC,GAAG,EAAqB,CAAC,kBAAkB,CACpE,EAAQ,KAAK,EAAM,KAAK,CAAC,GAAG,EAAiB,CAAC,kBAAkB,CAChE,EAAQ,KAAK,EAAM,KAAK,CAAC,GAAG,EAAiB,CAAC,kBAAkB,CAChE,EAAQ,KAAK,EAAM,KAAK,CAAC,GAAG,EAAqB,CAAC,kBAAkB,CACpE,EAAQ,KAAK,EAAM,KAAK,CAAC,GAAG,EAAc,CAAC,kBAAkB,CAC7D,EAAQ,KAAK,EAAM,KAAK,CAAC,GAAG,EAAsB,CAAC,kBAAkB,EACrE,CAKF,SAAgB,IAA6B,CAC3C,IAAM,EAAY,IAAIC,EAAAA,UAEtB,OADA,EAAU,KAAK,EAAgB,GAAY,CACpC,ECpDT,SAAgB,GAAa,EAA8B,CACzD,IAAM,EAAS,IAAIC,EAAAA,OACjB,CACE,KAAM,mBACN,QAAS,QACV,CACD,CACE,aAAc,CACZ,MAAO,EAAE,CACV,CACF,CACF,CAEK,EAAQ,EAAU,OAAa,EAAM,KAAK,CAC1C,EAAU,IAAI,IACpB,IAAK,IAAM,KAAQ,EAAO,CACxB,IAAM,EAAM,EAAK,eAAe,CAChC,EAAQ,IAAI,EAAI,KAAM,EAAK,CAgB7B,OAbA,EAAO,kBAAkBC,EAAAA,uBAAwB,UAAa,CAC5D,MAAO,EAAM,IAAK,GAAM,EAAE,eAAe,CAAC,CAC3C,EAAE,CAEH,EAAO,kBAAkBC,EAAAA,sBAAuB,KAAO,IAAY,CACjE,GAAM,CAAE,OAAM,UAAW,GAAS,EAAQ,OACpC,EAAO,EAAQ,IAAI,EAAK,CAC9B,GAAI,CAAC,EACH,MAAU,MAAM,iBAAiB,IAAO,CAE1C,OAAO,MAAM,EAAK,QAAQ,EAAK,EAC/B,CAEK,ECfT,IAAa,GAAb,KAAmC,CACjC,OACA,UAAiD,KAEjD,YAAY,EAAgB,CAC1B,KAAK,OAAS,EAGhB,MAAM,OAAuB,CAC3B,KAAK,UAAY,IAAIC,EAAAA,qBACrB,MAAM,KAAK,OAAO,QAAQ,KAAK,UAAU,CACzC,QAAQ,MAAM,2CAA2C,CAG3D,MAAM,MAAsB,CAC1B,AAEE,KAAK,aADL,MAAM,KAAK,UAAU,OAAO,CACX"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@agimon-ai/video-editor-mcp",
3
3
  "description": "MCP server for video editing with Remotion",
4
- "version": "0.2.2",
4
+ "version": "0.2.4",
5
5
  "license": "AGPL-3.0",
6
6
  "keywords": [
7
7
  "mcp",
@@ -1,4 +0,0 @@
1
- import{execFile as e}from"node:child_process";import t from"node:path";import"reflect-metadata";import{Container as n,ContainerModule as r,inject as i,injectable as a}from"inversify";import{promisify as o}from"node:util";import{bundle as s}from"@remotion/bundler";import{renderMedia as c,renderStill as l,selectComposition as u}from"@remotion/renderer";import d from"node:fs/promises";import f from"node:os";import{Server as p}from"@modelcontextprotocol/sdk/server/index.js";import{CallToolRequestSchema as m,ListToolsRequestSchema as h}from"@modelcontextprotocol/sdk/types.js";import{StdioServerTransport as g}from"@modelcontextprotocol/sdk/server/stdio.js";function _(e,t,n,r){var i=arguments.length,a=i<3?t:r===null?r=Object.getOwnPropertyDescriptor(t,n):r,o;if(typeof Reflect==`object`&&typeof Reflect.decorate==`function`)a=Reflect.decorate(e,t,n,r);else for(var s=e.length-1;s>=0;s--)(o=e[s])&&(a=(i<3?o(a):i>3?o(t,n,a):o(t,n))||a);return i>3&&a&&Object.defineProperty(t,n,a),a}let v=class{parseSrt(e){let t=[],n=e.split(`
2
- `),r=0;for(;r<n.length;){if(!n[r]?.trim()){r++;continue}r++;let e=n[r];if(!e?.includes(`-->`)){r++;continue}let[i,a]=e.split(`-->`).map(e=>e.trim());if(!i||!a){r++;continue}let o=this.parseTimestamp(i),s=this.parseTimestamp(a);r++;let c=[];for(;r<n.length&&n[r]?.trim();)c.push(n[r]??``),r++;let l=c.join(`
3
- `);l&&t.push({text:l,startMs:o,endMs:s})}return t}parseTimestamp(e){let[t,n]=e.split(`,`);if(!t||!n)throw Error(`Invalid timestamp format: ${e}`);let[r,i,a]=t.split(`:`).map(Number);if(r===void 0||i===void 0||a===void 0)throw Error(`Invalid time format: ${t}`);return(r*3600+i*60+a)*1e3+Number(n)}createTikTokCaptions(e,t){let n=[];if(e.length===0)return{pages:n};let r={startMs:e[0]?.startMs??0,endMs:e[0]?.endMs??0,tokens:[e[0]]};for(let i=1;i<e.length;i++){let a=e[i];a&&(a.startMs-r.endMs<=t?(r.tokens.push(a),r.endMs=a.endMs):(n.push(r),r={startMs:a.startMs,endMs:a.endMs,tokens:[a]}))}return r.tokens.length>0&&n.push(r),{pages:n}}};v=_([a()],v);const y=[`Inter`,`Roboto`,`Open Sans`,`Montserrat`,`Lato`,`Poppins`,`Oswald`,`Raleway`,`Playfair Display`,`Merriweather`,`Source Sans Pro`,`PT Sans`,`Ubuntu`,`Nunito`,`Rubik`,`Work Sans`,`Karla`,`Noto Sans`,`Fira Sans`,`DM Sans`];let b=class{getAvailableGoogleFonts(){return[...y]}validateFontFamily(e){let t=e.toLowerCase();return y.some(e=>e.toLowerCase()===t)}};b=_([a()],b);const x=o(e);let S=class{async getVideoDuration(e){try{let{stdout:t}=await x(`ffprobe`,[`-v`,`error`,`-show_entries`,`format=duration`,`-of`,`json`,e]),n=JSON.parse(t),r=Number.parseFloat(n.format?.duration??`0`);if(Number.isNaN(r)||r<=0)throw Error(`Invalid duration for video: ${e}`);return r}catch(t){throw Error(`Failed to get video duration for ${e}: ${t instanceof Error?t.message:String(t)}`)}}async getAudioDuration(e){try{let{stdout:t}=await x(`ffprobe`,[`-v`,`error`,`-show_entries`,`format=duration`,`-of`,`json`,e]),n=JSON.parse(t),r=Number.parseFloat(n.format?.duration??`0`);if(Number.isNaN(r)||r<=0)throw Error(`Invalid duration for audio: ${e}`);return r}catch(t){throw Error(`Failed to get audio duration for ${e}: ${t instanceof Error?t.message:String(t)}`)}}async getVideoDimensions(e){try{let{stdout:t}=await x(`ffprobe`,[`-v`,`error`,`-select_streams`,`v:0`,`-show_entries`,`stream=width,height`,`-of`,`json`,e]),n=JSON.parse(t).streams?.[0];if(!n?.width||!n?.height)throw Error(`No video stream found in: ${e}`);return{width:Number(n.width),height:Number(n.height)}}catch(t){throw Error(`Failed to get video dimensions for ${e}: ${t instanceof Error?t.message:String(t)}`)}}async canDecode(e){try{return await x(`ffprobe`,[`-v`,`error`,`-show_entries`,`format=duration`,`-of`,`json`,e]),!0}catch{return!1}}};S=_([a()],S);let C=class{bundlePromise=null;async getServeUrl(){if(!this.bundlePromise){let e=t.resolve(import.meta.dirname,`../..`);this.bundlePromise=s({entryPoint:t.resolve(e,`src/remotion/index.ts`),publicDir:t.resolve(e,`public`),webpackOverride:e=>({...e,resolve:{...e.resolve,extensionAlias:{".js":[`.ts`,`.tsx`,`.js`,`.jsx`]}}})})}return this.bundlePromise}async render(e){let t=await this.getServeUrl();return await c({composition:await u({serveUrl:t,id:e.compositionId,inputProps:e.inputProps}),serveUrl:t,codec:e.codec??`h264`,outputLocation:e.outputPath,inputProps:e.inputProps}),{outputPath:e.outputPath}}async renderStill(e){let t=await this.getServeUrl();return await l({composition:await u({serveUrl:t,id:e.compositionId,inputProps:e.inputProps}),serveUrl:t,output:e.outputPath,frame:e.frame??0,imageFormat:e.imageFormat??`png`,inputProps:e.inputProps}),{outputPath:e.outputPath}}};C=_([a()],C);const w=o(e),T=[`af_sarah`,`af_nicole`,`af_bella`,`af_sky`,`am_adam`,`am_michael`,`bf_emma`,`bf_isabella`,`bm_george`,`bm_lewis`];let E=class{async isAvailable(){try{return await w(`kokoro-tts`,[`--version`]),!0}catch{return!1}}async generateVoiceover(e){let{text:n,voice:r=`af_sarah`,speed:i=1,outputPath:a}=e,o=await d.mkdtemp(t.join(f.tmpdir(),`kokoro-`)),s=t.join(o,`input.txt`);try{return await d.writeFile(s,n,`utf-8`),await w(`kokoro-tts`,[s,a,`--voice`,r,`--speed`,i.toString()]),{outputPath:a}}catch(e){throw Error(`Failed to generate voiceover: ${e instanceof Error?e.message:String(e)}`)}finally{try{await d.rm(o,{recursive:!0,force:!0})}catch{}}}listVoices(){return[...T]}};E=_([a()],E);let D=class{success(e){return{content:[{type:`text`,text:e}]}}successJson(e){return{content:[{type:`text`,text:JSON.stringify(e,null,2)}]}}error(e){return{content:[{type:`text`,text:`Error: ${e}`}],isError:!0}}async safeExecute(e){try{return await e()}catch(e){return this.error(e instanceof Error?e.message:`Unknown error`)}}};D=_([a()],D);const O={RenderService:Symbol.for(`RenderService`),MediaInfoService:Symbol.for(`MediaInfoService`),CaptionService:Symbol.for(`CaptionService`),FontService:Symbol.for(`FontService`),VoiceoverService:Symbol.for(`VoiceoverService`),Tool:Symbol.for(`Tool`)};function k(e,t){if(typeof Reflect==`object`&&typeof Reflect.metadata==`function`)return Reflect.metadata(e,t)}function A(e,t){return function(n,r){t(n,r,e)}}var j,M;let N=class extends D{static{M=this}static TOOL_NAME=`generate_captions`;constructor(e){super(),this.captionService=e}getDefinition(){return{name:M.TOOL_NAME,description:`Create TikTok-style caption pages from a caption array for video overlay`,inputSchema:{type:`object`,properties:{captions:{type:`array`,description:`Array of caption objects with text, startMs, and endMs`,items:{type:`object`,properties:{text:{type:`string`},startMs:{type:`number`},endMs:{type:`number`}},required:[`text`,`startMs`,`endMs`]}},combineMs:{type:`number`,description:`Milliseconds threshold for combining captions into pages (default: 100)`}},required:[`captions`],additionalProperties:!1}}}async execute(e){try{let{captions:t,combineMs:n=100}=e,r=t.map(e=>({text:e.text,startMs:e.startMs,endMs:e.endMs})),i=this.captionService.createTikTokCaptions(r,n);return this.successJson(i)}catch(e){return this.error(e instanceof Error?e.message:`Unknown error`)}}};N=M=_([a(),A(0,i(O.CaptionService)),k(`design:paramtypes`,[typeof(j=v!==void 0&&v)==`function`?j:Object])],N);var P,F;let I=class extends D{static{F=this}static TOOL_NAME=`generate_voiceover`;constructor(e){super(),this.voiceoverService=e}getDefinition(){return{name:F.TOOL_NAME,description:`Generate voiceover audio from text using Kokoro TTS local engine`,inputSchema:{type:`object`,properties:{text:{type:`string`,description:`Text to convert to speech`},voice:{type:`string`,description:`Voice ID (default: af_sarah). Options: af_sarah, af_nicole, af_bella, af_sky, am_adam, am_michael, bf_emma, bf_isabella, bm_george, bm_lewis`},speed:{type:`number`,description:`Speech speed multiplier (default: 1.0)`},outputPath:{type:`string`,description:`Output file path for the generated audio`}},required:[`text`,`outputPath`],additionalProperties:!1}}}async execute(e){try{if(!await this.voiceoverService.isAvailable())return this.error(`Kokoro TTS is not installed. Install it with: pip install kokoro-tts`);let t=await this.voiceoverService.generateVoiceover({text:e.text,voice:e.voice,speed:e.speed,outputPath:e.outputPath});return this.successJson(t)}catch(e){return this.error(e instanceof Error?e.message:`Unknown error`)}}};I=F=_([a(),A(0,i(O.VoiceoverService)),k(`design:paramtypes`,[typeof(P=E!==void 0&&E)==`function`?P:Object])],I);var L,R;let z=class extends D{static{R=this}static TOOL_NAME=`get_media_info`;constructor(e){super(),this.mediaInfoService=e}getDefinition(){return{name:R.TOOL_NAME,description:`Get duration, dimensions, and decodability info for video/audio files`,inputSchema:{type:`object`,properties:{src:{type:`string`,description:`Path to the media file`},type:{type:`string`,enum:[`video`,`audio`],description:`Media type (default: video)`}},required:[`src`],additionalProperties:!1}}}async execute(e){try{let{src:t,type:n=`video`}=e,r=await this.mediaInfoService.canDecode(t);if(n===`audio`){let e=await this.mediaInfoService.getAudioDuration(t);return this.successJson({src:t,type:`audio`,duration:e,canDecode:r})}let[i,a]=await Promise.all([this.mediaInfoService.getVideoDuration(t),this.mediaInfoService.getVideoDimensions(t)]);return this.successJson({src:t,type:`video`,duration:i,width:a.width,height:a.height,canDecode:r})}catch(e){return this.error(e instanceof Error?e.message:`Unknown error`)}}};z=R=_([a(),A(0,i(O.MediaInfoService)),k(`design:paramtypes`,[typeof(L=S!==void 0&&S)==`function`?L:Object])],z);var B,V;let H=class extends D{static{V=this}static TOOL_NAME=`import_srt`;constructor(e){super(),this.captionService=e}getDefinition(){return{name:V.TOOL_NAME,description:`Parse SRT subtitle file content into structured Caption format for video overlay`,inputSchema:{type:`object`,properties:{srtContent:{type:`string`,description:`SRT file content as a string`}},required:[`srtContent`],additionalProperties:!1}}}async execute(e){try{let t=this.captionService.parseSrt(e.srtContent);return this.successJson({captionCount:t.length,captions:t})}catch(e){return this.error(e instanceof Error?e.message:`Unknown error`)}}};H=V=_([a(),A(0,i(O.CaptionService)),k(`design:paramtypes`,[typeof(B=v!==void 0&&v)==`function`?B:Object])],H);var U;let W=class extends D{static{U=this}static TOOL_NAME=`list_compositions`;getDefinition(){return{name:U.TOOL_NAME,description:`List all available Remotion compositions with their schemas, dimensions, and format info`,inputSchema:{type:`object`,properties:{},required:[],additionalProperties:!1}}}async execute(){let e=[{id:`Main`,description:`Main 16:9 landscape composition`,width:1920,height:1080,fps:30,durationInFrames:null,defaultProps:{clips:[],audioVolume:1,backgroundColor:`#000000`}},{id:`Vertical`,description:`Vertical 9:16 portrait composition (TikTok, Instagram Stories)`,width:1080,height:1920,fps:30,durationInFrames:null,defaultProps:{clips:[],audioVolume:1,backgroundColor:`#000000`}},{id:`Square`,description:`Square 1:1 composition (Instagram posts)`,width:1080,height:1080,fps:30,durationInFrames:null,defaultProps:{clips:[],audioVolume:1,backgroundColor:`#000000`}}];return this.successJson({compositionCount:e.length,compositions:e})}};W=U=_([a()],W);var G,K;let q=class extends D{static{K=this}static TOOL_NAME=`preview_frame`;constructor(e){super(),this.renderService=e}getDefinition(){return{name:K.TOOL_NAME,description:`Render a single frame from a composition as a PNG or JPEG image for preview`,inputSchema:{type:`object`,properties:{compositionId:{type:`string`,description:`Remotion composition ID (e.g., "Main", "Vertical", "Square")`},inputProps:{type:`object`,description:`JSON props to pass to the composition`},outputPath:{type:`string`,description:`Output file path for the rendered image`},frame:{type:`number`,description:`Frame number to render (default: 0)`},imageFormat:{type:`string`,enum:[`png`,`jpeg`],description:`Image format (default: png)`}},required:[`compositionId`,`inputProps`,`outputPath`],additionalProperties:!1}}}async execute(e){try{let t={compositionId:e.compositionId,inputProps:e.inputProps,outputPath:e.outputPath,frame:e.frame,imageFormat:e.imageFormat},n=await this.renderService.renderStill(t);return this.successJson(n)}catch(e){return this.error(e instanceof Error?e.message:`Unknown error`)}}};q=K=_([a(),A(0,i(O.RenderService)),k(`design:paramtypes`,[typeof(G=C!==void 0&&C)==`function`?G:Object])],q);var J,Y;let X=class extends D{static{Y=this}static TOOL_NAME=`render_video`;constructor(e){super(),this.renderService=e}getDefinition(){return{name:Y.TOOL_NAME,description:`Render a video from JSON props using Remotion`,inputSchema:{type:`object`,properties:{compositionId:{type:`string`,description:`Remotion composition ID (e.g., "Main", "Vertical", "Square")`},inputProps:{type:`object`,description:`JSON props to pass to the composition`},outputPath:{type:`string`,description:`Output file path for the rendered video`},codec:{type:`string`,enum:[`h264`,`h265`,`vp8`,`vp9`],description:`Video codec (default: h264)`}},required:[`compositionId`,`inputProps`,`outputPath`],additionalProperties:!1}}}async execute(e){try{let t={compositionId:e.compositionId,inputProps:e.inputProps,outputPath:e.outputPath,codec:e.codec},n=await this.renderService.render(t);return this.successJson(n)}catch(e){return this.error(e instanceof Error?e.message:`Unknown error`)}}};X=Y=_([a(),A(0,i(O.RenderService)),k(`design:paramtypes`,[typeof(J=C!==void 0&&C)==`function`?J:Object])],X);const Z=new r(e=>{e.bind(O.RenderService).to(C).inSingletonScope(),e.bind(O.MediaInfoService).to(S).inSingletonScope(),e.bind(O.CaptionService).to(v).inSingletonScope(),e.bind(O.FontService).to(b).inSingletonScope(),e.bind(O.VoiceoverService).to(E).inSingletonScope()}),Q=new r(e=>{e.bind(O.Tool).to(X).inSingletonScope(),e.bind(O.Tool).to(W).inSingletonScope(),e.bind(O.Tool).to(z).inSingletonScope(),e.bind(O.Tool).to(q).inSingletonScope(),e.bind(O.Tool).to(N).inSingletonScope(),e.bind(O.Tool).to(H).inSingletonScope(),e.bind(O.Tool).to(I).inSingletonScope()});function $(){let e=new n;return e.load(Z,Q),e}function ee(e){let t=new p({name:`video-editor-mcp`,version:`0.1.0`},{capabilities:{tools:{}}}),n=e.getAll(O.Tool),r=new Map;for(let e of n){let t=e.getDefinition();r.set(t.name,e)}return t.setRequestHandler(h,async()=>({tools:n.map(e=>e.getDefinition())})),t.setRequestHandler(m,async e=>{let{name:t,arguments:n}=e.params,i=r.get(t);if(!i)throw Error(`Unknown tool: ${t}`);return await i.execute(n)}),t}var te=class{server;transport=null;constructor(e){this.server=e}async start(){this.transport=new g,await this.server.connect(this.transport),console.error(`video-editor MCP server started on stdio`)}async stop(){this.transport&&=(await this.transport.close(),null)}};export{q as a,z as c,O as d,D as f,X as i,I as l,ee as n,W as o,C as p,$ as r,H as s,te as t,N as u};
4
- //# sourceMappingURL=stdio-C7h0m1FD.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"stdio-C7h0m1FD.mjs","names":["CaptionService","captions: Caption[]","textLines: string[]","pages: CaptionPage[]","currentPage: CaptionPage","FontService","execFileAsync","MediaInfoService","RenderService","VoiceoverService","BaseTool","GenerateCaptionsTool","captionService: CaptionService","captionArray: Caption[]","GenerateVoiceoverTool","voiceoverService: VoiceoverService","GetMediaInfoTool","mediaInfoService: MediaInfoService","duration","ImportSrtTool","captionService: CaptionService","ListCompositionsTool","PreviewFrameTool","renderService: RenderService","options: RenderStillOptions","RenderVideoTool","renderService: RenderService","options: RenderOptions"],"sources":["../src/services/CaptionService.ts","../src/services/FontService.ts","../src/services/MediaInfoService.ts","../src/services/RenderService.ts","../src/services/VoiceoverService.ts","../src/tools/BaseTool.ts","../src/types/index.ts","../src/tools/GenerateCaptionsTool.ts","../src/tools/GenerateVoiceoverTool.ts","../src/tools/GetMediaInfoTool.ts","../src/tools/ImportSrtTool.ts","../src/tools/ListCompositionsTool.ts","../src/tools/PreviewFrameTool.ts","../src/tools/RenderVideoTool.ts","../src/container/index.ts","../src/server/index.ts","../src/transports/stdio.ts"],"sourcesContent":["/**\n * CaptionService\n *\n * DESIGN PATTERNS:\n * - Service pattern for business logic encapsulation\n * - Dependency injection via InversifyJS\n * - Single responsibility principle\n *\n * CODING STANDARDS:\n * - Use async/await for asynchronous operations\n * - Throw descriptive errors for error cases\n * - Keep methods focused and well-named\n */\n\nimport { injectable } from 'inversify';\nimport type { Caption } from '../types/index.js';\n\n/**\n * TikTok-style caption page with grouped tokens\n */\nexport interface CaptionPage {\n startMs: number;\n endMs: number;\n tokens: Caption[];\n}\n\n@injectable()\nexport class CaptionService {\n /**\n * Parse SRT format content into Caption array\n *\n * SRT format:\n * 1\n * 00:00:01,000 --> 00:00:04,000\n * Welcome to the video\n *\n * 2\n * 00:00:04,500 --> 00:00:08,000\n * This is the second caption\n */\n parseSrt(srtContent: string): Caption[] {\n const captions: Caption[] = [];\n const lines = srtContent.split('\\n');\n let i = 0;\n\n while (i < lines.length) {\n // Skip empty lines\n if (!lines[i]?.trim()) {\n i++;\n continue;\n }\n\n // Skip sequence number\n i++;\n\n // Parse timestamp line\n const timestampLine = lines[i];\n if (!timestampLine?.includes('-->')) {\n i++;\n continue;\n }\n\n const [startStr, endStr] = timestampLine.split('-->').map((s) => s.trim());\n if (!startStr || !endStr) {\n i++;\n continue;\n }\n\n const startMs = this.parseTimestamp(startStr);\n const endMs = this.parseTimestamp(endStr);\n\n // Collect text lines until empty line or end\n i++;\n const textLines: string[] = [];\n while (i < lines.length && lines[i]?.trim()) {\n textLines.push(lines[i] ?? '');\n i++;\n }\n\n const text = textLines.join('\\n');\n if (text) {\n captions.push({ text, startMs, endMs });\n }\n }\n\n return captions;\n }\n\n /**\n * Parse SRT timestamp to milliseconds\n * Format: HH:MM:SS,mmm\n */\n private parseTimestamp(timestamp: string): number {\n const [time, ms] = timestamp.split(',');\n if (!time || !ms) {\n throw new Error(`Invalid timestamp format: ${timestamp}`);\n }\n\n const [hours, minutes, seconds] = time.split(':').map(Number);\n if (hours === undefined || minutes === undefined || seconds === undefined) {\n throw new Error(`Invalid time format: ${time}`);\n }\n\n const totalSeconds = hours * 3600 + minutes * 60 + seconds;\n return totalSeconds * 1000 + Number(ms);\n }\n\n /**\n * Create TikTok-style caption pages by grouping captions\n * Combines tokens that appear within combineMs milliseconds of each other\n */\n createTikTokCaptions(captions: Caption[], combineMs: number): { pages: CaptionPage[] } {\n const pages: CaptionPage[] = [];\n\n if (captions.length === 0) {\n return { pages };\n }\n\n let currentPage: CaptionPage = {\n startMs: captions[0]?.startMs ?? 0,\n endMs: captions[0]?.endMs ?? 0,\n tokens: [captions[0] as Caption],\n };\n\n for (let i = 1; i < captions.length; i++) {\n const caption = captions[i];\n if (!caption) continue;\n\n const gap = caption.startMs - currentPage.endMs;\n\n if (gap <= combineMs) {\n // Combine into current page\n currentPage.tokens.push(caption);\n currentPage.endMs = caption.endMs;\n } else {\n // Start new page\n pages.push(currentPage);\n currentPage = {\n startMs: caption.startMs,\n endMs: caption.endMs,\n tokens: [caption],\n };\n }\n }\n\n // Add final page\n if (currentPage.tokens.length > 0) {\n pages.push(currentPage);\n }\n\n return { pages };\n }\n}\n","/**\n * FontService\n *\n * DESIGN PATTERNS:\n * - Service pattern for business logic encapsulation\n * - Dependency injection via InversifyJS\n * - Single responsibility principle\n *\n * CODING STANDARDS:\n * - Use async/await for asynchronous operations\n * - Throw descriptive errors for error cases\n * - Keep methods focused and well-named\n */\n\nimport { injectable } from 'inversify';\n\n/**\n * Curated list of popular Google Fonts\n * These fonts are widely used and well-supported\n */\nconst AVAILABLE_GOOGLE_FONTS = [\n 'Inter',\n 'Roboto',\n 'Open Sans',\n 'Montserrat',\n 'Lato',\n 'Poppins',\n 'Oswald',\n 'Raleway',\n 'Playfair Display',\n 'Merriweather',\n 'Source Sans Pro',\n 'PT Sans',\n 'Ubuntu',\n 'Nunito',\n 'Rubik',\n 'Work Sans',\n 'Karla',\n 'Noto Sans',\n 'Fira Sans',\n 'DM Sans',\n] as const;\n\n@injectable()\nexport class FontService {\n /**\n * Get list of available Google Fonts\n */\n getAvailableGoogleFonts(): string[] {\n return [...AVAILABLE_GOOGLE_FONTS];\n }\n\n /**\n * Validate if a font family is in the available list\n * Case-insensitive comparison\n */\n validateFontFamily(family: string): boolean {\n const normalizedFamily = family.toLowerCase();\n return AVAILABLE_GOOGLE_FONTS.some((font) => font.toLowerCase() === normalizedFamily);\n }\n}\n","/**\n * MediaInfoService\n *\n * DESIGN PATTERNS:\n * - Service pattern for business logic encapsulation\n * - Dependency injection via InversifyJS\n * - Single responsibility principle\n *\n * CODING STANDARDS:\n * - Use async/await for asynchronous operations\n * - Throw descriptive errors for error cases\n * - Keep methods focused and well-named\n */\n\nimport { execFile } from 'node:child_process';\nimport { promisify } from 'node:util';\nimport { injectable } from 'inversify';\n\nconst execFileAsync = promisify(execFile);\n\n@injectable()\nexport class MediaInfoService {\n /**\n * Get video duration in seconds using ffprobe\n */\n async getVideoDuration(src: string): Promise<number> {\n try {\n const { stdout } = await execFileAsync('ffprobe', [\n '-v',\n 'error',\n '-show_entries',\n 'format=duration',\n '-of',\n 'json',\n src,\n ]);\n\n const result = JSON.parse(stdout);\n const duration = Number.parseFloat(result.format?.duration ?? '0');\n\n if (Number.isNaN(duration) || duration <= 0) {\n throw new Error(`Invalid duration for video: ${src}`);\n }\n\n return duration;\n } catch (error) {\n throw new Error(\n `Failed to get video duration for ${src}: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n }\n\n /**\n * Get audio duration in seconds using ffprobe\n */\n async getAudioDuration(src: string): Promise<number> {\n try {\n const { stdout } = await execFileAsync('ffprobe', [\n '-v',\n 'error',\n '-show_entries',\n 'format=duration',\n '-of',\n 'json',\n src,\n ]);\n\n const result = JSON.parse(stdout);\n const duration = Number.parseFloat(result.format?.duration ?? '0');\n\n if (Number.isNaN(duration) || duration <= 0) {\n throw new Error(`Invalid duration for audio: ${src}`);\n }\n\n return duration;\n } catch (error) {\n throw new Error(\n `Failed to get audio duration for ${src}: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n }\n\n /**\n * Get video dimensions (width and height) using ffprobe\n */\n async getVideoDimensions(src: string): Promise<{ width: number; height: number }> {\n try {\n const { stdout } = await execFileAsync('ffprobe', [\n '-v',\n 'error',\n '-select_streams',\n 'v:0',\n '-show_entries',\n 'stream=width,height',\n '-of',\n 'json',\n src,\n ]);\n\n const result = JSON.parse(stdout);\n const stream = result.streams?.[0];\n\n if (!stream?.width || !stream?.height) {\n throw new Error(`No video stream found in: ${src}`);\n }\n\n return {\n width: Number(stream.width),\n height: Number(stream.height),\n };\n } catch (error) {\n throw new Error(\n `Failed to get video dimensions for ${src}: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n }\n\n /**\n * Check if a media file can be decoded using ffprobe\n */\n async canDecode(src: string): Promise<boolean> {\n try {\n await execFileAsync('ffprobe', ['-v', 'error', '-show_entries', 'format=duration', '-of', 'json', src]);\n return true;\n } catch {\n return false;\n }\n }\n}\n","/**\n * RenderService\n *\n * Uses @remotion/renderer to render videos from JSON props.\n */\n\nimport path from 'node:path';\nimport { bundle } from '@remotion/bundler';\nimport { renderMedia, renderStill, selectComposition } from '@remotion/renderer';\nimport { injectable } from 'inversify';\nimport type { RenderOptions, RenderResult, RenderStillOptions } from '../types/index.js';\n\n@injectable()\nexport class RenderService {\n private bundlePromise: Promise<string> | null = null;\n\n private async getServeUrl(): Promise<string> {\n if (!this.bundlePromise) {\n const projectRoot = path.resolve(import.meta.dirname, '../..');\n this.bundlePromise = bundle({\n entryPoint: path.resolve(projectRoot, 'src/remotion/index.ts'),\n publicDir: path.resolve(projectRoot, 'public'),\n webpackOverride: (config) => ({\n ...config,\n resolve: {\n ...config.resolve,\n extensionAlias: {\n '.js': ['.ts', '.tsx', '.js', '.jsx'],\n },\n },\n }),\n });\n }\n return this.bundlePromise;\n }\n\n async render(options: RenderOptions): Promise<RenderResult> {\n const serveUrl = await this.getServeUrl();\n\n const composition = await selectComposition({\n serveUrl,\n id: options.compositionId,\n inputProps: options.inputProps,\n });\n\n await renderMedia({\n composition,\n serveUrl,\n codec: options.codec ?? 'h264',\n outputLocation: options.outputPath,\n inputProps: options.inputProps,\n });\n\n return { outputPath: options.outputPath };\n }\n\n async renderStill(options: RenderStillOptions): Promise<RenderResult> {\n const serveUrl = await this.getServeUrl();\n\n const composition = await selectComposition({\n serveUrl,\n id: options.compositionId,\n inputProps: options.inputProps,\n });\n\n await renderStill({\n composition,\n serveUrl,\n output: options.outputPath,\n frame: options.frame ?? 0,\n imageFormat: options.imageFormat ?? 'png',\n inputProps: options.inputProps,\n });\n\n return { outputPath: options.outputPath };\n }\n}\n","/**\n * VoiceoverService\n *\n * DESIGN PATTERNS:\n * - Service pattern for business logic encapsulation\n * - Dependency injection via InversifyJS\n * - Single responsibility principle\n *\n * CODING STANDARDS:\n * - Use async/await for asynchronous operations\n * - Throw descriptive errors for error cases\n * - Keep methods focused and well-named\n * - Use execFile for command execution (prevents command injection)\n */\n\nimport { execFile } from 'node:child_process';\nimport fs from 'node:fs/promises';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { promisify } from 'node:util';\nimport { injectable } from 'inversify';\n\nconst execFileAsync = promisify(execFile);\n\n/**\n * Available Kokoro TTS voices\n */\nconst AVAILABLE_VOICES = [\n 'af_sarah',\n 'af_nicole',\n 'af_bella',\n 'af_sky',\n 'am_adam',\n 'am_michael',\n 'bf_emma',\n 'bf_isabella',\n 'bm_george',\n 'bm_lewis',\n] as const;\n\nexport interface VoiceoverOptions {\n text: string;\n voice?: string;\n speed?: number;\n outputPath: string;\n}\n\nexport interface VoiceoverResult {\n outputPath: string;\n duration?: number;\n}\n\n@injectable()\nexport class VoiceoverService {\n /**\n * Check if kokoro-tts CLI is installed\n */\n async isAvailable(): Promise<boolean> {\n try {\n await execFileAsync('kokoro-tts', ['--version']);\n return true;\n } catch {\n return false;\n }\n }\n\n /**\n * Generate voiceover using Kokoro TTS\n */\n async generateVoiceover(options: VoiceoverOptions): Promise<VoiceoverResult> {\n const { text, voice = 'af_sarah', speed = 1.0, outputPath } = options;\n\n // Create temp file for text input\n const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'kokoro-'));\n const tempTextFile = path.join(tempDir, 'input.txt');\n\n try {\n // Write text to temp file\n await fs.writeFile(tempTextFile, text, 'utf-8');\n\n // Run kokoro-tts CLI\n const args = [tempTextFile, outputPath, '--voice', voice, '--speed', speed.toString()];\n\n await execFileAsync('kokoro-tts', args);\n\n return { outputPath };\n } catch (error) {\n throw new Error(`Failed to generate voiceover: ${error instanceof Error ? error.message : String(error)}`);\n } finally {\n // Cleanup temp file\n try {\n await fs.rm(tempDir, { recursive: true, force: true });\n } catch {\n // Ignore cleanup errors\n }\n }\n }\n\n /**\n * Get list of available Kokoro voice IDs\n */\n listVoices(): string[] {\n return [...AVAILABLE_VOICES];\n }\n}\n","/**\n * BaseTool - Abstract base class for video editor MCP tools\n *\n * DESIGN PATTERNS:\n * - Template Method pattern for consistent tool interface\n * - Dependency Injection via InversifyJS for services\n * - Helper methods for response formatting\n *\n * CODING STANDARDS:\n * - Use async/await for asynchronous operations\n * - Return CallToolResult with content array\n * - Handle errors gracefully with isError flag\n *\n * AVOID:\n * - Business logic in base class (delegate to services)\n * - Exposing internal errors to users\n */\n\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport { injectable } from 'inversify';\n\n@injectable()\nexport abstract class BaseTool {\n /**\n * Creates a success response with text content.\n * @param text - The success message or content.\n * @returns A CallToolResult with the content.\n */\n protected success(text: string): CallToolResult {\n return {\n content: [{ type: 'text', text }],\n };\n }\n\n /**\n * Creates a success response with JSON content.\n * @param data - The data to serialize as JSON.\n * @returns A CallToolResult with the JSON content.\n */\n protected successJson(data: unknown): CallToolResult {\n return {\n content: [{ type: 'text', text: JSON.stringify(data, null, 2) }],\n };\n }\n\n /**\n * Creates an error response.\n * @param message - The error message.\n * @returns A CallToolResult with isError flag set.\n */\n protected error(message: string): CallToolResult {\n return {\n content: [{ type: 'text', text: `Error: ${message}` }],\n isError: true,\n };\n }\n\n /**\n * Wraps execution with standard error handling.\n * @param fn - The async function to execute.\n * @returns The result of the function or an error response.\n */\n protected async safeExecute<T>(fn: () => Promise<T>): Promise<T | CallToolResult> {\n try {\n return await fn();\n } catch (err) {\n return this.error(err instanceof Error ? err.message : 'Unknown error');\n }\n }\n}\n","/**\n * Shared TypeScript Types\n *\n * DESIGN PATTERNS:\n * - Type-first development with Zod schema inference\n * - Interface segregation\n *\n * CODING STANDARDS:\n * - Export all shared types from this file\n * - Derive types from Zod schemas via z.infer<>\n */\n\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport type { z } from 'zod';\nimport type {\n AudioClipSchema,\n ClipSchema,\n GifClipSchema,\n ImageClipSchema,\n LottieClipSchema,\n SubtitleClipSchema,\n TextClipSchema,\n VideoClipSchema,\n} from '../schemas/clips.js';\nimport type {\n CaptionConfigSchema,\n CaptionSchema,\n FontConfigSchema,\n MainCompositionSchema,\n} from '../schemas/compositions.js';\n\n/**\n * DI Container Symbols\n */\nexport const TYPES = {\n RenderService: Symbol.for('RenderService'),\n MediaInfoService: Symbol.for('MediaInfoService'),\n CaptionService: Symbol.for('CaptionService'),\n FontService: Symbol.for('FontService'),\n VoiceoverService: Symbol.for('VoiceoverService'),\n Tool: Symbol.for('Tool'),\n} as const;\n\n/**\n * Tool definition for MCP\n */\nexport interface ToolDefinition {\n name: string;\n description: string;\n inputSchema: {\n type: string;\n properties: Record<string, unknown>;\n required?: string[];\n additionalProperties?: boolean;\n };\n}\n\n/**\n * Base tool interface following MCP SDK patterns\n */\nexport interface Tool<TInput = unknown> {\n getDefinition(): ToolDefinition;\n execute(input: TInput): Promise<CallToolResult>;\n}\n\n/**\n * Render options for Remotion\n */\nexport interface RenderOptions {\n compositionId: string;\n inputProps: Record<string, unknown>;\n outputPath: string;\n codec?: 'h264' | 'h265' | 'vp8' | 'vp9';\n}\n\n/**\n * Render still options\n */\nexport interface RenderStillOptions {\n compositionId: string;\n inputProps: Record<string, unknown>;\n outputPath: string;\n frame?: number;\n imageFormat?: 'png' | 'jpeg';\n}\n\n/**\n * Render result from Remotion\n */\nexport interface RenderResult {\n outputPath: string;\n}\n\n// Clip types derived from Zod schemas\nexport type VideoClip = z.infer<typeof VideoClipSchema>;\nexport type ImageClip = z.infer<typeof ImageClipSchema>;\nexport type TextClip = z.infer<typeof TextClipSchema>;\nexport type AudioClip = z.infer<typeof AudioClipSchema>;\nexport type SubtitleClip = z.infer<typeof SubtitleClipSchema>;\nexport type GifClip = z.infer<typeof GifClipSchema>;\nexport type LottieClip = z.infer<typeof LottieClipSchema>;\nexport type Clip = z.infer<typeof ClipSchema>;\n\n// Composition types derived from Zod schemas\nexport type Caption = z.infer<typeof CaptionSchema>;\nexport type CaptionConfig = z.infer<typeof CaptionConfigSchema>;\nexport type FontConfig = z.infer<typeof FontConfigSchema>;\nexport type MainCompositionProps = z.infer<typeof MainCompositionSchema>;\n","/**\n * GenerateCaptionsTool\n *\n * Creates TikTok-style caption pages from a caption array for video overlay.\n */\n\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport { inject, injectable } from 'inversify';\nimport { CaptionService } from '../services/CaptionService.js';\nimport type { Caption, Tool, ToolDefinition } from '../types/index.js';\nimport { TYPES } from '../types/index.js';\nimport { BaseTool } from './BaseTool.js';\n\ninterface GenerateCaptionsToolInput {\n captions: Array<{ text: string; startMs: number; endMs: number }>;\n combineMs?: number;\n}\n\n@injectable()\nexport class GenerateCaptionsTool extends BaseTool implements Tool<GenerateCaptionsToolInput> {\n static readonly TOOL_NAME = 'generate_captions';\n\n constructor(@inject(TYPES.CaptionService) private captionService: CaptionService) {\n super();\n }\n\n getDefinition(): ToolDefinition {\n return {\n name: GenerateCaptionsTool.TOOL_NAME,\n description: 'Create TikTok-style caption pages from a caption array for video overlay',\n inputSchema: {\n type: 'object',\n properties: {\n captions: {\n type: 'array',\n description: 'Array of caption objects with text, startMs, and endMs',\n items: {\n type: 'object',\n properties: {\n text: { type: 'string' },\n startMs: { type: 'number' },\n endMs: { type: 'number' },\n },\n required: ['text', 'startMs', 'endMs'],\n },\n },\n combineMs: {\n type: 'number',\n description: 'Milliseconds threshold for combining captions into pages (default: 100)',\n },\n },\n required: ['captions'],\n additionalProperties: false,\n },\n };\n }\n\n async execute(input: GenerateCaptionsToolInput): Promise<CallToolResult> {\n try {\n const { captions, combineMs = 100 } = input;\n const captionArray: Caption[] = captions.map((c) => ({\n text: c.text,\n startMs: c.startMs,\n endMs: c.endMs,\n }));\n const result = this.captionService.createTikTokCaptions(captionArray, combineMs);\n return this.successJson(result);\n } catch (error) {\n return this.error(error instanceof Error ? error.message : 'Unknown error');\n }\n }\n}\n","/**\n * GenerateVoiceoverTool\n *\n * Generates voiceover audio from text using Kokoro TTS local engine.\n */\n\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport { inject, injectable } from 'inversify';\nimport { VoiceoverService } from '../services/VoiceoverService.js';\nimport type { Tool, ToolDefinition } from '../types/index.js';\nimport { TYPES } from '../types/index.js';\nimport { BaseTool } from './BaseTool.js';\n\ninterface GenerateVoiceoverToolInput {\n text: string;\n voice?: string;\n speed?: number;\n outputPath: string;\n}\n\n@injectable()\nexport class GenerateVoiceoverTool extends BaseTool implements Tool<GenerateVoiceoverToolInput> {\n static readonly TOOL_NAME = 'generate_voiceover';\n\n constructor(@inject(TYPES.VoiceoverService) private voiceoverService: VoiceoverService) {\n super();\n }\n\n getDefinition(): ToolDefinition {\n return {\n name: GenerateVoiceoverTool.TOOL_NAME,\n description: 'Generate voiceover audio from text using Kokoro TTS local engine',\n inputSchema: {\n type: 'object',\n properties: {\n text: {\n type: 'string',\n description: 'Text to convert to speech',\n },\n voice: {\n type: 'string',\n description:\n 'Voice ID (default: af_sarah). Options: af_sarah, af_nicole, af_bella, af_sky, am_adam, am_michael, bf_emma, bf_isabella, bm_george, bm_lewis',\n },\n speed: {\n type: 'number',\n description: 'Speech speed multiplier (default: 1.0)',\n },\n outputPath: {\n type: 'string',\n description: 'Output file path for the generated audio',\n },\n },\n required: ['text', 'outputPath'],\n additionalProperties: false,\n },\n };\n }\n\n async execute(input: GenerateVoiceoverToolInput): Promise<CallToolResult> {\n try {\n const isAvailable = await this.voiceoverService.isAvailable();\n if (!isAvailable) {\n return this.error('Kokoro TTS is not installed. Install it with: pip install kokoro-tts');\n }\n\n const result = await this.voiceoverService.generateVoiceover({\n text: input.text,\n voice: input.voice,\n speed: input.speed,\n outputPath: input.outputPath,\n });\n\n return this.successJson(result);\n } catch (error) {\n return this.error(error instanceof Error ? error.message : 'Unknown error');\n }\n }\n}\n","/**\n * GetMediaInfoTool\n *\n * Gets duration, dimensions, and decodability info for video/audio files.\n */\n\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport { inject, injectable } from 'inversify';\nimport { MediaInfoService } from '../services/MediaInfoService.js';\nimport type { Tool, ToolDefinition } from '../types/index.js';\nimport { TYPES } from '../types/index.js';\nimport { BaseTool } from './BaseTool.js';\n\ninterface GetMediaInfoToolInput {\n src: string;\n type?: 'video' | 'audio';\n}\n\n@injectable()\nexport class GetMediaInfoTool extends BaseTool implements Tool<GetMediaInfoToolInput> {\n static readonly TOOL_NAME = 'get_media_info';\n\n constructor(@inject(TYPES.MediaInfoService) private mediaInfoService: MediaInfoService) {\n super();\n }\n\n getDefinition(): ToolDefinition {\n return {\n name: GetMediaInfoTool.TOOL_NAME,\n description: 'Get duration, dimensions, and decodability info for video/audio files',\n inputSchema: {\n type: 'object',\n properties: {\n src: {\n type: 'string',\n description: 'Path to the media file',\n },\n type: {\n type: 'string',\n enum: ['video', 'audio'],\n description: 'Media type (default: video)',\n },\n },\n required: ['src'],\n additionalProperties: false,\n },\n };\n }\n\n async execute(input: GetMediaInfoToolInput): Promise<CallToolResult> {\n try {\n const { src, type = 'video' } = input;\n const canDecode = await this.mediaInfoService.canDecode(src);\n\n if (type === 'audio') {\n const duration = await this.mediaInfoService.getAudioDuration(src);\n return this.successJson({\n src,\n type: 'audio',\n duration,\n canDecode,\n });\n }\n\n const [duration, dimensions] = await Promise.all([\n this.mediaInfoService.getVideoDuration(src),\n this.mediaInfoService.getVideoDimensions(src),\n ]);\n\n return this.successJson({\n src,\n type: 'video',\n duration,\n width: dimensions.width,\n height: dimensions.height,\n canDecode,\n });\n } catch (error) {\n return this.error(error instanceof Error ? error.message : 'Unknown error');\n }\n }\n}\n","/**\n * ImportSrtTool\n *\n * Parses SRT subtitle file content into structured Caption format for video overlay.\n */\n\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport { inject, injectable } from 'inversify';\nimport { CaptionService } from '../services/CaptionService.js';\nimport type { Tool, ToolDefinition } from '../types/index.js';\nimport { TYPES } from '../types/index.js';\nimport { BaseTool } from './BaseTool.js';\n\ninterface ImportSrtToolInput {\n srtContent: string;\n}\n\n@injectable()\nexport class ImportSrtTool extends BaseTool implements Tool<ImportSrtToolInput> {\n static readonly TOOL_NAME = 'import_srt';\n\n constructor(@inject(TYPES.CaptionService) private captionService: CaptionService) {\n super();\n }\n\n getDefinition(): ToolDefinition {\n return {\n name: ImportSrtTool.TOOL_NAME,\n description: 'Parse SRT subtitle file content into structured Caption format for video overlay',\n inputSchema: {\n type: 'object',\n properties: {\n srtContent: {\n type: 'string',\n description: 'SRT file content as a string',\n },\n },\n required: ['srtContent'],\n additionalProperties: false,\n },\n };\n }\n\n async execute(input: ImportSrtToolInput): Promise<CallToolResult> {\n try {\n const captions = this.captionService.parseSrt(input.srtContent);\n return this.successJson({\n captionCount: captions.length,\n captions,\n });\n } catch (error) {\n return this.error(error instanceof Error ? error.message : 'Unknown error');\n }\n }\n}\n","/**\n * ListCompositionsTool\n *\n * Lists all available Remotion compositions with their metadata.\n */\n\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport { injectable } from 'inversify';\nimport type { Tool, ToolDefinition } from '../types/index.js';\nimport { BaseTool } from './BaseTool.js';\n\n// eslint-disable-next-line @typescript-eslint/no-empty-object-type\ntype ListCompositionsToolInput = {};\n\n@injectable()\nexport class ListCompositionsTool extends BaseTool implements Tool<ListCompositionsToolInput> {\n static readonly TOOL_NAME = 'list_compositions';\n\n getDefinition(): ToolDefinition {\n return {\n name: ListCompositionsTool.TOOL_NAME,\n description: 'List all available Remotion compositions with their schemas, dimensions, and format info',\n inputSchema: {\n type: 'object',\n properties: {},\n required: [],\n additionalProperties: false,\n },\n };\n }\n\n async execute(): Promise<CallToolResult> {\n const compositions = [\n {\n id: 'Main',\n description: 'Main 16:9 landscape composition',\n width: 1920,\n height: 1080,\n fps: 30,\n durationInFrames: null,\n defaultProps: {\n clips: [],\n audioVolume: 1,\n backgroundColor: '#000000',\n },\n },\n {\n id: 'Vertical',\n description: 'Vertical 9:16 portrait composition (TikTok, Instagram Stories)',\n width: 1080,\n height: 1920,\n fps: 30,\n durationInFrames: null,\n defaultProps: {\n clips: [],\n audioVolume: 1,\n backgroundColor: '#000000',\n },\n },\n {\n id: 'Square',\n description: 'Square 1:1 composition (Instagram posts)',\n width: 1080,\n height: 1080,\n fps: 30,\n durationInFrames: null,\n defaultProps: {\n clips: [],\n audioVolume: 1,\n backgroundColor: '#000000',\n },\n },\n ];\n\n return this.successJson({\n compositionCount: compositions.length,\n compositions,\n });\n }\n}\n","/**\n * PreviewFrameTool\n *\n * Renders a single frame from a composition as a PNG or JPEG image for preview.\n */\n\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport { inject, injectable } from 'inversify';\nimport { RenderService } from '../services/RenderService.js';\nimport type { RenderStillOptions, Tool, ToolDefinition } from '../types/index.js';\nimport { TYPES } from '../types/index.js';\nimport { BaseTool } from './BaseTool.js';\n\ninterface PreviewFrameToolInput {\n compositionId: string;\n inputProps: Record<string, unknown>;\n outputPath: string;\n frame?: number;\n imageFormat?: 'png' | 'jpeg';\n}\n\n@injectable()\nexport class PreviewFrameTool extends BaseTool implements Tool<PreviewFrameToolInput> {\n static readonly TOOL_NAME = 'preview_frame';\n\n constructor(@inject(TYPES.RenderService) private renderService: RenderService) {\n super();\n }\n\n getDefinition(): ToolDefinition {\n return {\n name: PreviewFrameTool.TOOL_NAME,\n description: 'Render a single frame from a composition as a PNG or JPEG image for preview',\n inputSchema: {\n type: 'object',\n properties: {\n compositionId: {\n type: 'string',\n description: 'Remotion composition ID (e.g., \"Main\", \"Vertical\", \"Square\")',\n },\n inputProps: {\n type: 'object',\n description: 'JSON props to pass to the composition',\n },\n outputPath: {\n type: 'string',\n description: 'Output file path for the rendered image',\n },\n frame: {\n type: 'number',\n description: 'Frame number to render (default: 0)',\n },\n imageFormat: {\n type: 'string',\n enum: ['png', 'jpeg'],\n description: 'Image format (default: png)',\n },\n },\n required: ['compositionId', 'inputProps', 'outputPath'],\n additionalProperties: false,\n },\n };\n }\n\n async execute(input: PreviewFrameToolInput): Promise<CallToolResult> {\n try {\n const options: RenderStillOptions = {\n compositionId: input.compositionId,\n inputProps: input.inputProps,\n outputPath: input.outputPath,\n frame: input.frame,\n imageFormat: input.imageFormat,\n };\n const result = await this.renderService.renderStill(options);\n return this.successJson(result);\n } catch (error) {\n return this.error(error instanceof Error ? error.message : 'Unknown error');\n }\n }\n}\n","/**\n * RenderVideoTool\n *\n * Renders videos from JSON props using Remotion.\n */\n\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport { inject, injectable } from 'inversify';\nimport { RenderService } from '../services/RenderService.js';\nimport type { RenderOptions, Tool, ToolDefinition } from '../types/index.js';\nimport { TYPES } from '../types/index.js';\nimport { BaseTool } from './BaseTool.js';\n\ninterface RenderVideoToolInput {\n compositionId: string;\n inputProps: Record<string, unknown>;\n outputPath: string;\n codec?: 'h264' | 'h265' | 'vp8' | 'vp9';\n}\n\n@injectable()\nexport class RenderVideoTool extends BaseTool implements Tool<RenderVideoToolInput> {\n static readonly TOOL_NAME = 'render_video';\n\n constructor(@inject(TYPES.RenderService) private renderService: RenderService) {\n super();\n }\n\n getDefinition(): ToolDefinition {\n return {\n name: RenderVideoTool.TOOL_NAME,\n description: 'Render a video from JSON props using Remotion',\n inputSchema: {\n type: 'object',\n properties: {\n compositionId: {\n type: 'string',\n description: 'Remotion composition ID (e.g., \"Main\", \"Vertical\", \"Square\")',\n },\n inputProps: {\n type: 'object',\n description: 'JSON props to pass to the composition',\n },\n outputPath: {\n type: 'string',\n description: 'Output file path for the rendered video',\n },\n codec: {\n type: 'string',\n enum: ['h264', 'h265', 'vp8', 'vp9'],\n description: 'Video codec (default: h264)',\n },\n },\n required: ['compositionId', 'inputProps', 'outputPath'],\n additionalProperties: false,\n },\n };\n }\n\n async execute(input: RenderVideoToolInput): Promise<CallToolResult> {\n try {\n const options: RenderOptions = {\n compositionId: input.compositionId,\n inputProps: input.inputProps,\n outputPath: input.outputPath,\n codec: input.codec,\n };\n const result = await this.renderService.render(options);\n return this.successJson(result);\n } catch (error) {\n return this.error(error instanceof Error ? error.message : 'Unknown error');\n }\n }\n}\n","/**\n * DI Container Setup\n *\n * DESIGN PATTERNS:\n * - ContainerModule pattern for modular DI configuration\n * - Separation of concerns: services vs tools\n * - Singleton scope for stateful services\n *\n * CODING STANDARDS:\n * - Use ContainerModule for grouping related bindings\n * - Import reflect-metadata at top\n * - Bind services to their interface symbols from TYPES\n */\n\nimport 'reflect-metadata';\nimport { Container, ContainerModule, type ContainerModuleLoadOptions } from 'inversify';\nimport { CaptionService, FontService, MediaInfoService, RenderService, VoiceoverService } from '../services/index.js';\nimport {\n GenerateCaptionsTool,\n GenerateVoiceoverTool,\n GetMediaInfoTool,\n ImportSrtTool,\n ListCompositionsTool,\n PreviewFrameTool,\n RenderVideoTool,\n} from '../tools/index.js';\nimport { TYPES } from '../types/index.js';\n\n/**\n * Services module - binds all core services\n */\nexport const servicesModule = new ContainerModule((options: ContainerModuleLoadOptions) => {\n options.bind(TYPES.RenderService).to(RenderService).inSingletonScope();\n options.bind(TYPES.MediaInfoService).to(MediaInfoService).inSingletonScope();\n options.bind(TYPES.CaptionService).to(CaptionService).inSingletonScope();\n options.bind(TYPES.FontService).to(FontService).inSingletonScope();\n options.bind(TYPES.VoiceoverService).to(VoiceoverService).inSingletonScope();\n});\n\n/**\n * Tools module - binds MCP tools\n */\nexport const toolsModule = new ContainerModule((options: ContainerModuleLoadOptions) => {\n options.bind(TYPES.Tool).to(RenderVideoTool).inSingletonScope();\n options.bind(TYPES.Tool).to(ListCompositionsTool).inSingletonScope();\n options.bind(TYPES.Tool).to(GetMediaInfoTool).inSingletonScope();\n options.bind(TYPES.Tool).to(PreviewFrameTool).inSingletonScope();\n options.bind(TYPES.Tool).to(GenerateCaptionsTool).inSingletonScope();\n options.bind(TYPES.Tool).to(ImportSrtTool).inSingletonScope();\n options.bind(TYPES.Tool).to(GenerateVoiceoverTool).inSingletonScope();\n});\n\n/**\n * Creates and configures the DI container\n */\nexport function createContainer(): Container {\n const container = new Container();\n container.load(servicesModule, toolsModule);\n return container;\n}\n","import { Server } from '@modelcontextprotocol/sdk/server/index.js';\nimport { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';\nimport type { Container } from 'inversify';\nimport type { Tool } from '../types/index.js';\nimport { TYPES } from '../types/index.js';\n\nexport function createServer(container: Container): Server {\n const server = new Server(\n {\n name: 'video-editor-mcp',\n version: '0.1.0',\n },\n {\n capabilities: {\n tools: {},\n },\n },\n );\n\n const tools = container.getAll<Tool>(TYPES.Tool);\n const toolMap = new Map<string, Tool>();\n for (const tool of tools) {\n const def = tool.getDefinition();\n toolMap.set(def.name, tool);\n }\n\n server.setRequestHandler(ListToolsRequestSchema, async () => ({\n tools: tools.map((t) => t.getDefinition()),\n }));\n\n server.setRequestHandler(CallToolRequestSchema, async (request) => {\n const { name, arguments: args } = request.params;\n const tool = toolMap.get(name);\n if (!tool) {\n throw new Error(`Unknown tool: ${name}`);\n }\n return await tool.execute(args);\n });\n\n return server;\n}\n","/**\n * STDIO Transport\n *\n * DESIGN PATTERNS:\n * - Transport handler pattern implementing TransportHandler interface\n * - Standard I/O based communication for CLI usage\n *\n * CODING STANDARDS:\n * - Initialize server and transport properly\n * - Handle cleanup on shutdown with stop() method\n * - Use async/await for all operations\n *\n * AVOID:\n * - Forgetting to close transport on shutdown\n * - Missing error handling for connection failures\n */\n\nimport type { Server } from '@modelcontextprotocol/sdk/server/index.js';\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\n\n/**\n * Stdio transport handler for MCP server\n * Used for command-line and direct integrations\n */\nexport class StdioTransportHandler {\n private server: Server;\n private transport: StdioServerTransport | null = null;\n\n constructor(server: Server) {\n this.server = server;\n }\n\n async start(): Promise<void> {\n this.transport = new StdioServerTransport();\n await this.server.connect(this.transport);\n console.error('video-editor MCP server started on stdio');\n }\n\n async stop(): Promise<void> {\n if (this.transport) {\n await this.transport.close();\n this.transport = null;\n }\n }\n}\n"],"mappings":"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"}
@@ -1,4 +0,0 @@
1
- var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},s=(n,r,a)=>(a=n==null?{}:e(i(n)),o(r||!n||!n.__esModule?t(a,`default`,{value:n,enumerable:!0}):a,n));let c=require(`node:child_process`),l=require(`node:path`);l=s(l),require(`reflect-metadata`);let u=require(`inversify`),d=require(`node:util`),f=require(`@remotion/bundler`),p=require(`@remotion/renderer`),m=require(`node:fs/promises`);m=s(m);let h=require(`node:os`);h=s(h);let g=require(`@modelcontextprotocol/sdk/server/index.js`),_=require(`@modelcontextprotocol/sdk/types.js`),v=require(`@modelcontextprotocol/sdk/server/stdio.js`);function y(e,t,n,r){var i=arguments.length,a=i<3?t:r===null?r=Object.getOwnPropertyDescriptor(t,n):r,o;if(typeof Reflect==`object`&&typeof Reflect.decorate==`function`)a=Reflect.decorate(e,t,n,r);else for(var s=e.length-1;s>=0;s--)(o=e[s])&&(a=(i<3?o(a):i>3?o(t,n,a):o(t,n))||a);return i>3&&a&&Object.defineProperty(t,n,a),a}let b=class{parseSrt(e){let t=[],n=e.split(`
2
- `),r=0;for(;r<n.length;){if(!n[r]?.trim()){r++;continue}r++;let e=n[r];if(!e?.includes(`-->`)){r++;continue}let[i,a]=e.split(`-->`).map(e=>e.trim());if(!i||!a){r++;continue}let o=this.parseTimestamp(i),s=this.parseTimestamp(a);r++;let c=[];for(;r<n.length&&n[r]?.trim();)c.push(n[r]??``),r++;let l=c.join(`
3
- `);l&&t.push({text:l,startMs:o,endMs:s})}return t}parseTimestamp(e){let[t,n]=e.split(`,`);if(!t||!n)throw Error(`Invalid timestamp format: ${e}`);let[r,i,a]=t.split(`:`).map(Number);if(r===void 0||i===void 0||a===void 0)throw Error(`Invalid time format: ${t}`);return(r*3600+i*60+a)*1e3+Number(n)}createTikTokCaptions(e,t){let n=[];if(e.length===0)return{pages:n};let r={startMs:e[0]?.startMs??0,endMs:e[0]?.endMs??0,tokens:[e[0]]};for(let i=1;i<e.length;i++){let a=e[i];a&&(a.startMs-r.endMs<=t?(r.tokens.push(a),r.endMs=a.endMs):(n.push(r),r={startMs:a.startMs,endMs:a.endMs,tokens:[a]}))}return r.tokens.length>0&&n.push(r),{pages:n}}};b=y([(0,u.injectable)()],b);const x=[`Inter`,`Roboto`,`Open Sans`,`Montserrat`,`Lato`,`Poppins`,`Oswald`,`Raleway`,`Playfair Display`,`Merriweather`,`Source Sans Pro`,`PT Sans`,`Ubuntu`,`Nunito`,`Rubik`,`Work Sans`,`Karla`,`Noto Sans`,`Fira Sans`,`DM Sans`];let S=class{getAvailableGoogleFonts(){return[...x]}validateFontFamily(e){let t=e.toLowerCase();return x.some(e=>e.toLowerCase()===t)}};S=y([(0,u.injectable)()],S);const C=(0,d.promisify)(c.execFile);let w=class{async getVideoDuration(e){try{let{stdout:t}=await C(`ffprobe`,[`-v`,`error`,`-show_entries`,`format=duration`,`-of`,`json`,e]),n=JSON.parse(t),r=Number.parseFloat(n.format?.duration??`0`);if(Number.isNaN(r)||r<=0)throw Error(`Invalid duration for video: ${e}`);return r}catch(t){throw Error(`Failed to get video duration for ${e}: ${t instanceof Error?t.message:String(t)}`)}}async getAudioDuration(e){try{let{stdout:t}=await C(`ffprobe`,[`-v`,`error`,`-show_entries`,`format=duration`,`-of`,`json`,e]),n=JSON.parse(t),r=Number.parseFloat(n.format?.duration??`0`);if(Number.isNaN(r)||r<=0)throw Error(`Invalid duration for audio: ${e}`);return r}catch(t){throw Error(`Failed to get audio duration for ${e}: ${t instanceof Error?t.message:String(t)}`)}}async getVideoDimensions(e){try{let{stdout:t}=await C(`ffprobe`,[`-v`,`error`,`-select_streams`,`v:0`,`-show_entries`,`stream=width,height`,`-of`,`json`,e]),n=JSON.parse(t).streams?.[0];if(!n?.width||!n?.height)throw Error(`No video stream found in: ${e}`);return{width:Number(n.width),height:Number(n.height)}}catch(t){throw Error(`Failed to get video dimensions for ${e}: ${t instanceof Error?t.message:String(t)}`)}}async canDecode(e){try{return await C(`ffprobe`,[`-v`,`error`,`-show_entries`,`format=duration`,`-of`,`json`,e]),!0}catch{return!1}}};w=y([(0,u.injectable)()],w);let T=class{bundlePromise=null;async getServeUrl(){if(!this.bundlePromise){let e=l.default.resolve(__dirname,`../..`);this.bundlePromise=(0,f.bundle)({entryPoint:l.default.resolve(e,`src/remotion/index.ts`),publicDir:l.default.resolve(e,`public`),webpackOverride:e=>({...e,resolve:{...e.resolve,extensionAlias:{".js":[`.ts`,`.tsx`,`.js`,`.jsx`]}}})})}return this.bundlePromise}async render(e){let t=await this.getServeUrl();return await(0,p.renderMedia)({composition:await(0,p.selectComposition)({serveUrl:t,id:e.compositionId,inputProps:e.inputProps}),serveUrl:t,codec:e.codec??`h264`,outputLocation:e.outputPath,inputProps:e.inputProps}),{outputPath:e.outputPath}}async renderStill(e){let t=await this.getServeUrl();return await(0,p.renderStill)({composition:await(0,p.selectComposition)({serveUrl:t,id:e.compositionId,inputProps:e.inputProps}),serveUrl:t,output:e.outputPath,frame:e.frame??0,imageFormat:e.imageFormat??`png`,inputProps:e.inputProps}),{outputPath:e.outputPath}}};T=y([(0,u.injectable)()],T);const E=(0,d.promisify)(c.execFile),D=[`af_sarah`,`af_nicole`,`af_bella`,`af_sky`,`am_adam`,`am_michael`,`bf_emma`,`bf_isabella`,`bm_george`,`bm_lewis`];let O=class{async isAvailable(){try{return await E(`kokoro-tts`,[`--version`]),!0}catch{return!1}}async generateVoiceover(e){let{text:t,voice:n=`af_sarah`,speed:r=1,outputPath:i}=e,a=await m.default.mkdtemp(l.default.join(h.default.tmpdir(),`kokoro-`)),o=l.default.join(a,`input.txt`);try{return await m.default.writeFile(o,t,`utf-8`),await E(`kokoro-tts`,[o,i,`--voice`,n,`--speed`,r.toString()]),{outputPath:i}}catch(e){throw Error(`Failed to generate voiceover: ${e instanceof Error?e.message:String(e)}`)}finally{try{await m.default.rm(a,{recursive:!0,force:!0})}catch{}}}listVoices(){return[...D]}};O=y([(0,u.injectable)()],O);let k=class{success(e){return{content:[{type:`text`,text:e}]}}successJson(e){return{content:[{type:`text`,text:JSON.stringify(e,null,2)}]}}error(e){return{content:[{type:`text`,text:`Error: ${e}`}],isError:!0}}async safeExecute(e){try{return await e()}catch(e){return this.error(e instanceof Error?e.message:`Unknown error`)}}};k=y([(0,u.injectable)()],k);const A={RenderService:Symbol.for(`RenderService`),MediaInfoService:Symbol.for(`MediaInfoService`),CaptionService:Symbol.for(`CaptionService`),FontService:Symbol.for(`FontService`),VoiceoverService:Symbol.for(`VoiceoverService`),Tool:Symbol.for(`Tool`)};function j(e,t){if(typeof Reflect==`object`&&typeof Reflect.metadata==`function`)return Reflect.metadata(e,t)}function M(e,t){return function(n,r){t(n,r,e)}}var N,P;let F=class extends k{static{P=this}static TOOL_NAME=`generate_captions`;constructor(e){super(),this.captionService=e}getDefinition(){return{name:P.TOOL_NAME,description:`Create TikTok-style caption pages from a caption array for video overlay`,inputSchema:{type:`object`,properties:{captions:{type:`array`,description:`Array of caption objects with text, startMs, and endMs`,items:{type:`object`,properties:{text:{type:`string`},startMs:{type:`number`},endMs:{type:`number`}},required:[`text`,`startMs`,`endMs`]}},combineMs:{type:`number`,description:`Milliseconds threshold for combining captions into pages (default: 100)`}},required:[`captions`],additionalProperties:!1}}}async execute(e){try{let{captions:t,combineMs:n=100}=e,r=t.map(e=>({text:e.text,startMs:e.startMs,endMs:e.endMs})),i=this.captionService.createTikTokCaptions(r,n);return this.successJson(i)}catch(e){return this.error(e instanceof Error?e.message:`Unknown error`)}}};F=P=y([(0,u.injectable)(),M(0,(0,u.inject)(A.CaptionService)),j(`design:paramtypes`,[typeof(N=b!==void 0&&b)==`function`?N:Object])],F);var I,L;let R=class extends k{static{L=this}static TOOL_NAME=`generate_voiceover`;constructor(e){super(),this.voiceoverService=e}getDefinition(){return{name:L.TOOL_NAME,description:`Generate voiceover audio from text using Kokoro TTS local engine`,inputSchema:{type:`object`,properties:{text:{type:`string`,description:`Text to convert to speech`},voice:{type:`string`,description:`Voice ID (default: af_sarah). Options: af_sarah, af_nicole, af_bella, af_sky, am_adam, am_michael, bf_emma, bf_isabella, bm_george, bm_lewis`},speed:{type:`number`,description:`Speech speed multiplier (default: 1.0)`},outputPath:{type:`string`,description:`Output file path for the generated audio`}},required:[`text`,`outputPath`],additionalProperties:!1}}}async execute(e){try{if(!await this.voiceoverService.isAvailable())return this.error(`Kokoro TTS is not installed. Install it with: pip install kokoro-tts`);let t=await this.voiceoverService.generateVoiceover({text:e.text,voice:e.voice,speed:e.speed,outputPath:e.outputPath});return this.successJson(t)}catch(e){return this.error(e instanceof Error?e.message:`Unknown error`)}}};R=L=y([(0,u.injectable)(),M(0,(0,u.inject)(A.VoiceoverService)),j(`design:paramtypes`,[typeof(I=O!==void 0&&O)==`function`?I:Object])],R);var z,B;let V=class extends k{static{B=this}static TOOL_NAME=`get_media_info`;constructor(e){super(),this.mediaInfoService=e}getDefinition(){return{name:B.TOOL_NAME,description:`Get duration, dimensions, and decodability info for video/audio files`,inputSchema:{type:`object`,properties:{src:{type:`string`,description:`Path to the media file`},type:{type:`string`,enum:[`video`,`audio`],description:`Media type (default: video)`}},required:[`src`],additionalProperties:!1}}}async execute(e){try{let{src:t,type:n=`video`}=e,r=await this.mediaInfoService.canDecode(t);if(n===`audio`){let e=await this.mediaInfoService.getAudioDuration(t);return this.successJson({src:t,type:`audio`,duration:e,canDecode:r})}let[i,a]=await Promise.all([this.mediaInfoService.getVideoDuration(t),this.mediaInfoService.getVideoDimensions(t)]);return this.successJson({src:t,type:`video`,duration:i,width:a.width,height:a.height,canDecode:r})}catch(e){return this.error(e instanceof Error?e.message:`Unknown error`)}}};V=B=y([(0,u.injectable)(),M(0,(0,u.inject)(A.MediaInfoService)),j(`design:paramtypes`,[typeof(z=w!==void 0&&w)==`function`?z:Object])],V);var H,U;let W=class extends k{static{U=this}static TOOL_NAME=`import_srt`;constructor(e){super(),this.captionService=e}getDefinition(){return{name:U.TOOL_NAME,description:`Parse SRT subtitle file content into structured Caption format for video overlay`,inputSchema:{type:`object`,properties:{srtContent:{type:`string`,description:`SRT file content as a string`}},required:[`srtContent`],additionalProperties:!1}}}async execute(e){try{let t=this.captionService.parseSrt(e.srtContent);return this.successJson({captionCount:t.length,captions:t})}catch(e){return this.error(e instanceof Error?e.message:`Unknown error`)}}};W=U=y([(0,u.injectable)(),M(0,(0,u.inject)(A.CaptionService)),j(`design:paramtypes`,[typeof(H=b!==void 0&&b)==`function`?H:Object])],W);var G;let K=class extends k{static{G=this}static TOOL_NAME=`list_compositions`;getDefinition(){return{name:G.TOOL_NAME,description:`List all available Remotion compositions with their schemas, dimensions, and format info`,inputSchema:{type:`object`,properties:{},required:[],additionalProperties:!1}}}async execute(){let e=[{id:`Main`,description:`Main 16:9 landscape composition`,width:1920,height:1080,fps:30,durationInFrames:null,defaultProps:{clips:[],audioVolume:1,backgroundColor:`#000000`}},{id:`Vertical`,description:`Vertical 9:16 portrait composition (TikTok, Instagram Stories)`,width:1080,height:1920,fps:30,durationInFrames:null,defaultProps:{clips:[],audioVolume:1,backgroundColor:`#000000`}},{id:`Square`,description:`Square 1:1 composition (Instagram posts)`,width:1080,height:1080,fps:30,durationInFrames:null,defaultProps:{clips:[],audioVolume:1,backgroundColor:`#000000`}}];return this.successJson({compositionCount:e.length,compositions:e})}};K=G=y([(0,u.injectable)()],K);var q,J;let Y=class extends k{static{J=this}static TOOL_NAME=`preview_frame`;constructor(e){super(),this.renderService=e}getDefinition(){return{name:J.TOOL_NAME,description:`Render a single frame from a composition as a PNG or JPEG image for preview`,inputSchema:{type:`object`,properties:{compositionId:{type:`string`,description:`Remotion composition ID (e.g., "Main", "Vertical", "Square")`},inputProps:{type:`object`,description:`JSON props to pass to the composition`},outputPath:{type:`string`,description:`Output file path for the rendered image`},frame:{type:`number`,description:`Frame number to render (default: 0)`},imageFormat:{type:`string`,enum:[`png`,`jpeg`],description:`Image format (default: png)`}},required:[`compositionId`,`inputProps`,`outputPath`],additionalProperties:!1}}}async execute(e){try{let t={compositionId:e.compositionId,inputProps:e.inputProps,outputPath:e.outputPath,frame:e.frame,imageFormat:e.imageFormat},n=await this.renderService.renderStill(t);return this.successJson(n)}catch(e){return this.error(e instanceof Error?e.message:`Unknown error`)}}};Y=J=y([(0,u.injectable)(),M(0,(0,u.inject)(A.RenderService)),j(`design:paramtypes`,[typeof(q=T!==void 0&&T)==`function`?q:Object])],Y);var X,Z;let Q=class extends k{static{Z=this}static TOOL_NAME=`render_video`;constructor(e){super(),this.renderService=e}getDefinition(){return{name:Z.TOOL_NAME,description:`Render a video from JSON props using Remotion`,inputSchema:{type:`object`,properties:{compositionId:{type:`string`,description:`Remotion composition ID (e.g., "Main", "Vertical", "Square")`},inputProps:{type:`object`,description:`JSON props to pass to the composition`},outputPath:{type:`string`,description:`Output file path for the rendered video`},codec:{type:`string`,enum:[`h264`,`h265`,`vp8`,`vp9`],description:`Video codec (default: h264)`}},required:[`compositionId`,`inputProps`,`outputPath`],additionalProperties:!1}}}async execute(e){try{let t={compositionId:e.compositionId,inputProps:e.inputProps,outputPath:e.outputPath,codec:e.codec},n=await this.renderService.render(t);return this.successJson(n)}catch(e){return this.error(e instanceof Error?e.message:`Unknown error`)}}};Q=Z=y([(0,u.injectable)(),M(0,(0,u.inject)(A.RenderService)),j(`design:paramtypes`,[typeof(X=T!==void 0&&T)==`function`?X:Object])],Q);const $=new u.ContainerModule(e=>{e.bind(A.RenderService).to(T).inSingletonScope(),e.bind(A.MediaInfoService).to(w).inSingletonScope(),e.bind(A.CaptionService).to(b).inSingletonScope(),e.bind(A.FontService).to(S).inSingletonScope(),e.bind(A.VoiceoverService).to(O).inSingletonScope()}),ee=new u.ContainerModule(e=>{e.bind(A.Tool).to(Q).inSingletonScope(),e.bind(A.Tool).to(K).inSingletonScope(),e.bind(A.Tool).to(V).inSingletonScope(),e.bind(A.Tool).to(Y).inSingletonScope(),e.bind(A.Tool).to(F).inSingletonScope(),e.bind(A.Tool).to(W).inSingletonScope(),e.bind(A.Tool).to(R).inSingletonScope()});function te(){let e=new u.Container;return e.load($,ee),e}function ne(e){let t=new g.Server({name:`video-editor-mcp`,version:`0.1.0`},{capabilities:{tools:{}}}),n=e.getAll(A.Tool),r=new Map;for(let e of n){let t=e.getDefinition();r.set(t.name,e)}return t.setRequestHandler(_.ListToolsRequestSchema,async()=>({tools:n.map(e=>e.getDefinition())})),t.setRequestHandler(_.CallToolRequestSchema,async e=>{let{name:t,arguments:n}=e.params,i=r.get(t);if(!i)throw Error(`Unknown tool: ${t}`);return await i.execute(n)}),t}var re=class{server;transport=null;constructor(e){this.server=e}async start(){this.transport=new v.StdioServerTransport,await this.server.connect(this.transport),console.error(`video-editor MCP server started on stdio`)}async stop(){this.transport&&=(await this.transport.close(),null)}};Object.defineProperty(exports,`a`,{enumerable:!0,get:function(){return Y}}),Object.defineProperty(exports,`c`,{enumerable:!0,get:function(){return V}}),Object.defineProperty(exports,`d`,{enumerable:!0,get:function(){return A}}),Object.defineProperty(exports,`f`,{enumerable:!0,get:function(){return k}}),Object.defineProperty(exports,`i`,{enumerable:!0,get:function(){return Q}}),Object.defineProperty(exports,`l`,{enumerable:!0,get:function(){return R}}),Object.defineProperty(exports,`m`,{enumerable:!0,get:function(){return s}}),Object.defineProperty(exports,`n`,{enumerable:!0,get:function(){return ne}}),Object.defineProperty(exports,`o`,{enumerable:!0,get:function(){return K}}),Object.defineProperty(exports,`p`,{enumerable:!0,get:function(){return T}}),Object.defineProperty(exports,`r`,{enumerable:!0,get:function(){return te}}),Object.defineProperty(exports,`s`,{enumerable:!0,get:function(){return W}}),Object.defineProperty(exports,`t`,{enumerable:!0,get:function(){return re}}),Object.defineProperty(exports,`u`,{enumerable:!0,get:function(){return F}});
4
- //# sourceMappingURL=stdio-DRNt1yxK.cjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"stdio-DRNt1yxK.cjs","names":["CaptionService","captions: Caption[]","textLines: string[]","pages: CaptionPage[]","currentPage: CaptionPage","FontService","execFileAsync","execFile","MediaInfoService","RenderService","path","execFile","VoiceoverService","fs","path","os","BaseTool","GenerateCaptionsTool","captionService: CaptionService","captionArray: Caption[]","GenerateVoiceoverTool","voiceoverService: VoiceoverService","GetMediaInfoTool","mediaInfoService: MediaInfoService","duration","ImportSrtTool","captionService: CaptionService","ListCompositionsTool","PreviewFrameTool","renderService: RenderService","options: RenderStillOptions","RenderVideoTool","renderService: RenderService","options: RenderOptions","ContainerModule","Container","Server","ListToolsRequestSchema","CallToolRequestSchema","StdioServerTransport"],"sources":["../src/services/CaptionService.ts","../src/services/FontService.ts","../src/services/MediaInfoService.ts","../src/services/RenderService.ts","../src/services/VoiceoverService.ts","../src/tools/BaseTool.ts","../src/types/index.ts","../src/tools/GenerateCaptionsTool.ts","../src/tools/GenerateVoiceoverTool.ts","../src/tools/GetMediaInfoTool.ts","../src/tools/ImportSrtTool.ts","../src/tools/ListCompositionsTool.ts","../src/tools/PreviewFrameTool.ts","../src/tools/RenderVideoTool.ts","../src/container/index.ts","../src/server/index.ts","../src/transports/stdio.ts"],"sourcesContent":["/**\n * CaptionService\n *\n * DESIGN PATTERNS:\n * - Service pattern for business logic encapsulation\n * - Dependency injection via InversifyJS\n * - Single responsibility principle\n *\n * CODING STANDARDS:\n * - Use async/await for asynchronous operations\n * - Throw descriptive errors for error cases\n * - Keep methods focused and well-named\n */\n\nimport { injectable } from 'inversify';\nimport type { Caption } from '../types/index.js';\n\n/**\n * TikTok-style caption page with grouped tokens\n */\nexport interface CaptionPage {\n startMs: number;\n endMs: number;\n tokens: Caption[];\n}\n\n@injectable()\nexport class CaptionService {\n /**\n * Parse SRT format content into Caption array\n *\n * SRT format:\n * 1\n * 00:00:01,000 --> 00:00:04,000\n * Welcome to the video\n *\n * 2\n * 00:00:04,500 --> 00:00:08,000\n * This is the second caption\n */\n parseSrt(srtContent: string): Caption[] {\n const captions: Caption[] = [];\n const lines = srtContent.split('\\n');\n let i = 0;\n\n while (i < lines.length) {\n // Skip empty lines\n if (!lines[i]?.trim()) {\n i++;\n continue;\n }\n\n // Skip sequence number\n i++;\n\n // Parse timestamp line\n const timestampLine = lines[i];\n if (!timestampLine?.includes('-->')) {\n i++;\n continue;\n }\n\n const [startStr, endStr] = timestampLine.split('-->').map((s) => s.trim());\n if (!startStr || !endStr) {\n i++;\n continue;\n }\n\n const startMs = this.parseTimestamp(startStr);\n const endMs = this.parseTimestamp(endStr);\n\n // Collect text lines until empty line or end\n i++;\n const textLines: string[] = [];\n while (i < lines.length && lines[i]?.trim()) {\n textLines.push(lines[i] ?? '');\n i++;\n }\n\n const text = textLines.join('\\n');\n if (text) {\n captions.push({ text, startMs, endMs });\n }\n }\n\n return captions;\n }\n\n /**\n * Parse SRT timestamp to milliseconds\n * Format: HH:MM:SS,mmm\n */\n private parseTimestamp(timestamp: string): number {\n const [time, ms] = timestamp.split(',');\n if (!time || !ms) {\n throw new Error(`Invalid timestamp format: ${timestamp}`);\n }\n\n const [hours, minutes, seconds] = time.split(':').map(Number);\n if (hours === undefined || minutes === undefined || seconds === undefined) {\n throw new Error(`Invalid time format: ${time}`);\n }\n\n const totalSeconds = hours * 3600 + minutes * 60 + seconds;\n return totalSeconds * 1000 + Number(ms);\n }\n\n /**\n * Create TikTok-style caption pages by grouping captions\n * Combines tokens that appear within combineMs milliseconds of each other\n */\n createTikTokCaptions(captions: Caption[], combineMs: number): { pages: CaptionPage[] } {\n const pages: CaptionPage[] = [];\n\n if (captions.length === 0) {\n return { pages };\n }\n\n let currentPage: CaptionPage = {\n startMs: captions[0]?.startMs ?? 0,\n endMs: captions[0]?.endMs ?? 0,\n tokens: [captions[0] as Caption],\n };\n\n for (let i = 1; i < captions.length; i++) {\n const caption = captions[i];\n if (!caption) continue;\n\n const gap = caption.startMs - currentPage.endMs;\n\n if (gap <= combineMs) {\n // Combine into current page\n currentPage.tokens.push(caption);\n currentPage.endMs = caption.endMs;\n } else {\n // Start new page\n pages.push(currentPage);\n currentPage = {\n startMs: caption.startMs,\n endMs: caption.endMs,\n tokens: [caption],\n };\n }\n }\n\n // Add final page\n if (currentPage.tokens.length > 0) {\n pages.push(currentPage);\n }\n\n return { pages };\n }\n}\n","/**\n * FontService\n *\n * DESIGN PATTERNS:\n * - Service pattern for business logic encapsulation\n * - Dependency injection via InversifyJS\n * - Single responsibility principle\n *\n * CODING STANDARDS:\n * - Use async/await for asynchronous operations\n * - Throw descriptive errors for error cases\n * - Keep methods focused and well-named\n */\n\nimport { injectable } from 'inversify';\n\n/**\n * Curated list of popular Google Fonts\n * These fonts are widely used and well-supported\n */\nconst AVAILABLE_GOOGLE_FONTS = [\n 'Inter',\n 'Roboto',\n 'Open Sans',\n 'Montserrat',\n 'Lato',\n 'Poppins',\n 'Oswald',\n 'Raleway',\n 'Playfair Display',\n 'Merriweather',\n 'Source Sans Pro',\n 'PT Sans',\n 'Ubuntu',\n 'Nunito',\n 'Rubik',\n 'Work Sans',\n 'Karla',\n 'Noto Sans',\n 'Fira Sans',\n 'DM Sans',\n] as const;\n\n@injectable()\nexport class FontService {\n /**\n * Get list of available Google Fonts\n */\n getAvailableGoogleFonts(): string[] {\n return [...AVAILABLE_GOOGLE_FONTS];\n }\n\n /**\n * Validate if a font family is in the available list\n * Case-insensitive comparison\n */\n validateFontFamily(family: string): boolean {\n const normalizedFamily = family.toLowerCase();\n return AVAILABLE_GOOGLE_FONTS.some((font) => font.toLowerCase() === normalizedFamily);\n }\n}\n","/**\n * MediaInfoService\n *\n * DESIGN PATTERNS:\n * - Service pattern for business logic encapsulation\n * - Dependency injection via InversifyJS\n * - Single responsibility principle\n *\n * CODING STANDARDS:\n * - Use async/await for asynchronous operations\n * - Throw descriptive errors for error cases\n * - Keep methods focused and well-named\n */\n\nimport { execFile } from 'node:child_process';\nimport { promisify } from 'node:util';\nimport { injectable } from 'inversify';\n\nconst execFileAsync = promisify(execFile);\n\n@injectable()\nexport class MediaInfoService {\n /**\n * Get video duration in seconds using ffprobe\n */\n async getVideoDuration(src: string): Promise<number> {\n try {\n const { stdout } = await execFileAsync('ffprobe', [\n '-v',\n 'error',\n '-show_entries',\n 'format=duration',\n '-of',\n 'json',\n src,\n ]);\n\n const result = JSON.parse(stdout);\n const duration = Number.parseFloat(result.format?.duration ?? '0');\n\n if (Number.isNaN(duration) || duration <= 0) {\n throw new Error(`Invalid duration for video: ${src}`);\n }\n\n return duration;\n } catch (error) {\n throw new Error(\n `Failed to get video duration for ${src}: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n }\n\n /**\n * Get audio duration in seconds using ffprobe\n */\n async getAudioDuration(src: string): Promise<number> {\n try {\n const { stdout } = await execFileAsync('ffprobe', [\n '-v',\n 'error',\n '-show_entries',\n 'format=duration',\n '-of',\n 'json',\n src,\n ]);\n\n const result = JSON.parse(stdout);\n const duration = Number.parseFloat(result.format?.duration ?? '0');\n\n if (Number.isNaN(duration) || duration <= 0) {\n throw new Error(`Invalid duration for audio: ${src}`);\n }\n\n return duration;\n } catch (error) {\n throw new Error(\n `Failed to get audio duration for ${src}: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n }\n\n /**\n * Get video dimensions (width and height) using ffprobe\n */\n async getVideoDimensions(src: string): Promise<{ width: number; height: number }> {\n try {\n const { stdout } = await execFileAsync('ffprobe', [\n '-v',\n 'error',\n '-select_streams',\n 'v:0',\n '-show_entries',\n 'stream=width,height',\n '-of',\n 'json',\n src,\n ]);\n\n const result = JSON.parse(stdout);\n const stream = result.streams?.[0];\n\n if (!stream?.width || !stream?.height) {\n throw new Error(`No video stream found in: ${src}`);\n }\n\n return {\n width: Number(stream.width),\n height: Number(stream.height),\n };\n } catch (error) {\n throw new Error(\n `Failed to get video dimensions for ${src}: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n }\n\n /**\n * Check if a media file can be decoded using ffprobe\n */\n async canDecode(src: string): Promise<boolean> {\n try {\n await execFileAsync('ffprobe', ['-v', 'error', '-show_entries', 'format=duration', '-of', 'json', src]);\n return true;\n } catch {\n return false;\n }\n }\n}\n","/**\n * RenderService\n *\n * Uses @remotion/renderer to render videos from JSON props.\n */\n\nimport path from 'node:path';\nimport { bundle } from '@remotion/bundler';\nimport { renderMedia, renderStill, selectComposition } from '@remotion/renderer';\nimport { injectable } from 'inversify';\nimport type { RenderOptions, RenderResult, RenderStillOptions } from '../types/index.js';\n\n@injectable()\nexport class RenderService {\n private bundlePromise: Promise<string> | null = null;\n\n private async getServeUrl(): Promise<string> {\n if (!this.bundlePromise) {\n const projectRoot = path.resolve(import.meta.dirname, '../..');\n this.bundlePromise = bundle({\n entryPoint: path.resolve(projectRoot, 'src/remotion/index.ts'),\n publicDir: path.resolve(projectRoot, 'public'),\n webpackOverride: (config) => ({\n ...config,\n resolve: {\n ...config.resolve,\n extensionAlias: {\n '.js': ['.ts', '.tsx', '.js', '.jsx'],\n },\n },\n }),\n });\n }\n return this.bundlePromise;\n }\n\n async render(options: RenderOptions): Promise<RenderResult> {\n const serveUrl = await this.getServeUrl();\n\n const composition = await selectComposition({\n serveUrl,\n id: options.compositionId,\n inputProps: options.inputProps,\n });\n\n await renderMedia({\n composition,\n serveUrl,\n codec: options.codec ?? 'h264',\n outputLocation: options.outputPath,\n inputProps: options.inputProps,\n });\n\n return { outputPath: options.outputPath };\n }\n\n async renderStill(options: RenderStillOptions): Promise<RenderResult> {\n const serveUrl = await this.getServeUrl();\n\n const composition = await selectComposition({\n serveUrl,\n id: options.compositionId,\n inputProps: options.inputProps,\n });\n\n await renderStill({\n composition,\n serveUrl,\n output: options.outputPath,\n frame: options.frame ?? 0,\n imageFormat: options.imageFormat ?? 'png',\n inputProps: options.inputProps,\n });\n\n return { outputPath: options.outputPath };\n }\n}\n","/**\n * VoiceoverService\n *\n * DESIGN PATTERNS:\n * - Service pattern for business logic encapsulation\n * - Dependency injection via InversifyJS\n * - Single responsibility principle\n *\n * CODING STANDARDS:\n * - Use async/await for asynchronous operations\n * - Throw descriptive errors for error cases\n * - Keep methods focused and well-named\n * - Use execFile for command execution (prevents command injection)\n */\n\nimport { execFile } from 'node:child_process';\nimport fs from 'node:fs/promises';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { promisify } from 'node:util';\nimport { injectable } from 'inversify';\n\nconst execFileAsync = promisify(execFile);\n\n/**\n * Available Kokoro TTS voices\n */\nconst AVAILABLE_VOICES = [\n 'af_sarah',\n 'af_nicole',\n 'af_bella',\n 'af_sky',\n 'am_adam',\n 'am_michael',\n 'bf_emma',\n 'bf_isabella',\n 'bm_george',\n 'bm_lewis',\n] as const;\n\nexport interface VoiceoverOptions {\n text: string;\n voice?: string;\n speed?: number;\n outputPath: string;\n}\n\nexport interface VoiceoverResult {\n outputPath: string;\n duration?: number;\n}\n\n@injectable()\nexport class VoiceoverService {\n /**\n * Check if kokoro-tts CLI is installed\n */\n async isAvailable(): Promise<boolean> {\n try {\n await execFileAsync('kokoro-tts', ['--version']);\n return true;\n } catch {\n return false;\n }\n }\n\n /**\n * Generate voiceover using Kokoro TTS\n */\n async generateVoiceover(options: VoiceoverOptions): Promise<VoiceoverResult> {\n const { text, voice = 'af_sarah', speed = 1.0, outputPath } = options;\n\n // Create temp file for text input\n const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'kokoro-'));\n const tempTextFile = path.join(tempDir, 'input.txt');\n\n try {\n // Write text to temp file\n await fs.writeFile(tempTextFile, text, 'utf-8');\n\n // Run kokoro-tts CLI\n const args = [tempTextFile, outputPath, '--voice', voice, '--speed', speed.toString()];\n\n await execFileAsync('kokoro-tts', args);\n\n return { outputPath };\n } catch (error) {\n throw new Error(`Failed to generate voiceover: ${error instanceof Error ? error.message : String(error)}`);\n } finally {\n // Cleanup temp file\n try {\n await fs.rm(tempDir, { recursive: true, force: true });\n } catch {\n // Ignore cleanup errors\n }\n }\n }\n\n /**\n * Get list of available Kokoro voice IDs\n */\n listVoices(): string[] {\n return [...AVAILABLE_VOICES];\n }\n}\n","/**\n * BaseTool - Abstract base class for video editor MCP tools\n *\n * DESIGN PATTERNS:\n * - Template Method pattern for consistent tool interface\n * - Dependency Injection via InversifyJS for services\n * - Helper methods for response formatting\n *\n * CODING STANDARDS:\n * - Use async/await for asynchronous operations\n * - Return CallToolResult with content array\n * - Handle errors gracefully with isError flag\n *\n * AVOID:\n * - Business logic in base class (delegate to services)\n * - Exposing internal errors to users\n */\n\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport { injectable } from 'inversify';\n\n@injectable()\nexport abstract class BaseTool {\n /**\n * Creates a success response with text content.\n * @param text - The success message or content.\n * @returns A CallToolResult with the content.\n */\n protected success(text: string): CallToolResult {\n return {\n content: [{ type: 'text', text }],\n };\n }\n\n /**\n * Creates a success response with JSON content.\n * @param data - The data to serialize as JSON.\n * @returns A CallToolResult with the JSON content.\n */\n protected successJson(data: unknown): CallToolResult {\n return {\n content: [{ type: 'text', text: JSON.stringify(data, null, 2) }],\n };\n }\n\n /**\n * Creates an error response.\n * @param message - The error message.\n * @returns A CallToolResult with isError flag set.\n */\n protected error(message: string): CallToolResult {\n return {\n content: [{ type: 'text', text: `Error: ${message}` }],\n isError: true,\n };\n }\n\n /**\n * Wraps execution with standard error handling.\n * @param fn - The async function to execute.\n * @returns The result of the function or an error response.\n */\n protected async safeExecute<T>(fn: () => Promise<T>): Promise<T | CallToolResult> {\n try {\n return await fn();\n } catch (err) {\n return this.error(err instanceof Error ? err.message : 'Unknown error');\n }\n }\n}\n","/**\n * Shared TypeScript Types\n *\n * DESIGN PATTERNS:\n * - Type-first development with Zod schema inference\n * - Interface segregation\n *\n * CODING STANDARDS:\n * - Export all shared types from this file\n * - Derive types from Zod schemas via z.infer<>\n */\n\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport type { z } from 'zod';\nimport type {\n AudioClipSchema,\n ClipSchema,\n GifClipSchema,\n ImageClipSchema,\n LottieClipSchema,\n SubtitleClipSchema,\n TextClipSchema,\n VideoClipSchema,\n} from '../schemas/clips.js';\nimport type {\n CaptionConfigSchema,\n CaptionSchema,\n FontConfigSchema,\n MainCompositionSchema,\n} from '../schemas/compositions.js';\n\n/**\n * DI Container Symbols\n */\nexport const TYPES = {\n RenderService: Symbol.for('RenderService'),\n MediaInfoService: Symbol.for('MediaInfoService'),\n CaptionService: Symbol.for('CaptionService'),\n FontService: Symbol.for('FontService'),\n VoiceoverService: Symbol.for('VoiceoverService'),\n Tool: Symbol.for('Tool'),\n} as const;\n\n/**\n * Tool definition for MCP\n */\nexport interface ToolDefinition {\n name: string;\n description: string;\n inputSchema: {\n type: string;\n properties: Record<string, unknown>;\n required?: string[];\n additionalProperties?: boolean;\n };\n}\n\n/**\n * Base tool interface following MCP SDK patterns\n */\nexport interface Tool<TInput = unknown> {\n getDefinition(): ToolDefinition;\n execute(input: TInput): Promise<CallToolResult>;\n}\n\n/**\n * Render options for Remotion\n */\nexport interface RenderOptions {\n compositionId: string;\n inputProps: Record<string, unknown>;\n outputPath: string;\n codec?: 'h264' | 'h265' | 'vp8' | 'vp9';\n}\n\n/**\n * Render still options\n */\nexport interface RenderStillOptions {\n compositionId: string;\n inputProps: Record<string, unknown>;\n outputPath: string;\n frame?: number;\n imageFormat?: 'png' | 'jpeg';\n}\n\n/**\n * Render result from Remotion\n */\nexport interface RenderResult {\n outputPath: string;\n}\n\n// Clip types derived from Zod schemas\nexport type VideoClip = z.infer<typeof VideoClipSchema>;\nexport type ImageClip = z.infer<typeof ImageClipSchema>;\nexport type TextClip = z.infer<typeof TextClipSchema>;\nexport type AudioClip = z.infer<typeof AudioClipSchema>;\nexport type SubtitleClip = z.infer<typeof SubtitleClipSchema>;\nexport type GifClip = z.infer<typeof GifClipSchema>;\nexport type LottieClip = z.infer<typeof LottieClipSchema>;\nexport type Clip = z.infer<typeof ClipSchema>;\n\n// Composition types derived from Zod schemas\nexport type Caption = z.infer<typeof CaptionSchema>;\nexport type CaptionConfig = z.infer<typeof CaptionConfigSchema>;\nexport type FontConfig = z.infer<typeof FontConfigSchema>;\nexport type MainCompositionProps = z.infer<typeof MainCompositionSchema>;\n","/**\n * GenerateCaptionsTool\n *\n * Creates TikTok-style caption pages from a caption array for video overlay.\n */\n\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport { inject, injectable } from 'inversify';\nimport { CaptionService } from '../services/CaptionService.js';\nimport type { Caption, Tool, ToolDefinition } from '../types/index.js';\nimport { TYPES } from '../types/index.js';\nimport { BaseTool } from './BaseTool.js';\n\ninterface GenerateCaptionsToolInput {\n captions: Array<{ text: string; startMs: number; endMs: number }>;\n combineMs?: number;\n}\n\n@injectable()\nexport class GenerateCaptionsTool extends BaseTool implements Tool<GenerateCaptionsToolInput> {\n static readonly TOOL_NAME = 'generate_captions';\n\n constructor(@inject(TYPES.CaptionService) private captionService: CaptionService) {\n super();\n }\n\n getDefinition(): ToolDefinition {\n return {\n name: GenerateCaptionsTool.TOOL_NAME,\n description: 'Create TikTok-style caption pages from a caption array for video overlay',\n inputSchema: {\n type: 'object',\n properties: {\n captions: {\n type: 'array',\n description: 'Array of caption objects with text, startMs, and endMs',\n items: {\n type: 'object',\n properties: {\n text: { type: 'string' },\n startMs: { type: 'number' },\n endMs: { type: 'number' },\n },\n required: ['text', 'startMs', 'endMs'],\n },\n },\n combineMs: {\n type: 'number',\n description: 'Milliseconds threshold for combining captions into pages (default: 100)',\n },\n },\n required: ['captions'],\n additionalProperties: false,\n },\n };\n }\n\n async execute(input: GenerateCaptionsToolInput): Promise<CallToolResult> {\n try {\n const { captions, combineMs = 100 } = input;\n const captionArray: Caption[] = captions.map((c) => ({\n text: c.text,\n startMs: c.startMs,\n endMs: c.endMs,\n }));\n const result = this.captionService.createTikTokCaptions(captionArray, combineMs);\n return this.successJson(result);\n } catch (error) {\n return this.error(error instanceof Error ? error.message : 'Unknown error');\n }\n }\n}\n","/**\n * GenerateVoiceoverTool\n *\n * Generates voiceover audio from text using Kokoro TTS local engine.\n */\n\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport { inject, injectable } from 'inversify';\nimport { VoiceoverService } from '../services/VoiceoverService.js';\nimport type { Tool, ToolDefinition } from '../types/index.js';\nimport { TYPES } from '../types/index.js';\nimport { BaseTool } from './BaseTool.js';\n\ninterface GenerateVoiceoverToolInput {\n text: string;\n voice?: string;\n speed?: number;\n outputPath: string;\n}\n\n@injectable()\nexport class GenerateVoiceoverTool extends BaseTool implements Tool<GenerateVoiceoverToolInput> {\n static readonly TOOL_NAME = 'generate_voiceover';\n\n constructor(@inject(TYPES.VoiceoverService) private voiceoverService: VoiceoverService) {\n super();\n }\n\n getDefinition(): ToolDefinition {\n return {\n name: GenerateVoiceoverTool.TOOL_NAME,\n description: 'Generate voiceover audio from text using Kokoro TTS local engine',\n inputSchema: {\n type: 'object',\n properties: {\n text: {\n type: 'string',\n description: 'Text to convert to speech',\n },\n voice: {\n type: 'string',\n description:\n 'Voice ID (default: af_sarah). Options: af_sarah, af_nicole, af_bella, af_sky, am_adam, am_michael, bf_emma, bf_isabella, bm_george, bm_lewis',\n },\n speed: {\n type: 'number',\n description: 'Speech speed multiplier (default: 1.0)',\n },\n outputPath: {\n type: 'string',\n description: 'Output file path for the generated audio',\n },\n },\n required: ['text', 'outputPath'],\n additionalProperties: false,\n },\n };\n }\n\n async execute(input: GenerateVoiceoverToolInput): Promise<CallToolResult> {\n try {\n const isAvailable = await this.voiceoverService.isAvailable();\n if (!isAvailable) {\n return this.error('Kokoro TTS is not installed. Install it with: pip install kokoro-tts');\n }\n\n const result = await this.voiceoverService.generateVoiceover({\n text: input.text,\n voice: input.voice,\n speed: input.speed,\n outputPath: input.outputPath,\n });\n\n return this.successJson(result);\n } catch (error) {\n return this.error(error instanceof Error ? error.message : 'Unknown error');\n }\n }\n}\n","/**\n * GetMediaInfoTool\n *\n * Gets duration, dimensions, and decodability info for video/audio files.\n */\n\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport { inject, injectable } from 'inversify';\nimport { MediaInfoService } from '../services/MediaInfoService.js';\nimport type { Tool, ToolDefinition } from '../types/index.js';\nimport { TYPES } from '../types/index.js';\nimport { BaseTool } from './BaseTool.js';\n\ninterface GetMediaInfoToolInput {\n src: string;\n type?: 'video' | 'audio';\n}\n\n@injectable()\nexport class GetMediaInfoTool extends BaseTool implements Tool<GetMediaInfoToolInput> {\n static readonly TOOL_NAME = 'get_media_info';\n\n constructor(@inject(TYPES.MediaInfoService) private mediaInfoService: MediaInfoService) {\n super();\n }\n\n getDefinition(): ToolDefinition {\n return {\n name: GetMediaInfoTool.TOOL_NAME,\n description: 'Get duration, dimensions, and decodability info for video/audio files',\n inputSchema: {\n type: 'object',\n properties: {\n src: {\n type: 'string',\n description: 'Path to the media file',\n },\n type: {\n type: 'string',\n enum: ['video', 'audio'],\n description: 'Media type (default: video)',\n },\n },\n required: ['src'],\n additionalProperties: false,\n },\n };\n }\n\n async execute(input: GetMediaInfoToolInput): Promise<CallToolResult> {\n try {\n const { src, type = 'video' } = input;\n const canDecode = await this.mediaInfoService.canDecode(src);\n\n if (type === 'audio') {\n const duration = await this.mediaInfoService.getAudioDuration(src);\n return this.successJson({\n src,\n type: 'audio',\n duration,\n canDecode,\n });\n }\n\n const [duration, dimensions] = await Promise.all([\n this.mediaInfoService.getVideoDuration(src),\n this.mediaInfoService.getVideoDimensions(src),\n ]);\n\n return this.successJson({\n src,\n type: 'video',\n duration,\n width: dimensions.width,\n height: dimensions.height,\n canDecode,\n });\n } catch (error) {\n return this.error(error instanceof Error ? error.message : 'Unknown error');\n }\n }\n}\n","/**\n * ImportSrtTool\n *\n * Parses SRT subtitle file content into structured Caption format for video overlay.\n */\n\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport { inject, injectable } from 'inversify';\nimport { CaptionService } from '../services/CaptionService.js';\nimport type { Tool, ToolDefinition } from '../types/index.js';\nimport { TYPES } from '../types/index.js';\nimport { BaseTool } from './BaseTool.js';\n\ninterface ImportSrtToolInput {\n srtContent: string;\n}\n\n@injectable()\nexport class ImportSrtTool extends BaseTool implements Tool<ImportSrtToolInput> {\n static readonly TOOL_NAME = 'import_srt';\n\n constructor(@inject(TYPES.CaptionService) private captionService: CaptionService) {\n super();\n }\n\n getDefinition(): ToolDefinition {\n return {\n name: ImportSrtTool.TOOL_NAME,\n description: 'Parse SRT subtitle file content into structured Caption format for video overlay',\n inputSchema: {\n type: 'object',\n properties: {\n srtContent: {\n type: 'string',\n description: 'SRT file content as a string',\n },\n },\n required: ['srtContent'],\n additionalProperties: false,\n },\n };\n }\n\n async execute(input: ImportSrtToolInput): Promise<CallToolResult> {\n try {\n const captions = this.captionService.parseSrt(input.srtContent);\n return this.successJson({\n captionCount: captions.length,\n captions,\n });\n } catch (error) {\n return this.error(error instanceof Error ? error.message : 'Unknown error');\n }\n }\n}\n","/**\n * ListCompositionsTool\n *\n * Lists all available Remotion compositions with their metadata.\n */\n\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport { injectable } from 'inversify';\nimport type { Tool, ToolDefinition } from '../types/index.js';\nimport { BaseTool } from './BaseTool.js';\n\n// eslint-disable-next-line @typescript-eslint/no-empty-object-type\ntype ListCompositionsToolInput = {};\n\n@injectable()\nexport class ListCompositionsTool extends BaseTool implements Tool<ListCompositionsToolInput> {\n static readonly TOOL_NAME = 'list_compositions';\n\n getDefinition(): ToolDefinition {\n return {\n name: ListCompositionsTool.TOOL_NAME,\n description: 'List all available Remotion compositions with their schemas, dimensions, and format info',\n inputSchema: {\n type: 'object',\n properties: {},\n required: [],\n additionalProperties: false,\n },\n };\n }\n\n async execute(): Promise<CallToolResult> {\n const compositions = [\n {\n id: 'Main',\n description: 'Main 16:9 landscape composition',\n width: 1920,\n height: 1080,\n fps: 30,\n durationInFrames: null,\n defaultProps: {\n clips: [],\n audioVolume: 1,\n backgroundColor: '#000000',\n },\n },\n {\n id: 'Vertical',\n description: 'Vertical 9:16 portrait composition (TikTok, Instagram Stories)',\n width: 1080,\n height: 1920,\n fps: 30,\n durationInFrames: null,\n defaultProps: {\n clips: [],\n audioVolume: 1,\n backgroundColor: '#000000',\n },\n },\n {\n id: 'Square',\n description: 'Square 1:1 composition (Instagram posts)',\n width: 1080,\n height: 1080,\n fps: 30,\n durationInFrames: null,\n defaultProps: {\n clips: [],\n audioVolume: 1,\n backgroundColor: '#000000',\n },\n },\n ];\n\n return this.successJson({\n compositionCount: compositions.length,\n compositions,\n });\n }\n}\n","/**\n * PreviewFrameTool\n *\n * Renders a single frame from a composition as a PNG or JPEG image for preview.\n */\n\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport { inject, injectable } from 'inversify';\nimport { RenderService } from '../services/RenderService.js';\nimport type { RenderStillOptions, Tool, ToolDefinition } from '../types/index.js';\nimport { TYPES } from '../types/index.js';\nimport { BaseTool } from './BaseTool.js';\n\ninterface PreviewFrameToolInput {\n compositionId: string;\n inputProps: Record<string, unknown>;\n outputPath: string;\n frame?: number;\n imageFormat?: 'png' | 'jpeg';\n}\n\n@injectable()\nexport class PreviewFrameTool extends BaseTool implements Tool<PreviewFrameToolInput> {\n static readonly TOOL_NAME = 'preview_frame';\n\n constructor(@inject(TYPES.RenderService) private renderService: RenderService) {\n super();\n }\n\n getDefinition(): ToolDefinition {\n return {\n name: PreviewFrameTool.TOOL_NAME,\n description: 'Render a single frame from a composition as a PNG or JPEG image for preview',\n inputSchema: {\n type: 'object',\n properties: {\n compositionId: {\n type: 'string',\n description: 'Remotion composition ID (e.g., \"Main\", \"Vertical\", \"Square\")',\n },\n inputProps: {\n type: 'object',\n description: 'JSON props to pass to the composition',\n },\n outputPath: {\n type: 'string',\n description: 'Output file path for the rendered image',\n },\n frame: {\n type: 'number',\n description: 'Frame number to render (default: 0)',\n },\n imageFormat: {\n type: 'string',\n enum: ['png', 'jpeg'],\n description: 'Image format (default: png)',\n },\n },\n required: ['compositionId', 'inputProps', 'outputPath'],\n additionalProperties: false,\n },\n };\n }\n\n async execute(input: PreviewFrameToolInput): Promise<CallToolResult> {\n try {\n const options: RenderStillOptions = {\n compositionId: input.compositionId,\n inputProps: input.inputProps,\n outputPath: input.outputPath,\n frame: input.frame,\n imageFormat: input.imageFormat,\n };\n const result = await this.renderService.renderStill(options);\n return this.successJson(result);\n } catch (error) {\n return this.error(error instanceof Error ? error.message : 'Unknown error');\n }\n }\n}\n","/**\n * RenderVideoTool\n *\n * Renders videos from JSON props using Remotion.\n */\n\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport { inject, injectable } from 'inversify';\nimport { RenderService } from '../services/RenderService.js';\nimport type { RenderOptions, Tool, ToolDefinition } from '../types/index.js';\nimport { TYPES } from '../types/index.js';\nimport { BaseTool } from './BaseTool.js';\n\ninterface RenderVideoToolInput {\n compositionId: string;\n inputProps: Record<string, unknown>;\n outputPath: string;\n codec?: 'h264' | 'h265' | 'vp8' | 'vp9';\n}\n\n@injectable()\nexport class RenderVideoTool extends BaseTool implements Tool<RenderVideoToolInput> {\n static readonly TOOL_NAME = 'render_video';\n\n constructor(@inject(TYPES.RenderService) private renderService: RenderService) {\n super();\n }\n\n getDefinition(): ToolDefinition {\n return {\n name: RenderVideoTool.TOOL_NAME,\n description: 'Render a video from JSON props using Remotion',\n inputSchema: {\n type: 'object',\n properties: {\n compositionId: {\n type: 'string',\n description: 'Remotion composition ID (e.g., \"Main\", \"Vertical\", \"Square\")',\n },\n inputProps: {\n type: 'object',\n description: 'JSON props to pass to the composition',\n },\n outputPath: {\n type: 'string',\n description: 'Output file path for the rendered video',\n },\n codec: {\n type: 'string',\n enum: ['h264', 'h265', 'vp8', 'vp9'],\n description: 'Video codec (default: h264)',\n },\n },\n required: ['compositionId', 'inputProps', 'outputPath'],\n additionalProperties: false,\n },\n };\n }\n\n async execute(input: RenderVideoToolInput): Promise<CallToolResult> {\n try {\n const options: RenderOptions = {\n compositionId: input.compositionId,\n inputProps: input.inputProps,\n outputPath: input.outputPath,\n codec: input.codec,\n };\n const result = await this.renderService.render(options);\n return this.successJson(result);\n } catch (error) {\n return this.error(error instanceof Error ? error.message : 'Unknown error');\n }\n }\n}\n","/**\n * DI Container Setup\n *\n * DESIGN PATTERNS:\n * - ContainerModule pattern for modular DI configuration\n * - Separation of concerns: services vs tools\n * - Singleton scope for stateful services\n *\n * CODING STANDARDS:\n * - Use ContainerModule for grouping related bindings\n * - Import reflect-metadata at top\n * - Bind services to their interface symbols from TYPES\n */\n\nimport 'reflect-metadata';\nimport { Container, ContainerModule, type ContainerModuleLoadOptions } from 'inversify';\nimport { CaptionService, FontService, MediaInfoService, RenderService, VoiceoverService } from '../services/index.js';\nimport {\n GenerateCaptionsTool,\n GenerateVoiceoverTool,\n GetMediaInfoTool,\n ImportSrtTool,\n ListCompositionsTool,\n PreviewFrameTool,\n RenderVideoTool,\n} from '../tools/index.js';\nimport { TYPES } from '../types/index.js';\n\n/**\n * Services module - binds all core services\n */\nexport const servicesModule = new ContainerModule((options: ContainerModuleLoadOptions) => {\n options.bind(TYPES.RenderService).to(RenderService).inSingletonScope();\n options.bind(TYPES.MediaInfoService).to(MediaInfoService).inSingletonScope();\n options.bind(TYPES.CaptionService).to(CaptionService).inSingletonScope();\n options.bind(TYPES.FontService).to(FontService).inSingletonScope();\n options.bind(TYPES.VoiceoverService).to(VoiceoverService).inSingletonScope();\n});\n\n/**\n * Tools module - binds MCP tools\n */\nexport const toolsModule = new ContainerModule((options: ContainerModuleLoadOptions) => {\n options.bind(TYPES.Tool).to(RenderVideoTool).inSingletonScope();\n options.bind(TYPES.Tool).to(ListCompositionsTool).inSingletonScope();\n options.bind(TYPES.Tool).to(GetMediaInfoTool).inSingletonScope();\n options.bind(TYPES.Tool).to(PreviewFrameTool).inSingletonScope();\n options.bind(TYPES.Tool).to(GenerateCaptionsTool).inSingletonScope();\n options.bind(TYPES.Tool).to(ImportSrtTool).inSingletonScope();\n options.bind(TYPES.Tool).to(GenerateVoiceoverTool).inSingletonScope();\n});\n\n/**\n * Creates and configures the DI container\n */\nexport function createContainer(): Container {\n const container = new Container();\n container.load(servicesModule, toolsModule);\n return container;\n}\n","import { Server } from '@modelcontextprotocol/sdk/server/index.js';\nimport { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';\nimport type { Container } from 'inversify';\nimport type { Tool } from '../types/index.js';\nimport { TYPES } from '../types/index.js';\n\nexport function createServer(container: Container): Server {\n const server = new Server(\n {\n name: 'video-editor-mcp',\n version: '0.1.0',\n },\n {\n capabilities: {\n tools: {},\n },\n },\n );\n\n const tools = container.getAll<Tool>(TYPES.Tool);\n const toolMap = new Map<string, Tool>();\n for (const tool of tools) {\n const def = tool.getDefinition();\n toolMap.set(def.name, tool);\n }\n\n server.setRequestHandler(ListToolsRequestSchema, async () => ({\n tools: tools.map((t) => t.getDefinition()),\n }));\n\n server.setRequestHandler(CallToolRequestSchema, async (request) => {\n const { name, arguments: args } = request.params;\n const tool = toolMap.get(name);\n if (!tool) {\n throw new Error(`Unknown tool: ${name}`);\n }\n return await tool.execute(args);\n });\n\n return server;\n}\n","/**\n * STDIO Transport\n *\n * DESIGN PATTERNS:\n * - Transport handler pattern implementing TransportHandler interface\n * - Standard I/O based communication for CLI usage\n *\n * CODING STANDARDS:\n * - Initialize server and transport properly\n * - Handle cleanup on shutdown with stop() method\n * - Use async/await for all operations\n *\n * AVOID:\n * - Forgetting to close transport on shutdown\n * - Missing error handling for connection failures\n */\n\nimport type { Server } from '@modelcontextprotocol/sdk/server/index.js';\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\n\n/**\n * Stdio transport handler for MCP server\n * Used for command-line and direct integrations\n */\nexport class StdioTransportHandler {\n private server: Server;\n private transport: StdioServerTransport | null = null;\n\n constructor(server: Server) {\n this.server = server;\n }\n\n async start(): Promise<void> {\n this.transport = new StdioServerTransport();\n await this.server.connect(this.transport);\n console.error('video-editor MCP server started on stdio');\n }\n\n async stop(): Promise<void> {\n if (this.transport) {\n await this.transport.close();\n this.transport = null;\n }\n }\n}\n"],"mappings":"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"}