From 2fed95da059cec8cf87787506d61e3ffbd7557c6 Mon Sep 17 00:00:00 2001 From: Mathieu Moran Date: Mon, 7 Sep 2026 23:53:55 -0400 Subject: [PATCH] Skip cover/metadata scraping for NoPayStation themes and DLC --- README.md | 4 ++++ app/covers.py | 11 +++++++++++ app/gui.py | 43 ++++++++++++++++++++++++++++++++----------- app/info.py | 2 ++ 4 files changed, 49 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 8f987d7..475509a 100644 --- a/README.md +++ b/README.md @@ -308,6 +308,10 @@ Requirements & behaviour: > [`npdrm_free`](https://github.com/qwikrazor87/npdrm_free) plugin (ARK and most > CFW already ship it). The desktop app only downloads and decrypts; it doesn't > strip DRM. +> +> **Covers/metadata:** themes and DLC have no cover art or game metadata, so +> the app never scrapes IGDB/libretro/etc. for them (their Library rows show a +> placeholder icon instead of a mis-matched game cover). ## Server setup diff --git a/app/covers.py b/app/covers.py index c1794dd..cde51c7 100644 --- a/app/covers.py +++ b/app/covers.py @@ -30,6 +30,15 @@ _PLATFORMS = { "ps1": {"libretro_repo": "Sony_-_PlayStation", "igdb_id": 7, "ss_id": "57"}, } +# Content types that have no meaningful cover/metadata (IGDB/etc. lookups +# return unrelated games, so scraping is disabled for them). +NON_COVER_SYSTEMS = {"theme", "dlc"} + + +def is_scrapable(system): + """Whether cover/metadata scraping makes sense for this item type.""" + return (system or "games") not in NON_COVER_SYSTEMS + def _platform(system): """Platform parameters (libretro repo, IGDB id, ScreenScraper id).""" @@ -1142,6 +1151,8 @@ def fetch_cover(name, release_id, system, timeout=10, quick=False): ``quick`` limits the game fallback to libretro only (index-driven and fast), for bulk store loading; details views use the full IGDB/ScreenScraper path. """ + if not is_scrapable(system): + return None dest = cover_path(release_id, system) if dest.exists(): if file_is_image(dest): diff --git a/app/gui.py b/app/gui.py index da95730..4552901 100644 --- a/app/gui.py +++ b/app/gui.py @@ -122,10 +122,11 @@ class CoverLoader(QObject): from concurrent.futures import ThreadPoolExecutor, as_completed pool = ThreadPoolExecutor(max_workers=8) - futures = { - pool.submit(load_cover, it): i - for i, it in enumerate(self.items) - } + futures = {} + for i, it in enumerate(self.items): + if not covers.is_scrapable(it.get("system")): + continue # themes/DLC have no covers; keep the placeholder + futures[pool.submit(load_cover, it)] = i try: for fut in as_completed(futures): if self._cancel.is_set(): @@ -210,6 +211,8 @@ def load_cover(item, timeout=8, quick=False): name = item["name"] rid = item["release_id"] system = item["system"] + if not covers.is_scrapable(system): + return None # themes/DLC have no covers if system == "videos": return _load_video_cover(name, rid, system, timeout) return covers.fetch_cover(name, rid, system, timeout, quick=quick) @@ -429,7 +432,7 @@ class CoverShowcase(QWidget): if item is None: return data = item.data(Qt.UserRole) - if data and data.get("system") != "videos": + if data and data.get("system") not in ("videos", "theme", "dlc"): self.play_requested.emit(data) def _on_failed(self, gen, index, name): @@ -467,13 +470,13 @@ class CoverShowcase(QWidget): if it is not None and it.get("path"): menu.addAction("Open containing folder", lambda: open_containing_folder(it.get("path"))) if it is not None: - if it.get("system") != "videos": + if it.get("system") not in ("videos", "theme", "dlc"): menu.addAction("Play on PPSSPP", lambda: self.play_requested.emit(it)) menu.addAction("Copy to PSP", lambda: self.copy_requested.emit(it)) if it.get("on_psp"): menu.addAction("Delete from PSP", lambda: self.delete_psp_requested.emit(it)) menu.addAction("Delete…", lambda: self.delete_requested.emit(it)) - if it is not None: + if it is not None and covers.is_scrapable(it.get("system")): menu.addAction("Scrape…", lambda: self.scrape_requested.emit(it)) menu.exec(self.list.mapToGlobal(pos)) @@ -2021,6 +2024,8 @@ class StorePage(QWidget): rid, system = lc else: rid, system = covers.stable_key(it["name"]), it.get("system") or "games" + if not covers.is_scrapable(system): + return # themes/DLC have no covers p = covers.cover_path(rid, system) if p.exists() and covers.file_is_image(p): item.setIcon(QIcon(_fit_pixmap(QPixmap(str(p)), self.ICON_W, self.ICON_H))) @@ -2187,6 +2192,9 @@ class StorePage(QWidget): rid, system = lc else: rid, system = covers.stable_key(it["name"]), it.get("system") or "games" + if not covers.is_scrapable(system): + self.details.set_metadata({}) + return cached = info.load(str(rid), system) if cached is not None: self.details.set_metadata(cached) @@ -2218,6 +2226,8 @@ class StorePage(QWidget): return lc = item.get("_lib_cover") rid, system = lc if lc is not None else (covers.stable_key(item["name"]), item.get("system") or "games") + if not covers.is_scrapable(system): + return dlg = ScrapeDialog(item, self, rid=rid, system=system) if dlg.exec() != QDialog.Accepted: return @@ -2241,7 +2251,8 @@ class StorePage(QWidget): menu.addAction("Download", lambda: self.download_requested.emit([it])) if it.get("path"): menu.addAction("Open containing folder", lambda: open_containing_folder(it.get("path"))) - menu.addAction("Scrape…", lambda: self._search_cover(it)) + if covers.is_scrapable(it.get("system")): + menu.addAction("Scrape…", lambda: self._search_cover(it)) menu.exec(self.grid.mapToGlobal(pos)) def _on_table_menu(self, pos): @@ -2256,11 +2267,14 @@ class StorePage(QWidget): menu.addAction("Download", lambda: self.download_requested.emit([it])) if it.get("path"): menu.addAction("Open containing folder", lambda: open_containing_folder(it.get("path"))) - menu.addAction("Scrape…", lambda: self._search_cover(it)) + if covers.is_scrapable(it.get("system")): + menu.addAction("Scrape…", lambda: self._search_cover(it)) menu.exec(self.table.viewport().mapToGlobal(pos)) def _search_cover(self, it): system = it.get("system") or "games" + if not covers.is_scrapable(system): + return rid = covers.stable_key(it["name"]) dlg = ScrapeDialog({"name": it["name"], "system": system}, self, rid=rid, system=system) if dlg.exec() == QDialog.Accepted: @@ -2297,6 +2311,8 @@ class StorePage(QWidget): rid, system = lc else: rid, system = covers.stable_key(it["name"]), it.get("system") or "games" + if not covers.is_scrapable(system): + continue # themes/DLC have no covers/metadata key = (str(rid), system) if key in seen: continue @@ -4317,7 +4333,7 @@ class MainWindow(QMainWindow): return cell = self.library_table.item(item.row(), 0) it = cell.data(Qt.UserRole) if cell is not None else None - if it and it.get("system") != "videos": + if it and it.get("system") not in ("videos", "theme", "dlc"): self._play_item_on_ppsspp(it) def _on_library_table_menu(self, pos): @@ -4330,7 +4346,7 @@ class MainWindow(QMainWindow): if not it: return menu = QMenu(self) - if it.get("system") != "videos": + if it.get("system") not in ("videos", "theme", "dlc"): menu.addAction("Play on PPSSPP", lambda: self._play_item_on_ppsspp(it)) menu.addAction("Copy to PSP", lambda: self._copy_to_psp(it)) if it.get("on_psp"): @@ -4344,6 +4360,9 @@ class MainWindow(QMainWindow): self.details_panel.set_item(item) rid = str(item["release_id"]) system = item.get("system") + if not covers.is_scrapable(system): + self.details_panel.set_metadata({}) + return cached = info.load(rid, system) if cached is not None: self.details_panel.set_metadata(cached) @@ -4404,6 +4423,8 @@ class MainWindow(QMainWindow): item = getattr(self, "_current_item", None) if item is None: return + if not covers.is_scrapable(item.get("system")): + return # themes/DLC have no metadata/cover rid = str(item.get("release_id") or covers.stable_key(item.get("name") or "")) system = item.get("system") or "games" dlg = ScrapeDialog(item, self, rid=rid, system=system) diff --git a/app/info.py b/app/info.py index 380e79e..1601fa0 100644 --- a/app/info.py +++ b/app/info.py @@ -182,6 +182,8 @@ def cached_release_dates(): def get_or_fetch(release_id, system, fetch_title): """Return cached info (override or fetched) or fetch-and-cache it.""" + if not covers.is_scrapable(system): + return None info = load(release_id, system) if info is not None: return info