Fix scrape crash + cover compose responsiveness
- Cache the UMD/game cover templates AND their transparent-window rects (computed once, under a lock). The old code re-ran an O(w*h) Python pixel scan on every composited cover, stalling video cover loading and scraping for ~1s per cover with GIL contention. - ScrapeDialog: ignore stale search/scrape results via a generation counter and cancel in-flight workers when a new search/scrape starts; clear threads on close. - MetadataWorker (library details): cancel stale metadata fetches and delete finished workers/threads instead of leaking one per item selection.
This commit is contained in:
+33
-22
@@ -773,11 +773,16 @@ def save_cover(release_id, system, data):
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Game cover compositing (IGDB key art -> full PSP cover)
|
||||
# --------------------------------------------------------------------------- #
|
||||
_game_template_image = None
|
||||
_game_template_cache = None # (template QImage or None, window (x, y, w, h))
|
||||
_game_template_lock = threading.Lock()
|
||||
|
||||
|
||||
def _transparent_rect(img):
|
||||
"""Bounding box of the fully-transparent pixels in `img`."""
|
||||
"""Bounding box of the fully-transparent pixels in `img`.
|
||||
|
||||
Run once per template (the result is cached) — an O(w*h) scan is far too
|
||||
slow to repeat for every composed cover.
|
||||
"""
|
||||
minx, miny, maxx, maxy = img.width(), img.height(), -1, -1
|
||||
for y in range(img.height()):
|
||||
for x in range(img.width()):
|
||||
@@ -790,24 +795,31 @@ def _transparent_rect(img):
|
||||
|
||||
|
||||
def _game_template():
|
||||
"""Load the PSP game cover template (top header + transparent window)."""
|
||||
global _game_template_image
|
||||
if _game_template_image is None:
|
||||
try:
|
||||
from PySide6.QtGui import QImage
|
||||
except Exception: # noqa: BLE001 - headless API may lack PySide6
|
||||
return None
|
||||
img = QImage()
|
||||
for path in (
|
||||
config.COVERS_DIR / "UMD_GAME_COVER_TEMPLATE.png",
|
||||
config.DATA_DIR / "covers" / "UMD_GAME_COVER_TEMPLATE.png",
|
||||
):
|
||||
if path.exists():
|
||||
img.load(str(path))
|
||||
if not img.isNull():
|
||||
break
|
||||
_game_template_image = img.convertToFormat(QImage.Format_ARGB32) if not img.isNull() else None
|
||||
return _game_template_image
|
||||
"""Load the PSP game cover template; return ``(template, window)``."""
|
||||
global _game_template_cache
|
||||
if _game_template_cache is None:
|
||||
with _game_template_lock:
|
||||
if _game_template_cache is None:
|
||||
try:
|
||||
from PySide6.QtGui import QImage
|
||||
except Exception: # noqa: BLE001 - headless API may lack PySide6
|
||||
_game_template_cache = (None, (0, 0, 0, 0))
|
||||
return _game_template_cache
|
||||
img = QImage()
|
||||
for path in (
|
||||
config.COVERS_DIR / "UMD_GAME_COVER_TEMPLATE.png",
|
||||
config.DATA_DIR / "covers" / "UMD_GAME_COVER_TEMPLATE.png",
|
||||
):
|
||||
if path.exists():
|
||||
img.load(str(path))
|
||||
if not img.isNull():
|
||||
break
|
||||
img = img.convertToFormat(QImage.Format_ARGB32) if not img.isNull() else None
|
||||
_game_template_cache = (
|
||||
img,
|
||||
_transparent_rect(img) if img is not None and not img.isNull() else (0, 0, 0, 0),
|
||||
)
|
||||
return _game_template_cache
|
||||
|
||||
|
||||
def compose_game_cover(key_art_bytes):
|
||||
@@ -822,14 +834,13 @@ def compose_game_cover(key_art_bytes):
|
||||
except Exception: # noqa: BLE001
|
||||
return key_art_bytes
|
||||
|
||||
template = _game_template()
|
||||
template, (x, y, w, h) = _game_template()
|
||||
art = QImage.fromData(key_art_bytes)
|
||||
if template is None or template.isNull() or art.isNull():
|
||||
return key_art_bytes
|
||||
art = art.convertToFormat(QImage.Format_ARGB32)
|
||||
out = QImage(template.size(), QImage.Format_ARGB32)
|
||||
out.fill(Qt.transparent)
|
||||
x, y, w, h = _transparent_rect(template)
|
||||
|
||||
painter = QPainter(out)
|
||||
if w > 0 and h > 0:
|
||||
|
||||
Reference in New Issue
Block a user