/
vaskaz
/
flexin_flow
Обзор
Документация
Войти
/
vaskaz
/
flexin_flow
Код
Запросы
1
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
dev
metadata.py
287 строк
8 KB
vaskaz
newborn
10 июн 2026, 00:16
10 июн 2026, 00:16
dc9da65
Код
Авторство
О чём код?
"""Platform definitions, extension mapping, and ROM title heuristics.""" import re from pathlib import Path # ── Platform definitions ────────────────────────────────────────────── PlatformDef = dict[str, str | list[str]] # name, emulator, short, extensions PLATFORMS: dict[str, PlatformDef] = { "nes": { "name": "Nintendo Entertainment System", "short": "NES", "emulator": "Mesen / FCEUX / Nestopia", "extensions": [".nes", ".unif", ".unf"], }, "snes": { "name": "Super Nintendo Entertainment System", "short": "SNES", "emulator": "Mesen2 / Snes9x / bsnes", "extensions": [".smc", ".sfc", ".swc", ".fig"], }, "n64": { "name": "Nintendo 64", "short": "N64", "emulator": "Project64 / Mupen64Plus / Ares", "extensions": [".n64", ".z64", ".v64", ".rom"], }, "gb": { "name": "Game Boy", "short": "GB", "emulator": "Gambatte / SameBoy / BGB", "extensions": [".gb", ".dmg"], }, "gbc": { "name": "Game Boy Color", "short": "GBC", "emulator": "Gambatte / SameBoy / BGB", "extensions": [".gbc", ".cgb"], }, "gba": { "name": "Game Boy Advance", "short": "GBA", "emulator": "mGBA / Visual Boy Advance / NanoBoyAdvance", "extensions": [".gba", ".agb"], }, "nds": { "name": "Nintendo DS", "short": "NDS", "emulator": "melonDS / DeSmuME / RetroArch", "extensions": [".nds", ".dsi"], }, "3ds": { "name": "Nintendo 3DS", "short": "3DS", "emulator": "Citra / Lime3DS", "extensions": [".3ds", ".cci", ".cxi", ".app"], }, "psx": { "name": "PlayStation", "short": "PS1", "emulator": "DuckStation / ePSXe / Mednafen", "extensions": [".iso", ".bin", ".cue", ".img", ".mdf", ".pbp", ".psexe"], }, "ps2": { "name": "PlayStation 2", "short": "PS2", "emulator": "PCSX2", "extensions": [".iso", ".bin", ".cue", ".mdf", ".nrg", ".gz"], }, "psp": { "name": "PlayStation Portable", "short": "PSP", "emulator": "PPSSPP", "extensions": [".iso", ".cso"], }, "genesis": { "name": "Sega Genesis / Mega Drive", "short": "Genesis", "emulator": "BlastEm / Gens / Kega Fusion", "extensions": [".md", ".gen", ".smd", ".bin"], }, "saturn": { "name": "Sega Saturn", "short": "Saturn", "emulator": "Mednafen / Yaba Sanshiro / Kronos", "extensions": [".iso", ".bin", ".cue", ".mdf"], }, "dreamcast": { "name": "Sega Dreamcast", "short": "Dreamcast", "emulator": "Flycast / Redream / Demul", "extensions": [".gdi", ".cdi", ".chd", ".iso"], }, "sms": { "name": "Sega Master System", "short": "SMS", "emulator": "Mesen2 / Kega Fusion / RetroArch", "extensions": [".sms", ".sg"], }, "gg": { "name": "Game Gear", "short": "Game Gear", "emulator": "Kega Fusion / RetroArch", "extensions": [".gg"], }, "pce": { "name": "PC Engine / TurboGrafx-16", "short": "PCE", "emulator": "Mednafen / RetroArch / Ootake", "extensions": [".pce", ".sgx", ".bin", ".iso", ".cue"], }, "neogeo": { "name": "SNK Neo Geo AES / MVS", "short": "Neo Geo", "emulator": "FinalBurn Neo / MAME / NeoRaine", "extensions": [".neo", ".zip"], }, "ngp": { "name": "Neo Geo Pocket", "short": "NGP", "emulator": "Mednafen / RetroArch", "extensions": [".ngp", ".ngc"], }, "atari2600": { "name": "Atari 2600", "short": "Atari 2600", "emulator": "Stella", "extensions": [".a26", ".bin", ".rom"], }, "atari5200": { "name": "Atari 5200", "short": "Atari 5200", "emulator": "Atari800 / Altirra / RetroArch", "extensions": [".a52", ".bin", ".rom"], }, "atari7800": { "name": "Atari 7800", "short": "Atari 7800", "emulator": "ProSystem / A7800 / RetroArch", "extensions": [".a78", ".bin"], }, "lynx": { "name": "Atari Lynx", "short": "Lynx", "emulator": "Mednafen / Handy / RetroArch", "extensions": [".lnx", ".lyx"], }, "jaguar": { "name": "Atari Jaguar", "short": "Jaguar", "emulator": "BigPEmu / Virtual Jaguar", "extensions": [".jag", ".j64", ".rom"], }, "coleco": { "name": "ColecoVision", "short": "Coleco", "emulator": "MAME / BlueMSX / RetroArch", "extensions": [".col", ".rom"], }, "intellivision": { "name": "Intellivision", "short": "Intellivision", "emulator": "Nostalgia / MAME / RetroArch", "extensions": [".int", ".bin"], }, "wonderswan": { "name": "WonderSwan", "short": "WonderSwan", "emulator": "Mednafen / RetroArch / Oswan", "extensions": [".ws", ".wsc"], }, "vb": { "name": "Virtual Boy", "short": "Virtual Boy", "emulator": "Mednafen / RetroArch / Redri", "extensions": [".vb", ".vboy"], }, "mame": { "name": "MAME / Arcade", "short": "Arcade", "emulator": "MAME / FinalBurn Neo", "extensions": [".zip"], }, "pcenginecd": { "name": "PC Engine CD / TurboGrafx-CD", "short": "PCE CD", "emulator": "Mednafen / RetroArch", "extensions": [".cue", ".iso"], }, } # Build reverse extension → platform lookup EXT_TO_PLATFORM: dict[str, str] = {} for plat_id, pdef in PLATFORMS.items(): for ext in pdef["extensions"]: EXT_TO_PLATFORM[ext.lower()] = plat_id # Extension cleanup patterns CLEANUP_PATTERNS = [ # Region tags (r"\([^)]*\)", ""), # (USA), (Europe), (Japan), (En,Fr) (r"\[[^\]]*\]", ""), # [!], [b1], [h1C], [T+Eng] # File extension (r"\.\w+$", ""), # Disc number (r" \(Disc \d+\)", ""), (r" - Disc \d+", ""), # Extra whitespace (r"\s+", " "), (r"^[\s.-]+|[\s.-]+$", ""), # Rev/Proto (r" Rev \d+", ""), (r" Proto", ""), (r" Beta", ""), (r" Sample", ""), # (v1.0) style (r" v\d+\.\d+", ""), ] # Known "bad" ROM name prefixes/suffixes to strip STRIP_PREFIXES = [ "The ", "the ", "A ", "a ", "An ", "an ", ] STRIP_SUFFIXES = [ " (U)", " (J)", " (E)", " (A)", " (G)", " (Fr)", " (De)", " (Es)", " (It)", " (Nl)", " (Sv)", " (No)", " (Da)", " (Fi)", " (Pt)", " (Unl)", " (PD)", " (Hack)", " (Demo)", ] def detect_platform(filename: str) -> tuple[str, str, str]: """Detect platform from file extension. Returns (platform_id, platform_name, recommended_emulator). """ ext = Path(filename).suffix.lower() plat_id = EXT_TO_PLATFORM.get(ext) if plat_id: pdef = PLATFORMS[plat_id] return plat_id, pdef["name"], pdef["emulator"] return "unknown", "Unknown", "" def clean_title(filename: str) -> str: """Extract a clean game title from a ROM filename.""" name = Path(filename).stem # Remove extension # Apply cleanup patterns for pattern, replacement in CLEANUP_PATTERNS: name = re.sub(pattern, replacement, name) # Replace separators with spaces name = re.sub(r"[_.,]", " ", name) name = re.sub(r"\s+", " ", name).strip() # Capitalize properly name = name.title() # Fix common articles for prefix in STRIP_PREFIXES: if name.startswith(prefix): name = name[len(prefix):] + ", " + prefix.strip() break if not name: name = Path(filename).stem return name def format_size(size_bytes: int) -> str: """Format bytes to human-readable size.""" for unit in ["B", "KB", "MB", "GB"]: if size_bytes < 1024: return f"{size_bytes:.1f} {unit}" size_bytes /= 1024 return f"{size_bytes:.1f} TB" def is_rom_file(filename: str) -> bool: """Check if a file has a known ROM extension.""" ext = Path(filename).suffix.lower() return ext in EXT_TO_PLATFORM def get_all_extensions() -> list[str]: """Return all known ROM extensions for scanning.""" return sorted(set(EXT_TO_PLATFORM.keys()))