resize-video
Resize video files to standard resolutions (360p, 480p, 720p, 1080p) using MCP FFmpeg Video Processor. Specify input video path and desired output resolutions for efficient conversion.
Instructions
Resize a video to one or more standard resolutions
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| outputDir | No | Optional directory to save the output files (defaults to a temporary directory) | |
| resolutions | Yes | Resolutions to convert the video to | |
| videoPath | Yes | Path to the video file to resize |
Implementation Reference
- src/mcp-ffmpeg.ts:113-237 (handler)The main execution logic for the resize-video tool. Handles input validation, file existence checks, user permission via notification, output directory setup, FFmpeg command construction and execution for each specified resolution, error handling, and formatted result reporting.async ({ videoPath, resolutions, outputDir }) => { try { // Resolve the absolute path const absVideoPath = path.resolve(videoPath); // Check if file exists try { await fs.access(absVideoPath); } catch (error) { return { isError: true, content: [{ type: "text" as const, text: `Error: Video file not found at ${absVideoPath}` }] }; } // Determine output directory let outputDirectory = outputDir ? path.resolve(outputDir) : await ensureDirectoriesExist(); // Check if output directory exists and is writable try { await fs.access(outputDirectory, fs.constants.W_OK); } catch (error) { return { isError: true, content: [{ type: "text" as const, text: `Error: Output directory ${outputDirectory} does not exist or is not writable` }] }; } // Format command for permission request const resolutionsStr = resolutions.join(', '); const permissionMessage = `Resize video ${path.basename(absVideoPath)} to ${resolutionsStr}`; // Ask for permission const permitted = await askPermission(permissionMessage); if (!permitted) { return { isError: true, content: [{ type: "text" as const, text: "Permission denied by user" }] }; } // Get video filename without extension const videoFilename = path.basename(absVideoPath, path.extname(absVideoPath)); // Define the type for our results type ResizeResult = { resolution: "360p" | "480p" | "720p" | "1080p"; outputPath: string; success: boolean; error?: string; }; // Process each resolution const results: ResizeResult[] = []; for (const resolution of resolutions) { const { width, height } = RESOLUTIONS[resolution as keyof typeof RESOLUTIONS]; const outputFilename = `${videoFilename}_${resolution}${path.extname(absVideoPath)}`; const outputPath = path.join(outputDirectory, outputFilename); // Build FFmpeg command const command = `ffmpeg -i "${absVideoPath}" -vf "scale=${width}:${height}" -c:v libx264 -crf 23 -preset medium -c:a aac -b:a 128k "${outputPath}"`; try { // Execute FFmpeg command const { stdout, stderr } = await execAsync(command); results.push({ resolution, outputPath, success: true }); } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); results.push({ resolution, outputPath, success: false, error: errorMessage }); } } // Format results const successCount = results.filter(r => r.success).length; const failCount = results.length - successCount; let resultText = `Processed ${results.length} resolutions (${successCount} successful, ${failCount} failed)\n\n`; results.forEach(result => { if (result.success) { resultText += `✅ ${result.resolution}: ${result.outputPath}\n`; } else { resultText += `❌ ${result.resolution}: Failed - ${result.error}\n`; } }); return { content: [{ type: "text" as const, text: resultText }] }; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); return { isError: true, content: [{ type: "text" as const, text: `Error resizing video: ${errorMessage}` }] }; } }
- src/mcp-ffmpeg.ts:108-112 (schema)Zod input schema defining parameters: videoPath (required string), resolutions (required array of '360p'|'480p'|'720p'|'1080p'), outputDir (optional string).{ videoPath: z.string().describe("Path to the video file to resize"), resolutions: z.array(z.enum(["360p", "480p", "720p", "1080p"])).describe("Resolutions to convert the video to"), outputDir: z.string().optional().describe("Optional directory to save the output files (defaults to a temporary directory)") },
- src/mcp-ffmpeg.ts:106-238 (registration)Registers the resize-video tool on the MCP server with name, description, input schema, and handler function."resize-video", "Resize a video to one or more standard resolutions", { videoPath: z.string().describe("Path to the video file to resize"), resolutions: z.array(z.enum(["360p", "480p", "720p", "1080p"])).describe("Resolutions to convert the video to"), outputDir: z.string().optional().describe("Optional directory to save the output files (defaults to a temporary directory)") }, async ({ videoPath, resolutions, outputDir }) => { try { // Resolve the absolute path const absVideoPath = path.resolve(videoPath); // Check if file exists try { await fs.access(absVideoPath); } catch (error) { return { isError: true, content: [{ type: "text" as const, text: `Error: Video file not found at ${absVideoPath}` }] }; } // Determine output directory let outputDirectory = outputDir ? path.resolve(outputDir) : await ensureDirectoriesExist(); // Check if output directory exists and is writable try { await fs.access(outputDirectory, fs.constants.W_OK); } catch (error) { return { isError: true, content: [{ type: "text" as const, text: `Error: Output directory ${outputDirectory} does not exist or is not writable` }] }; } // Format command for permission request const resolutionsStr = resolutions.join(', '); const permissionMessage = `Resize video ${path.basename(absVideoPath)} to ${resolutionsStr}`; // Ask for permission const permitted = await askPermission(permissionMessage); if (!permitted) { return { isError: true, content: [{ type: "text" as const, text: "Permission denied by user" }] }; } // Get video filename without extension const videoFilename = path.basename(absVideoPath, path.extname(absVideoPath)); // Define the type for our results type ResizeResult = { resolution: "360p" | "480p" | "720p" | "1080p"; outputPath: string; success: boolean; error?: string; }; // Process each resolution const results: ResizeResult[] = []; for (const resolution of resolutions) { const { width, height } = RESOLUTIONS[resolution as keyof typeof RESOLUTIONS]; const outputFilename = `${videoFilename}_${resolution}${path.extname(absVideoPath)}`; const outputPath = path.join(outputDirectory, outputFilename); // Build FFmpeg command const command = `ffmpeg -i "${absVideoPath}" -vf "scale=${width}:${height}" -c:v libx264 -crf 23 -preset medium -c:a aac -b:a 128k "${outputPath}"`; try { // Execute FFmpeg command const { stdout, stderr } = await execAsync(command); results.push({ resolution, outputPath, success: true }); } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); results.push({ resolution, outputPath, success: false, error: errorMessage }); } } // Format results const successCount = results.filter(r => r.success).length; const failCount = results.length - successCount; let resultText = `Processed ${results.length} resolutions (${successCount} successful, ${failCount} failed)\n\n`; results.forEach(result => { if (result.success) { resultText += `✅ ${result.resolution}: ${result.outputPath}\n`; } else { resultText += `❌ ${result.resolution}: Failed - ${result.error}\n`; } }); return { content: [{ type: "text" as const, text: resultText }] }; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); return { isError: true, content: [{ type: "text" as const, text: `Error resizing video: ${errorMessage}` }] }; } } );
- src/mcp-ffmpeg.ts:20-24 (helper)RESOLUTIONS constant mapping resolution strings to {width, height} objects, used exclusively by the resize-video handler.const RESOLUTIONS = { "360p": { width: 640, height: 360 }, "480p": { width: 854, height: 480 }, "720p": { width: 1280, height: 720 }, "1080p": { width: 1920, height: 1080 }
- src/mcp-ffmpeg.ts:30-55 (helper)askPermission helper function used by resize-video (and others) to request user approval via desktop notification before executing FFmpeg.async function askPermission(action: string): Promise<boolean> { // Skip notification if DISABLE_NOTIFICATIONS is set if (process.env.DISABLE_NOTIFICATIONS === 'true') { console.log(`Auto-allowing action (notifications disabled): ${action}`); return true; } return new Promise((resolve) => { notifier.notify({ title: 'FFmpeg Processor Permission Request', message: `${action}`, wait: true, timeout: 60, actions: 'Allow', closeLabel: 'Deny' }, (err, response, metadata) => { if (err) { console.error('Error showing notification:', err); resolve(false); return; } const buttonPressed = metadata?.activationValue || response; resolve(buttonPressed !== 'Deny'); }); });