# rename_images_to_slug.py # # Renames character images inside a selected pack's /characters folder # to their manifest slug. # # Example: # "Ryu Render.png" -> "ryu.png" # # Matching rules: # - compares normalized filenames against character names # - ignores spaces, punctuation, apostrophes and case # - preserves original extension # # Safe behavior: # - skips already-correct files # - warns about unmatched files # - warns about duplicate matches # - never overwrites existing files from __future__ import annotations import json import re from pathlib import Path from typing import Optional REGISTRY_FILENAME = "registry.json" IMAGE_EXTENSIONS = ( ".png", ".jpg", ".jpeg", ".webp", ".avif", ) def load_json(path: Path) -> dict: return json.loads(path.read_text(encoding="utf-8")) def normalize(text: str) -> str: """ Normalize text for fuzzy filename matching. Example: "Chun-Li" -> "chunli" "Ryu Render" -> "ryurender" """ text = text.lower() # Remove extension if present text = Path(text).stem # Remove non-alphanumeric chars text = re.sub(r"[^a-z0-9]", "", text) return text def select_pack(packs: list[dict]) -> Optional[dict]: print("\nAvailable packs:\n") for index, pack in enumerate(packs, start=1): print(f"{index}. {pack['name']} ({pack['id']})") raw = input("\nSelect pack number: ").strip() try: selected_index = int(raw) - 1 except ValueError: print("\n❌ Invalid number") return None if selected_index < 0 or selected_index >= len(packs): print("\n❌ Invalid selection") return None return packs[selected_index] def build_character_lookup(characters: list[dict]) -> dict[str, str]: """ Build normalized name -> slug mapping. Example: "chunli" -> "chun-li" """ lookup = {} for character in characters: name = character["name"] slug = character["slug"] lookup[normalize(name)] = slug return lookup def rename_images(pack_dir: Path) -> None: manifest_path = pack_dir / "manifest.json" characters_dir = pack_dir / "characters" if not manifest_path.exists(): print("\n❌ manifest.json not found") return if not characters_dir.exists(): print("\n❌ characters folder not found") return manifest = load_json(manifest_path) lookup = build_character_lookup( manifest.get("characters", []) ) image_files = [ file for file in characters_dir.iterdir() if file.is_file() and file.suffix.lower() in IMAGE_EXTENSIONS ] if not image_files: print("\n❌ No images found") return print(f'\nScanning "{manifest["name"]}"...\n') renamed_count = 0 for image_file in image_files: normalized_filename = normalize( image_file.name ) matched_slug = None # Exact normalized match if normalized_filename in lookup: matched_slug = lookup[ normalized_filename ] else: # Partial fuzzy fallback matches = [ slug for normalized_name, slug in lookup.items() if normalized_name in normalized_filename or normalized_filename in normalized_name ] if len(matches) == 1: matched_slug = matches[0] elif len(matches) > 1: print( f"⚠ Multiple matches for " f"{image_file.name}" ) continue if matched_slug is None: print( f"⚠ No character match for " f"{image_file.name}" ) continue new_filename = ( matched_slug + image_file.suffix.lower() ) new_path = ( characters_dir / new_filename ) # Already correct if image_file.name.lower() == new_filename.lower(): if image_file.name != new_filename: image_file.rename(new_path) print( f"✓ {image_file.name} " f"-> {new_filename}" ) renamed_count += 1 else: print(f"✓ {image_file.name}") continue # Prevent overwrite if new_path.exists(): print( f"⚠ Target already exists: " f"{new_filename}" ) continue image_file.rename(new_path) renamed_count += 1 print( f"✓ {image_file.name} " f"-> {new_filename}" ) print("\n✅ Done!\n") print(f"Renamed: {renamed_count}") def main() -> None: root = Path(__file__).parent.resolve() registry_path = ( root / REGISTRY_FILENAME ) if not registry_path.exists(): print( "❌ registry.json not found" ) return registry = load_json( registry_path ) packs = registry.get( "packs", [], ) if not packs: print( "❌ No packs found" ) return selected_pack = select_pack( packs ) if selected_pack is None: return pack_dir = ( root / selected_pack["id"] ) rename_images(pack_dir) if __name__ == "__main__": main()