Files
libretro/scripts/make_og_cards.py
T
Abdessamad Derraz cdffbcdfe4 feat: share cards for manufacturer pages
One card per systems page, titled with the manufacturer and listing its consoles as far as three lines hold. Rendered once and committed like the platform cards.
2026-10-23 10:56:33 +02:00

138 lines
5.5 KiB
Python

#!/usr/bin/env python3
"""Render the sharing cards (Open Graph images) to PNG, once.
Each card is an SVG written by ``sitediagrams.og_card_svg`` from the
registry, rasterised by a headless Chromium through Playwright, then
committed under ``docs_assets/og/``. The site build copies the PNG files and
never runs this script, so it adds nothing to the build's dependencies.
Run it again when a section's wording, the platform count, the list of
platforms or the consoles of a manufacturer change.
python scripts/make_og_cards.py
python scripts/make_og_cards.py --only platforms
python scripts/make_og_cards.py --only systems
A platform page shares a card with its own name (``og/platforms/<id>.png``),
a manufacturer's systems page one with the manufacturer's name and its
consoles (``og/systems/<slug>.png``), the other pages their section's.
"""
from __future__ import annotations
import argparse
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(ROOT / "scripts"))
from common import list_registered_platforms, load_platform_config # noqa: E402
from sitediagrams import OG_SECTIONS, og_card_svg, wrap # noqa: E402
OUT = ROOT / "docs_assets" / "og"
LOGO = ROOT / ".github" / "assets" / "logo.png"
PLATFORM_TAGLINE = "Which BIOS files it needs, and where each one goes"
# The tagline's box on the card: og_card_svg wraps it at this width and
# size, and three lines fit above the address.
TAGLINE_WIDTH, TAGLINE_SIZE, TAGLINE_LINES = 1000, 34, 3
def platform_cards() -> dict[str, tuple[str, str]]:
"""Each platform page's card, by its path under og/, with its title."""
platforms = ROOT / "platforms"
return {
f"platforms/{name}": (load_platform_config(name, str(platforms))["platform"],
PLATFORM_TAGLINE)
for name in list_registered_platforms(str(platforms), include_archived=True)
}
def consoles_tagline(consoles: list[str], lead: str = "BIOS files for") -> str:
""""BIOS files for PlayStation, PlayStation 2 and 5 more": as many
names as the card's three lines hold, the rest counted."""
names = sorted(consoles, key=str.casefold)
for shown in range(len(names), 0, -1):
rest = len(names) - shown
listed = names[:shown]
if rest:
text = f"{lead} {', '.join(listed)} and {rest} more"
elif len(listed) > 1:
text = f"{lead} {', '.join(listed[:-1])} and {listed[-1]}"
else:
text = f"{lead} {listed[0]}"
if len(wrap(text, TAGLINE_WIDTH, TAGLINE_SIZE)) <= TAGLINE_LINES:
return text
return f"{lead} {len(names)} sections"
def system_cards() -> dict[str, tuple[str, str]]:
"""Each manufacturer page's card under og/systems/, titled with the
manufacturer and listing its consoles. Read from the same grouping the
system pages are written from."""
from common import GAME_DATA_TOPS, load_database
from generate_site import _group_by_manufacturer, _slug
def lead(manufacturer: str) -> str:
# Other holds one folder per emulator, a game publisher's page its
# games' data: neither is a list of consoles.
if manufacturer in GAME_DATA_TOPS:
return "Game data for"
return "Files kept for the emulators" if manufacturer == "Other" else "BIOS files for"
manufacturers = _group_by_manufacturer(load_database(str(ROOT / "database.json")))
return {
f"systems/{_slug(manufacturer)}": (
manufacturer, consoles_tagline(list(consoles), lead(manufacturer)))
for manufacturer, consoles in sorted(manufacturers.items())
}
def card_svg(key: str, logo: bytes, titles: dict[str, tuple[str, str]]) -> str:
if key in OG_SECTIONS:
return og_card_svg(key, logo)
# A page card is a section card with the page's own title and tagline.
# The tagline is a format string there: braces in a name stay text.
title, tagline = titles[key]
OG_SECTIONS[key] = (title, tagline.replace("{", "{{").replace("}", "}}"))
try:
return og_card_svg(key, logo)
finally:
del OG_SECTIONS[key]
def main() -> None:
parser = argparse.ArgumentParser(description="Render the sharing cards to PNG.",
allow_abbrev=False)
parser.add_argument("--only", choices=("sections", "platforms", "systems"),
help="render one kind of card (default: all)")
args = parser.parse_args()
try:
from playwright.sync_api import sync_playwright
except ImportError as exc:
raise SystemExit("Playwright with Chromium is needed: pip install playwright") from exc
titles: dict[str, tuple[str, str]] = {}
if args.only in (None, "platforms"):
titles.update(platform_cards())
if args.only in (None, "systems"):
titles.update(system_cards())
keys = [*(OG_SECTIONS if args.only in (None, "sections") else []), *titles]
logo = LOGO.read_bytes()
with sync_playwright() as playwright:
browser = playwright.chromium.launch()
page = browser.new_page(viewport={"width": 1200, "height": 630})
for key in keys:
page.set_content(
'<html><body style="margin:0">' + card_svg(key, logo, titles) + "</body></html>"
)
path = OUT / f"{key}.png"
path.parent.mkdir(parents=True, exist_ok=True)
page.screenshot(path=str(path), clip={"x": 0, "y": 0, "width": 1200, "height": 630})
print(path.relative_to(ROOT))
browser.close()
if __name__ == "__main__":
from common import run_cli
run_cli(main)