Skip to content

Commit 11dd607

Browse files
authored
Merge pull request #2720 from vargash/batocera-gamelist
Update /gamelist/export to generate a correct Batocera gamelist.xml
2 parents 4ca805c + bb35119 commit 11dd607

File tree

5 files changed

+75
-28
lines changed

5 files changed

+75
-28
lines changed

backend/config/config_manager.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ class EjsControlsButton(TypedDict):
3838
class MetadataMediaType(enum.StrEnum):
3939
BEZEL = "bezel"
4040
BOX2D = "box2d"
41+
BOX2D_BACK = "box2d_back"
4142
BOX3D = "box3d"
4243
MIXIMAGE = "miximage"
4344
PHYSICAL = "physical"

backend/endpoints/gamelist.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ async def export_gamelist(
2323
platform_ids: Annotated[
2424
List[int], Query(description="List of platform IDs to export")
2525
],
26+
local_export: Annotated[
27+
bool, Query(description="Use local paths instead of URLs")
28+
] = False,
2629
) -> Response:
2730
"""Export platforms/ROMs to gamelist.xml format and write to platform directories"""
2831
if not platform_ids:
@@ -32,12 +35,15 @@ async def export_gamelist(
3235
)
3336

3437
try:
35-
exporter = GamelistExporter()
38+
exporter = GamelistExporter(local_export=local_export)
3639
files_written = []
3740

3841
# Export each platform to its respective directory
3942
for platform_id in platform_ids:
40-
success = await exporter.export_platform_to_file(platform_id, request)
43+
success = await exporter.export_platform_to_file(
44+
platform_id,
45+
request,
46+
)
4147
if success:
4248
files_written.append(f"gamelist_{platform_id}.xml")
4349
else:

backend/handler/metadata/ss_handler.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ class SSMetadataMedia(TypedDict):
155155
box3d_url: str | None # box-3D
156156
fanart_url: str | None # fanart
157157
fullbox_url: str | None # box-texture
158-
logo_url: str | None # wheel-hd
158+
logo_url: str | None # wheel-hd or wheel
159159
manual_url: str | None # manual
160160
marquee_url: str | None # screenmarquee
161161
miximage_url: str | None # mixrbv1 | mixrbv2
@@ -168,7 +168,9 @@ class SSMetadataMedia(TypedDict):
168168

169169
# Resources stored in filesystem
170170
bezel_path: str | None
171+
box2d_back_path: str | None
171172
box3d_path: str | None
173+
fanart_path: str | None
172174
miximage_path: str | None
173175
physical_path: str | None
174176
marquee_path: str | None
@@ -213,7 +215,9 @@ def extract_media_from_ss_game(rom: Rom, game: SSGame) -> SSMetadataMedia:
213215
video_url=None,
214216
video_normalized_url=None,
215217
bezel_path=None,
218+
box2d_back_path=None,
216219
box3d_path=None,
220+
fanart_path=None,
217221
miximage_path=None,
218222
physical_path=None,
219223
marquee_path=None,
@@ -228,6 +232,10 @@ def extract_media_from_ss_game(rom: Rom, game: SSGame) -> SSMetadataMedia:
228232

229233
if media.get("type") == "box-2D-back" and not ss_media["box2d_back_url"]:
230234
ss_media["box2d_back_url"] = media["url"]
235+
if MetadataMediaType.BOX2D_BACK in preferred_media_types:
236+
ss_media["box2d_back_path"] = (
237+
f"{fs_resource_handler.get_media_resources_path(rom.platform_id, rom.id, MetadataMediaType.BOX2D_BACK)}/box2d_back.png"
238+
)
231239
elif media.get("type") == "bezel-16-9" and not ss_media["bezel_url"]:
232240
ss_media["bezel_url"] = media["url"]
233241
if MetadataMediaType.BEZEL in preferred_media_types:
@@ -238,11 +246,21 @@ def extract_media_from_ss_game(rom: Rom, game: SSGame) -> SSMetadataMedia:
238246
ss_media["box2d_url"] = media["url"]
239247
elif media.get("type") == "fanart" and not ss_media["fanart_url"]:
240248
ss_media["fanart_url"] = media["url"]
249+
if MetadataMediaType.FANART in preferred_media_types:
250+
ss_media["fanart_path"] = (
251+
f"{fs_resource_handler.get_media_resources_path(rom.platform_id, rom.id, MetadataMediaType.FANART)}/fanart.png"
252+
)
241253
elif media.get("type") == "box-texture" and not ss_media["fullbox_url"]:
242254
ss_media["fullbox_url"] = media["url"]
243255
elif media.get("type") == "wheel-hd" and not ss_media["logo_url"]:
244256
ss_media["logo_url"] = media["url"]
245257

