#!/usr/bin/env python3 """Downloads club crests into the APK's assets at build time. The badges are trademarks: they are fetched into a generated, git-ignored folder so the repository stays free of them while the app ships with them and needs no network at runtime. """ import json import os import re import sys import time import urllib.parse import urllib.request OUT = sys.argv[1] if len(sys.argv) > 1 else "app/src/main/assets/crests" URLS = "tools/crest-sources.json" SRC = "app/src/main/java/dev/castarr/tv/data/TeamFilters.kt" SUMMARY = "https://de.wikipedia.org/api/rest_v1/page/summary/" UA = {"User-Agent": "Castarr build script (private use)"} os.makedirs(OUT, exist_ok=True) # Image URLs are plain text and safe to commit; the artwork itself is not. sources = {} if os.path.exists(URLS): sources = json.load(open(URLS, encoding="utf-8")) kotlin = open(SRC, encoding="utf-8").read() entries = re.findall(r'club\((.*?)\)\s*,\s*(?://.*)?$', kotlin, re.M | re.S) clubs = [] # Wikipedia serves ~330px thumbnails. The rail draws them at 20dp, which on a # 1080p TV is 40px -- an 8x downscale that Android does with a cheap filter, # and the edges come out ragged. Resampling once here with a proper filter # fixes that and shrinks the APK. CREST_PX = 128 def downscale(data: bytes) -> bytes: try: from PIL import Image except ImportError: return data import io image = Image.open(io.BytesIO(data)).convert("RGBA") if max(image.size) <= CREST_PX: return data scale = CREST_PX / max(image.size) size = (max(1, round(image.width * scale)), max(1, round(image.height * scale))) out = io.BytesIO() image.resize(size, Image.LANCZOS).save(out, format="PNG", optimize=True) return out.getvalue() for raw in re.findall(r'club\(\s*"([^"]+)",\s*"[^"]*",\s*"([^"]+)"[^\n]*', kotlin): clubs.append(raw) # an explicit article = "..." wins over the club name overrides = dict(re.findall(r'club\(\s*"([^"]+)"[^\n]*article = "([^"]+)"', kotlin)) clubs = [(k, overrides.get(k, n)) for k, n in clubs] fetched = skipped = failed = 0 for key, full_name in clubs: target = os.path.join(OUT, f"{key}.png") if os.path.exists(target) and os.path.getsize(target) > 0: skipped += 1 continue try: thumb = sources.get(key) if not thumb: url = SUMMARY + urllib.parse.quote(full_name) with urllib.request.urlopen(urllib.request.Request(url, headers=UA), timeout=20) as r: thumb = json.load(r).get("thumbnail", {}).get("source") if not thumb: raise ValueError("no thumbnail") sources[key] = thumb.split("?")[0] thumb = sources[key] with urllib.request.urlopen(urllib.request.Request(thumb, headers=UA), timeout=20) as r: data = r.read() with open(target, "wb") as f: f.write(downscale(data)) fetched += 1 except Exception as exc: # noqa: BLE001 - best effort, shield is the fallback print(f" {key}: {exc}", file=sys.stderr) failed += 1 # Wikipedia rate-limits bursts; this runs rarely and caches. time.sleep(1.2) with open(URLS, "w", encoding="utf-8") as f: json.dump(dict(sorted(sources.items())), f, indent=2, ensure_ascii=False) f.write("\n") print(f"crests: {fetched} geladen, {skipped} vorhanden, {failed} fehlgeschlagen")