prix-carburant / generate_og.py
SamDNX's picture
Reliability + accuracy + features: routing fallback, tolls, EV price, filters, trends, dark mode, URL state, social card
ed70eb7
Raw
History Blame Contribute Delete
3.47 kB
"""Generate the social-share card (Open Graph / Twitter image), 1200x630.
Run once; the PNG is committed to static/ and copied to the site root at build
time. The container does not need Pillow at runtime. Re-run only if the wording
or brand colours change: python generate_og.py
"""
from __future__ import annotations
import os
from PIL import Image, ImageDraw, ImageFont
from generate_icons import make_icon
STATIC_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "static")
W, H, SS = 1200, 630, 2 # final size + supersample factor for crisp text
INK = (33, 37, 41)
GREY = (90, 99, 110)
GREEN = (26, 152, 80)
# Price legend gradient (matches the map): cheap green -> pricey red.
LEGEND = [(26, 152, 80), (145, 207, 96), (254, 224, 139), (252, 141, 89), (215, 48, 39)]
# Font candidates per weight: Arial on macOS (local), DejaVu in the container.
FONTS = {
"bold": ["/System/Library/Fonts/Supplemental/Arial Bold.ttf",
"/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf",
"/Library/Fonts/Arial Bold.ttf"],
"regular": ["/System/Library/Fonts/Supplemental/Arial.ttf",
"/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf",
"/Library/Fonts/Arial.ttf"],
}
def _font(weight: str, size: int) -> ImageFont.FreeTypeFont:
for path in FONTS[weight]:
if os.path.exists(path):
return ImageFont.truetype(path, size)
return ImageFont.load_default()
def _lerp(a, b, t):
return tuple(round(a[i] + (b[i] - a[i]) * t) for i in range(3))
def _gradient_bar(draw: ImageDraw.ImageDraw, x0, y0, x1, y1) -> None:
"""Horizontal multi-stop gradient bar (the price scale)."""
span = x1 - x0
seg = span / (len(LEGEND) - 1)
for x in range(x0, x1):
f = (x - x0) / seg
i = min(int(f), len(LEGEND) - 2)
draw.line([(x, y0), (x, y1)], fill=_lerp(LEGEND[i], LEGEND[i + 1], f - i))
def build() -> Image.Image:
w, h = W * SS, H * SS
img = Image.new("RGB", (w, h), (255, 255, 255))
d = ImageDraw.Draw(img)
# Left: the brand icon tile.
icon = make_icon(360 * SS, rounded=True)
img.paste(icon, (70 * SS, (h - icon.height) // 2), icon)
tx = 470 * SS # text column
d.text((tx, 160 * SS), "Prix des carburants", font=_font("bold", 60 * SS), fill=INK)
d.text((tx, 240 * SS), "en France", font=_font("bold", 60 * SS), fill=GREEN)
d.text((tx, 350 * SS), "Carte en temps réel — gazole, SP95,",
font=_font("regular", 34 * SS), fill=GREY)
d.text((tx, 396 * SS), "SP98, E85, GPL & bornes de recharge",
font=_font("regular", 34 * SS), fill=GREY)
_gradient_bar(d, tx, 470 * SS, (W - 80) * SS, 492 * SS)
d.text((tx, 504 * SS), "moins cher", font=_font("regular", 26 * SS), fill=GREY)
pricey = "plus cher"
pf = _font("regular", 26 * SS)
d.text(((W - 80) * SS - d.textlength(pricey, font=pf), 504 * SS), pricey, font=pf, fill=GREY)
d.text((tx, 556 * SS), "samdnx-prix-carburant.hf.space",
font=_font("bold", 26 * SS), fill=GREEN)
return img.resize((W, H), Image.LANCZOS)
def save(path: str) -> None:
"""Render the card and write it to path (used at build time)."""
build().save(path, optimize=True)
def main() -> None:
os.makedirs(STATIC_DIR, exist_ok=True)
out = os.path.join(STATIC_DIR, "og-card.png")
save(out)
print("wrote", out, f"({os.path.getsize(out) // 1024} KB)")
if __name__ == "__main__":
main()