FFmpeg Transcode
ffmpeg_transcodeTranscode or resize videos using safe preset options. Set aspect ratio, frame rate, quality (CRF), and dimensions.
Instructions
Transcode or resize a video using safe preset options.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| inputPath | Yes | ||
| outputPath | Yes | ||
| aspect | No | keep | |
| fps | No | ||
| crf | No | ||
| width | No | ||
| height | No | ||
| overwrite | No |
Implementation Reference
- src/tools/media.ts:43-65 (handler)The async handler function that executes the ffmpeg_transcode tool logic. It builds ffmpeg arguments with optional overwrite, input/output paths, fps scaling, aspect ratio filters (9:16, 16:9, 1:1), and codec settings (libx264, aac), then runs ffmpeg via runCommand.
async ({ inputPath, outputPath, aspect, fps, crf, width, height, overwrite }) => { try { const input = safePath(inputPath); const output = safePath(outputPath); const args: string[] = []; if (overwrite) args.push('-y'); args.push('-i', input); const filters: string[] = []; if (fps) filters.push(`fps=${fps}`); if (width && height) filters.push(`scale=${width}:${height}:force_original_aspect_ratio=decrease,pad=${width}:${height}:(ow-iw)/2:(oh-ih)/2`); else if (aspect === '9:16') filters.push('scale=1080:1920:force_original_aspect_ratio=increase,crop=1080:1920'); else if (aspect === '16:9') filters.push('scale=1920:1080:force_original_aspect_ratio=increase,crop=1920:1080'); else if (aspect === '1:1') filters.push('scale=1080:1080:force_original_aspect_ratio=increase,crop=1080:1080'); if (filters.length) args.push('-vf', filters.join(',')); args.push('-c:v', 'libx264', '-preset', 'medium', '-crf', String(crf), '-c:a', 'aac', '-b:a', '192k', output); const result = await runCommand(config.ffmpegBin, args); if (result.code !== 0) return errorResult('ffmpeg failed', result.stderr); return textResult({ ok: true, outputPath, command: [config.ffmpegBin, ...args].join(' ') }); } catch (err) { return errorResult('Failed to transcode media', String(err)); } } ); - src/tools/media.ts:32-41 (schema)Zod input schema for ffmpeg_transcode defining inputPath (string), outputPath (string), aspect (enum: keep, 9:16, 16:9, 1:1, default keep), fps (optional positive int ≤120), crf (int 12-35, default 20), width (optional positive int), height (optional positive int), and overwrite (boolean, default true).
inputSchema: z.object({ inputPath: z.string(), outputPath: z.string(), aspect: z.enum(['keep', '9:16', '16:9', '1:1']).default('keep'), fps: z.number().int().positive().max(120).optional(), crf: z.number().int().min(12).max(35).default(20), width: z.number().int().positive().optional(), height: z.number().int().positive().optional(), overwrite: z.boolean().default(true) }) - src/tools/media.ts:27-65 (registration)Registration of the 'ffmpeg_transcode' tool via server.registerTool() inside the registerMediaTools function. The function is called from src/index.ts line 21.
server.registerTool( 'ffmpeg_transcode', { title: 'FFmpeg Transcode', description: 'Transcode or resize a video using safe preset options.', inputSchema: z.object({ inputPath: z.string(), outputPath: z.string(), aspect: z.enum(['keep', '9:16', '16:9', '1:1']).default('keep'), fps: z.number().int().positive().max(120).optional(), crf: z.number().int().min(12).max(35).default(20), width: z.number().int().positive().optional(), height: z.number().int().positive().optional(), overwrite: z.boolean().default(true) }) }, async ({ inputPath, outputPath, aspect, fps, crf, width, height, overwrite }) => { try { const input = safePath(inputPath); const output = safePath(outputPath); const args: string[] = []; if (overwrite) args.push('-y'); args.push('-i', input); const filters: string[] = []; if (fps) filters.push(`fps=${fps}`); if (width && height) filters.push(`scale=${width}:${height}:force_original_aspect_ratio=decrease,pad=${width}:${height}:(ow-iw)/2:(oh-ih)/2`); else if (aspect === '9:16') filters.push('scale=1080:1920:force_original_aspect_ratio=increase,crop=1080:1920'); else if (aspect === '16:9') filters.push('scale=1920:1080:force_original_aspect_ratio=increase,crop=1920:1080'); else if (aspect === '1:1') filters.push('scale=1080:1080:force_original_aspect_ratio=increase,crop=1080:1080'); if (filters.length) args.push('-vf', filters.join(',')); args.push('-c:v', 'libx264', '-preset', 'medium', '-crf', String(crf), '-c:a', 'aac', '-b:a', '192k', output); const result = await runCommand(config.ffmpegBin, args); if (result.code !== 0) return errorResult('ffmpeg failed', result.stderr); return textResult({ ok: true, outputPath, command: [config.ffmpegBin, ...args].join(' ') }); } catch (err) { return errorResult('Failed to transcode media', String(err)); } } ); - src/index.ts:20-21 (registration)Call site where registerMediaTools is invoked, which registers the ffmpeg_transcode tool on the MCP server.
registerComfyTools(server); registerMediaTools(server); - src/utils.ts:15-24 (helper)The runCommand helper used by the ffmpeg_transcode handler to spawn the ffmpeg process with provided arguments.
export function runCommand(command: string, args: string[], cwd?: string): Promise<{ code: number; stdout: string; stderr: string }> { return new Promise((resolve) => { const child = spawn(command, args, { cwd, shell: false }); let stdout = ''; let stderr = ''; child.stdout.on('data', (d) => (stdout += d.toString())); child.stderr.on('data', (d) => (stderr += d.toString())); child.on('close', (code) => resolve({ code: code ?? -1, stdout, stderr })); }); }