diff --git a/reelforge/services/frame_html.py b/reelforge/services/frame_html.py index d2a2ef2..cfc9357 100644 --- a/reelforge/services/frame_html.py +++ b/reelforge/services/frame_html.py @@ -2,8 +2,16 @@ HTML-based Frame Generator Service Renders HTML templates to frame images with variable substitution + +Linux Environment Requirements: + - fontconfig package must be installed + - Basic fonts (e.g., fonts-liberation, fonts-noto) recommended + + Ubuntu/Debian: sudo apt-get install -y fontconfig fonts-liberation fonts-noto-cjk + CentOS/RHEL: sudo yum install -y fontconfig liberation-fonts google-noto-cjk-fonts """ +import os import uuid from typing import Dict, Any, Optional from pathlib import Path @@ -38,8 +46,45 @@ class HTMLFrameGenerator: self.template_path = template_path self.template = self._load_template(template_path) self.hti = None # Lazy init to avoid overhead + self._check_linux_dependencies() logger.debug(f"Loaded HTML template: {template_path}") + def _check_linux_dependencies(self): + """Check Linux system dependencies and warn if missing""" + if os.name != 'posix': + return + + try: + import subprocess + + # Check fontconfig + result = subprocess.run( + ['fc-list'], + capture_output=True, + timeout=2 + ) + + if result.returncode != 0: + logger.warning( + "⚠️ fontconfig not found or not working properly. " + "Install with: sudo apt-get install -y fontconfig fonts-liberation fonts-noto-cjk" + ) + elif not result.stdout: + logger.warning( + "⚠️ No fonts detected by fontconfig. " + "Install fonts with: sudo apt-get install -y fonts-liberation fonts-noto-cjk" + ) + else: + logger.debug(f"✓ Fontconfig detected {len(result.stdout.splitlines())} fonts") + + except FileNotFoundError: + logger.warning( + "⚠️ fontconfig (fc-list) not found on system. " + "Install with: sudo apt-get install -y fontconfig" + ) + except Exception as e: + logger.debug(f"Could not check fontconfig status: {e}") + def _load_template(self, template_path: str) -> str: """Load HTML template from file""" path = Path(template_path) @@ -55,8 +100,31 @@ class HTMLFrameGenerator: def _ensure_hti(self, width: int, height: int): """Lazily initialize Html2Image instance""" if self.hti is None: - self.hti = Html2Image(size=(width, height)) - logger.debug(f"Initialized Html2Image with size ({width}, {height})") + # Configure Chrome flags for Linux headless environment + custom_flags = [ + '--no-sandbox', # Bypass AppArmor/sandbox restrictions + '--disable-dev-shm-usage', # Avoid shared memory issues + '--disable-gpu', # Disable GPU acceleration + '--disable-software-rasterizer', # Disable software rasterizer + '--disable-extensions', # Disable extensions + '--disable-setuid-sandbox', # Additional sandbox bypass + '--disable-dbus', # Disable DBus to avoid permission errors + '--hide-scrollbars', # Hide scrollbars for cleaner output + '--mute-audio', # Mute audio + '--disable-background-networking', # Disable background networking + '--disable-features=TranslateUI', # Disable translate UI + '--disable-ipc-flooding-protection', # Improve performance + '--no-first-run', # Skip first run dialogs + '--no-default-browser-check', # Skip default browser check + '--disable-backgrounding-occluded-windows', # Improve performance + '--disable-renderer-backgrounding', # Improve performance + ] + + self.hti = Html2Image( + size=(width, height), + custom_flags=custom_flags + ) + logger.debug(f"Initialized Html2Image with size ({width}, {height}) and {len(custom_flags)} custom flags") async def generate_frame( self,