Skip to main content
Glama
theme_manager.py9.96 kB
# chuk-motion/src/chuk_motion/themes/themes_manager.py """ Theme manager for Remotion video compositions. Central system for managing and applying themes. The theme system provides: - Built-in YouTube-optimized themes - Custom theme creation and registration - Theme discovery and comparison - Theme validation - Export/import for sharing """ import json from typing import TYPE_CHECKING, Any if TYPE_CHECKING: from chuk_virtual_fs import AsyncVirtualFileSystem from .models import Theme from .youtube_themes import YOUTUBE_THEMES class ThemeManager: """ Manages themes for Remotion video compositions. Provides theme registration, selection, discovery, and application. """ def __init__(self, vfs: "AsyncVirtualFileSystem"): """ Initialize theme manager with built-in themes. Args: vfs: Virtual filesystem for file operations """ self.vfs = vfs self.themes: dict[str, Theme] = {} self.current_theme: str | None = None self._register_builtin_themes() def _register_builtin_themes(self): """Register all built-in YouTube-optimized themes.""" # YOUTUBE_THEMES is now a ThemeCollection with Pydantic Theme models for theme_key, theme in YOUTUBE_THEMES.items(): self.themes[theme_key] = theme def register_theme(self, theme_key: str, theme: Theme) -> None: """ Register a custom theme. Args: theme_key: Unique identifier for the theme theme: Theme object to register """ self.themes[theme_key] = theme def list_themes(self) -> list[str]: """ List all registered theme keys. Returns: List of theme keys """ return list(self.themes.keys()) def get_theme(self, theme_key: str) -> Theme | None: """ Get a theme by key. Args: theme_key: Theme identifier Returns: Theme object or None if not found """ return self.themes.get(theme_key) def get_theme_info(self, theme_key: str) -> dict[str, Any] | None: """ Get detailed information about a theme. Args: theme_key: Theme identifier Returns: Dictionary with theme information or None """ theme = self.get_theme(theme_key) if not theme: return None # Use model_dump() to convert Pydantic models to dicts return theme.model_dump() def set_current_theme(self, theme_key: str) -> bool: """ Set the current active theme. Args: theme_key: Theme identifier Returns: True if successful, False if theme not found """ if theme_key not in self.themes: return False self.current_theme = theme_key return True def get_current_theme(self) -> str | None: """ Get the currently active theme key. Returns: Current theme key or None """ return self.current_theme def compare_themes(self, theme_key1: str, theme_key2: str) -> dict[str, Any]: """ Compare two themes side by side. Args: theme_key1: First theme identifier theme_key2: Second theme identifier Returns: Dictionary with comparison data """ theme1 = self.get_theme(theme_key1) theme2 = self.get_theme(theme_key2) if not theme1 or not theme2: return {"error": "One or both themes not found"} return { "themes": [theme_key1, theme_key2], "comparison": { "names": [theme1.name, theme2.name], "descriptions": [theme1.description, theme2.description], "primary_colors": [ theme1.colors.primary, theme2.colors.primary, ], "accent_colors": [theme1.colors.accent, theme2.colors.accent], "motion_feel": [ theme1.motion.default_spring.feel, theme2.motion.default_spring.feel, ], "use_cases": [theme1.use_cases, theme2.use_cases], }, } def search_themes(self, query: str) -> list[str]: """ Search themes by name, description, or use case. Args: query: Search query string Returns: List of matching theme keys """ query_lower = query.lower() matches = [] for theme_key, theme in self.themes.items(): # Search in name if query_lower in theme.name.lower(): matches.append(theme_key) continue # Search in description if query_lower in theme.description.lower(): matches.append(theme_key) continue # Search in use cases for use_case in theme.use_cases: if query_lower in use_case.lower(): matches.append(theme_key) break return matches def get_themes_by_category(self, category: str) -> list[str]: """ Get themes suitable for a content category. Args: category: Content category (e.g., "gaming", "education", "business") Returns: List of suitable theme keys """ return self.search_themes(category) def validate_theme(self, theme_data: dict[str, Any]) -> dict[str, Any]: """ Validate theme data structure using Pydantic model. Args: theme_data: Theme dictionary to validate Returns: Dictionary with validation results """ try: # Pydantic will validate the structure Theme(**theme_data) return {"valid": True, "errors": []} except Exception as e: return {"valid": False, "errors": [str(e)]} async def export_theme(self, theme_key: str, file_path: str | None = None) -> str: """ Export theme to JSON file. Args: theme_key: Theme identifier file_path: Optional output file path (defaults to theme_name.json) Returns: Path to exported file or error message """ theme = self.get_theme(theme_key) if not theme: return f"Error: Theme '{theme_key}' not found" if not file_path: file_path = f"{theme_key}_theme.json" try: # Use model_dump_json() for direct JSON serialization json_content = theme.model_dump_json(indent=2) await self.vfs.write_file(file_path, json_content) return file_path except Exception as e: return f"Error exporting theme: {str(e)}" async def import_theme(self, file_path: str, theme_key: str | None = None) -> str: """ Import theme from JSON file. Args: file_path: Path to JSON file theme_key: Optional key to register theme under (defaults to name from file) Returns: Success message or error """ try: json_content = await self.vfs.read_text(file_path) theme_data = json.loads(json_content) # Validate and create theme using Pydantic theme = Theme(**theme_data) # Register theme key = theme_key or theme.name.lower().replace(" ", "_") self.register_theme(key, theme) return f"Successfully imported theme '{theme.name}' as '{key}'" except Exception as e: return f"Error importing theme: {str(e)}" def create_custom_theme( self, name: str, description: str, base_theme: str | None = None, color_overrides: dict[str, Any] | None = None, typography_overrides: dict[str, Any] | None = None, motion_overrides: dict[str, Any] | None = None, ) -> str: """ Create a custom theme, optionally based on an existing theme. Args: name: Custom theme name description: Theme description base_theme: Optional base theme to start from color_overrides: Color tokens to override typography_overrides: Typography tokens to override motion_overrides: Motion tokens to override Returns: Theme key of created theme or error message """ try: # Start with base theme or use defaults if base_theme and base_theme in self.themes: base = self.themes[base_theme] # Use model_copy for Pydantic models theme_dict = base.model_dump() # Apply overrides if color_overrides: theme_dict["colors"].update(color_overrides) if typography_overrides: theme_dict["typography"].update(typography_overrides) if motion_overrides: theme_dict["motion"].update(motion_overrides) # Update name and description theme_dict["name"] = name theme_dict["description"] = description # Create new theme from modified dict theme = Theme(**theme_dict) else: # Create minimal theme (will fail validation, user must provide all required fields) return "Error: Must provide a base_theme or all required token overrides" # Register theme_key = name.lower().replace(" ", "_") self.register_theme(theme_key, theme) return theme_key except Exception as e: return f"Error creating custom theme: {str(e)}"

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/chrishayuk/chuk-mcp-remotion'

If you have feedback or need assistance with the MCP directory API, please join our Discord server