diff --git a/app/covers.py b/app/covers.py index 0144a78..d36a923 100644 --- a/app/covers.py +++ b/app/covers.py @@ -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: diff --git a/app/gui.py b/app/gui.py index 88ec646..ea0f348 100644 --- a/app/gui.py +++ b/app/gui.py @@ -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("Preview")) + 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