diff --git a/mcp_video_editor/server.py b/mcp_video_editor/server.py index 0f72703..65f43a2 100644 --- a/mcp_video_editor/server.py +++ b/mcp_video_editor/server.py @@ -2,6 +2,7 @@ import os from pathlib import Path +from typing import Union from fastmcp import FastMCP @@ -30,6 +31,82 @@ except ImportError: mcp = FastMCP("MCP Video Editor") +def get_available_fonts(): + """Get list of available system fonts for text overlays.""" + fonts = [] + + # Try to get system fonts using different methods + try: + # Method 1: Try matplotlib font manager + import matplotlib.font_manager as fm + font_paths = fm.findSystemFonts() + font_names = [] + for font_path in font_paths[:50]: # Limit to first 50 fonts + try: + prop = fm.FontProperties(fname=font_path) + name = prop.get_name() + if name and name not in font_names: + font_names.append(name) + fonts.append({ + "name": name, + "path": font_path, + "family": prop.get_family()[0] if prop.get_family() else "Unknown" + }) + except: + continue + except ImportError: + pass + + # Method 2: Add common web-safe fonts + common_fonts = [ + {"name": "Arial", "family": "sans-serif", "web_safe": True}, + {"name": "Helvetica", "family": "sans-serif", "web_safe": True}, + {"name": "Times New Roman", "family": "serif", "web_safe": True}, + {"name": "Courier New", "family": "monospace", "web_safe": True}, + {"name": "Verdana", "family": "sans-serif", "web_safe": True}, + {"name": "Georgia", "family": "serif", "web_safe": True}, + {"name": "Comic Sans MS", "family": "cursive", "web_safe": True}, + {"name": "Impact", "family": "fantasy", "web_safe": True}, + ] + + # Add common fonts if not already found + existing_names = [f["name"] for f in fonts] + for common_font in common_fonts: + if common_font["name"] not in existing_names: + fonts.append(common_font) + + return fonts[:30] # Return max 30 fonts + + +# Add font resources to MCP server +@mcp.resource("font://available") +def get_available_fonts_resource(): + """List all available fonts for text overlays.""" + fonts = get_available_fonts() + return { + "description": "Available fonts for video text overlays", + "total_fonts": len(fonts), + "fonts": fonts + } + + +@mcp.resource("font://web-safe") +def get_websafe_fonts_resource(): + """List web-safe fonts guaranteed to work.""" + web_safe_fonts = [ + {"name": "Arial", "family": "sans-serif", "description": "Clean, modern sans-serif"}, + {"name": "Helvetica", "family": "sans-serif", "description": "Professional sans-serif"}, + {"name": "Times New Roman", "family": "serif", "description": "Classic serif font"}, + {"name": "Courier New", "family": "monospace", "description": "Fixed-width font"}, + {"name": "Verdana", "family": "sans-serif", "description": "Web-optimized sans-serif"}, + {"name": "Georgia", "family": "serif", "description": "Web-optimized serif"}, + ] + return { + "description": "Web-safe fonts guaranteed to work across platforms", + "fonts": web_safe_fonts + } + + def _check_moviepy_dependency(): """Check if MoviePy is available for video processing.""" if not MOVIEPY_AVAILABLE: @@ -385,8 +462,8 @@ def mcp_video_add_overlay( "output_path": output_path, "overlay_type": overlay_type, "position": position, - "start_time": start_time, - "duration": duration or (clip.duration - start_time), + "start_time": str(start_time), + "duration": str(duration or (clip.duration - start_time)), "status": "success", } @@ -769,6 +846,85 @@ def mcp_video_resolution_optimizer( return {"error": f"Resolution optimization failed: {e!s}"} +@mcp.tool() +def mcp_video_test_fonts( + output_path: str, + test_text: str = "InterNACHI Expert Agent", + font_names: list[str] = None +) -> dict[str, Union[str, int, list]]: + """ + Create a video showcasing different fonts for testing. + + Args: + output_path: Output video file path + test_text: Text to display with each font + font_names: List of font names to test (uses web-safe fonts if not provided) + + Returns: + Dict with output path and font test results + """ + try: + if not MOVIEPY_AVAILABLE: + return {"error": "MoviePy is not available for video processing"} + + # Use web-safe fonts if none provided + if not font_names: + font_names = ["Arial", "Times New Roman", "Courier New", "Verdana"] + + # Create font test clips + test_clips = [] + working_fonts = [] + failed_fonts = [] + + for i, font_name in enumerate(font_names): + try: + # Try to create text clip with this font + text_clip = TextClip( + text=f"{test_text} - {font_name}", + font_size=48, + color='white', + duration=2 + ).with_position('center') + + # Create background for contrast + bg_clip = ColorClip(size=(1280, 720), color=(50, 50, 50)).with_duration(2) + + # Composite text on background + composite = CompositeVideoClip([bg_clip, text_clip]) + test_clips.append(composite) + working_fonts.append(font_name) + + except Exception as e: + failed_fonts.append({"font": font_name, "error": str(e)}) + continue + + if not test_clips: + return {"error": "No fonts worked for testing"} + + # Concatenate all test clips + final_video = concatenate_videoclips(test_clips) + final_video.write_videofile(output_path, fps=24) + + # Cleanup + for clip in test_clips: + clip.close() + final_video.close() + + return { + "output_path": output_path, + "total_fonts_tested": len(font_names), + "working_fonts": working_fonts, + "failed_fonts": failed_fonts, + "working_count": len(working_fonts), + "failed_count": len(failed_fonts), + "video_duration": len(working_fonts) * 2, + "status": "success" + } + + except Exception as e: + return {"error": f"Font testing failed: {str(e)}"} + + def main(): """Main entry point for the MCP Video Editor server.""" mcp.run() diff --git a/uv.lock b/uv.lock index d4c8a88..1b370fb 100644 --- a/uv.lock +++ b/uv.lock @@ -562,6 +562,7 @@ version = "0.1.0" source = { editable = "." } dependencies = [ { name = "fastmcp" }, + { name = "moviepy" }, ] [package.optional-dependencies] @@ -582,6 +583,7 @@ video = [ requires-dist = [ { name = "fastmcp", specifier = ">=2.12.2" }, { name = "ffmpeg-python", marker = "extra == 'video'", specifier = ">=0.2.0" }, + { name = "moviepy", specifier = ">=2.2.1" }, { name = "moviepy", marker = "extra == 'video'", specifier = ">=1.0.3" }, { name = "numpy", marker = "extra == 'video'", specifier = ">=1.24.0" }, { name = "opencv-python", marker = "extra == 'video'", specifier = ">=4.8.0" },