← back to Beverlyhillsvideos
filmgen/make_card.py
69 lines
#!/usr/bin/env python3
"""Render a luxe Beverly Hills title card PNG (1920x1080) for a place."""
import sys, json, textwrap
from PIL import Image, ImageDraw, ImageFont, ImageFilter
W, H = 1920, 1080
INK = (21, 19, 16)
CREAM = (246, 243, 236)
GOLD = (184, 147, 90)
MUTED = (150, 143, 130)
LINE = (70, 66, 60)
SERIF = "/System/Library/Fonts/Supplemental/Didot.ttc" # display
SANS = "/System/Library/Fonts/Supplemental/Futura.ttc" # eyebrow / meta
def font(path, size, idx=0):
return ImageFont.truetype(path, size, index=idx)
def tracked(draw, xy, text, fnt, fill, track, anchor_center_x):
"""Draw letter-spaced text centered on anchor_center_x at y=xy[1]."""
widths = [draw.textlength(c, font=fnt) for c in text]
total = sum(widths) + track * (len(text) - 1)
x = anchor_center_x - total / 2
y = xy[1]
for c, w in zip(text, widths):
draw.text((x, y), c, font=fnt, fill=fill)
x += w + track
def center(draw, y, text, fnt, fill):
w = draw.textlength(text, font=fnt)
draw.text((W/2 - w/2, y), text, font=fnt, fill=fill)
return w
def render(place, out):
name = place["name"]; cat = place.get("category",""); addr = place.get("address","")
img = Image.new("RGB", (W, H), INK)
d = ImageDraw.Draw(img)
# vignette
vig = Image.new("L", (W, H), 0); vd = ImageDraw.Draw(vig)
vd.ellipse([-W*0.3, -H*0.3, W*1.3, H*1.3], fill=90)
vig = vig.filter(ImageFilter.GaussianBlur(220))
dark = Image.new("RGB", (W, H), (8,7,6))
img = Image.composite(img, dark, vig)
d = ImageDraw.Draw(img)
# eyebrow (category, tracked, gold)
eb = font(SANS, 34)
tracked(d, (0, H*0.30), cat.upper(), eb, GOLD, 10, W/2)
# title (Didot, wrap long names)
lines = textwrap.wrap(name, width=18) or [name]
tsize = 150 if len(lines) == 1 and len(name) <= 14 else (120 if len(lines)==1 else 104)
tf = font(SERIF, tsize)
ty = H*0.5 - (len(lines)*tsize*0.62)/2 - 10
for ln in lines:
center(d, ty, ln, tf, CREAM); ty += tsize*0.92
# hairline rule
ry = H*0.66
d.line([(W/2-70, ry), (W/2+70, ry)], fill=GOLD, width=2)
# address (muted sans)
af = font(SANS, 32)
center(d, ry+40, addr.replace(", Beverly Hills, CA", "").split(", CA")[0], af, MUTED)
# wordmark
wf = font(SANS, 24)
tracked(d, (0, H-90), "BEVERLY HILLS VIDEOS", wf, MUTED, 8, W/2)
img.save(out, "PNG")
print("card ->", out)
if __name__ == "__main__":
place = json.loads(sys.argv[1]); render(place, sys.argv[2])