#!/usr/bin/env python3 """Install chosen card art onto monsterrobot.games. Usage: install_picks.py "cratewars=vector stylus9=neon ..." [--dry] For each pick it: 1. trims the paper border z-image sometimes paints around 'paint' variants, 2. writes covers/.jpg at the tile's native 896x1152, 3. queues a matching risograph _riso.jpg for the page's hover flip, 4. rsyncs both to the lander (host path — the nginx mount is read-only). """ import json, os, subprocess, sys, tempfile S = os.path.dirname(os.path.abspath(__file__)) CARDS = f'{S}/cards' RISO = f'{S}/riso' STAGE = f'{S}/install' VPS = 'humanjing@100.71.119.27' LANDER = '/home/humanjing/monsterrobot.games/covers' PROFILES = {p['id']: p for p in json.load(open(f'{S}/profiles.json'))} RISO_STYLE = ("risograph screenprint poster, three flat spot colours only, visible halftone " "dot texture, slight misregistration, bold graphic shapes, flat matte inks") READS = ("single bold hero subject filling the frame, strong clean silhouette, high contrast, " "poster composition, reads clearly at thumbnail size") NOTEXT = ("completely wordless, no text, no letters, no words, no logo, no watermark, " "no signature, blank unlabelled sleeves") def sh(*a): r = subprocess.run(a, capture_output=True) if r.returncode: sys.exit(f'FAILED: {" ".join(a)}\n{r.stderr.decode()[:400]}') def to_cover(src, dst, trim): """896x1152 jpeg, optionally trimming a painted paper border first.""" with tempfile.TemporaryDirectory() as td: cur = src if trim: w = int(subprocess.run(['sips', '-g', 'pixelWidth', src], capture_output=True) .stdout.decode().split(':')[-1]) h = int(subprocess.run(['sips', '-g', 'pixelHeight', src], capture_output=True) .stdout.decode().split(':')[-1]) cur = os.path.join(td, 'c.png') sh('sips', '-c', str(int(h * 0.955)), str(int(w * 0.955)), src, '--out', cur) sh('sips', '-s', 'format', 'jpeg', '-s', 'formatOptions', '82', '-z', '1152', '896', cur, '--out', dst) def main(): picks = dict(p.split('=') for p in sys.argv[1].split() if '=' in p) dry = '--dry' in sys.argv os.makedirs(STAGE, exist_ok=True) os.makedirs(RISO, exist_ok=True) # 1. main covers from the picked variants riso_spec = [] for gid, variant in picks.items(): src = f'{CARDS}/{gid}__{variant}.png' if not os.path.exists(src): print(f' ! missing {src}'); continue name = PROFILES[gid].get('current_cover', gid) if name == '__none__': name = gid to_cover(src, f'{STAGE}/{name}.jpg', trim=(variant == 'paint')) print(f' āœ“ {name}.jpg ({variant})') p = PROFILES[gid] riso_spec.append({ 'out': f'{RISO}/{name}_riso.png', 'operator': 'flux_local', 'params': {'prompt': f"{RISO_STYLE}. {p['hero_subject']}. {READS}. {NOTEXT}", 'model': 'flux2-klein-4b', 'steps': 4, 'width': 896, 'height': 1152, 'seed': 7}}) # 2. the hover b-sides json.dump(riso_spec, open(f'{RISO}/spec.json', 'w'), indent=1) if not dry: sh(sys.executable, f'{os.path.expanduser("~")}/Documents/paradramorama/tools/mbgen.py', f'{RISO}/spec.json') for item in riso_spec: base = os.path.basename(item['out'])[:-4] if os.path.exists(item['out']): to_cover(item['out'], f'{STAGE}/{base}.jpg', trim=False) print(f' āœ“ {base}.jpg') # 3. ship if dry: print('\n[dry] staged only, nothing uploaded') return sh('rsync', '-az', f'{STAGE}/', f'{VPS}:{LANDER}/') print(f'\nāœ“ uploaded {len(os.listdir(STAGE))} files to {LANDER}') if __name__ == '__main__': main()