Add PS1 games to the Library with covers, metadata, and EBOOT actions
This commit is contained in:
+103
-9
@@ -559,6 +559,7 @@ class MetadataWorker(QObject):
|
||||
class GameDetailsPanel(QWidget):
|
||||
edit_requested = Signal() # emitted when the user wants to hand-tag the item
|
||||
link_requested = Signal() # emitted when the user wants to pick a movie/game to link
|
||||
convert_requested = Signal() # emitted when the user wants to convert a PS1 disc to EBOOT
|
||||
copy_requested = Signal() # emitted when the user wants to copy the current item to the PSP
|
||||
delete_requested = Signal() # emitted when the user wants to delete the current item
|
||||
play_requested = Signal() # emitted when the user wants to launch the item in PPSSPP
|
||||
@@ -569,6 +570,7 @@ class GameDetailsPanel(QWidget):
|
||||
self.setFixedWidth(300)
|
||||
self._youtube_url = ""
|
||||
self._meta = None
|
||||
self._copy_available = False
|
||||
|
||||
outer = QVBoxLayout(self)
|
||||
outer.setContentsMargins(12, 12, 0, 12)
|
||||
@@ -627,6 +629,12 @@ class GameDetailsPanel(QWidget):
|
||||
self.play_btn.setToolTip("Launch this game in PPSSPP")
|
||||
self.layout.addWidget(self.play_btn)
|
||||
|
||||
self.convert_btn = QPushButton("\u2699 Convert to EBOOT")
|
||||
self.convert_btn.clicked.connect(self.convert_requested.emit)
|
||||
self.convert_btn.setVisible(False)
|
||||
self.convert_btn.setToolTip("Convert this PS1 disc to an EBOOT.PBP")
|
||||
self.layout.addWidget(self.convert_btn)
|
||||
|
||||
btn_grid = QGridLayout()
|
||||
btn_grid.setHorizontalSpacing(6)
|
||||
btn_grid.setVerticalSpacing(6)
|
||||
@@ -658,14 +666,28 @@ class GameDetailsPanel(QWidget):
|
||||
|
||||
def set_item(self, item):
|
||||
self.system = item.get("system")
|
||||
self._ps1_kind = item.get("ps1_kind")
|
||||
path = item.get("path") or ""
|
||||
self.play_btn.setEnabled(item.get("system") == "games" and bool(path) and Path(path).suffix.lower() == ".iso")
|
||||
is_ps1 = item.get("system") == "ps1"
|
||||
is_ps1_disc = is_ps1 and item.get("ps1_kind") == "disc"
|
||||
self.play_btn.setEnabled(
|
||||
(item.get("system") == "games" and bool(path) and Path(path).suffix.lower() == ".iso")
|
||||
or (is_ps1 and item.get("ps1_kind") == "eboot" and bool(path))
|
||||
)
|
||||
self.play_btn.setVisible(not is_ps1_disc)
|
||||
self.convert_btn.setVisible(is_ps1_disc)
|
||||
self.copy_btn.setEnabled(self._copy_available and not is_ps1_disc)
|
||||
self.copy_btn.setToolTip(
|
||||
"Copy this EBOOT to /PSP/GAME" if is_ps1 else "Copy this disc to the connected PSP"
|
||||
)
|
||||
self.del_psp_btn.setEnabled(bool(item.get("on_psp")))
|
||||
self.title.setText(item.get("title_override") or item.get("name") or "Unknown")
|
||||
parts = []
|
||||
if item.get("serial"):
|
||||
parts.append(item["serial"])
|
||||
if item.get("region"):
|
||||
if is_ps1 and item.get("ps1_status"):
|
||||
parts.append(item["ps1_status"])
|
||||
elif item.get("region"):
|
||||
parts.append(item["region"])
|
||||
self.subtitle.setText(" · ".join(parts))
|
||||
self._clear_meta()
|
||||
@@ -676,7 +698,8 @@ class GameDetailsPanel(QWidget):
|
||||
self._set_cover(item.get("release_id"), item.get("system"))
|
||||
|
||||
def set_copy_available(self, available):
|
||||
self.copy_btn.setEnabled(bool(available))
|
||||
self._copy_available = bool(available)
|
||||
self.copy_btn.setEnabled(self._copy_available and getattr(self, "_ps1_kind", None) != "disc")
|
||||
|
||||
def set_psp_installed(self, on_psp):
|
||||
self.del_psp_btn.setEnabled(bool(on_psp))
|
||||
@@ -875,7 +898,7 @@ def _scrape_cover(system, candidate, rid, timeout=20):
|
||||
if data:
|
||||
if system == "videos":
|
||||
return compose_umd_cover(data)
|
||||
if candidate.get("source") == "igdb":
|
||||
if candidate.get("source") == "igdb" and system == "games":
|
||||
return covers.compose_game_cover(data)
|
||||
return data
|
||||
if title:
|
||||
@@ -3024,7 +3047,7 @@ class PspSyncPage(QWidget):
|
||||
self._workers = []
|
||||
|
||||
|
||||
def build_library_items():
|
||||
def build_library_items(ps1_dir="", output_dir=""):
|
||||
"""Build the library item list from the DB with a single joined query."""
|
||||
items = []
|
||||
seen = set()
|
||||
@@ -3093,16 +3116,59 @@ def build_library_items():
|
||||
conn.commit()
|
||||
finally:
|
||||
conn.close()
|
||||
items.extend(_ps1_library_items(ps1_dir, output_dir, overrides, dates))
|
||||
return items
|
||||
|
||||
|
||||
def _ps1_library_items(ps1_dir, output_dir, overrides, dates):
|
||||
"""PS1 discs (pending) + converted EBOOTs, merged into the library list."""
|
||||
if not ps1_dir:
|
||||
return []
|
||||
try:
|
||||
entries = ps1.scan_library(ps1_dir, output_dir)
|
||||
except Exception: # noqa: BLE001
|
||||
return []
|
||||
out = []
|
||||
for e in entries:
|
||||
# A converted disc is represented by its EBOOT entry (one row per game).
|
||||
if e["kind"] == "disc" and e.get("status") == "converted":
|
||||
continue
|
||||
name = e["name"]
|
||||
rid = covers.stable_key(name)
|
||||
path = Path(e["path"])
|
||||
try:
|
||||
size = path.stat().st_size if path.exists() else 0
|
||||
except OSError:
|
||||
size = 0
|
||||
out.append({
|
||||
"name": name,
|
||||
"release_id": rid,
|
||||
"system": "ps1",
|
||||
"canonical": name,
|
||||
"serial": e.get("serial") or "",
|
||||
"region": None,
|
||||
"title_override": overrides.get(("ps1", str(rid))),
|
||||
"release_date": dates.get((str(rid), "ps1")) or "",
|
||||
"path": str(path),
|
||||
"size": size,
|
||||
"ps1_kind": e["kind"],
|
||||
"ps1_status": e.get("status") or "pending",
|
||||
})
|
||||
return out
|
||||
|
||||
|
||||
class LibraryWorker(QObject):
|
||||
result = Signal(list)
|
||||
finished = Signal()
|
||||
|
||||
def __init__(self, ps1_dir="", output_dir=""):
|
||||
super().__init__()
|
||||
self.ps1_dir = ps1_dir
|
||||
self.output_dir = output_dir
|
||||
|
||||
def run(self):
|
||||
try:
|
||||
items = build_library_items()
|
||||
items = build_library_items(self.ps1_dir, self.output_dir)
|
||||
except Exception: # noqa: BLE001
|
||||
items = []
|
||||
self.result.emit(items)
|
||||
@@ -3470,7 +3536,7 @@ class MainWindow(QMainWindow):
|
||||
self.library_search.textChanged.connect(self._apply_library_filter)
|
||||
bar.addWidget(self.library_search, 1)
|
||||
self.library_type = QComboBox()
|
||||
self.library_type.addItems(["All types", "Games", "Videos"])
|
||||
self.library_type.addItems(["All types", "Games", "Videos", "PS1"])
|
||||
self.library_type.currentTextChanged.connect(self._apply_library_filter)
|
||||
bar.addWidget(self.library_type)
|
||||
self.library_psp = QComboBox()
|
||||
@@ -3520,6 +3586,7 @@ class MainWindow(QMainWindow):
|
||||
self.cover_grid.scrape_requested.connect(self._scrape_grid_item)
|
||||
self.details_panel.edit_requested.connect(self._edit_details)
|
||||
self.details_panel.link_requested.connect(self._scrape_item)
|
||||
self.details_panel.convert_requested.connect(self._convert_current_item)
|
||||
self.details_panel.copy_requested.connect(self._copy_current_to_psp)
|
||||
self.details_panel.delete_requested.connect(self._delete_current)
|
||||
self.details_panel.delete_psp_requested.connect(self._delete_current_from_psp)
|
||||
@@ -3688,7 +3755,7 @@ class MainWindow(QMainWindow):
|
||||
self.statusBar().showMessage("Loading library…")
|
||||
log.info("loading library…")
|
||||
thread = QThread(self)
|
||||
worker = LibraryWorker()
|
||||
worker = LibraryWorker(self._ps1_dir, self._ps1_output_dir)
|
||||
worker.moveToThread(thread)
|
||||
thread.started.connect(worker.run)
|
||||
worker.result.connect(self._on_library_loaded)
|
||||
@@ -3726,6 +3793,8 @@ class MainWindow(QMainWindow):
|
||||
continue
|
||||
if typ == "Videos" and it["system"] != "videos":
|
||||
continue
|
||||
if typ == "PS1" and it["system"] != "ps1":
|
||||
continue
|
||||
if q and q not in (it["canonical"] or it["name"]).lower():
|
||||
continue
|
||||
if psp_f == "On PSP" and not it.get("on_psp"):
|
||||
@@ -3751,7 +3820,8 @@ class MainWindow(QMainWindow):
|
||||
title.setIcon(self._psp_dot_icon())
|
||||
title.setToolTip("Installed on PSP")
|
||||
self.library_table.setItem(row, 0, title)
|
||||
self.library_table.setItem(row, 1, QTableWidgetItem("Game" if it["system"] == "games" else "Video"))
|
||||
kind_label = {"games": "Game", "videos": "Video", "ps1": "PS1"}.get(it["system"], "Game")
|
||||
self.library_table.setItem(row, 1, QTableWidgetItem(kind_label))
|
||||
self.library_table.setItem(row, 2, QTableWidgetItem(it.get("region") or ""))
|
||||
self.library_table.setItem(row, 3, QTableWidgetItem(it.get("serial") or ""))
|
||||
size = it.get("size")
|
||||
@@ -3886,6 +3956,18 @@ class MainWindow(QMainWindow):
|
||||
self.details_panel.set_item(it)
|
||||
self._scrape_item()
|
||||
|
||||
def _convert_current_item(self):
|
||||
item = getattr(self, "_current_item", None)
|
||||
if item is None or item.get("system") != "ps1" or item.get("ps1_kind") != "disc":
|
||||
return
|
||||
game_id = item.get("serial") or ""
|
||||
if not game_id:
|
||||
text, ok = QInputDialog.getText(self, "PS1", "Game ID (e.g. SLUS00594):")
|
||||
if not ok or not text.strip():
|
||||
return
|
||||
game_id = text.strip()
|
||||
self._on_ps1_convert({"source": item.get("path"), "title": item.get("name"), "game_id": game_id})
|
||||
|
||||
def _on_store_download(self, items):
|
||||
if not items:
|
||||
return
|
||||
@@ -3967,6 +4049,8 @@ class MainWindow(QMainWindow):
|
||||
return
|
||||
if item.get("system") == "videos":
|
||||
return # UMD Video discs can't be played in PPSSPP
|
||||
if item.get("system") == "ps1" and item.get("ps1_kind") != "eboot":
|
||||
return # raw PS1 discs are converted, not played
|
||||
path = item.get("path")
|
||||
if not path or not Path(path).exists():
|
||||
QMessageBox.warning(self, "Missing file", f"File not found:\n{path}")
|
||||
@@ -4039,6 +4123,15 @@ class MainWindow(QMainWindow):
|
||||
psp_root = Path(self._psp_root)
|
||||
if system == "videos":
|
||||
dest_dir = psp_root / "ISO" / "VIDEO"
|
||||
elif system == "ps1":
|
||||
# PS1 EBOOTs install to /PSP/GAME/<folder>/EBOOT.PBP.
|
||||
folder = item.get("serial") or item.get("name") or "PS1"
|
||||
folder = "".join(c for c in folder if c not in '/\\:*?"<>|').strip() or "PS1"
|
||||
dest_dir = psp_root / "PSP" / "GAME" / folder
|
||||
log.info("copying ps1 eboot to psp: %s -> %s", item["path"], dest_dir / "EBOOT.PBP")
|
||||
self.queue.enqueue(transfers.FileCopySource(item["path"]), dest_dir,
|
||||
{"dest_name": "EBOOT.PBP"}, item.get("name") or folder, kind="psp-install")
|
||||
return True
|
||||
else:
|
||||
cat = "".join(c for c in (self._iso_category or "").strip() if c not in "/\\")
|
||||
dest_dir = (psp_root / "ISO" / cat) if cat else (psp_root / "ISO")
|
||||
@@ -4179,6 +4272,7 @@ class MainWindow(QMainWindow):
|
||||
QTimer.singleShot(0, self._refresh_saves)
|
||||
if kind == "ps1-convert":
|
||||
QTimer.singleShot(0, self.ps1_page.refresh)
|
||||
QTimer.singleShot(0, self.load_library)
|
||||
|
||||
# -- Common ------------------------------------------------------------ #
|
||||
@staticmethod
|
||||
|
||||
Reference in New Issue
Block a user