Spaces:
Running
Running
| """ | |
| Video Processing Tools for Manim MCP Server | |
| This module provides tools for video processing, merging, and file management using FFmpeg. | |
| """ | |
| import asyncio | |
| import json | |
| import logging | |
| from pathlib import Path | |
| from typing import Any, Dict | |
| from mcp.types import CallToolResult, TextContent | |
| logger = logging.getLogger(__name__) | |
| async def process_video_with_ffmpeg(arguments: Dict[str, Any]) -> CallToolResult: | |
| """ | |
| Process video files using FFmpeg. | |
| Provides flexible video processing capabilities including conversion, | |
| filtering, and combining multiple inputs. | |
| Args: | |
| arguments: Dictionary containing: | |
| - input_files (list): List of input video/audio file paths | |
| - output_file (str): Output file path | |
| - ffmpeg_args (list, optional): Additional FFmpeg command-line arguments | |
| Returns: | |
| CallToolResult indicating success or failure | |
| """ | |
| input_files = arguments["input_files"] | |
| output_file = arguments["output_file"] | |
| ffmpeg_args = arguments.get("ffmpeg_args", []) | |
| try: | |
| # Ensure output directory exists | |
| Path(output_file).parent.mkdir(parents=True, exist_ok=True) | |
| # Build FFmpeg command | |
| cmd = ["ffmpeg"] | |
| # Add input files | |
| for input_file in input_files: | |
| cmd.extend(["-i", input_file]) | |
| # Add additional arguments | |
| cmd.extend(ffmpeg_args) | |
| # Add output file | |
| cmd.append(output_file) | |
| logger.info(f"Running FFmpeg command: {' '.join(cmd)}") | |
| # Execute FFmpeg | |
| process = await asyncio.create_subprocess_exec( | |
| *cmd, | |
| stdout=asyncio.subprocess.PIPE, | |
| stderr=asyncio.subprocess.PIPE, | |
| ) | |
| stdout, stderr = await process.communicate() | |
| if process.returncode != 0: | |
| error_msg = f"FFmpeg processing failed:\n{stderr.decode()}" | |
| logger.error(error_msg) | |
| return CallToolResult( | |
| content=[TextContent(type="text", text=error_msg)], | |
| isError=True, | |
| ) | |
| result_msg = f"Successfully processed video with FFmpeg: {output_file}" | |
| logger.info(result_msg) | |
| return CallToolResult(content=[TextContent(type="text", text=result_msg)]) | |
| except Exception as e: | |
| error_msg = f"Error during FFmpeg processing: {str(e)}" | |
| logger.error(error_msg) | |
| return CallToolResult( | |
| content=[TextContent(type="text", text=error_msg)], | |
| isError=True, | |
| ) | |
| async def merge_video_audio(arguments: Dict[str, Any]) -> CallToolResult: | |
| """ | |
| Merge video and audio files into a single output file. | |
| Combines a video file with an audio file using FFmpeg. The video stream | |
| is copied without re-encoding, while the audio is encoded to AAC. | |
| The output duration matches the shorter of the two inputs. | |
| Args: | |
| arguments: Dictionary containing: | |
| - video_file (str): Path to the input video file | |
| - audio_file (str): Path to the input audio file | |
| - output_file (str): Path to the output merged file | |
| Returns: | |
| CallToolResult indicating success or failure | |
| """ | |
| video_file = arguments["video_file"] | |
| audio_file = arguments["audio_file"] | |
| output_file = arguments["output_file"] | |
| try: | |
| # Ensure output directory exists | |
| Path(output_file).parent.mkdir(parents=True, exist_ok=True) | |
| # Build FFmpeg merge command | |
| cmd = [ | |
| "ffmpeg", | |
| "-i", | |
| video_file, | |
| "-i", | |
| audio_file, | |
| "-c:v", | |
| "copy", # Copy video stream without re-encoding | |
| "-c:a", | |
| "aac", # Encode audio to AAC | |
| "-shortest", # Match duration of shortest input | |
| "-y", # Overwrite output file if it exists | |
| output_file, | |
| ] | |
| logger.info(f"Merging video and audio: {' '.join(cmd)}") | |
| # Execute FFmpeg | |
| process = await asyncio.create_subprocess_exec( | |
| *cmd, | |
| stdout=asyncio.subprocess.PIPE, | |
| stderr=asyncio.subprocess.PIPE, | |
| ) | |
| stdout, stderr = await process.communicate() | |
| if process.returncode != 0: | |
| error_msg = f"Video/audio merge failed:\n{stderr.decode()}" | |
| logger.error(error_msg) | |
| return CallToolResult( | |
| content=[TextContent(type="text", text=error_msg)], | |
| isError=True, | |
| ) | |
| result_msg = f"Successfully merged video and audio: {output_file}" | |
| logger.info(result_msg) | |
| return CallToolResult(content=[TextContent(type="text", text=result_msg)]) | |
| except Exception as e: | |
| error_msg = f"Error during video/audio merge: {str(e)}" | |
| logger.error(error_msg) | |
| return CallToolResult( | |
| content=[TextContent(type="text", text=error_msg)], | |
| isError=True, | |
| ) | |
| async def check_file_exists(arguments: Dict[str, Any]) -> CallToolResult: | |
| """ | |
| Check if a file exists and return its metadata. | |
| Provides information about file existence, type, size, and timestamps. | |
| Useful for verifying outputs before processing or debugging file issues. | |
| Args: | |
| arguments: Dictionary containing: | |
| - filepath (str): Path to the file to check | |
| Returns: | |
| CallToolResult with file metadata or error if file doesn't exist | |
| """ | |
| filepath = arguments["filepath"] | |
| try: | |
| path = Path(filepath) | |
| if not path.exists(): | |
| return CallToolResult( | |
| content=[ | |
| TextContent( | |
| type="text", | |
| text=f"File does not exist: {filepath}", | |
| ) | |
| ], | |
| isError=True, | |
| ) | |
| stat = path.stat() | |
| metadata = { | |
| "filepath": str(path.absolute()), | |
| "exists": True, | |
| "is_file": path.is_file(), | |
| "is_directory": path.is_dir(), | |
| "size_bytes": stat.st_size, | |
| "created": stat.st_ctime, | |
| "modified": stat.st_mtime, | |
| } | |
| logger.info(f"File exists: {filepath} ({stat.st_size} bytes)") | |
| return CallToolResult( | |
| content=[ | |
| TextContent( | |
| type="text", | |
| text=f"File metadata:\n{json.dumps(metadata, indent=2)}", | |
| ) | |
| ] | |
| ) | |
| except Exception as e: | |
| error_msg = f"Error checking file: {str(e)}" | |
| logger.error(error_msg) | |
| return CallToolResult( | |
| content=[TextContent(type="text", text=error_msg)], | |
| isError=True, | |
| ) | |