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,