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:
parent
3a4c3e8f77
commit
406cc891c3
@ -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
2
uv.lock
generated
@ -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" },
|
||||
|
Loading…
x
Reference in New Issue
Block a user