mirror of
https://github.com/rommapp/romm.git
synced 2026-06-27 22:35:57 +00:00
prefer rom's own region tag for ScreenScraper and IGDB artwork
When a ROM filename carries a region tag (e.g. (Europe)), use that region first when picking artwork and localized titles, falling back to the configured scan.priority.region. Previously the configured priority was the only signal, so a US-first config would force US covers onto European ROMs even when an EU asset was available. Adds a shared name->provider-shortcode map and threads the rom through the IGDB and SS lookup APIs so the rom-aware locale/region selection can run for both providers. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1256,7 +1256,7 @@ async def update_rom(
|
||||
cleaned_data.update({"ss_id": None, "ss_metadata": {}})
|
||||
|
||||
if cleaned_data["igdb_id"] and int(cleaned_data["igdb_id"]) != rom.igdb_id:
|
||||
igdb_rom = await meta_igdb_handler.get_rom_by_id(cleaned_data["igdb_id"])
|
||||
igdb_rom = await meta_igdb_handler.get_rom_by_id(rom, cleaned_data["igdb_id"])
|
||||
if igdb_rom.get("igdb_id"):
|
||||
cleaned_data.update(igdb_rom)
|
||||
elif rom.igdb_id and not cleaned_data["igdb_id"]:
|
||||
|
||||
@@ -100,7 +100,7 @@ async def search_rom(
|
||||
if search_by.lower() == "id":
|
||||
try:
|
||||
igdb_rom, moby_rom, ss_rom, lb_rom = await asyncio.gather(
|
||||
meta_igdb_handler.get_matched_rom_by_id(int(search_term)),
|
||||
meta_igdb_handler.get_matched_rom_by_id(rom, int(search_term)),
|
||||
meta_moby_handler.get_matched_rom_by_id(int(search_term)),
|
||||
meta_ss_handler.get_matched_rom_by_id(rom, int(search_term)),
|
||||
meta_launchbox_handler.get_matched_rom_by_id(int(search_term)),
|
||||
@@ -125,7 +125,7 @@ async def search_rom(
|
||||
launchbox_matched_roms,
|
||||
) = await asyncio.gather(
|
||||
meta_igdb_handler.get_matched_roms_by_name(
|
||||
search_term, get_main_platform_igdb_id(rom.platform)
|
||||
rom, search_term, get_main_platform_igdb_id(rom.platform)
|
||||
),
|
||||
meta_moby_handler.get_matched_roms_by_name(
|
||||
search_term, rom.platform.moby_id
|
||||
|
||||
@@ -88,6 +88,34 @@ REGIONS = (
|
||||
REGIONS_BY_SHORTCODE = {region[0]: region[1] for region in REGIONS}
|
||||
REGIONS_NAME_KEYS = frozenset(region[1].lower() for region in REGIONS)
|
||||
|
||||
# Maps full REGIONS names to lowercase shortcodes used by metadata providers
|
||||
REGION_NAME_TO_PROVIDER_SHORTCODE: dict[str, str] = {
|
||||
"Australia": "au",
|
||||
"Asia": "asi",
|
||||
"Brazil": "br",
|
||||
"Canada": "ca",
|
||||
"China": "cn",
|
||||
"England": "uk",
|
||||
"Europe": "eu",
|
||||
"Finland": "fi",
|
||||
"France": "fr",
|
||||
"Germany": "de",
|
||||
"Greece": "gr",
|
||||
"Holland": "nl",
|
||||
"Hong Kong": "hk",
|
||||
"Italy": "it",
|
||||
"Japan": "jp",
|
||||
"Korea": "kr",
|
||||
"Netherlands": "nl",
|
||||
"Norway": "no",
|
||||
"Russia": "ru",
|
||||
"Spain": "sp",
|
||||
"Sweden": "se",
|
||||
"Taiwan": "tw",
|
||||
"USA": "us",
|
||||
"World": "wor",
|
||||
}
|
||||
|
||||
LANGUAGES_BY_SHORTCODE = {lang[0]: lang[1] for lang in LANGUAGES}
|
||||
LANGUAGES_NAME_KEYS = frozenset(lang[1].lower() for lang in LANGUAGES)
|
||||
|
||||
|
||||
@@ -14,8 +14,10 @@ from adapters.services.igdb_types import (
|
||||
)
|
||||
from config import IGDB_CLIENT_ID, IGDB_CLIENT_SECRET, IS_PYTEST_RUN
|
||||
from config.config_manager import config_manager as cm
|
||||
from handler.filesystem.base_handler import REGION_NAME_TO_PROVIDER_SHORTCODE
|
||||
from handler.redis_handler import async_cache
|
||||
from logger.logger import log
|
||||
from models.rom import Rom
|
||||
from utils.context import ctx_httpx_client
|
||||
|
||||
from .base_handler import (
|
||||
@@ -325,18 +327,22 @@ REGION_TO_IGDB_LOCALE: dict[str, str | None] = {
|
||||
}
|
||||
|
||||
|
||||
def get_igdb_preferred_locale() -> str | None:
|
||||
"""Get IGDB locale from scan.priority.region configuration.
|
||||
def get_igdb_preferred_locale(rom: Rom | None = None) -> str | None:
|
||||
"""Get IGDB locale, preferring the rom's own region tag when available.
|
||||
|
||||
Maps region priority codes to IGDB's game_localizations region identifiers.
|
||||
Returns the first matching region from the priority list, or None for default.
|
||||
Checks the rom's tagged regions first, then falls back to scan.priority.region.
|
||||
|
||||
Returns:
|
||||
IGDB region identifier (e.g., "ja-JP", "EU") or None for default
|
||||
"""
|
||||
config = cm.get_config()
|
||||
if rom is not None and isinstance(rom.regions, list):
|
||||
for region_name in rom.regions:
|
||||
code = REGION_NAME_TO_PROVIDER_SHORTCODE.get(region_name)
|
||||
if code and code in REGION_TO_IGDB_LOCALE:
|
||||
return REGION_TO_IGDB_LOCALE[code]
|
||||
|
||||
# Check each region in priority order and return first match
|
||||
config = cm.get_config()
|
||||
for region in config.SCAN_REGION_PRIORITY:
|
||||
if region.lower() in REGION_TO_IGDB_LOCALE:
|
||||
return REGION_TO_IGDB_LOCALE[region.lower()]
|
||||
@@ -587,7 +593,7 @@ class IGDBHandler(MetadataHandler):
|
||||
|
||||
return IGDBPlatform(igdb_id=None, slug=slug)
|
||||
|
||||
async def get_rom(self, fs_name: str, platform_igdb_id: int) -> IGDBRom:
|
||||
async def get_rom(self, rom: Rom, fs_name: str, platform_igdb_id: int) -> IGDBRom:
|
||||
from handler.filesystem import fs_rom_handler
|
||||
|
||||
if not self.is_enabled():
|
||||
@@ -600,7 +606,7 @@ class IGDBHandler(MetadataHandler):
|
||||
igdb_id_from_tag = self.extract_igdb_id_from_filename(fs_name)
|
||||
if igdb_id_from_tag:
|
||||
log.debug(f"Found IGDB ID tag in filename: {igdb_id_from_tag}")
|
||||
rom_by_id = await self.get_rom_by_id(igdb_id_from_tag)
|
||||
rom_by_id = await self.get_rom_by_id(rom, igdb_id_from_tag)
|
||||
if rom_by_id["igdb_id"]:
|
||||
log.debug(
|
||||
f"Successfully matched ROM by IGDB ID tag: {fs_name} -> {igdb_id_from_tag}"
|
||||
@@ -678,18 +684,20 @@ class IGDBHandler(MetadataHandler):
|
||||
search_term = self.normalize_search_term(search_term)
|
||||
|
||||
log.debug("Searching for %s on IGDB with game_type", search_term)
|
||||
rom = await self._search_rom(search_term, platform_igdb_id, with_game_type=True)
|
||||
if not rom:
|
||||
res = await self._search_rom(search_term, platform_igdb_id, with_game_type=True)
|
||||
if not res:
|
||||
log.debug("Searching for %s on IGDB without game_type", search_term)
|
||||
rom = await self._search_rom(search_term, platform_igdb_id)
|
||||
res = await self._search_rom(search_term, platform_igdb_id)
|
||||
|
||||
# IGDB search is fuzzy so no need to split the search term by special characters
|
||||
if not rom:
|
||||
if not res:
|
||||
return fallback_rom
|
||||
|
||||
return build_igdb_rom(self, rom, get_igdb_preferred_locale(), platform_igdb_id)
|
||||
return build_igdb_rom(
|
||||
self, res, get_igdb_preferred_locale(rom=rom), platform_igdb_id
|
||||
)
|
||||
|
||||
async def get_rom_by_id(self, igdb_id: int) -> IGDBRom:
|
||||
async def get_rom_by_id(self, rom: Rom, igdb_id: int) -> IGDBRom:
|
||||
if not self.is_enabled():
|
||||
return IGDBRom(igdb_id=None)
|
||||
|
||||
@@ -701,17 +709,17 @@ class IGDBHandler(MetadataHandler):
|
||||
if not roms:
|
||||
return IGDBRom(igdb_id=None)
|
||||
|
||||
return build_igdb_rom(self, roms[0], get_igdb_preferred_locale(), None)
|
||||
return build_igdb_rom(self, roms[0], get_igdb_preferred_locale(rom=rom), None)
|
||||
|
||||
async def get_matched_rom_by_id(self, igdb_id: int) -> IGDBRom | None:
|
||||
async def get_matched_rom_by_id(self, rom: Rom, igdb_id: int) -> IGDBRom | None:
|
||||
if not self.is_enabled():
|
||||
return None
|
||||
|
||||
rom = await self.get_rom_by_id(igdb_id)
|
||||
return rom if rom["igdb_id"] else None
|
||||
result = await self.get_rom_by_id(rom, igdb_id)
|
||||
return result if result["igdb_id"] else None
|
||||
|
||||
async def get_matched_roms_by_name(
|
||||
self, search_term: str, platform_igdb_id: int | None
|
||||
self, rom: Rom, search_term: str, platform_igdb_id: int | None
|
||||
) -> list[IGDBRom]:
|
||||
if not self.is_enabled():
|
||||
return []
|
||||
@@ -762,7 +770,7 @@ class IGDBHandler(MetadataHandler):
|
||||
if rom["id"] not in unique_ids
|
||||
]
|
||||
|
||||
preferred_locale = get_igdb_preferred_locale()
|
||||
preferred_locale = get_igdb_preferred_locale(rom=rom)
|
||||
return [
|
||||
build_igdb_rom(self, rom, preferred_locale, platform_igdb_id)
|
||||
for rom in matched_roms
|
||||
|
||||
@@ -13,6 +13,7 @@ from config import SCREENSCRAPER_PASSWORD, SCREENSCRAPER_USER
|
||||
from config.config_manager import MetadataMediaType
|
||||
from config.config_manager import config_manager as cm
|
||||
from handler.filesystem import fs_resource_handler
|
||||
from handler.filesystem.base_handler import REGION_NAME_TO_PROVIDER_SHORTCODE
|
||||
from logger.logger import log
|
||||
from models.rom import Rom, RomFile
|
||||
|
||||
@@ -34,12 +35,21 @@ SS_DEV_PASSWORD: Final = base64.b64decode("eFRKd29PRmpPUUc=").decode()
|
||||
SENSITIVE_KEYS = {"ssid", "sspassword"}
|
||||
|
||||
|
||||
def get_preferred_regions() -> list[str]:
|
||||
"""Get preferred regions from config"""
|
||||
def get_preferred_regions(rom: Rom | None = None) -> list[str]:
|
||||
"""Get preferred regions, prepending the rom's own region tags when available."""
|
||||
rom_codes: list[str] = []
|
||||
if rom is not None and isinstance(rom.regions, list):
|
||||
for region_name in rom.regions:
|
||||
code = REGION_NAME_TO_PROVIDER_SHORTCODE.get(region_name)
|
||||
if code:
|
||||
rom_codes.append(code)
|
||||
|
||||
config = cm.get_config()
|
||||
return list(
|
||||
dict.fromkeys(
|
||||
config.SCAN_REGION_PRIORITY + ["us", "wor", "ss", "eu", "jp", "cus"]
|
||||
rom_codes
|
||||
+ config.SCAN_REGION_PRIORITY
|
||||
+ ["us", "wor", "ss", "eu", "jp", "cus"]
|
||||
)
|
||||
) + ["unk"]
|
||||
|
||||
@@ -174,7 +184,7 @@ def extract_media_from_ss_game(rom: Rom, game: SSGame) -> SSMetadataMedia:
|
||||
video_normalized_path=None,
|
||||
)
|
||||
|
||||
for region in get_preferred_regions():
|
||||
for region in get_preferred_regions(rom):
|
||||
for media in game.get("medias", []):
|
||||
if media.get("region", "unk") != region or media.get("parent") != "jeu":
|
||||
continue
|
||||
@@ -409,7 +419,7 @@ def build_ss_game(rom: Rom, game: SSGame) -> SSRom:
|
||||
preferred_media_types = get_preferred_media_types()
|
||||
|
||||
res_name = ""
|
||||
for region in get_preferred_regions():
|
||||
for region in get_preferred_regions(rom):
|
||||
res_name = next(
|
||||
(
|
||||
name["text"]
|
||||
|
||||
@@ -457,7 +457,7 @@ async def scan_rom(
|
||||
f"{hl(str(h_igdb_id), color=BLUE)} {emoji.EMOJI_ALIEN_MONSTER}",
|
||||
extra=LOGGER_MODULE_NAME,
|
||||
)
|
||||
return await meta_igdb_handler.get_rom_by_id(h_igdb_id)
|
||||
return await meta_igdb_handler.get_rom_by_id(rom, h_igdb_id)
|
||||
|
||||
# Use Playmatch matches to get the IGDB ID
|
||||
if playmatch_rom["igdb_id"] is not None:
|
||||
@@ -467,16 +467,20 @@ async def scan_rom(
|
||||
extra=LOGGER_MODULE_NAME,
|
||||
)
|
||||
|
||||
return await meta_igdb_handler.get_rom_by_id(playmatch_rom["igdb_id"])
|
||||
return await meta_igdb_handler.get_rom_by_id(
|
||||
rom, playmatch_rom["igdb_id"]
|
||||
)
|
||||
|
||||
main_platform_igdb_id = get_main_platform_igdb_id(platform)
|
||||
if scan_type == ScanType.UPDATE and rom.igdb_id:
|
||||
# Use the ID to refetch the metadata from IGDB
|
||||
return await meta_igdb_handler.get_rom_by_id(rom.igdb_id)
|
||||
return await meta_igdb_handler.get_rom_by_id(rom, rom.igdb_id)
|
||||
else:
|
||||
# If no matches found, use the file name to get the IGDB ID
|
||||
return await meta_igdb_handler.get_rom(
|
||||
rom_attrs["fs_name"], main_platform_igdb_id or platform.igdb_id
|
||||
rom,
|
||||
rom_attrs["fs_name"],
|
||||
main_platform_igdb_id or platform.igdb_id,
|
||||
)
|
||||
|
||||
return IGDBRom(igdb_id=None)
|
||||
|
||||
@@ -69,10 +69,11 @@ class TestGetPreferredRegions:
|
||||
class TestExtractMediaFromSsGame:
|
||||
"""Tests for extract_media_from_ss_game."""
|
||||
|
||||
def _make_rom(self) -> MagicMock:
|
||||
def _make_rom(self, regions: list[str] | None = None) -> MagicMock:
|
||||
rom = MagicMock()
|
||||
rom.platform_id = 1
|
||||
rom.id = 100
|
||||
rom.regions = regions
|
||||
return rom
|
||||
|
||||
def _make_game_with_cus_only(self) -> SSGame:
|
||||
|
||||
Reference in New Issue
Block a user