Scrape: include libretro + OpenVGDB covers alongside IGDB
This commit is contained in:
+97
-14
@@ -466,6 +466,33 @@ def _ovgdb_name_list():
|
||||
return _ovgdb_names
|
||||
|
||||
|
||||
_ovgdb_index = None
|
||||
|
||||
|
||||
def _load_ovgdb_index():
|
||||
"""Load {key: {"title", "url"}} written by app/openvgdb.py, or {}.
|
||||
|
||||
Looks in the runtime covers dir first, then the shipped ``data/covers``
|
||||
copy (so a fresh install still shows readable titles without re-running
|
||||
the full OpenVGDB scrape).
|
||||
"""
|
||||
global _ovgdb_index
|
||||
if _ovgdb_index is not None:
|
||||
return _ovgdb_index
|
||||
_ovgdb_index = {}
|
||||
for path in (
|
||||
config.COVERS_DIR / "openvgdb-index.json",
|
||||
config.LEGACY_COVERS_DIR / "openvgdb-index.json",
|
||||
):
|
||||
if path.exists():
|
||||
try:
|
||||
_ovgdb_index = json.loads(path.read_text(encoding="utf-8"))
|
||||
break
|
||||
except Exception: # noqa: BLE001
|
||||
_ovgdb_index = {}
|
||||
return _ovgdb_index
|
||||
|
||||
|
||||
def fetch_openvgdb_local(name):
|
||||
"""Return a bundled OpenVGDB cover image for `name`, or None."""
|
||||
names = _ovgdb_name_list()
|
||||
@@ -1028,6 +1055,45 @@ def _search_libretro(query, limit):
|
||||
return results
|
||||
|
||||
|
||||
def _search_openvgdb(query, limit):
|
||||
"""Return [{title, source, thumb_url, image_url}] from the offline OpenVGDB
|
||||
bundle, using the display titles stored in openvgdb-index.json."""
|
||||
names = _ovgdb_name_list()
|
||||
if not names:
|
||||
return []
|
||||
index = _load_ovgdb_index()
|
||||
db = _norm(query)
|
||||
if not db:
|
||||
return []
|
||||
scored = []
|
||||
for key in names:
|
||||
if key == db:
|
||||
score = 5
|
||||
elif len(key) >= 4 and key.startswith(db):
|
||||
score = 4
|
||||
elif len(key) >= 4 and db in key:
|
||||
score = 3
|
||||
elif len(db) >= 4 and len(key) >= 4 and key in db:
|
||||
score = 2
|
||||
else:
|
||||
continue
|
||||
info = index.get(key) or {}
|
||||
title = info.get("title") or key
|
||||
scored.append((score, title, key))
|
||||
scored.sort(key=lambda t: (-t[0], t[1]))
|
||||
return [
|
||||
{
|
||||
"id": None,
|
||||
"title": title,
|
||||
"source": "openvgdb",
|
||||
"thumb_url": "",
|
||||
"image_url": "",
|
||||
"_key": key,
|
||||
}
|
||||
for _score, title, key in scored[:limit]
|
||||
]
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Game metadata (details sidebar)
|
||||
# --------------------------------------------------------------------------- #
|
||||
@@ -1411,29 +1477,46 @@ def movie_metadata_from_wikipedia(page_title, timeout=10):
|
||||
|
||||
|
||||
def search_metadata_candidates(system, query, limit=20, timeout=10):
|
||||
"""Search candidates the user can link an item to."""
|
||||
"""Search candidates the user can link an item to.
|
||||
|
||||
Games: IGDB (rich metadata) first, then libretro box art and OpenVGDB
|
||||
(GameFAQs) box art so covers can be scraped even without an IGDB key.
|
||||
"""
|
||||
if system == "videos":
|
||||
return search_movies(query, limit, timeout)
|
||||
results = search_games_igdb(query, limit, timeout)
|
||||
if results:
|
||||
return results
|
||||
# No IGDB results (or no key): fall back to libretro catalog names so the
|
||||
# user can still link to the correct entry (title-only).
|
||||
results = _search_libretro(query, limit)
|
||||
for r in results:
|
||||
r["source"] = "libretro"
|
||||
r["id"] = None
|
||||
return results
|
||||
results = []
|
||||
seen = set()
|
||||
|
||||
def add(r):
|
||||
key = (r.get("source"), _norm(r.get("title") or ""))
|
||||
if key[1] and key not in seen:
|
||||
seen.add(key)
|
||||
results.append(r)
|
||||
|
||||
for r in search_games_igdb(query, limit, timeout):
|
||||
add(r)
|
||||
for r in _search_libretro(query, limit):
|
||||
r.setdefault("id", None)
|
||||
add(r)
|
||||
for r in _search_openvgdb(query, limit):
|
||||
add(r)
|
||||
return results[:limit]
|
||||
|
||||
|
||||
def link_metadata(system, candidate, timeout=10):
|
||||
"""Fetch full metadata for a search candidate the user picked."""
|
||||
if candidate.get("source") == "wikipedia":
|
||||
return movie_metadata_from_wikipedia(candidate.get("title") or "", timeout)
|
||||
if candidate.get("source") == "libretro":
|
||||
# Catalog-name link only: no metadata backend, just the correct title.
|
||||
if candidate.get("source") in ("libretro", "openvgdb"):
|
||||
# Cover repositories have no metadata backend; try IGDB by title so the
|
||||
# user still gets synopsis/genre/etc. when a key is configured.
|
||||
title = candidate.get("title") or ""
|
||||
if title:
|
||||
meta = get_game_metadata_igdb(title, timeout)
|
||||
if meta:
|
||||
return meta
|
||||
return {
|
||||
"title": candidate.get("title") or "",
|
||||
"title": title,
|
||||
"release_date": "",
|
||||
"overview": "",
|
||||
"genres": [],
|
||||
|
||||
Reference in New Issue
Block a user