Skip cover/metadata scraping for NoPayStation themes and DLC

This commit is contained in:
2026-09-07 23:53:55 -04:00
parent c94bcdc993
commit 2fed95da05
4 changed files with 49 additions and 11 deletions
+4
View File
@@ -308,6 +308,10 @@ Requirements & behaviour:
> [`npdrm_free`](https://github.com/qwikrazor87/npdrm_free) plugin (ARK and most > [`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 > CFW already ship it). The desktop app only downloads and decrypts; it doesn't
> strip DRM. > 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 ## Server setup
+11
View File
@@ -30,6 +30,15 @@ _PLATFORMS = {
"ps1": {"libretro_repo": "Sony_-_PlayStation", "igdb_id": 7, "ss_id": "57"}, "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): def _platform(system):
"""Platform parameters (libretro repo, IGDB id, ScreenScraper id).""" """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), ``quick`` limits the game fallback to libretro only (index-driven and fast),
for bulk store loading; details views use the full IGDB/ScreenScraper path. 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) dest = cover_path(release_id, system)
if dest.exists(): if dest.exists():
if file_is_image(dest): if file_is_image(dest):
+32 -11
View File
@@ -122,10 +122,11 @@ class CoverLoader(QObject):
from concurrent.futures import ThreadPoolExecutor, as_completed from concurrent.futures import ThreadPoolExecutor, as_completed
pool = ThreadPoolExecutor(max_workers=8) pool = ThreadPoolExecutor(max_workers=8)
futures = { futures = {}
pool.submit(load_cover, it): i for i, it in enumerate(self.items):
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: try:
for fut in as_completed(futures): for fut in as_completed(futures):
if self._cancel.is_set(): if self._cancel.is_set():
@@ -210,6 +211,8 @@ def load_cover(item, timeout=8, quick=False):
name = item["name"] name = item["name"]
rid = item["release_id"] rid = item["release_id"]
system = item["system"] system = item["system"]
if not covers.is_scrapable(system):
return None # themes/DLC have no covers
if system == "videos": if system == "videos":
return _load_video_cover(name, rid, system, timeout) return _load_video_cover(name, rid, system, timeout)
return covers.fetch_cover(name, rid, system, timeout, quick=quick) return covers.fetch_cover(name, rid, system, timeout, quick=quick)
@@ -429,7 +432,7 @@ class CoverShowcase(QWidget):
if item is None: if item is None:
return return
data = item.data(Qt.UserRole) 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) self.play_requested.emit(data)
def _on_failed(self, gen, index, name): def _on_failed(self, gen, index, name):
@@ -467,13 +470,13 @@ class CoverShowcase(QWidget):
if it is not None and it.get("path"): if it is not None and it.get("path"):
menu.addAction("Open containing folder", lambda: open_containing_folder(it.get("path"))) menu.addAction("Open containing folder", lambda: open_containing_folder(it.get("path")))
if it is not None: 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("Play on PPSSPP", lambda: self.play_requested.emit(it))
menu.addAction("Copy to PSP", lambda: self.copy_requested.emit(it)) menu.addAction("Copy to PSP", lambda: self.copy_requested.emit(it))
if it.get("on_psp"): if it.get("on_psp"):
menu.addAction("Delete from PSP", lambda: self.delete_psp_requested.emit(it)) menu.addAction("Delete from PSP", lambda: self.delete_psp_requested.emit(it))
menu.addAction("Delete…", lambda: self.delete_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.addAction("Scrape…", lambda: self.scrape_requested.emit(it))
menu.exec(self.list.mapToGlobal(pos)) menu.exec(self.list.mapToGlobal(pos))
@@ -2021,6 +2024,8 @@ class StorePage(QWidget):
rid, system = lc rid, system = lc
else: else:
rid, system = covers.stable_key(it["name"]), it.get("system") or "games" 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) p = covers.cover_path(rid, system)
if p.exists() and covers.file_is_image(p): if p.exists() and covers.file_is_image(p):
item.setIcon(QIcon(_fit_pixmap(QPixmap(str(p)), self.ICON_W, self.ICON_H))) item.setIcon(QIcon(_fit_pixmap(QPixmap(str(p)), self.ICON_W, self.ICON_H)))
@@ -2187,6 +2192,9 @@ class StorePage(QWidget):
rid, system = lc rid, system = lc
else: else:
rid, system = covers.stable_key(it["name"]), it.get("system") or "games" 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) cached = info.load(str(rid), system)
if cached is not None: if cached is not None:
self.details.set_metadata(cached) self.details.set_metadata(cached)
@@ -2218,6 +2226,8 @@ class StorePage(QWidget):
return return
lc = item.get("_lib_cover") lc = item.get("_lib_cover")
rid, system = lc if lc is not None else (covers.stable_key(item["name"]), item.get("system") or "games") 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) dlg = ScrapeDialog(item, self, rid=rid, system=system)
if dlg.exec() != QDialog.Accepted: if dlg.exec() != QDialog.Accepted:
return return
@@ -2241,7 +2251,8 @@ class StorePage(QWidget):
menu.addAction("Download", lambda: self.download_requested.emit([it])) menu.addAction("Download", lambda: self.download_requested.emit([it]))
if it.get("path"): if it.get("path"):
menu.addAction("Open containing folder", lambda: open_containing_folder(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)) menu.exec(self.grid.mapToGlobal(pos))
def _on_table_menu(self, pos): def _on_table_menu(self, pos):
@@ -2256,11 +2267,14 @@ class StorePage(QWidget):
menu.addAction("Download", lambda: self.download_requested.emit([it])) menu.addAction("Download", lambda: self.download_requested.emit([it]))
if it.get("path"): if it.get("path"):
menu.addAction("Open containing folder", lambda: open_containing_folder(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)) menu.exec(self.table.viewport().mapToGlobal(pos))
def _search_cover(self, it): def _search_cover(self, it):
system = it.get("system") or "games" system = it.get("system") or "games"
if not covers.is_scrapable(system):
return
rid = covers.stable_key(it["name"]) rid = covers.stable_key(it["name"])
dlg = ScrapeDialog({"name": it["name"], "system": system}, self, rid=rid, system=system) dlg = ScrapeDialog({"name": it["name"], "system": system}, self, rid=rid, system=system)
if dlg.exec() == QDialog.Accepted: if dlg.exec() == QDialog.Accepted:
@@ -2297,6 +2311,8 @@ class StorePage(QWidget):
rid, system = lc rid, system = lc
else: else:
rid, system = covers.stable_key(it["name"]), it.get("system") or "games" 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) key = (str(rid), system)
if key in seen: if key in seen:
continue continue
@@ -4317,7 +4333,7 @@ class MainWindow(QMainWindow):
return return
cell = self.library_table.item(item.row(), 0) cell = self.library_table.item(item.row(), 0)
it = cell.data(Qt.UserRole) if cell is not None else None 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) self._play_item_on_ppsspp(it)
def _on_library_table_menu(self, pos): def _on_library_table_menu(self, pos):
@@ -4330,7 +4346,7 @@ class MainWindow(QMainWindow):
if not it: if not it:
return return
menu = QMenu(self) 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("Play on PPSSPP", lambda: self._play_item_on_ppsspp(it))
menu.addAction("Copy to PSP", lambda: self._copy_to_psp(it)) menu.addAction("Copy to PSP", lambda: self._copy_to_psp(it))
if it.get("on_psp"): if it.get("on_psp"):
@@ -4344,6 +4360,9 @@ class MainWindow(QMainWindow):
self.details_panel.set_item(item) self.details_panel.set_item(item)
rid = str(item["release_id"]) rid = str(item["release_id"])
system = item.get("system") system = item.get("system")
if not covers.is_scrapable(system):
self.details_panel.set_metadata({})
return
cached = info.load(rid, system) cached = info.load(rid, system)
if cached is not None: if cached is not None:
self.details_panel.set_metadata(cached) self.details_panel.set_metadata(cached)
@@ -4404,6 +4423,8 @@ class MainWindow(QMainWindow):
item = getattr(self, "_current_item", None) item = getattr(self, "_current_item", None)
if item is None: if item is None:
return 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 "")) rid = str(item.get("release_id") or covers.stable_key(item.get("name") or ""))
system = item.get("system") or "games" system = item.get("system") or "games"
dlg = ScrapeDialog(item, self, rid=rid, system=system) dlg = ScrapeDialog(item, self, rid=rid, system=system)
+2
View File
@@ -182,6 +182,8 @@ def cached_release_dates():
def get_or_fetch(release_id, system, fetch_title): def get_or_fetch(release_id, system, fetch_title):
"""Return cached info (override or fetched) or fetch-and-cache it.""" """Return cached info (override or fetched) or fetch-and-cache it."""
if not covers.is_scrapable(system):
return None
info = load(release_id, system) info = load(release_id, system)
if info is not None: if info is not None:
return info return info