ps1: preview EBOOT icon/background in the PS1 tab before converting

Selecting a pending disc in the PS1 tab now shows a live preview of the
ICON0 (144x80 XMB icon) and PIC1 (480x272 launch background) that the
converter would embed, fetched asynchronously so the UI stays responsive.
Converted discs/EBOOTs show an 'already converted' notice, and ps1_art now
falls back to a name-derived cache key when a disc has no serial.
This commit is contained in:
2026-09-07 18:42:46 -04:00
parent fb2a98dd21
commit c1fcf6176d
2 changed files with 118 additions and 2 deletions
+2 -1
View File
@@ -861,8 +861,9 @@ def ps1_art(title, release_id, timeout=10):
sources (libretro, ScreenScraper, IGDB, Wikipedia). Returns
``(None, None)`` when no art can be produced.
"""
key = release_id or stable_key(title)
try:
path = fetch_cover(title, release_id, "ps1", timeout=timeout)
path = fetch_cover(title, key, "ps1", timeout=timeout)
except Exception: # noqa: BLE001
return None, None
if path is None:
+116 -1
View File
@@ -3365,6 +3365,27 @@ class Ps1ScanWorker(QObject):
self.failed.emit(str(exc))
class Ps1PreviewWorker(QObject):
"""Fetch a disc's cover art and build the EBOOT icon/background preview."""
result = Signal(int, str, object, object) # gen, game_id, icon0 bytes, pic1 bytes
finished = Signal()
def __init__(self, gen, title, game_id):
super().__init__()
self.gen = gen
self.title = title
self.game_id = game_id
def run(self):
try:
icon0, pic1 = covers.ps1_art(self.title, self.game_id)
except Exception: # noqa: BLE001
icon0, pic1 = None, None
self.result.emit(self.gen, self.game_id, icon0, pic1)
self.finished.emit()
class Ps1Page(QWidget):
"""PS1 tab: list raw source discs, converted EBOOTs and PSN games."""
@@ -3377,6 +3398,9 @@ class Ps1Page(QWidget):
self._entries = []
self._thread = None
self._worker = None
self._preview_gen = 0
self._preview_threads = []
self._preview_workers = []
layout = QVBoxLayout(self)
bar = QHBoxLayout()
@@ -3396,7 +3420,98 @@ class Ps1Page(QWidget):
self.table.setEditTriggers(QAbstractItemView.NoEditTriggers)
self.table.verticalHeader().setVisible(False)
self.table.horizontalHeader().setSectionResizeMode(1, QHeaderView.Stretch)
layout.addWidget(self.table, 1)
self.table.itemSelectionChanged.connect(self._on_preview_changed)
self.preview_title = QLabel("Select a disc to preview its XMB look.")
self.preview_title.setWordWrap(True)
self.preview_title.setStyleSheet("font-weight: bold;")
self.preview_icon0 = QLabel()
self.preview_icon0.setAlignment(Qt.AlignCenter)
self.preview_icon0.setFixedSize(288, 160)
self.preview_icon0.setStyleSheet("background: #14151a; border: 1px solid #333; color: #888;")
self.preview_pic1 = QLabel()
self.preview_pic1.setAlignment(Qt.AlignCenter)
self.preview_pic1.setFixedSize(480, 272)
self.preview_pic1.setStyleSheet("background: #14151a; border: 1px solid #333; color: #888;")
preview = QWidget()
pv = QVBoxLayout(preview)
pv.setContentsMargins(8, 0, 0, 0)
pv.addWidget(QLabel("<b>Preview</b>"))
pv.addWidget(self.preview_title)
pv.addWidget(QLabel("ICON0 — XMB icon"))
pv.addWidget(self.preview_icon0)
pv.addWidget(QLabel("PIC1 — launch background"))
pv.addWidget(self.preview_pic1)
pv.addStretch(1)
splitter = QSplitter(Qt.Horizontal)
splitter.addWidget(self.table)
splitter.addWidget(preview)
splitter.setStretchFactor(0, 1)
layout.addWidget(splitter, 1)
def _on_preview_changed(self):
row = self.table.currentRow()
if row < 0 or row >= len(self._entries):
self._reset_preview("Select a disc to preview its XMB look.")
return
e = self._entries[row]
if e["kind"] != "disc":
self._reset_preview("This game is already converted.")
return
if e.get("status") == "converted":
self._reset_preview("This disc has already been converted.")
return
self.preview_title.setText(e["name"])
self.preview_icon0.setPixmap(QPixmap())
self.preview_icon0.setText("Loading…")
self.preview_pic1.setPixmap(QPixmap())
self.preview_pic1.setText("Loading…")
self._preview_gen += 1
self._start_preview(self._preview_gen, e["name"], e.get("serial") or "")
def _start_preview(self, gen, title, game_id):
worker = Ps1PreviewWorker(gen, title, game_id)
thread = QThread(self)
worker.moveToThread(thread)
thread.started.connect(worker.run)
worker.result.connect(self._on_preview_result)
worker.finished.connect(thread.quit)
worker.finished.connect(worker.deleteLater)
thread.finished.connect(thread.deleteLater)
thread.start()
self._preview_threads.append(thread)
self._preview_workers.append(worker)
def _on_preview_result(self, gen, game_id, icon0, pic1):
if gen != self._preview_gen:
return
self.preview_icon0.setText("")
self.preview_pic1.setText("")
if icon0:
pm = QPixmap()
pm.loadFromData(icon0)
self.preview_icon0.setPixmap(
pm.scaled(self.preview_icon0.width(), self.preview_icon0.height(),
Qt.KeepAspectRatio, Qt.SmoothTransformation))
else:
self.preview_icon0.setText("No art found")
if pic1:
pm = QPixmap()
pm.loadFromData(pic1)
self.preview_pic1.setPixmap(
pm.scaled(self.preview_pic1.width(), self.preview_pic1.height(),
Qt.KeepAspectRatio, Qt.SmoothTransformation))
else:
self.preview_pic1.setText("No art found — the default icon will be used")
def _reset_preview(self, message):
self.preview_title.setText(message)
self.preview_icon0.setPixmap(QPixmap())
self.preview_icon0.setText("")
self.preview_pic1.setPixmap(QPixmap())
self.preview_pic1.setText("")
def set_dirs(self, ps1_dir, output_dir):
self._ps1_dir = ps1_dir