From 43263aa64457b9f3a2d7c0d165c1778170f71b52 Mon Sep 17 00:00:00 2001 From: Mathieu Moran Date: Mon, 7 Sep 2026 19:29:19 -0400 Subject: [PATCH] ps1: IGDB-first covers, elided PIC0 text, drop PS3 badge - Cover art: try IGDB first for PS1 so the cover is clean box art without the PlayStation banner that libretro/ScreenScraper thumbnails bake in (redundant with the ICON0 case frame). - PIC0: elide long titles with an ellipsis and word-wrap the description to 3 lines with a trailing ellipsis instead of clipping/overflowing. - PIC0: remove the PS3 badge; PSP only for now (PS3/PS4 packaging is future work). --- app/covers.py | 114 ++++++++++++++++++++++++++++++++------------------ 1 file changed, 73 insertions(+), 41 deletions(-) diff --git a/app/covers.py b/app/covers.py index 0181cd8..2ab8248 100644 --- a/app/covers.py +++ b/app/covers.py @@ -952,28 +952,31 @@ def _make_pic0(title, description, esrb, QImage, QPainter, QPainterPath, QColor, _draw_ps_glyphs(p, W - 34, 12, 22, QColor(200, 200, 205), QRectF, QPointF, QPolygonF, QPen, Qt) - # Title (bold, upper-left). + # Title (bold, upper-left), elided so long titles end with an ellipsis + # instead of clipping mid-character. f_title = QFont("DejaVu Sans") f_title.setPixelSize(19) f_title.setBold(True) p.setFont(f_title) p.setPen(QColor(255, 255, 255)) - p.drawText(QRectF(18, 16, W - 64, 28), Qt.AlignLeft | Qt.AlignVCenter, - title or "") + title_width = W - 70 + fm_title = QFontMetrics(f_title) + title_text = fm_title.elidedText(title or "", Qt.ElideRight, title_width) + p.drawText(QRectF(18, 16, title_width, 28), Qt.AlignLeft | Qt.AlignVCenter, + title_text) - # Description (wrapped, below title). + # Description (wrapped to at most 3 lines, last line elided). if description: f_desc = QFont("DejaVu Sans") f_desc.setPixelSize(12) p.setFont(f_desc) p.setPen(QColor(215, 215, 215)) - text = description.strip() fm = QFontMetrics(f_desc) - # Rough 3-line budget (~40 chars/line at 12px across 272px). - if fm.horizontalAdvance(text) > 3 * (W - 36): - text = text[:130].rstrip() + "\u2026" - p.drawText(QRectF(18, 46, W - 36, 74), - Qt.AlignLeft | Qt.AlignTop | Qt.TextWordWrap, text) + lines = _wrap_elided(fm, description.strip(), W - 36, 3, Qt) + y = 46 + for line in lines: + p.drawText(QRectF(18, y, W - 36, 16), Qt.AlignLeft | Qt.AlignVCenter, line) + y += 16 # ESRB badge (bottom-left) + caption. if esrb: @@ -981,9 +984,8 @@ def _make_pic0(title, description, esrb, QImage, QPainter, QPainterPath, QColor, if badge: _draw_esrb(p, badge, 18, 140, QRectF, QColor, QFont, QFontMetrics, QPen, Qt) - # Platform badges (bottom-right). - _draw_platform_badge(p, "PS3", W - 14 - 64, 146, QColor(94, 146, 210), - QRectF, QColor, QFont, QFontMetrics, Qt) + # Platform badge (bottom-right). Only PSP for now; PS3/PS4 packaging is a + # separate future feature. _draw_platform_badge(p, "PSP", W - 14 - 30, 146, QColor(212, 175, 55), QRectF, QColor, QFont, QFontMetrics, Qt) @@ -991,6 +993,28 @@ def _make_pic0(title, description, esrb, QImage, QPainter, QPainterPath, QColor, return _save(img) +def _wrap_elided(fm, text, width, max_lines, Qt): + """Word-wrap ``text`` to ``width`` px, eliding the final line when it runs + past ``max_lines``. Returns a list of up to ``max_lines`` strings.""" + words = text.split() + lines = [] + cur = "" + for i, w in enumerate(words): + cand = (cur + " " + w).strip() if cur else w + if fm.horizontalAdvance(cand) <= width: + cur = cand + continue + if len(lines) == max_lines - 1: + rest = " ".join([cur] + words[i:]) if cur else " ".join(words[i:]) + lines.append(fm.elidedText(rest, Qt.ElideRight, width)) + return lines + lines.append(cur) + cur = w + if cur: + lines.append(cur) + return lines[:max_lines] + + def _draw_ps_glyphs(p, x, y, size, color, QRectF, QPointF, QPolygonF, QPen, Qt): """Draw the four PlayStation face-button glyphs in a 2x2 grid.""" cell = size / 2.0 @@ -1133,34 +1157,42 @@ def fetch_cover(name, release_id, system, timeout=10, quick=False): if data is not None: log.info("cover %r <- manual", name) elif system != "videos": - # 2. Games/PS1: libretro (full box art) first, then the shipped - # OpenVGDB bundle (PSP only, offline), then ScreenScraper (non-quick), - # then Wikipedia (keyless, non-quick), then IGDB last. - data = _fetch_libretro(name, t, system=system, indexed_only=quick) - if data is not None: - log.info("cover %r <- libretro", name) - else: - data = None - if system == "games": - data = fetch_openvgdb_local(name) - if data is not None: - log.info("cover %r <- openvgdb", name) - if data is None and not quick and time.time() < deadline: - data = _fetch_screenscraper(name, t, system=system) - if data is not None: - log.info("cover %r <- screenscraper", name) - if data is None and not quick and time.time() < deadline: - data = _fetch_wikipedia_cover(name, t) - if data is not None: - log.info("cover %r <- wikipedia", name) - if data is None and time.time() < deadline: - data = _fetch_igdb_cover(name, t, system=system) - if data is not None: - if system == "games": - # IGDB only has cropped 3:4 PSP key art; composite it - # into the full PSP cover template (header + artwork). - data = compose_game_cover(data) - log.info("cover %r <- igdb", name) + # 2. PS1: IGDB first — its covers are clean box art without the + # PlayStation banner that libretro/ScreenScraper thumbnails bake in + # (redundant with our ICON0 frame). Games: libretro first, then the + # shipped OpenVGDB bundle (offline), then ScreenScraper (non-quick), + # then Wikipedia (keyless), then IGDB last. + data = None + if system == "ps1" and not quick and time.time() < deadline: + data = _fetch_igdb_cover(name, t, system=system) + if data is not None: + log.info("cover %r <- igdb", name) + if data is None: + data = _fetch_libretro(name, t, system=system, indexed_only=quick) + if data is not None: + log.info("cover %r <- libretro", name) + else: + data = None + if system == "games": + data = fetch_openvgdb_local(name) + if data is not None: + log.info("cover %r <- openvgdb", name) + if data is None and not quick and time.time() < deadline: + data = _fetch_screenscraper(name, t, system=system) + if data is not None: + log.info("cover %r <- screenscraper", name) + if data is None and not quick and time.time() < deadline: + data = _fetch_wikipedia_cover(name, t) + if data is not None: + log.info("cover %r <- wikipedia", name) + if data is None and system != "ps1" and time.time() < deadline: + data = _fetch_igdb_cover(name, t, system=system) + if data is not None: + if system == "games": + # IGDB only has cropped 3:4 PSP key art; composite + # it into the full PSP cover template. + data = compose_game_cover(data) + log.info("cover %r <- igdb", name) if data is None: _neg_cache[key] = time.time()