Add PokeDB sprite downloading (100x100 WebP)
Download pokemon sprites from PokeDB CDN during import, cached locally
as {pokeapi_id}.webp. Replaces PokeAPI GitHub sprite URLs. ~4.6MB for
all 1119 unique sprites.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,11 +1,11 @@
|
|||||||
---
|
---
|
||||||
# nuzlocke-tracker-rfg0
|
# nuzlocke-tracker-rfg0
|
||||||
title: Core encounter processing
|
title: Core encounter processing
|
||||||
status: in-progress
|
status: completed
|
||||||
type: task
|
type: task
|
||||||
priority: normal
|
priority: normal
|
||||||
created_at: 2026-02-11T08:43:12Z
|
created_at: 2026-02-11T08:43:12Z
|
||||||
updated_at: 2026-02-11T09:03:52Z
|
updated_at: 2026-02-11T09:12:59Z
|
||||||
parent: nuzlocke-tracker-bs05
|
parent: nuzlocke-tracker-bs05
|
||||||
blocking:
|
blocking:
|
||||||
- nuzlocke-tracker-gkcy
|
- nuzlocke-tracker-gkcy
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ from pathlib import Path
|
|||||||
from .loader import load_pokedb_data, load_seed_config
|
from .loader import load_pokedb_data, load_seed_config
|
||||||
from .mappings import PokemonMapper, LocationMapper, build_version_map, map_encounter_method
|
from .mappings import PokemonMapper, LocationMapper, build_version_map, map_encounter_method
|
||||||
from .processing import filter_encounters_for_game, process_encounters, build_routes
|
from .processing import filter_encounters_for_game, process_encounters, build_routes
|
||||||
|
from .sprites import download_sprites
|
||||||
|
|
||||||
SEEDS_DIR_CANDIDATES = [
|
SEEDS_DIR_CANDIDATES = [
|
||||||
Path("backend/src/app/seeds"), # from repo root
|
Path("backend/src/app/seeds"), # from repo root
|
||||||
@@ -131,8 +132,11 @@ def main(argv: list[str] | None = None) -> None:
|
|||||||
print(f" - {m}", file=sys.stderr)
|
print(f" - {m}", file=sys.stderr)
|
||||||
|
|
||||||
# Spot-check pokemon mapping on actual encounter data
|
# Spot-check pokemon mapping on actual encounter data
|
||||||
form_ids_in_encounters = {e.get("pokemon_form_identifier", "") for e in pokedb.encounters}
|
form_ids_in_encounters: set[str] = set()
|
||||||
form_ids_in_encounters.discard("")
|
for e in pokedb.encounters:
|
||||||
|
fid = e.get("pokemon_form_identifier")
|
||||||
|
if fid:
|
||||||
|
form_ids_in_encounters.add(fid)
|
||||||
mapped_forms = 0
|
mapped_forms = 0
|
||||||
for fid in form_ids_in_encounters:
|
for fid in form_ids_in_encounters:
|
||||||
if pokemon_mapper.lookup(fid) is not None:
|
if pokemon_mapper.lookup(fid) is not None:
|
||||||
@@ -182,6 +186,12 @@ def main(argv: list[str] | None = None) -> None:
|
|||||||
print(f" Routes: {total_routes}")
|
print(f" Routes: {total_routes}")
|
||||||
print(f" Encounter entries: {total_enc}")
|
print(f" Encounter entries: {total_enc}")
|
||||||
|
|
||||||
|
# Download sprites for all encountered pokemon
|
||||||
|
print("\nDownloading sprites...")
|
||||||
|
sprites_dir = output_dir / "sprites"
|
||||||
|
sprite_map = download_sprites(pokemon_mapper, form_ids_in_encounters, sprites_dir)
|
||||||
|
print(f" Sprite map covers {len(sprite_map)} forms")
|
||||||
|
|
||||||
print("\nProcessing complete. Output not yet written (subtask gkcy).")
|
print("\nProcessing complete. Output not yet written (subtask gkcy).")
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -464,6 +464,19 @@ class PokemonMapper:
|
|||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def get_sprite_url(self, pokemon_form_identifier: str | None) -> str | None:
|
||||||
|
"""Get the PokeDB CDN sprite URL (100x100 medium) for a form identifier."""
|
||||||
|
if not pokemon_form_identifier:
|
||||||
|
return None
|
||||||
|
form_record = self._pokedb_form_index.get(pokemon_form_identifier, {})
|
||||||
|
return form_record.get("main_image_normal_path_medium")
|
||||||
|
|
||||||
|
def get_form_data(self, pokemon_form_identifier: str | None) -> dict | None:
|
||||||
|
"""Get the full PokeDB form record for a form identifier."""
|
||||||
|
if not pokemon_form_identifier:
|
||||||
|
return None
|
||||||
|
return self._pokedb_form_index.get(pokemon_form_identifier)
|
||||||
|
|
||||||
def report_unmapped(self) -> None:
|
def report_unmapped(self) -> None:
|
||||||
"""Print warnings for any unmapped identifiers."""
|
"""Print warnings for any unmapped identifiers."""
|
||||||
if self._unmapped:
|
if self._unmapped:
|
||||||
|
|||||||
78
tools/import-pokedb/import_pokedb/sprites.py
Normal file
78
tools/import-pokedb/import_pokedb/sprites.py
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
"""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
|
||||||
|
|
||||||
|
|
||||||
|
def sprite_path_for_pokemon(pokeapi_id: int, sprites_dir_name: str = "sprites") -> str:
|
||||||
|
"""Generate the relative sprite path for use in pokemon.json.
|
||||||
|
|
||||||
|
Returns a path like "sprites/25.webp" suitable for the sprite_url field.
|
||||||
|
"""
|
||||||
|
return f"{sprites_dir_name}/{pokeapi_id}.webp"
|
||||||
Reference in New Issue
Block a user