"""Generate local first-frame PNG previews for the Telegram emoji catalog.""" from __future__ import annotations import argparse import re import sys from concurrent.futures import ThreadPoolExecutor, as_completed from io import BytesIO from pathlib import Path from urllib.parse import quote from urllib.request import Request, urlopen from PIL import Image ROOT = Path(__file__).resolve().parent CATALOG_DIR = ROOT / "js" / "components" OUTPUT_DIR = ROOT / "assets" / "emoji" / "telegram-previews" ASSET_BASE_URL = "https://raw.githubusercontent.com/Tarikul-Islam-Anik/Telegram-Animated-Emojis/main/" FILE_PATTERN = re.compile(r'"file"\s*:\s*"([^"]+\.webp)"', re.IGNORECASE) def catalog_files() -> list[str]: files: set[str] = set() for path in sorted(CATALOG_DIR.glob("telegram-emoji-*.js")): files.update(FILE_PATTERN.findall(path.read_text(encoding="utf-8"))) return sorted(files) def preview_path(source_file: str) -> Path: return OUTPUT_DIR / Path(source_file).with_suffix(".png") def generate_one(source_file: str, force: bool) -> tuple[str, str]: output = preview_path(source_file) if output.exists() and not force: return source_file, "cached" url = ASSET_BASE_URL + quote(source_file, safe="/") request = Request(url, headers={"User-Agent": "SHiNE emoji preview generator"}) with urlopen(request, timeout=30) as response: payload = response.read() with Image.open(BytesIO(payload)) as source: source.seek(0) frame = source.convert("RGBA") frame.thumbnail((96, 96), Image.Resampling.LANCZOS) canvas = Image.new("RGBA", (96, 96), (0, 0, 0, 0)) offset = ((96 - frame.width) // 2, (96 - frame.height) // 2) canvas.alpha_composite(frame, offset) output.parent.mkdir(parents=True, exist_ok=True) canvas.save(output, format="PNG", optimize=True) return source_file, "generated" def main() -> int: parser = argparse.ArgumentParser() parser.add_argument("--force", action="store_true", help="Regenerate existing previews") parser.add_argument("--workers", type=int, default=8) args = parser.parse_args() files = catalog_files() if not files: print("No catalog files found", file=sys.stderr) return 1 print(f"Generating {len(files)} previews into {OUTPUT_DIR}") failures: list[tuple[str, str]] = [] generated = cached = 0 with ThreadPoolExecutor(max_workers=max(1, args.workers)) as executor: futures = {executor.submit(generate_one, source_file, args.force): source_file for source_file in files} for future in as_completed(futures): source_file = futures[future] try: _, status = future.result() except Exception as error: # noqa: BLE001 - report all asset failures together failures.append((source_file, str(error))) continue generated += status == "generated" cached += status == "cached" print(f"Generated: {generated}; cached: {cached}; failed: {len(failures)}") for source_file, error in failures: print(f"FAILED {source_file}: {error}", file=sys.stderr) return 1 if failures else 0 if __name__ == "__main__": raise SystemExit(main())