Skip to content

Commit fcfb74a

Browse files
authored
Merge pull request #3119 from rommapp/copilot/use-pico-8-cover-art
feat: use PICO-8 built-in cover art from .p8.png cartridge files
2 parents 94c8e65 + 744d92d commit fcfb74a

File tree

3 files changed

+98
-1
lines changed

3 files changed

+98
-1
lines changed

backend/handler/filesystem/roms_handler.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,10 @@
6060
)
6161
)
6262

63+
# PICO-8 cartridges are often stored as PNG files
64+
PICO8_CARTRIDGE_EXTENSION = ".p8.png"
65+
66+
6367
# CHD (Compressed Hunks of Data) v5 format constants
6468
# See: https://github.com/mamedev/mame/blob/master/src/lib/util/chd.h
6569
CHD_SIGNATURE: Final = b"MComprHD"
@@ -728,3 +732,19 @@ async def rename_fs_rom(self, old_name: str, new_name: str, fs_path: str) -> Non
728732
await self.move_file_or_folder(
729733
f"{fs_path}/{old_name}", f"{fs_path}/{new_name}"
730734
)
735+
736+
def get_pico8_cover_url(
737+
self, platform_slug: str, fs_name: str, fs_path: str
738+
) -> str | None:
739+
"""Return a ``file://`` URL for a PICO-8 cartridge label, or ``None``.
740+
741+
PICO-8 ``.p8.png`` files are valid PNG images whose visual content *is*
742+
the cartridge label/cover art. When such a ROM is found we can use the
743+
file itself as the cover instead of fetching one from an external source.
744+
"""
745+
if platform_slug == UPS.PICO and fs_name.lower().endswith(
746+
PICO8_CARTRIDGE_EXTENSION
747+
):
748+
rom_path = self.validate_path(f"{fs_path}/{fs_name}")
749+
return f"file://{rom_path}"
750+
return None

backend/handler/scan_handler.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from config.config_manager import config_manager as cm
99
from endpoints.responses.rom import SimpleRomSchema
1010
from handler.database import db_platform_handler, db_rom_handler
11-
from handler.filesystem import fs_asset_handler, fs_firmware_handler
11+
from handler.filesystem import fs_asset_handler, fs_firmware_handler, fs_rom_handler
1212
from handler.filesystem.roms_handler import FSRom
1313
from handler.metadata import (
1414
meta_flashpoint_handler,
@@ -765,6 +765,16 @@ async def fetch_hasheous_rom(hasheous_rom: HasheousRom) -> HasheousRom:
765765
}
766766
)
767767

768+
# Use PICO-8 cartridge PNG as cover art if no cover is set.
769+
# PICO-8 .p8.png files are valid PNG images whose visual content is the
770+
# cartridge label, so the ROM file itself serves as the cover art.
771+
if not rom_attrs.get("url_cover") and not rom_attrs.get("path_cover_s"):
772+
pico8_url = fs_rom_handler.get_pico8_cover_url(
773+
platform.slug, rom_attrs["fs_name"], rom_attrs["fs_path"]
774+
)
775+
if pico8_url:
776+
rom_attrs["url_cover"] = pico8_url
777+
768778
# If not found in any metadata source, we return the rom with the default values
769779
if (
770780
not rom_attrs.get("igdb_id")

backend/tests/endpoints/sockets/test_scan.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
1+
from pathlib import Path
12
from unittest.mock import Mock
23

34
import pytest
45
import socketio
56

7+
from config import LIBRARY_BASE_PATH
68
from endpoints.sockets.scan import ScanStats, _should_scan_rom
9+
from handler.filesystem.roms_handler import FSRomsHandler
10+
from handler.metadata.base_handler import UniversalPlatformSlug as UPS
711
from handler.scan_handler import ScanType
812
from models.rom import Rom
913

@@ -244,3 +248,66 @@ def test_comprehensive_scenarios(
244248

245249
result = _should_scan_rom(scan_type, rom, roms_ids, ["igdb"])
246250
assert result is expected
251+
252+
253+
class TestGetPico8CoverUrl:
254+
"""Tests for the PICO-8 cover art URL helper on FSRomsHandler."""
255+
256+
@pytest.fixture
257+
def handler(self):
258+
return FSRomsHandler()
259+
260+
def test_returns_file_url_for_pico8_cartridge(self, handler: FSRomsHandler):
261+
url = handler.get_pico8_cover_url(
262+
platform_slug=UPS.PICO,
263+
fs_name="mygame.p8.png",
264+
fs_path="pico/roms",
265+
)
266+
expected = f"file://{Path(LIBRARY_BASE_PATH).resolve() / 'pico/roms' / 'mygame.p8.png'}"
267+
assert url == expected
268+
269+
def test_returns_none_for_non_pico8_platform(self, handler: FSRomsHandler):
270+
url = handler.get_pico8_cover_url(
271+
platform_slug="snes",
272+
fs_name="mygame.p8.png",
273+
fs_path="snes/roms",
274+
)
275+
assert url is None
276+
277+
def test_returns_none_for_plain_p8_text_file(self, handler: FSRomsHandler):
278+
"""Plain .p8 files are text-only and have no embedded PNG image."""
279+
url = handler.get_pico8_cover_url(
280+
platform_slug=UPS.PICO,
281+
fs_name="mygame.p8",
282+
fs_path="pico/roms",
283+
)
284+
assert url is None
285+
286+
def test_returns_none_for_unrelated_extension(self, handler: FSRomsHandler):
287+
url = handler.get_pico8_cover_url(
288+
platform_slug=UPS.PICO,
289+
fs_name="mygame.zip",
290+
fs_path="pico/roms",
291+
)
292+
assert url is None
293+
294+
def test_url_starts_with_file_scheme(self, handler: FSRomsHandler):
295+
url = handler.get_pico8_cover_url(
296+
platform_slug=UPS.PICO,
297+
fs_name="cart.p8.png",
298+
fs_path="pico/roms",
299+
)
300+
assert url is not None
301+
assert url.startswith("file://")
302+
303+
def test_url_contains_fs_path_and_name(self, handler: FSRomsHandler):
304+
fs_path = "pico/roms"
305+
fs_name = "celeste.p8.png"
306+
url = handler.get_pico8_cover_url(
307+
platform_slug=UPS.PICO,
308+
fs_name=fs_name,
309+
fs_path=fs_path,
310+
)
311+
assert url is not None
312+
assert fs_path in url
313+
assert fs_name in url

0 commit comments

Comments
 (0)