Skip to content

Commit

Permalink
Merge pull request #3 from jaspersiebring/gui
Browse files Browse the repository at this point in the history
Graphical user interface and general refactor
  • Loading branch information
jaspersiebring committed Jul 31, 2023
2 parents 62d194e + 3a42690 commit 0055ecd
Show file tree
Hide file tree
Showing 10 changed files with 620 additions and 114 deletions.
83 changes: 54 additions & 29 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,66 +3,91 @@
![PyPI - Python Version](https://img.shields.io/pypi/pyversions/libretro-finder)
![PyPI - License](https://img.shields.io/pypi/l/libretro-finder)

Simple command line utility that recursively looks for specific BIOS files for RetroArch cores and, if found, refactors them to the expected format as documented by Libretro [here](https://github.com/libretro/libretro-database/blob/4a98ea9726b3954a4e5a940d255bd14c307ddfba/dat/System.dat) (i.e. name and directory structure). This is useful if you source your BIOS files from many different places and have them saved them under different names (often with duplicates). This script is able to find these exact BIOS files by comparing their hashes against their known counterparts. Uses concurrency and vectorization for added performance.
Simple tool that finds and prepares your BIOS files for usage with Libretro (or its RetroArch frontend).

No more need to manually select, rename and move your BIOS files to some RetroArch installation somewhere, just dump them in LibretroFinder and let it sort it out for you. It does this by generating checksums for your local files (i.e. unique identifiers) and comparing them against their known counterparts as documented by Libretro [here](https://github.com/libretro/libretro-database/blob/4a98ea9726b3954a4e5a940d255bd14c307ddfba/dat/System.dat). It then refactors *copies* of all matching files to the format expected by Libretro (name *and* folder structure).

This repository does **NOT** include the BIOS files themselves.

## Installation
Available through the Python Package Index (PyPI):
## Features
- Simple graphical user interface (GUI)
- Scriptable command line interface (CLI)
- Works on Windows, Linux and MacOS
- Available through the Python Package Index (Python >=3.8)
- Available as portable executable (currently only on X86 Windows machines)


<p float="left">
<img src="https://github.com/jaspersiebring/libretro_finder/assets/25051531/36e3a236-ef4f-46e2-bcf3-19fe0ddb4e65" width="45%" height = 250/>
<img src="https://github.com/jaspersiebring/libretro_finder/assets/25051531/280f6d97-1232-48ee-8dc4-e6cd229263ad" width="45%" height = 250 />
</p>

# Installation

Installing from the Python Package Index (PyPI):
````
# with pip
# Install from PYPI with Python's package installer (pip)
pip install libretro-finder
# with poetry
poetry add libretro-finder
# [Optional] Install as isolated application through pipx (https://pypa.github.io/pipx/)
pipx install libretro-finder
````
You can also download the prebuilt standalone executable which includes Python and all the program's dependencies, no installation required (currently only available for X86 Windows machines). See [releases](https://github.com/jaspersiebring/libretro_finder/releases).

## Example of usage:
````
some_user@some_machine:~ libretro_finder ~/Downloads/bios_files/ ~/Downloads/libretro_bios
<p float="left">
<img src="https://github.com/jaspersiebring/libretro_finder/assets/25051531/3ee6056c-0311-4071-8423-2b120ffff68e" width="150" height = 130/>
</p>

89 matching BIOS files were found for 3 unique systems:
Sega - Mega Drive - Genesis (1)
Sony - PlayStation (19)
Sony - PlayStation 2 (69)
100%|█████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 89/89 [00:00<00:00, 617.57it/s]
````
## Example of usage

#### Command line interface

If installed with pip, LibretroFinder can be called directly from your preferred terminal by running `libretro_finder`. You can use the tool entirely from your terminal by providing values for the search directory (e.g. `~/Downloads/bios_files/`) and the output directory (`~/.config/retroarch/system/`):

You can also safely use the `system` directory of retroarch as `output_dir` since we never overwrite files (i.e. libretro_finder only adds to your list of BIOS files)
````
some_user@some_machine:~ libretro_finder ~/Downloads/bios_files/ ~/.config/retroarch/system/
Hashing files: 100%|█████████████████████████████████████████████████████████████████████████████████| 983/983 [00:00<00:00, 3333.95it/s]
89 matching BIOS files were found for 3 unique systems:
Sega - Mega Drive - Genesis (1)
Sony - PlayStation (19)
Sony - PlayStation 2 (69)
100%|█████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 89/89 [00:00<00:00, 617.57it/s]
````

You can also use libretro's `system` directory as `search_dir` to find all aliased BIOS files (i.e. identical BIOS files that are usable by different cores for the same system but just under different names).
Although the output directory defaults to retroarch's `system` folder (if `retroarch` was found), you can manually specify whatever output folder you want and `libretro_finder` will create it for you. If your path contains spaces, wrap it in double quotes like so:

````
some_user@some_machine:~ libretro_finder ~/.config/retroarch/system/ ~/.config/retroarch/system/
some_user@some_machine:~ libretro_finder "D:\Games\My Roms" "C:\Program Files (x86)\Steam\steamapps\common\RetroArch\system"
Hashing files: 100%|█████████████████████████████████████████████████████████████████████████████████| 983/983 [00:00<00:00, 3333.95it/s]
89 matching BIOS files were found for 3 unique systems:
Sega - Mega Drive - Genesis (1)
Sony - PlayStation (19)
Sony - PlayStation 2 (69)
100%|█████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 89/89 [00:00<00:00, 617.57it/s]
````

Help page:
No matter what you select as search- or output directory, rest assured that no existing files on your file system will be modified. You can also call `libretro_finder` with `--help` to get some more information on the expected input:
````
some_user@some_machine:~ libretro_finder --help
Local BIOS file scraper for Retroarch
Locate and prepare your BIOS files for libretro.
positional arguments:
search_dir Directory to look for BIOS files
output_dir Directory to refactor found BIOS files to
Search directory Where to look for BIOS files
Output directory Where to output refactored BIOS files (defaults to ./retroarch/system)
optional arguments:
-h, --help show this help message and exit
-g GLOB, --glob GLOB Glob pattern to use for file matching
````
````


#### Graphical user interface

If `libretro_finder` is called without any additional arguments, LibretroFinder will start with a graphical interface. This is functionally identical to the CLI version with the only real difference that it automatically tries to set the output directory to retroarch's `system` folder.

<p float="left">
<img src="https://github.com/jaspersiebring/libretro_finder/assets/25051531/36e3a236-ef4f-46e2-bcf3-19fe0ddb4e65" width="45%" />
<img src="https://github.com/jaspersiebring/libretro_finder/assets/25051531/2b74cc06-c031-466a-9eaa-24135be06194" width="45%" />
</p>


### Missing features? Have some feedback? Let me know!
- [My Reddit account](https://www.reddit.com/user/qtieb/)
- [My Github account](https://github.com/jaspersiebring)
18 changes: 14 additions & 4 deletions config/__init__.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import sys
import pathlib
import re
import urllib.request

import pandas as pd
from libretro_finder.utils import find_retroarch

# not expecting BIOS files over 15mb
MAX_BIOS_BYTES = 15728640
SEED = 0

# Pulling all BIOS names and hashes from Libretro's system.dat (https://docs.libretro.com/)
Expand All @@ -19,9 +19,9 @@
print("Done.")

# Parsing Libretro's system.dat and formatting as pandas dataframe
index = 0 # pylint: disable=invalid-name
index = 0 # pylint: disable=invalid-name
SYSTEMS = []
with open(FILE_PATH, "r", encoding='utf-8') as file:
with open(FILE_PATH, "r", encoding="utf-8") as file:
for line in file:
line = line.strip()
if line.startswith("comment"):
Expand All @@ -45,3 +45,13 @@
# join dfs and drop features without checksums
SYSTEMS = pd.concat(SYSTEMS)
SYSTEMS = SYSTEMS[~SYSTEMS["md5"].isnull()].reset_index(drop=True)

# path to retroarch/system (if found)
RETROARCH_PATH = find_retroarch()


# 'cli' if user passes arguments else 'start gui'
# Needs to be present before the @Gooey decorator (https://github.com/chriskiehl/Gooey/issues/449)
if len(sys.argv) >= 2:
if not "--ignore-gooey" in sys.argv:
sys.argv.append("--ignore-gooey")
67 changes: 37 additions & 30 deletions libretro_finder/main.py
Original file line number Diff line number Diff line change
@@ -1,31 +1,28 @@
import argparse
import shutil
import pathlib
import numpy as np
import tqdm
from gooey import Gooey, GooeyParser

from config import SYSTEMS as system_df
from config import RETROARCH_PATH
from typing import List, Optional
from libretro_finder.utils import match_arrays, recursive_hash


def organize(
search_dir: pathlib.Path, output_dir: pathlib.Path, glob: str = "*"
) -> None:
def organize(search_dir: pathlib.Path, output_dir: pathlib.Path) -> None:
"""
Non-destructive function that finds, copies and refactors files to the format expected by
libretro (and its cores). This is useful if you source your BIOS files from many different
places and have them saved them under different names (often with duplicates).
:param search_dir:
:param output_dir:
:param glob:
:param overwrite:
:param search_dir: starting location of recursive search
:param output_dir: path to output directory (will be created if it doesn't exist)
:return:
"""

# Indexing files to be checked for matching MD5 checksums
output_dir.mkdir(parents=True, exist_ok=True)
file_paths, file_hashes = recursive_hash(directory=search_dir, glob=glob)
file_paths, file_hashes = recursive_hash(directory=search_dir)

# Element-wise matching of files against libretro's files
matching_values, file_indices, system_indices = match_arrays(
Expand All @@ -49,7 +46,7 @@ def organize(
# printing matches per system
matches = system_subset.groupby("system").count()
print(
f"{matches['name'].sum()} matching BIOS files were found for {matches.shape[0]}"
f"{matches['name'].sum()} matching BIOS files were found for {matches.shape[0]} "
"unique systems:"
)
for name, row in matches.iterrows():
Expand All @@ -61,7 +58,7 @@ def organize(
# checking whether our input and output paths are of equal length
assert len(srcs) == len(dsts)

for i in tqdm.tqdm(range(srcs.size), total=srcs.size):
for i in range(srcs.size):
dst = output_dir / dsts[i]
parent = dst.parent
if dst.exists() or srcs[i] == dst:
Expand All @@ -72,31 +69,41 @@ def organize(
shutil.copy(src=srcs[i], dst=dst)


def main() -> None:
"""Simple argparse wrapper for packaging."""
parser = argparse.ArgumentParser(
description="CLI that finds, copies and refactors BIOS files "
"to the format expected by libretro (i.e. name and directory structure).",
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
@Gooey(program_name="LibretroFinder", default_size=(610, 530), required_cols=1)
def main(argv: Optional[List[str]] = None) -> None:
"""
A simple command line utility that finds and prepares your BIOS files for all documented
RetroArch cores. If called without any arguments, a simple graphical user interface with
the same functionality will be started (courtesy of Gooey).
"""

parser = GooeyParser(
description="Locate and prepare your BIOS files for libretro.",
)
parser.add_argument("search_dir", help="Directory to look for BIOS files", type=str)
parser.add_argument(
"output_dir", help="Directory to refactor found BIOS files to", type=str
"Search directory",
help="Where to look for BIOS files",
type=pathlib.Path,
widget="DirChooser",
)
parser.add_argument(
"-g",
"--glob",
help="Glob pattern to use for file matching",
type=str,
default="*",
"Output directory",
help="Where to output refactored BIOS files (defaults to ./retroarch/system)",
type=pathlib.Path,
widget="DirChooser",
default=str(RETROARCH_PATH) if RETROARCH_PATH else None,
)
args = vars(parser.parse_args())
args = vars(parser.parse_args(argv))

search_directory = args["Search directory"]
output_directory = args["Output directory"]

search_directory = pathlib.Path(args["search_dir"])
output_directory = pathlib.Path(args["output_dir"])
search_glob = args["glob"]
if not search_directory.exists():
raise FileNotFoundError("Search directory does not exist..")
elif not search_directory.is_dir():
raise NotADirectoryError("Search directory needs to be a directory..")

organize(search_dir=search_directory, output_dir=output_directory, glob=search_glob)
organize(search_dir=search_directory, output_dir=output_directory)


if __name__ == "__main__":
Expand Down
Loading

0 comments on commit 0055ecd

Please sign in to comment.