Files

109 lines
6.4 KiB
Python
Executable File

#!/usr/bin/env python3
"""Lay out App Store artwork around untouched real-app screenshots. Requires Pillow."""
from pathlib import Path
from PIL import Image, ImageDraw, ImageFont, ImageFilter
import json, math, shutil
ROOT = Path(__file__).resolve().parents[1]
FONT = Path('/System/Library/Fonts/Supplemental')
def font(size, bold=False):
return ImageFont.truetype(str(FONT / ('Arial Bold.ttf' if bold else 'Arial.ttf')), round(size))
def backdrop(w, h, accent):
small = Image.new('RGB', (220, max(1, round(h*220/w))))
pix = small.load()
for y in range(small.height):
for x in range(small.width):
glow = math.exp(-(((x/small.width-.85)/.65)**2+((y/small.height-.32)/.35)**2))
pix[x,y] = tuple(round(base+glow*(a-base)*.45) for base,a in zip((5,9,25),accent))
return small.resize((w,h), Image.Resampling.BICUBIC)
def rounded_paste(canvas, screen, box):
x,y,w,h=map(round,box)
radius=round(w*.065)
shadow=Image.new('RGBA',canvas.size)
ImageDraw.Draw(shadow).rounded_rectangle((x-8,y+12,x+w+8,y+h+20),radius,fill=(0,0,0,180))
shadow=shadow.filter(ImageFilter.GaussianBlur(max(12,w*.025)))
canvas=Image.alpha_composite(canvas.convert('RGBA'),shadow)
draw=ImageDraw.Draw(canvas)
draw.rounded_rectangle((x-8,y-8,x+w+8,y+h+8),radius+8,fill=(92,106,149,255))
screen=screen.convert('RGB').resize((w,h),Image.Resampling.LANCZOS)
mask=Image.new('L',(w,h));ImageDraw.Draw(mask).rounded_rectangle((0,0,w-1,h-1),radius,fill=255)
canvas.paste(screen,(x,y),mask)
return canvas.convert('RGB')
SCENES=[
('01-home','Your server.','At a glance.','Live speeds. Clear history. Total control.',(104,78,255)),
('02-library','Every transfer.','Beautifully organised.','Search, filter and manage your whole library.',(0,170,184)),
('03-details','Take control.','Keep the detail.','Pause, resume and inspect every transfer.',(60,113,255)),
('04-files','Choose what','matters.','Set file priorities from wherever you are.',(0,180,170)),
('05-settings','Your server.','Your settings.','Browse and adjust the options your server exposes.',(94,88,245)),
('06-add','Add a link.','Let your server work.','Magnet links and .torrent files. In one place.',(154,74,230)),
]
icon=Image.open(ROOT/'icons/Tarnax-AppStore-1024.png').convert('RGB')
for size in [512,256,180,128,64,32]:
icon.resize((size,size),Image.Resampling.LANCZOS).save(ROOT/f'icons/Tarnax-{size}.png')
manifest=[]
for device in ['iphone-6.9','ipad-13']:
for name,line1,line2,sub,accent in SCENES:
src=ROOT/f'screenshots/raw/{device}/{name}.png'
raw=Image.open(src).convert('RGB')
w,h=raw.size
canvas=backdrop(w,h,accent)
d=ImageDraw.Draw(canvas)
ipad=device=='ipad-13'
pad=round(w*.075)
logo=round(w*.045)
canvas.paste(icon.resize((logo,logo),Image.Resampling.LANCZOS),(pad,round(h*.027)))
d=ImageDraw.Draw(canvas)
d.text((pad+logo+round(w*.015),round(h*.027)), 'Tarnax',font=font(w*.035,True),fill='white')
title_size=w*(.068 if not ipad else .062)
title_font=font(title_size,True)
while max(d.textlength(line1,font=title_font),d.textlength(line2,font=title_font))>w-2*pad:
title_size-=1;title_font=font(title_size,True)
ty=round(h*.067)
d.text((pad,ty),line1,font=title_font,fill='white')
d.text((pad,ty+title_size*1.15),line2,font=title_font,fill=(170,185,255) if accent[2]>200 else (100,230,222))
sub_font=font(w*.025)
while d.textlength(sub,font=sub_font)>w-2*pad:sub_font=font(sub_font.size-1)
d.text((pad,ty+title_size*2.55),sub,font=sub_font,fill=(202,211,233))
sw=round(w*(.74 if ipad else .78));sh=round(sw*raw.height/raw.width)
sy=round(h*(.225 if ipad else .185))
if sy+sh>h*.968:
sh=round(h*.968-sy);sw=round(sh*raw.width/raw.height)
canvas=rounded_paste(canvas,raw,((w-sw)//2,sy,sw,sh))
d=ImageDraw.Draw(canvas)
footer='Requires your own qBittorrent server'
ff=font(w*.018)
d.text(((w-d.textlength(footer,font=ff))/2,h*.982),footer,font=ff,fill=(158,172,203),anchor='mm')
dst=ROOT/f'screenshots/upload/{device}/{name}.png';canvas.save(dst,optimize=True)
manifest.append({'file':str(dst.relative_to(ROOT)),'width':w,'height':h,'scene':name,'appearance':'dark' if name in ['01-home','04-files'] else 'light','source':str(src.relative_to(ROOT))})
# Press/social assets, deliberately separate from store-upload screenshots.
for name,w,h in [('social-landscape',1200,630),('social-square',1080,1080),('press-header',2400,1200)]:
canvas=backdrop(w,h,(110,70,245));d=ImageDraw.Draw(canvas)
pad=round(w*.07);size=round(w*.15)
canvas.paste(icon.resize((size,size),Image.Resampling.LANCZOS),(pad,round(h*.11)))
d=ImageDraw.Draw(canvas)
d.text((pad,round(h*.11)+size+round(h*.055)),'Your server.',font=font(w*.068,True),fill='white')
d.text((pad,round(h*.11)+size+round(h*.055)+w*.078),'Your control.',font=font(w*.068,True),fill=(169,183,255))
d.text((pad,h*.86),'Tarnax · Remote for qBittorrent',font=font(w*.025),fill=(204,215,239))
if w/h>1.6:
screen=Image.open(ROOT/'screenshots/raw/iphone-6.9/01-home.png')
sh=round(h*.85);sw=round(sh*screen.width/screen.height)
canvas=rounded_paste(canvas,screen,(w-sw-pad,h*.075,sw,sh))
canvas.save(ROOT/f'marketing/{name}.png',optimize=True)
# Asset contact sheets for convenient visual QA.
for device in ['iphone-6.9','ipad-13']:
thumbs=[]
for scene,*_ in SCENES:
im=Image.open(ROOT/f'screenshots/upload/{device}/{scene}.png').convert('RGB');im.thumbnail((330,640));thumbs.append(im.copy())
sheet=Image.new('RGB',(3*350,2*680),(15,20,38))
for i,im in enumerate(thumbs):sheet.paste(im,(i%3*350+10,i//3*680+10))
sheet.save(ROOT/f'marketing/contact-sheet-{device}.jpg',quality=92)
(ROOT/'asset-manifest.json').write_text(json.dumps({'screenshots':manifest,'sample_data':True,'production_fixture_included':False},indent=2)+'\n')
assets=ROOT/'website/assets';assets.mkdir(exist_ok=True)
shutil.copy2(ROOT/'icons/Tarnax-180.png',assets/'apple-touch-icon.png')
shutil.copy2(ROOT/'icons/Tarnax-32.png',assets/'favicon.png')
shutil.copy2(ROOT/'icons/Tarnax-AppStore-1024.png',assets/'icon.png')
shutil.copy2(ROOT/'marketing/social-landscape.png',assets/'social.png')
print('Created',len(manifest),'upload screenshots, icon derivatives, marketing images and contact sheets.')