diff --git a/backend/config/config_manager.py b/backend/config/config_manager.py index b04e5d59c..39ce9d439 100644 --- a/backend/config/config_manager.py +++ b/backend/config/config_manager.py @@ -127,6 +127,7 @@ class Config: SCAN_LANGUAGE_PRIORITY: list[str] SCAN_MEDIA: list[str] GAMELIST_THUMBNAIL_MEDIA: str + GAMELIST_IMAGE_MEDIA: str def __init__(self, **entries): self.__dict__.update(entries) @@ -347,6 +348,11 @@ class ConfigManager: "export.gamelist.media.thumbnail", "cover", ), + GAMELIST_IMAGE_MEDIA=pydash.get( + self._raw_config, + "export.gamelist.media.image", + "screenshot", + ), ) def _get_ejs_controls(self) -> dict[str, EjsControls]: diff --git a/backend/handler/metadata/gamelist_handler.py b/backend/handler/metadata/gamelist_handler.py index 27f7dd490..6108284c1 100644 --- a/backend/handler/metadata/gamelist_handler.py +++ b/backend/handler/metadata/gamelist_handler.py @@ -243,6 +243,12 @@ def populate_rom_specific_paths( updated_metadata["physical_path"] = ( f"{fs_resource_handler.get_media_resources_path(rom.platform_id, rom.id, MetadataMediaType.PHYSICAL)}/physical.png" ) + if MetadataMediaType.TITLE_SCREEN in preferred_media_types and rom_metadata.get( + "title_screen_url" + ): + updated_metadata["title_screen_path"] = ( + f"{fs_resource_handler.get_media_resources_path(rom.platform_id, rom.id, MetadataMediaType.TITLE_SCREEN)}/title_screen.png" + ) if MetadataMediaType.VIDEO in preferred_media_types and rom_metadata.get( "video_url" ): diff --git a/backend/handler/metadata/ss_handler.py b/backend/handler/metadata/ss_handler.py index f04b35ed3..cf3107f70 100644 --- a/backend/handler/metadata/ss_handler.py +++ b/backend/handler/metadata/ss_handler.py @@ -116,6 +116,7 @@ class SSMetadataMedia(TypedDict): physical_path: str | None marquee_path: str | None logo_path: str | None + title_screen_path: str | None video_path: str | None video_normalized_path: str | None @@ -166,6 +167,7 @@ def extract_media_from_ss_game(rom: Rom, game: SSGame) -> SSMetadataMedia: physical_path=None, marquee_path=None, logo_path=None, + title_screen_path=None, video_path=None, video_normalized_path=None, ) @@ -281,6 +283,10 @@ def extract_media_from_ss_game(rom: Rom, game: SSGame) -> SSMetadataMedia: ss_media["title_screen_url"] = strip_sensitive_query_params( media["url"], SENSITIVE_KEYS ) + if MetadataMediaType.TITLE_SCREEN in preferred_media_types: + ss_media["title_screen_path"] = ( + f"{fs_resource_handler.get_media_resources_path(rom.platform_id, rom.id, MetadataMediaType.TITLE_SCREEN)}/title_screen.png" + ) elif media.get("type") == "video" and not ss_media["video_url"]: ss_media["video_url"] = strip_sensitive_query_params( media["url"], SENSITIVE_KEYS diff --git a/backend/tests/config/fixtures/config/config.yml b/backend/tests/config/fixtures/config/config.yml index 78a6fa97c..94781ffb1 100644 --- a/backend/tests/config/fixtures/config/config.yml +++ b/backend/tests/config/fixtures/config/config.yml @@ -80,3 +80,4 @@ export: gamelist: media: thumbnail: "box3d" + image: "title_screen" diff --git a/backend/tests/config/test_config_loader.py b/backend/tests/config/test_config_loader.py index e5faf6cec..6c4aad16e 100644 --- a/backend/tests/config/test_config_loader.py +++ b/backend/tests/config/test_config_loader.py @@ -55,6 +55,7 @@ def test_config_loader(): assert loader.config.SCAN_REGION_PRIORITY == ["jp", "eu", "wor"] assert loader.config.SCAN_LANGUAGE_PRIORITY == ["jp", "es"] assert loader.config.GAMELIST_THUMBNAIL_MEDIA == "box3d" + assert loader.config.GAMELIST_IMAGE_MEDIA == "title_screen" def test_empty_config_loader(): @@ -88,3 +89,4 @@ def test_empty_config_loader(): assert loader.config.EJS_SETTINGS == {} assert loader.config.EJS_CONTROLS == {} assert loader.config.GAMELIST_THUMBNAIL_MEDIA == "cover" + assert loader.config.GAMELIST_IMAGE_MEDIA == "screenshot" diff --git a/backend/utils/gamelist_exporter.py b/backend/utils/gamelist_exporter.py index c903a83e1..add452585 100644 --- a/backend/utils/gamelist_exporter.py +++ b/backend/utils/gamelist_exporter.py @@ -87,6 +87,27 @@ class GamelistExporter: f"{FRONTEND_RESOURCES_PATH}/{rom.path_screenshots[0]}" ) + image_path: str | None = None + image_option = cm.config.GAMELIST_IMAGE_MEDIA + if image_option == "title_screen": + if rom.ss_metadata and rom.ss_metadata.get("title_screen_path"): + image_path = f"{FRONTEND_RESOURCES_PATH}/{rom.ss_metadata['title_screen_path']}" + elif rom.gamelist_metadata and rom.gamelist_metadata.get("title_screen_path"): + image_path = f"{FRONTEND_RESOURCES_PATH}/{rom.gamelist_metadata['title_screen_path']}" + elif image_option == "miximage": + if rom.ss_metadata and rom.ss_metadata.get("miximage_path"): + image_path = f"{FRONTEND_RESOURCES_PATH}/{rom.ss_metadata['miximage_path']}" + elif rom.gamelist_metadata and rom.gamelist_metadata.get("miximage_path"): + image_path = f"{FRONTEND_RESOURCES_PATH}/{rom.gamelist_metadata['miximage_path']}" + elif image_option == "cover": + if rom.path_cover_l: + image_path = f"{FRONTEND_RESOURCES_PATH}/{rom.path_cover_l}" + # "screenshot" (default) and anything else falls through to path_screenshots + if image_path is None and rom.path_screenshots: + image_path = f"{FRONTEND_RESOURCES_PATH}/{rom.path_screenshots[0]}" + if image_path: + SubElement(game, "image").text = image_path + if rom.path_manual: SubElement(game, "manual").text = ( f"{FRONTEND_RESOURCES_PATH}/{rom.path_manual}" @@ -154,9 +175,9 @@ class GamelistExporter: SubElement(game, "physicalmedia").text = ( f"{FRONTEND_RESOURCES_PATH}/{rom.ss_metadata["physical_path"]}" ) - if rom.ss_metadata.get("title_screen"): + if rom.ss_metadata.get("title_screen_path"): SubElement(game, "title_screen").text = ( - f"{FRONTEND_RESOURCES_PATH}/{rom.ss_metadata["title_screen"]}" + f"{FRONTEND_RESOURCES_PATH}/{rom.ss_metadata['title_screen_path']}" ) if rom.ss_metadata.get("bezel_path"): SubElement(game, "bezel").text = ( @@ -172,16 +193,18 @@ class GamelistExporter: SubElement(game, "fanart").text = rom.gamelist_metadata["fanart"] if rom.gamelist_metadata.get("marquee"): SubElement(game, "marquee").text = rom.gamelist_metadata["marquee"] - if rom.gamelist_metadata.get("miximage"): - SubElement(game, "miximage").text = rom.gamelist_metadata["miximage"] - if rom.gamelist_metadata.get("physical"): - SubElement(game, "physicalmedia").text = rom.gamelist_metadata[ - "physical" - ] - if rom.gamelist_metadata.get("title_screen"): - SubElement(game, "title_screen").text = rom.gamelist_metadata[ - "title_screen" - ] + if rom.gamelist_metadata.get("miximage_path"): + SubElement(game, "miximage").text = ( + f"{FRONTEND_RESOURCES_PATH}/{rom.gamelist_metadata['miximage_path']}" + ) + if rom.gamelist_metadata.get("physical_path"): + SubElement(game, "physicalmedia").text = ( + f"{FRONTEND_RESOURCES_PATH}/{rom.gamelist_metadata['physical_path']}" + ) + if rom.gamelist_metadata.get("title_screen_path"): + SubElement(game, "title_screen").text = ( + f"{FRONTEND_RESOURCES_PATH}/{rom.gamelist_metadata['title_screen_path']}" + ) # Add scraping info scrap = SubElement(game, "scrap") diff --git a/examples/config.batocera-retrobat.yml b/examples/config.batocera-retrobat.yml index c72520cb3..429020078 100644 --- a/examples/config.batocera-retrobat.yml +++ b/examples/config.batocera-retrobat.yml @@ -191,3 +191,7 @@ export: # Falls back to front cover if 3D box art has not been scraped # Other options: cover (default), box2d, miximage, physical thumbnail: box3d + # Use the miximage as the tag, preferred by Batocera/RetroBAT + # Falls back to first screenshot if not available + # Other options: "screenshot" (default), "title_screen", "cover" + image: "miximage" \ No newline at end of file diff --git a/examples/config.example.yml b/examples/config.example.yml index 525fe2d0f..2c67f4040 100644 --- a/examples/config.example.yml +++ b/examples/config.example.yml @@ -145,6 +145,12 @@ # # miximage - mixed image (falls back to cover if not available) # # physical - disc/cartridge image (falls back to cover if not available) # thumbnail: cover +# # Media asset to use as the tag in the exported gamelist.xml +# # screenshot - first screenshot (default) +# # title_screen - title screen image (falls back to screenshot if not available) +# # miximage - mixed image (falls back to screenshot if not available) +# # cover - front cover art (falls back to screenshot if not available) +# image: screenshot # EmulatorJS per-core options # emulatorjs: