Add font resources and fix validation issues

- Add font:// MCP resources for available and web-safe fonts
- Create mcp_video_test_fonts tool for font testing
- Fix return value validation (convert numbers to strings)
- Add Union import for type hints
- Clients can now discover available fonts before using overlays
This commit is contained in:
Ryan Malloy 2025-09-05 05:37:12 -06:00
parent 3a4c3e8f77
commit 406cc891c3
2 changed files with 160 additions and 2 deletions

View File

@ -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()

2
uv.lock generated
View File

@ -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" },