From be54ff00aa789e771e683ff8f936b011495b52b7 Mon Sep 17 00:00:00 2001 From: Mathieu Moran Date: Tue, 8 Sep 2026 23:04:24 -0400 Subject: [PATCH] Re-enable cover/metadata scraping for themes and DLC --- README.md | 7 ++++--- app/covers.py | 11 ----------- app/gui.py | 46 ++++++---------------------------------------- app/info.py | 2 -- 4 files changed, 10 insertions(+), 56 deletions(-) diff --git a/README.md b/README.md index 3291e05..d542c6f 100644 --- a/README.md +++ b/README.md @@ -309,9 +309,10 @@ Requirements & behaviour: > 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). +> **Covers/metadata:** themes and DLC have no cover art or game metadata of +> their own — the app scrapes them like games (IGDB/libretro/etc.), which can +> return unrelated results. Downloaded themes keep their embedded preview as +> the primary cover (used instead of a scraped image when available). ## Server setup diff --git a/app/covers.py b/app/covers.py index cde51c7..c1794dd 100644 --- a/app/covers.py +++ b/app/covers.py @@ -30,15 +30,6 @@ _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).""" @@ -1151,8 +1142,6 @@ 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 2ed5a20..5b13389 100644 --- a/app/gui.py +++ b/app/gui.py @@ -123,11 +123,10 @@ class CoverLoader(QObject): from concurrent.futures import ThreadPoolExecutor, as_completed pool = ThreadPoolExecutor(max_workers=8) - 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 + futures = { + pool.submit(load_cover, it): i + for i, it in enumerate(self.items) + } try: for fut in as_completed(futures): if self._cancel.is_set(): @@ -212,8 +211,6 @@ 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) @@ -377,13 +374,6 @@ class CoverShowcase(QWidget): item.setTextAlignment(Qt.AlignHCenter | Qt.AlignTop) item.setToolTip(it["name"]) item.setData(Qt.UserRole, it) - # Themes/DLC show their locally-extracted preview (no scraping). - if not covers.is_scrapable(it.get("system")): - rid = it.get("release_id") - system = it.get("system") - path = covers.cover_path(rid, system) - if path is not None and path.exists() and covers.file_is_image(path): - item.setIcon(self._fit_icon(QPixmap(str(path)))) self.list.addItem(item) self._start_loader(gen) @@ -484,7 +474,6 @@ class CoverShowcase(QWidget): 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 and covers.is_scrapable(it.get("system")): menu.addAction("Scrape…", lambda: self.scrape_requested.emit(it)) menu.exec(self.list.mapToGlobal(pos)) @@ -2032,13 +2021,6 @@ 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): - # Themes/DLC aren't scraped, but a downloaded theme's preview is - # cached locally — show it when available. - 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))) - return 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))) @@ -2205,9 +2187,6 @@ 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) @@ -2239,8 +2218,6 @@ 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 @@ -2264,8 +2241,7 @@ 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"))) - if covers.is_scrapable(it.get("system")): - menu.addAction("Scrape…", lambda: self._search_cover(it)) + menu.addAction("Scrape…", lambda: self._search_cover(it)) menu.exec(self.grid.mapToGlobal(pos)) def _on_table_menu(self, pos): @@ -2280,14 +2256,11 @@ 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"))) - if covers.is_scrapable(it.get("system")): - menu.addAction("Scrape…", lambda: self._search_cover(it)) + 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: @@ -2324,8 +2297,6 @@ 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 @@ -4445,9 +4416,6 @@ 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) @@ -4508,8 +4476,6 @@ 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 1601fa0..380e79e 100644 --- a/app/info.py +++ b/app/info.py @@ -182,8 +182,6 @@ 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