"""Startup check: verify Blender >= 5.0.1 is available. Run before starting the Celery worker. Exits with code 1 if Blender is missing or below the minimum required version. """ import os import re import subprocess import sys from pathlib import Path MIN_VERSION = (5, 0, 1) MIN_VERSION_STR = ".".join(str(v) for v in MIN_VERSION) def find_blender() -> str: import shutil env_bin = os.environ.get("BLENDER_BIN", "") if env_bin and Path(env_bin).exists(): return env_bin found = shutil.which("blender") return found or "blender" def check_version(): blender_bin = find_blender() if not Path(blender_bin).exists(): print(f"ERROR: Blender not found at {blender_bin}", file=sys.stderr) print( "Mount Blender >= 5.0.1 from the host via:\n" " volumes:\n" " - /opt/blender:/opt/blender:ro", file=sys.stderr, ) sys.exit(1) try: result = subprocess.run( [blender_bin, "--version"], capture_output=True, text=True, timeout=15 ) output = result.stdout or result.stderr or "" except Exception as exc: print(f"ERROR: Could not run Blender: {exc}", file=sys.stderr) sys.exit(1) match = re.search(r"Blender\s+(\d+)\.(\d+)\.(\d+)", output) if not match: print(f"ERROR: Could not parse Blender version from output:\n{output[:200]}", file=sys.stderr) sys.exit(1) version = tuple(int(x) for x in match.groups()) version_str = ".".join(str(v) for v in version) if version < MIN_VERSION: print( f"ERROR: Blender {version_str} < required {MIN_VERSION_STR}.\n" f"Update your host Blender installation.", file=sys.stderr, ) sys.exit(1) print(f"Blender {version_str} OK (>= {MIN_VERSION_STR})") def check_gpu(): """Run the Blender GPU probe script and report results. Respects CYCLES_DEVICE env var: - "cpu" → skip probe entirely - "gpu" → require GPU; abort startup if none found - "auto" (default) → warn if no GPU found, but continue """ cycles_device = os.environ.get("CYCLES_DEVICE", "auto").lower() if cycles_device == "cpu": print("[check_version] GPU check skipped (CYCLES_DEVICE=cpu)", flush=True) return blender_bin = find_blender() probe_script = Path("/render-scripts/gpu_probe.py") if not probe_script.exists(): print( f"[check_version] WARNING: gpu_probe.py not found at {probe_script}", flush=True, ) return try: result = subprocess.run( [blender_bin, "--background", "--python", str(probe_script)], capture_output=True, text=True, timeout=45, ) if result.returncode == 0: for line in result.stdout.splitlines(): if "GPU_PROBE_OK" in line: print(f"[check_version] {line}", flush=True) break else: msg = "No GPU detected — renders will use CPU" for line in result.stdout.splitlines(): if "GPU_PROBE_FAIL" in line: msg = line break if cycles_device == "gpu": print(f"[check_version] ERROR: {msg}", flush=True) print( "[check_version] CYCLES_DEVICE=gpu requires GPU — aborting startup", flush=True, ) sys.exit(1) else: print( f"[check_version] WARNING: {msg} (set CYCLES_DEVICE=gpu to enforce)", flush=True, ) except subprocess.TimeoutExpired: print("[check_version] WARNING: GPU probe timed out after 45s", flush=True) except Exception as e: print(f"[check_version] WARNING: GPU probe failed: {e}", flush=True) if __name__ == "__main__": check_version() check_gpu()