← 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()