← back to Homesonspec

apps/mobile/scripts/caption-shot.py

71 lines

#!/usr/bin/env python3
"""Overlay a branded caption band on an App Store screenshot.
Usage: caption-shot.py <in.png> <out.png> "<caption>" [top|bottom]
Band = brand navy #1a3a6e, bold white centered wrapped text. Sized for 1320x2868 (6.9").
"""
import sys
from PIL import Image, ImageDraw, ImageFont

NAVY = (26, 58, 110)          # #1a3a6e — app splash/adaptive bg
WHITE = (255, 255, 255)
BAND_FRAC = 0.135             # band height as fraction of image height
PAD_X = 90

def load_font(px):
    for p in [
        "/System/Library/Fonts/SFNSDisplay-Bold.otf",
        "/System/Library/Fonts/SFNS.ttf",
        "/System/Library/Fonts/Supplemental/Arial Bold.ttf",
        "/Library/Fonts/Arial Bold.ttf",
    ]:
        try:
            return ImageFont.truetype(p, px)
        except Exception:
            continue
    return ImageFont.load_default()

def wrap(draw, text, font, maxw):
    words, lines, cur = text.split(), [], ""
    for w in words:
        t = (cur + " " + w).strip()
        if draw.textlength(t, font=font) <= maxw:
            cur = t
        else:
            if cur:
                lines.append(cur)
            cur = w
    if cur:
        lines.append(cur)
    return lines

def main():
    inp, outp, caption = sys.argv[1], sys.argv[2], sys.argv[3]
    pos = sys.argv[4] if len(sys.argv) > 4 else "top"
    img = Image.open(inp).convert("RGB")
    W, H = img.size
    band_h = int(H * BAND_FRAC)
    band = Image.new("RGB", (W, band_h), NAVY)
    y0 = 0 if pos == "top" else H - band_h
    img.paste(band, (0, y0))
    draw = ImageDraw.Draw(img)
    fpx = int(band_h * 0.30)
    font = load_font(fpx)
    lines = wrap(draw, caption, font, W - 2 * PAD_X)
    # shrink to fit at most 2 lines
    while len(lines) > 2 and fpx > 20:
        fpx -= 4
        font = load_font(fpx)
        lines = wrap(draw, caption, font, W - 2 * PAD_X)
    lh = int(fpx * 1.18)
    total = lh * len(lines)
    ty = y0 + (band_h - total) // 2
    for ln in lines:
        tw = draw.textlength(ln, font=font)
        draw.text(((W - tw) / 2, ty), ln, font=font, fill=WHITE)
        ty += lh
    img.save(outp, "PNG")
    print(f"captioned -> {outp}  ({len(lines)} line(s), {fpx}px, band {pos})")

if __name__ == "__main__":
    main()