2026-02-11 10:24:43 +01:00
|
|
|
"""Download and manage PokeDB pokemon sprites."""
|
|
|
|
|
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
import sys
|
|
|
|
|
import urllib.request
|
|
|
|
|
from pathlib import Path
|
|
|
|
|
from typing import Any
|
|
|
|
|
|
|
|
|
|
from .mappings import PokemonMapper
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def download_sprites(
|
|
|
|
|
pokemon_mapper: PokemonMapper,
|
|
|
|
|
encountered_form_ids: set[str],
|
|
|
|
|
sprites_dir: Path,
|
|
|
|
|
) -> dict[str, str]:
|
|
|
|
|
"""Download sprites for all encountered pokemon forms.
|
|
|
|
|
|
|
|
|
|
Returns a mapping of pokemon_form_identifier → local sprite filename.
|
|
|
|
|
Skips already-downloaded sprites.
|
|
|
|
|
"""
|
|
|
|
|
sprites_dir.mkdir(parents=True, exist_ok=True)
|
|
|
|
|
|
|
|
|
|
to_download: list[tuple[str, str, Path]] = [] # (form_id, url, dest)
|
|
|
|
|
result: dict[str, str] = {}
|
|
|
|
|
|
|
|
|
|
for form_id in sorted(encountered_form_ids):
|
|
|
|
|
info = pokemon_mapper.lookup(form_id)
|
|
|
|
|
if info is None:
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
pokeapi_id, _ = info
|
|
|
|
|
sprite_url = pokemon_mapper.get_sprite_url(form_id)
|
|
|
|
|
if not sprite_url:
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
filename = f"{pokeapi_id}.webp"
|
|
|
|
|
dest = sprites_dir / filename
|
|
|
|
|
result[form_id] = filename
|
|
|
|
|
|
|
|
|
|
if not dest.exists():
|
|
|
|
|
to_download.append((form_id, sprite_url, dest))
|
|
|
|
|
|
|
|
|
|
if not to_download:
|
|
|
|
|
print(f" Sprites: {len(result)} already cached")
|
|
|
|
|
return result
|
|
|
|
|
|
|
|
|
|
print(f" Downloading {len(to_download)} sprites ({len(result) - len(to_download)} cached)...")
|
|
|
|
|
|
|
|
|
|
failed = 0
|
|
|
|
|
for i, (form_id, url, dest) in enumerate(to_download, 1):
|
|
|
|
|
try:
|
|
|
|
|
urllib.request.urlretrieve(url, dest)
|
|
|
|
|
except Exception as e:
|
|
|
|
|
print(f" Warning: Failed to download sprite for {form_id}: {e}", file=sys.stderr)
|
|
|
|
|
failed += 1
|
|
|
|
|
# Remove the failed entry from results
|
|
|
|
|
result.pop(form_id, None)
|
|
|
|
|
|
|
|
|
|
# Progress every 100
|
|
|
|
|
if i % 100 == 0:
|
|
|
|
|
print(f" {i}/{len(to_download)}...")
|
|
|
|
|
|
|
|
|
|
if failed:
|
|
|
|
|
print(f" Sprites: {len(result)} downloaded, {failed} failed")
|
|
|
|
|
else:
|
|
|
|
|
print(f" Sprites: {len(result)} total ({len(to_download)} new)")
|
|
|
|
|
|
|
|
|
|
return result
|
|
|
|
|
|
|
|
|
|
|
2026-02-11 11:52:51 +01:00
|
|
|
def download_all_sprites(
|
|
|
|
|
pokemon_mapper: PokemonMapper,
|
|
|
|
|
sprites_dir: Path,
|
|
|
|
|
) -> set[int]:
|
|
|
|
|
"""Download sprites for all known pokemon (not just encountered ones).
|
|
|
|
|
|
|
|
|
|
Returns a set of pokeapi_ids that have sprites downloaded.
|
|
|
|
|
"""
|
|
|
|
|
sprites_dir.mkdir(parents=True, exist_ok=True)
|
|
|
|
|
|
|
|
|
|
to_download: list[tuple[int, str, Path]] = []
|
|
|
|
|
have_sprites: set[int] = set()
|
|
|
|
|
|
|
|
|
|
for pokeapi_id, (_ndex, _name) in pokemon_mapper.all_pokemon():
|
|
|
|
|
if pokemon_mapper.has_sprite_for_id(pokeapi_id):
|
|
|
|
|
filename = f"{pokeapi_id}.webp"
|
|
|
|
|
dest = sprites_dir / filename
|
|
|
|
|
have_sprites.add(pokeapi_id)
|
|
|
|
|
|
|
|
|
|
if not dest.exists():
|
|
|
|
|
# Get the sprite URL via the mapper
|
|
|
|
|
url = pokemon_mapper.get_sprite_url_for_id(pokeapi_id)
|
|
|
|
|
if url:
|
|
|
|
|
to_download.append((pokeapi_id, url, dest))
|
|
|
|
|
|
|
|
|
|
if not to_download:
|
|
|
|
|
print(f" All sprites: {len(have_sprites)} already cached")
|
|
|
|
|
return have_sprites
|
|
|
|
|
|
|
|
|
|
print(f" Downloading {len(to_download)} additional sprites ({len(have_sprites) - len(to_download)} cached)...")
|
|
|
|
|
|
|
|
|
|
failed = 0
|
|
|
|
|
for i, (pid, url, dest) in enumerate(to_download, 1):
|
|
|
|
|
try:
|
|
|
|
|
urllib.request.urlretrieve(url, dest)
|
|
|
|
|
except Exception as e:
|
|
|
|
|
print(f" Warning: Failed to download sprite for pokemon {pid}: {e}", file=sys.stderr)
|
|
|
|
|
failed += 1
|
|
|
|
|
have_sprites.discard(pid)
|
|
|
|
|
|
|
|
|
|
if i % 100 == 0:
|
|
|
|
|
print(f" {i}/{len(to_download)}...")
|
|
|
|
|
|
|
|
|
|
if failed:
|
|
|
|
|
print(f" All sprites: {len(have_sprites)} downloaded, {failed} failed")
|
|
|
|
|
else:
|
|
|
|
|
print(f" All sprites: {len(have_sprites)} total ({len(to_download)} new)")
|
|
|
|
|
|
|
|
|
|
return have_sprites
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def sprite_path_for_pokemon(pokeapi_id: int) -> str:
|
|
|
|
|
"""Generate the sprite URL path for use in pokemon.json.
|
2026-02-11 10:24:43 +01:00
|
|
|
|
2026-02-11 11:52:51 +01:00
|
|
|
Returns an absolute path like "/sprites/25.webp" for the frontend
|
|
|
|
|
(files in frontend/public/ are served at the root).
|
2026-02-11 10:24:43 +01:00
|
|
|
"""
|
2026-02-11 11:52:51 +01:00
|
|
|
return f"/sprites/{pokeapi_id}.webp"
|