258+
if MetadataMediaType.LOGO in preferred_media_types:
259+
ss_media["logo_path"] = (
260+
f"{fs_resource_handler.get_media_resources_path(rom.platform_id, rom.id, MetadataMediaType.LOGO)}/logo.png"
261+
)
262+
elif media.get("type") == "wheel" and not ss_media["logo_url"]:
263+
ss_media["logo_url"] = media["url"]
246264
if MetadataMediaType.LOGO in preferred_media_types:
247265
ss_media["logo_path"] = (
248266
f"{fs_resource_handler.get_media_resources_path(rom.platform_id, rom.id, MetadataMediaType.LOGO)}/logo.png"

backend/utils/gamelist_exporter.py

Lines changed: 44 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
tostring,
77
)
88

9+
from fastapi import Request
10+
911
from config import FRONTEND_RESOURCES_PATH, YOUTUBE_BASE_URL
1012
from handler.database import db_platform_handler, db_rom_handler
1113
from handler.filesystem import fs_platform_handler
@@ -16,34 +18,38 @@
1618
class GamelistExporter:
1719
"""Export RomM collections to ES-DE gamelist.xml format"""
1820

21+
def __init__(self, local_export: bool = False):
22+
self.local_export = local_export
23+
1924
def _format_release_date(self, timestamp: int) -> str:
2025
"""Format release date to YYYYMMDDTHHMMSS format"""
2126
return datetime.fromtimestamp(timestamp / 1000).strftime("%Y%m%dT%H%M%S")
2227

23-
def _create_game_element(self, rom: Rom, request=None) -> Element:
28+
def _create_game_element(self, rom: Rom, request: Request) -> Element:
2429
"""Create a <game> element for a ROM"""
2530
game = Element("game")
2631

2732
# Basic game info
28-
if request:
33+
if self.local_export:
34+
SubElement(game, "path").text = f"./{rom.fs_name}"
35+
else:
2936
SubElement(game, "path").text = str(
3037
request.url_for(
3138
"get_rom_content",
3239
id=rom.id,
3340
file_name=rom.fs_name,
3441
)
3542
)
36-
else:
37-
SubElement(game, "path").text = f"./{rom.fs_name}"
43+
3844
SubElement(game, "name").text = rom.name or rom.fs_name
3945

4046
if rom.summary:
4147
SubElement(game, "desc").text = rom.summary
4248

