#!/usr/bin/env python3
"""Render a ClassroomIO thumbnail HTML to a 1920x1080 PNG via headless Chrome.
Resolves the two stable brand assets from the repo and inlines them as base64
so the render is deterministic (a Google Fonts does NOT load before the
screenshot — that is the #1 cause of off-brand headlines). Third-party logos are
NOT handled here: fetch the official svg per task and inline it in your HTML.
Placeholders replaced in the HTML before rendering:
__GEIST_WOFF2_B64__ -> apps/dashboard/static/fonts/geist/geist-latin.woff2
__CIO_LOGO_B64__ -> apps/website/static/logo-512.png
Usage:
python3 render.py
"""
import base64
import pathlib
import re
import subprocess
import sys
REPO_ROOT = pathlib.Path(__file__).resolve().parents[2]
GEIST = REPO_ROOT / "apps/dashboard/static/fonts/geist/geist-latin.woff2"
CIO_LOGO = REPO_ROOT / "apps/website/static/logo-512.png"
CHROME = "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"
def b64(path: pathlib.Path) -> str:
if not path.exists():
sys.exit(f"Missing brand asset: {path}")
return base64.b64encode(path.read_bytes()).decode()
def inline_local_stylesheets(html: str, base_dir: pathlib.Path) -> str:
"""Replace with an inline "
return pattern.sub(replace, html)
def main() -> None:
if len(sys.argv) != 3:
sys.exit("Usage: python3 render.py ")
src = pathlib.Path(sys.argv[1]).resolve()
out = pathlib.Path(sys.argv[2]).resolve()
if not src.exists():
sys.exit(f"Input HTML not found: {src}")
html = src.read_text()
html = inline_local_stylesheets(html, src.parent)
html = html.replace("__GEIST_WOFF2_B64__", b64(GEIST))
html = html.replace("__CIO_LOGO_B64__", b64(CIO_LOGO))
inlined = src.with_suffix(".inlined.html")
inlined.write_text(html)
subprocess.run(
[
CHROME,
"--headless",
"--disable-gpu",
"--hide-scrollbars",
"--force-device-scale-factor=1",
"--window-size=1920,1080",
f"--screenshot={out}",
f"file://{inlined}",
],
check=True,
)
print(f"Rendered {out}")
if __name__ == "__main__":
main()