70db46d4f2
Retocat: fbneo_roms_by_manufacturer.py
311 lines
12 KiB
Python
311 lines
12 KiB
Python
# Script para copiar roms a partir de un xml de fbneo
|
|
# Copia las roms por desarrollador y sin clones
|
|
|
|
# Por hacer:
|
|
# pasar por parametro si se quieren clones
|
|
|
|
import os
|
|
import shutil
|
|
import sys
|
|
import argparse
|
|
import config
|
|
from xml.dom import minidom
|
|
|
|
def normalize_path(path):
|
|
"""Elimina los caracteres ilegales de la cadena de texto"""
|
|
# Lista de caracteres ilegales que deben ser eliminados
|
|
illegal_chars = ["<", ">", ":", '"', "/", "\\", "|", "?", "*"]
|
|
# Carácter de reemplazo
|
|
replace_with = "_"
|
|
|
|
# Reemplaza cada carácter ilegal en la cadena de texto
|
|
for char in illegal_chars:
|
|
path = path.replace(char, replace_with)
|
|
|
|
return path
|
|
|
|
def parse_arguments():
|
|
"""Parsea los argumentos de la línea de comandos"""
|
|
parser = argparse.ArgumentParser(
|
|
description="Gestiona las roms de fbneo por desarrolladores a partir de un fichero .dat"
|
|
)
|
|
|
|
# Grupo de argumentos mutuos exclusivos
|
|
group = parser.add_mutually_exclusive_group()
|
|
group.add_argument(
|
|
"-l",
|
|
"--list",
|
|
help="Muestra la lista de desarrolladores o de juegos",
|
|
action="store_true",
|
|
)
|
|
group.add_argument(
|
|
"-c",
|
|
"--copy",
|
|
help="Copia las roms",
|
|
action="store_true"
|
|
)
|
|
group.add_argument(
|
|
"-f",
|
|
"--find",
|
|
help="Busca juegos que contengan la cadena de texto especificada",
|
|
)
|
|
|
|
# Otros argumentos
|
|
parser.add_argument(
|
|
"-s",
|
|
"--sort",
|
|
help="Ordena los resultados por carpetas de desarrollador al copiar todas las roms",
|
|
action="store_true",
|
|
)
|
|
parser.add_argument(
|
|
"-cf",
|
|
"--create_folder",
|
|
help="Copia las roms dentro de la carpeta del desarrollador",
|
|
action="store_true",
|
|
)
|
|
parser.add_argument(
|
|
"-m",
|
|
"--manufacturer",
|
|
help="Selecciona un desarrollador"
|
|
)
|
|
parser.add_argument(
|
|
"-d",
|
|
"--dat",
|
|
help="Ruta del fichero .dat con información de las roms"
|
|
)
|
|
parser.add_argument(
|
|
"-i",
|
|
"--input",
|
|
help="Ruta donde se encuentran las roms",
|
|
default=""
|
|
)
|
|
parser.add_argument(
|
|
"-o",
|
|
"--output",
|
|
help="Ruta donde depositar las roms",
|
|
default=""
|
|
)
|
|
|
|
return parser.parse_args()
|
|
|
|
def load_dat_file(dat_path):
|
|
"""Carga y analiza el fichero .dat especificado"""
|
|
if not dat_path:
|
|
print("No se ha especificado un fichero .dat")
|
|
if os.path.exists(config.XML_FILE):
|
|
# Usa el fichero por defecto si no se especifica uno
|
|
print(f"Usando el fichero por defecto: {config.XML_FILE}")
|
|
dat_path = config.XML_FILE
|
|
else:
|
|
# Salir si no se encuentra el fichero por defecto
|
|
sys.exit(2)
|
|
elif not os.path.isfile(dat_path):
|
|
# Salir si el fichero especificado no existe
|
|
print(f"No se encuentra el fichero .dat especificado: {dat_path}")
|
|
sys.exit(2)
|
|
|
|
print(f"Analizando el fichero {dat_path}")
|
|
return minidom.parse(dat_path)
|
|
|
|
def list_manufacturers(file):
|
|
"""Lista todos los desarrolladores presentes en el fichero .dat"""
|
|
print("Lista de todos los desarrolladores:")
|
|
|
|
manufacturers = []
|
|
games = file.getElementsByTagName("game")
|
|
|
|
# Recoge todos los desarrolladores de los juegos
|
|
for game in games:
|
|
manufacturer = game.getElementsByTagName("manufacturer")[0]
|
|
manufacturers.append(manufacturer.firstChild.data)
|
|
|
|
# Elimina los duplicados
|
|
manufacturers = list(dict.fromkeys(manufacturers))
|
|
# Ordena alfabéticamente la lista de desarrolladores
|
|
manufacturers.sort()
|
|
|
|
# Imprime cada desarrollador en una nueva línea
|
|
for manufacturer in manufacturers:
|
|
print(manufacturer)
|
|
|
|
def list_games_by_manufacturer(file, manufacturer):
|
|
"""Lista todos los juegos de un desarrollador específico"""
|
|
games = file.getElementsByTagName("game")
|
|
filtered_games = []
|
|
|
|
# Filtra los juegos según el fabricante especificado y excluye clones y BIOS
|
|
for game in games:
|
|
cloneof = game.getAttribute("cloneof")
|
|
isbios = game.getAttribute("isbios")
|
|
game_manufacturer = game.getElementsByTagName("manufacturer")[0]
|
|
description = game.getElementsByTagName("description")[0]
|
|
if (
|
|
(game_manufacturer.firstChild.data == manufacturer or manufacturer == 'all')
|
|
and not cloneof
|
|
and not isbios
|
|
):
|
|
filtered_games.append((description.firstChild.data, game.getAttribute('name'), game_manufacturer.firstChild.data))
|
|
|
|
# Encuentra el ancho máximo para cada columna
|
|
max_desc_length = max(len("Descripción"), max(len(row[0]) for row in filtered_games))
|
|
max_name_length = max(len("Nombre del archivo"), max(len(row[1] + '.zip') for row in filtered_games))
|
|
max_manufacturer_length = max(len("Desarrollador"), max(len(row[2]) for row in filtered_games))
|
|
|
|
# Imprime el encabezado de la tabla
|
|
print(f"Lista de todos los juegos de {manufacturer}: {len(filtered_games)}")
|
|
print(f"{'='*(max_desc_length+max_name_length+max_manufacturer_length+10)}")
|
|
print(f"| {'Descripción':<{max_desc_length}} | {'Nombre del archivo':<{max_name_length}} | {'Desarrollador':<{max_manufacturer_length}} |")
|
|
print(f"|{'-'*(max_desc_length+2)}|{'-'*(max_name_length+2)}|{'-'*(max_manufacturer_length+2)}|")
|
|
|
|
# Imprime cada juego en la tabla
|
|
for description, name, game_manufacturer in filtered_games:
|
|
print(f"| {description:<{max_desc_length}} | {name + '.zip':<{max_name_length}} | {game_manufacturer:<{max_manufacturer_length}} |")
|
|
|
|
# Imprime el pie de la tabla
|
|
print(f"{'='*(max_desc_length+max_name_length+max_manufacturer_length+10)}")
|
|
|
|
def find_games(file, search_string):
|
|
"""Busca juegos que contengan la cadena de texto especificada"""
|
|
games = file.getElementsByTagName("game")
|
|
filtered_games = []
|
|
|
|
# Filtra los juegos según la cadena de búsqueda especificada
|
|
for game in games:
|
|
description = game.getElementsByTagName("description")[0]
|
|
if search_string.casefold() in description.firstChild.data.casefold():
|
|
game_manufacturer = game.getElementsByTagName("manufacturer")[0]
|
|
filtered_games.append((description.firstChild.data, game.getAttribute('name'), game_manufacturer.firstChild.data))
|
|
|
|
# Encuentra el ancho máximo para cada columna
|
|
max_desc_length = max(len("Descripción"), max(len(row[0]) for row in filtered_games))
|
|
max_name_length = max(len("Nombre del archivo"), max(len(row[1] + '.zip') for row in filtered_games))
|
|
max_manufacturer_length = max(len("Desarrollador"), max(len(row[2]) for row in filtered_games))
|
|
|
|
# Imprime el encabezado de la tabla
|
|
print(f"Juegos que contienen '{search_string}': {len(filtered_games)}")
|
|
print(f"{'='*(max_desc_length+max_name_length+max_manufacturer_length+10)}")
|
|
print(f"| {'Descripción':<{max_desc_length}} | {'Nombre del archivo':<{max_name_length}} | {'Desarrollador':<{max_manufacturer_length}} |")
|
|
print(f"|{'-'*(max_desc_length+2)}|{'-'*(max_name_length+2)}|{'-'*(max_manufacturer_length+2)}|")
|
|
|
|
# Imprime cada juego en la tabla
|
|
for description, name, game_manufacturer in filtered_games:
|
|
print(f"| {description:<{max_desc_length}} | {name + '.zip':<{max_name_length}} | {game_manufacturer:<{max_manufacturer_length}} |")
|
|
|
|
# Imprime el pie de la tabla
|
|
print(f"{'='*(max_desc_length+max_name_length+max_manufacturer_length+10)}")
|
|
|
|
def copy_games(file, args, all_games=False):
|
|
"""Copia los juegos según los criterios especificados"""
|
|
if args.create_folder and not all_games:
|
|
# Normaliza el nombre del desarrollador para usarlo como nombre de carpeta
|
|
normalized_manufacturer = normalize_path(args.manufacturer)
|
|
os.mkdir(os.path.join(args.output, args.manufacturer))
|
|
|
|
# Listas para almacenar los juegos no encontrados, ignorados y copiados
|
|
notfound = []
|
|
ignored_games = []
|
|
copied_games = []
|
|
|
|
games = file.getElementsByTagName("game")
|
|
|
|
if all_games:
|
|
print(f"Copiando todos los juegos: {len(games)}")
|
|
else:
|
|
print(f"Copiando todos los juegos de {args.manufacturer}: {len(games)}")
|
|
|
|
for game in games:
|
|
isignored = False
|
|
name = game.getAttribute("name") + ".zip"
|
|
cloneof = game.getAttribute("cloneof")
|
|
isbios = game.getAttribute("isbios")
|
|
romof = game.getAttribute("romof")
|
|
manufacturer = game.getElementsByTagName("manufacturer")[0]
|
|
description = game.getElementsByTagName("description")[0]
|
|
driver = game.getElementsByTagName("driver")
|
|
status = (
|
|
game.getElementsByTagName("driver")[0].getAttribute("status")
|
|
if driver
|
|
else ""
|
|
)
|
|
|
|
# Verifica si el juego cumple con los criterios para ser copiado
|
|
if (all_games or manufacturer.firstChild.data == args.manufacturer) and not cloneof and not isbios and status == "good":
|
|
# Verifica si el juego debe ser ignorado
|
|
for ignore_element in config.IGNORE_LIST:
|
|
if ignore_element.casefold() in description.firstChild.data.casefold():
|
|
ignored_games.append(description.firstChild.data)
|
|
isignored = True
|
|
|
|
for ignore_element in config.IGNORE_SYSTEM:
|
|
if ignore_element.casefold() in romof.casefold():
|
|
ignored_games.append(description.firstChild.data)
|
|
isignored = True
|
|
|
|
if not isignored:
|
|
# Define la ruta de origen y destino del archivo
|
|
src = os.path.join(args.input, name)
|
|
if args.sort:
|
|
x = manufacturer.firstChild.data
|
|
x = x.replace(r"/", r"-")
|
|
dst = os.path.join(args.output, x, name)
|
|
if not os.path.isdir(os.path.join(args.output, x)):
|
|
os.mkdir(os.path.join(args.output, x))
|
|
else:
|
|
if args.create_folder and not all_games:
|
|
dst = os.path.join(args.output, normalized_manufacturer, name)
|
|
else:
|
|
dst = os.path.join(args.output, name)
|
|
|
|
# Copia el archivo si existe en la ruta de origen
|
|
if os.path.isfile(src):
|
|
shutil.copyfile(src, dst)
|
|
copied_games.append(description.firstChild.data)
|
|
print(description.firstChild.data)
|
|
else:
|
|
notfound.append(description.firstChild.data)
|
|
|
|
# Imprime los resultados de los juegos no encontrados, ignorados y copiados
|
|
print(f"\nJuegos faltantes: {len(notfound)}")
|
|
for game in notfound:
|
|
print(game)
|
|
|
|
print(f"\nJuegos ignorados: {len(ignored_games)}")
|
|
for game in ignored_games:
|
|
print(game)
|
|
|
|
print(f"\nJuegos copiados: {len(copied_games)}")
|
|
print(f"Juegos faltantes: {len(notfound)}")
|
|
print(f"Juegos ignorados: {len(ignored_games)}")
|
|
|
|
def main():
|
|
"""Función principal que coordina la ejecución del script"""
|
|
# Parsear los argumentos de la línea de comandos
|
|
args = parse_arguments()
|
|
# Cargar y analizar el fichero .dat
|
|
file = load_dat_file(dat_path=args.dat)
|
|
|
|
# Manejar la opción de listar desarrolladores o juegos
|
|
if args.list:
|
|
if args.manufacturer:
|
|
list_games_by_manufacturer(file=file, manufacturer=args.manufacturer)
|
|
else:
|
|
list_manufacturers(file=file)
|
|
|
|
# Manejar la opción de copiar roms
|
|
if args.copy:
|
|
if args.manufacturer:
|
|
copy_games(file=file, args=args, all_games=False)
|
|
else:
|
|
copy_games(file=file, args=args, all_games=True)
|
|
|
|
# Manejar la opción de búsqueda de juegos
|
|
if args.find:
|
|
find_games(file=file, search_string=args.find)
|
|
|
|
if __name__ == "__main__":
|
|
main()
|
|
|
|
|
|
|
|
|