initial commit
This commit is contained in:
+10
@@ -0,0 +1,10 @@
|
|||||||
|
# Python-generated files
|
||||||
|
__pycache__/
|
||||||
|
*.py[oc]
|
||||||
|
build/
|
||||||
|
dist/
|
||||||
|
wheels/
|
||||||
|
*.egg-info
|
||||||
|
|
||||||
|
# Virtual environments
|
||||||
|
.venv
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
3.14
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
# ES Multi-Disc Game Organizer
|
||||||
|
|
||||||
|
Organizes multi-disc game files in an ES ROMs system directory so that ES will treat them as a single game with many discs rather than many single-disc games
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### Pre-requisites
|
||||||
|
|
||||||
|
* [uv](https://docs.astral.sh/uv/)
|
||||||
|
|
||||||
|
### Instructions
|
||||||
|
|
||||||
|
1. Clone this repository or download and unzip it
|
||||||
|
2. Open a terminal change to the directory you just cloned or unzipped
|
||||||
|
3. Run `uv run es-multi-disc-game-organizer /path/to/ROMs/system` replacing the path with the full path to a system folder in your ES-DE ROMs folder containing multi-disc games
|
||||||
|
|
||||||
|
### Important Caveats
|
||||||
|
|
||||||
|
* This tool supports .iso, .zip, .chd, and .cso ROM files (sorry, bin/cue is just not easy to work with, and you can easily convert those to .chd using [chdman](https://wiki.recalbox.com/en/tutorials/utilities/rom-conversion/chdman))
|
||||||
|
* This tool assumes that your ROM files are named in the format "Game Name (disc 1).ext" or "Game Name (disk 1 of 2).ext". Many ROMs will come with a lot of superfluous information (region, software version, disk pressing, the name of the person who ripped the disc, etc.). There's just far too many naming conventions out there for me to even begin to try to support them all.
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
[project]
|
||||||
|
name = "es-multi-disc-game-organizer"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "Add your description here"
|
||||||
|
readme = "README.md"
|
||||||
|
authors = [
|
||||||
|
{ name = "Brennen Raimer", email = "5969754+norweeg@users.noreply.github.com" }
|
||||||
|
]
|
||||||
|
requires-python = ">=3.14"
|
||||||
|
dependencies = [
|
||||||
|
"toolz>=1.1.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[project.scripts]
|
||||||
|
es-multi-disc-game-organizer = "es_multi_disc_game_organizer:main"
|
||||||
|
|
||||||
|
[build-system]
|
||||||
|
requires = ["uv_build>=0.12.5,<0.13.0"]
|
||||||
|
build-backend = "uv_build"
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
from argparse import ArgumentParser
|
||||||
|
from operator import methodcaller, attrgetter
|
||||||
|
from pathlib import Path
|
||||||
|
from shutil import move
|
||||||
|
from re import compile, IGNORECASE
|
||||||
|
|
||||||
|
from toolz import compose_left, first, groupby
|
||||||
|
from toolz.curried.operator import contains
|
||||||
|
|
||||||
|
# Extensions typically used for rom files of disc-based games. This is not an exhaustive list, but it should cover most cases.
|
||||||
|
ROM_FILE_EXTS = (".cso", ".chd", ".zip", ".iso")
|
||||||
|
|
||||||
|
# This regex expects ROM files named in the format "Game Name (disc 1).ext" or "Game Name (disk 1 of 2).ext". The regex is case-insensitive.
|
||||||
|
# The game name is captured in group 1, which is used to create the playlist and subdirectory and m3u playlist names.
|
||||||
|
DISC_REGEX = compile(r"(.+) \(dis[ck] \d+( of \d+)?\)$", IGNORECASE)
|
||||||
|
|
||||||
|
# filter predicates for identifying rom files
|
||||||
|
is_romfile = compose_left(
|
||||||
|
attrgetter("suffix"), methodcaller("lower"), contains(ROM_FILE_EXTS)
|
||||||
|
)
|
||||||
|
|
||||||
|
# filter predicate for identifying multi-disc games
|
||||||
|
is_multidisc = compose_left(attrgetter("stem"), DISC_REGEX.match)
|
||||||
|
|
||||||
|
|
||||||
|
def organize_multidisc_games(folder: Path) -> None:
|
||||||
|
# Get all the rom files in the folder, filter for multi-disc games, and group them by game name
|
||||||
|
rom_files = filter(is_romfile, folder.glob("*"))
|
||||||
|
multi_disc_games = filter(is_multidisc, rom_files)
|
||||||
|
|
||||||
|
games = groupby(
|
||||||
|
compose_left(attrgetter("stem"), DISC_REGEX.match, methodcaller("group", 1)),
|
||||||
|
multi_disc_games,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create a playlist for each multi-disc game and move the disc files into a subdirectory
|
||||||
|
for game, discs in games.items():
|
||||||
|
first_file = first(discs)
|
||||||
|
playlist_path = first_file.parent / f"{game}.m3u"
|
||||||
|
discs_path = first_file.parent / game
|
||||||
|
discs_path.mkdir(exist_ok=True)
|
||||||
|
|
||||||
|
# noload.txt tells ES to ignore this directory for scanning, so we don't get duplicate entries in the gamelist.xml
|
||||||
|
(discs_path / "noload.txt").touch(exist_ok=True)
|
||||||
|
|
||||||
|
with open(playlist_path, "w") as playlist:
|
||||||
|
for file in sorted(discs, key=attrgetter("name")):
|
||||||
|
playlist.write(f"{game}/{file.name}\n")
|
||||||
|
move(file, discs_path / file.name)
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
parser = ArgumentParser(description="create playlists for multdisc games")
|
||||||
|
parser.add_argument("folder", type=Path)
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
organize_multidisc_games(args.folder)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
version = 1
|
||||||
|
revision = 3
|
||||||
|
requires-python = ">=3.14"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "es-multi-disc-game-organizer"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = { editable = "." }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "toolz" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.metadata]
|
||||||
|
requires-dist = [{ name = "toolz", specifier = ">=1.1.0" }]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "toolz"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/11/d6/114b492226588d6ff54579d95847662fc69196bdeec318eb45393b24c192/toolz-1.1.0.tar.gz", hash = "sha256:27a5c770d068c110d9ed9323f24f1543e83b2f300a687b7891c1a6d56b697b5b", size = 52613, upload-time = "2025-10-17T04:03:21.661Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/fb/12/5911ae3eeec47800503a238d971e51722ccea5feb8569b735184d5fcdbc0/toolz-1.1.0-py3-none-any.whl", hash = "sha256:15ccc861ac51c53696de0a5d6d4607f99c210739caf987b5d2054f3efed429d8", size = 58093, upload-time = "2025-10-17T04:03:20.435Z" },
|
||||||
|
]
|
||||||
Reference in New Issue
Block a user