4349
# Media files
44-
if rom.path_cover_large:
45-
SubElement(game, "cover").text = (
46-
f"{FRONTEND_RESOURCES_PATH}/{rom.path_cover_large}"
50+
if rom.path_cover_l:
51+
SubElement(game, "thumbnail").text = (
52+
f"{FRONTEND_RESOURCES_PATH}/{rom.path_cover_l}"
4753
)
4854

4955
if rom.youtube_video_id:
@@ -91,26 +97,40 @@ def _create_game_element(self, rom: Rom, request=None) -> Element:
9197

9298
# Provider specific metadata
9399
if rom.ss_metadata:
94-
if rom.ss_metadata.get("box3d"):
95-
SubElement(game, "box3d").text = rom.ss_metadata["box3d"]
96-
if rom.ss_metadata.get("box2d_back"):
97-
SubElement(game, "backcover").text = rom.ss_metadata["box2d_back"]
98-
if rom.ss_metadata.get("fanart"):
99-
SubElement(game, "fanart").text = rom.ss_metadata["fanart"]
100-
if rom.ss_metadata.get("marquee"):
101-
SubElement(game, "marquee").text = rom.ss_metadata["marquee"]
102-
if rom.ss_metadata.get("miximage"):
103-
SubElement(game, "miximage").text = rom.ss_metadata["miximage"]
104-
if rom.ss_metadata.get("physical"):
105-
SubElement(game, "physicalmedia").text = rom.ss_metadata["physical"]
100+
if rom.ss_metadata.get("box3d_path"):
101+
SubElement(game, "box3d").text = (
102+
f"{FRONTEND_RESOURCES_PATH}/{rom.ss_metadata["box3d_path"]}"
103+
)
104+
if rom.ss_metadata.get("box2d_back_path"):
105+
SubElement(game, "boxback").text = (
106+
f"{FRONTEND_RESOURCES_PATH}/{rom.ss_metadata["box2d_back_path"]}"
107+
)
108+
if rom.ss_metadata.get("fanart_path"):
109+
SubElement(game, "fanart").text = (
110+
f"{FRONTEND_RESOURCES_PATH}/{rom.ss_metadata["fanart_path"]}"
111+
)
112+
if rom.ss_metadata.get("logo_path"):
113+
SubElement(game, "marquee").text = (
114+
f"{FRONTEND_RESOURCES_PATH}/{rom.ss_metadata["logo_path"]}"
115+
)
116+
if rom.ss_metadata.get("miximage_path"):
117+
SubElement(game, "miximage").text = (
118+
f"{FRONTEND_RESOURCES_PATH}/{rom.ss_metadata["miximage_path"]}"
119+
)
120+
if rom.ss_metadata.get("physical_path"):
121+
SubElement(game, "physicalmedia").text = (
122+
f"{FRONTEND_RESOURCES_PATH}/{rom.ss_metadata["physical_path"]}"
123+
)
106124
if rom.ss_metadata.get("title_screen"):
107-
SubElement(game, "title_screen").text = rom.ss_metadata["title_screen"]
125+
SubElement(game, "title_screen").text = (
126+
f"{FRONTEND_RESOURCES_PATH}/{rom.ss_metadata["title_screen"]}"
127+
)
108128

109129
if rom.gamelist_metadata:
110130
if rom.gamelist_metadata.get("box3d"):
111131
SubElement(game, "box3d").text = rom.gamelist_metadata["box3d"]
112132
if rom.gamelist_metadata.get("box2d_back"):
113-
SubElement(game, "backcover").text = rom.gamelist_metadata["box2d_back"]
133+
SubElement(game, "boxback").text = rom.gamelist_metadata["box2d_back"]
114134
if rom.gamelist_metadata.get("fanart"):
115135
SubElement(game, "fanart").text = rom.gamelist_metadata["fanart"]
116136
if rom.gamelist_metadata.get("marquee"):
@@ -135,7 +155,7 @@ def _create_game_element(self, rom: Rom, request=None) -> Element:
135155

136156
return game
137157

138-
def export_platform_to_xml(self, platform_id: int, request=None) -> str:
158+
def export_platform_to_xml(self, platform_id: int, request: Request) -> str:
139159
"""Export a platform's ROMs to gamelist.xml format
140160
141161
Args:
@@ -154,7 +174,7 @@ def export_platform_to_xml(self, platform_id: int, request=None) -> str:
154174
root = Element("gameList")
155175

156176
for rom in roms:
157-
if rom and not rom.missing_from_fs:
177+
if rom and not rom.missing_from_fs and rom.fs_name != "gamelist.xml":
158178
game_element = self._create_game_element(rom, request=request)
159179
root.append(game_element)
160180

@@ -168,7 +188,7 @@ def export_platform_to_xml(self, platform_id: int, request=None) -> str:
168188
async def export_platform_to_file(
169189
self,
170190
platform_id: int,
171-
request=None,
191+
request: Request,
172192
) -> bool:
173193
"""Export platform ROMs to gamelist.xml file in the platform's directory
174194

examples/config.example.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,9 +98,11 @@ filesystem: {} # { roms_folder: 'roms' } For example if your folder structure is
9898
# - manual # Manual (enabled by default)
9999
# # Gameplay video
100100
# - video # Video (warning: large file size)
101+
# # Media used for batocera gamelist.xml export
102+
# - box2d_back # Back cover art
103+
# - logo # Transparent logo
101104
# # Other media assets (might be used in the future)
102105
# - marquee # Custom marquee
103-
# - logo # Transparent logo
104106

105107
# EmulatorJS per-core options
106108
# emulatorjs:

0 commit comments

Comments
 (0)