› Foros › Retro y descatalogado › Arcade y emulación
manumedia escribió:SirAzraelGrotesque escribió:Muchas gracias. Sí, eso casi me sirve. ¡Por poco! Lástima que parece que no se puede seleccionar otro núcleo de Retroarch, que es justo lo que quería hacer. O no he sabido cómo hacerlo. Me deja cambiar por ejemplo a MAME externo, pero yo quiero cambiar del núcleo de FinalBurn Neo al de MAME.
Ah vale, leí emuladores y no pensé en núcleos de RetroArch, yo en RetroArch tendría que investigar ya que sólo lo he usado en la Xbox, también puede que haya algún parámetro en Launchbox que permita cargar la rom en RetroArch con el Core indicado. Si veo algo lo comento aquí.
SirAzraelGrotesque escribió:Ya he averiguado cómo hacer lo de lanzar juegos específicos con un núcleo distinto de Retroarch. Es prácticamente como tú me indicaste, @manumedia sólo que antes de seleccionar el emu, habría que crear una segunda instancia de Retroarch, donde podremos asignar un núcleo diferente para el sistema. En mi caso, el MAME. De esta manera, lanzo todos por defecto mediante FinalBurn y los que no funcionan en este emulador, pero sí en MAME, los voy cambiando manualmente a esa segunda instancia de Retroarch con el núcleo de MAME.
Pidgey escribió:Sin Problema @SirAzraelGrotesque , dejo aqui una imagen de donde se debe añadir el comando para mas facilidad, ya que no pude ponerla en el comentario anterior.
import os
import shutil
import xml.etree.ElementTree as ET
import tkinter as tk
from tkinter import messagebox, filedialog
from PIL import Image, ImageTk
import subprocess
from tkinter import ttk
class LaunchBoxManager:
def __init__(self, root):
self.root = root
self.root.title("LaunchBox Media Manager")
self.root.geometry("800x600")
self.platforms = {}
self.launchbox_path = None
self.filters = ["Box - Front", "Clear Logo"]
self.show_images = False
self.image_references = {}
self.last_sorted_column = None
self.videos_enabled = False
self.manual_enabled = False
self.sort_ascending = True
self.cell_size_text = (100, 50)
self.cell_size_image = (150, 150)
self.columns = 0
self.image_cache_ratio = "150x150"
self.alternative_path = None
self.max_games_per_page_imagemode = 100
self.make_cache = True
self.current_page = 0
self.total_pages = 0
max_retries = 2
retries = 0
while retries < max_retries:
if self.load_launchbox_path():
break
else:
print("Error: No se pudo cargar la configuración de LaunchBox. Reintentando...")
retries += 1
if retries == max_retries:
print("Error: No se pudo cargar la configuración de LaunchBox después de varios intentos.")
self.root.destroy()
self.setup_ui()
def setup_ui(self):
# Frame superior (para la selección de plataformas)
self.top_frame = tk.Frame(self.root)
self.top_frame.pack(fill="x", padx=10, pady=10)
# Etiqueta y Combobox para seleccionar la plataforma
self.platform_label = tk.Label(self.top_frame, text="Plataforma:")
self.platform_label.pack(side="left", padx=(0, 5))
self.platform_combobox = tk.StringVar()
self.platform_menu = ttk.Combobox(self.top_frame, textvariable=self.platform_combobox, state="readonly")
self.platform_menu.pack(side="left", fill="x", expand=True, padx=(0, 10))
self.platform_combobox.trace("w", self.on_platform_select)
# Botón para cargar plataformas
self.load_button = tk.Button(self.top_frame, text="Cargar Plataformas", command=self.load_platforms)
self.load_button.pack(side="left")
# Frame para los Checkbuttons (Modo Imágenes y Ocultar todo SI)
self.checkbutton_frame = tk.Frame(self.top_frame)
self.checkbutton_frame.pack(side="left", padx=(10, 0))
# Checkbutton para el modo imágenes
self.switch_var = tk.BooleanVar()
self.switch = tk.Checkbutton(self.checkbutton_frame, text="Modo Imágenes", variable=self.switch_var,
command=self.toggle_view_mode)
self.switch.pack(anchor="w") # Alineado a la izquierda dentro del Frame
# Checkbutton para ocultar juegos con "SI" en todas las celdas
self.hide_all_si_var = tk.BooleanVar(value=False) # Desactivado por defecto
self.hide_all_si_check = tk.Checkbutton(self.checkbutton_frame, text="Ocultar todo SI",
variable=self.hide_all_si_var,
command=self.toggle_hide_all_si)
self.hide_all_si_check.pack(anchor="w") # Alineado a la izquierda dentro del Frame
# Botón para abrir la configuración (a la derecha)
self.settings_button = tk.Button(self.top_frame, text="Configuración", command=self.open_settings)
self.settings_button.pack(side="right", padx=(10, 0))
# Frame para los filtros y la tabla
self.filter_frame = tk.LabelFrame(self.root, text="Estado de imágenes")
self.filter_frame.pack(fill="both", expand=True, padx=10, pady=10)
# Canvas y scrollbars para la tabla
self.header_canvas = tk.Canvas(self.filter_frame, borderwidth=1, relief="solid", height=self.cell_size_text[1])
self.header_canvas.pack(fill="x", side="top")
self.header_scrollbar_x = tk.Scrollbar(self.filter_frame, orient="horizontal",
command=lambda *args: self.sync_scroll(*args))
self.header_scrollbar_x.pack(side="top", fill="x")
self.header_canvas.config(xscrollcommand=self.header_scrollbar_x.set)
self.canvas_frame = tk.Frame(self.filter_frame)
self.canvas_frame.pack(fill="both", expand=True)
self.canvas = tk.Canvas(self.canvas_frame, borderwidth=1, relief="solid")
self.canvas.pack(side="left", fill="both", expand=True)
self.canvas_scrollbar_y = tk.Scrollbar(self.canvas_frame, orient="vertical", command=self.canvas.yview)
self.canvas_scrollbar_y.pack(side="right", fill="y")
self.canvas.config(yscrollcommand=self.canvas_scrollbar_y.set)
self.canvas_scrollbar_x = tk.Scrollbar(self.filter_frame, orient="horizontal",
command=lambda *args: self.sync_scroll(*args))
self.canvas_scrollbar_x.pack(side="bottom", fill="x")
self.canvas.config(xscrollcommand=self.canvas_scrollbar_x.set)
self.canvas.bind("<Configure>", lambda e: self.sync_scroll())
# Vincular el evento de la rueda del ratón al Canvas
self.canvas.bind("<MouseWheel>", self.on_mouse_wheel_cells)
# Frame para la paginación y botones adicionales
self.pagination_frame = tk.Frame(self.root)
self.pagination_frame.pack(fill="x", padx=10, pady=10)
self.center_frame = tk.Frame(self.pagination_frame)
self.center_frame.pack()
# Botón "Anterior"
self.prev_button = tk.Button(self.center_frame, text="Anterior", command=self.prev_page)
self.prev_button.pack(side="left")
# Etiqueta de la página actual
self.page_label = tk.Label(self.center_frame, text="Página 1 de 1")
self.page_label.pack(side="left", padx=10)
# Botón "Siguiente"
self.next_button = tk.Button(self.center_frame, text="Siguiente", command=self.next_page)
self.next_button.pack(side="left")
# Botón "Generar todo Cache"
self.generate_all_cache_button = tk.Button(self.center_frame, text="Generar todo Cache",
command=self.generate_all_cache)
self.generate_all_cache_button.pack(side="left", padx=(10, 0))
# Botón "Regenerar caché"
self.regenerate_cache_button = tk.Button(self.center_frame, text="Regenerar caché",
command=self.regenerate_cache)
self.regenerate_cache_button.pack(side="left", padx=(10, 0))
# Ocultar o mostrar los botones de caché según make_cache
self.update_cache_buttons_visibility()
def on_mouse_wheel_cells(self, event):
menu_state = str(self.platform_menu.cget("state")) # Guardarlo como string inmutable
if menu_state == "readonly":
delta = -event.delta
if delta > 0:
self.canvas.yview_scroll(1, "units")
else:
self.canvas.yview_scroll(-1, "units")
def on_mouse_wheel_platforms(self, event):
"""
Maneja el desplazamiento vertical con la rueda del ratón para la lista de plataformas.
"""
menu_state = self.platform_menu.cget("state")
if menu_state == "normal": # Solo desplazar si el menú está desplegado
current_index = self.platform_menu.current()
if event.delta > 0:
new_index = max(0, current_index - 1) # Desplazar hacia arriba
else:
new_index = min(len(self.platform_menu["values"]) - 1, current_index + 1) # Desplazar hacia abajo
self.platform_menu.current(new_index)
self.platform_combobox.set(self.platform_menu["values"][new_index])
self.on_platform_select()
def sync_scroll(self, *args):
"""
Sincroniza el desplazamiento horizontal entre el header_canvas y el canvas principal.
No afecta el desplazamiento vertical.
"""
if len(args) == 2 and args[0] == "moveto":
fraction = float(args[1])
self.header_canvas.xview_moveto(fraction)
self.canvas.xview_moveto(fraction)
elif len(args) == 2 and args[0] == "scroll":
amount, units = args[1].split()
self.header_canvas.xview_scroll(amount, units)
self.canvas.xview_scroll(amount, units)
else:
fraction = self.canvas.xview()[0]
self.header_canvas.xview_moveto(fraction)
def toggle_hide_all_si(self):
# Si el Checkbutton está activado, ocultamos los juegos con "SI" en todas las celdas
if self.hide_all_si_var.get():
# Ordenar los juegos alfabéticamente antes de aplicar el filtro
selected_platform = self.platform_combobox.get()
if not selected_platform:
return
xml_path = self.platforms[selected_platform]
tree = ET.parse(xml_path)
root = tree.getroot()
games = root.findall("Game")
game_dict = {}
for game in games:
title_element = game.find("Title")
title = title_element.text if title_element is not None else None
app_path_element = game.find("ApplicationPath")
if app_path_element is not None and app_path_element.text:
app_filename = os.path.basename(app_path_element.text)
app_name = os.path.splitext(app_filename)[0]
else:
app_name = None
if title:
game_dict[title] = [title]
if app_name and app_name != title:
game_dict[title].append(app_name)
# Ordenar los juegos alfabéticamente (de la A a la Z)
sorted_game_dict = dict(
sorted(game_dict.items(), key=lambda item: (self.natural_sort_key(item[0]), item[0])))
# Aplicar el filtro "Ocultar todo SI" sobre la lista ordenada
self.hide_all_si(sorted_game_dict)
else:
# Si está desactivado, mostramos todos los juegos nuevamente
self.on_platform_select()
def update_cache_buttons_visibility(self):
"""Actualiza la visibilidad de los botones de caché según el valor de make_cache."""
if self.make_cache:
self.generate_all_cache_button.pack(side="left", padx=(10, 0))
self.regenerate_cache_button.pack(side="left", padx=(10, 0))
else:
self.generate_all_cache_button.pack_forget()
self.regenerate_cache_button.pack_forget()
def load_platforms(self):
self.platform_combobox.set("") # Limpiar la selección actual
self.platforms.clear() # Limpiar el diccionario de plataformas
# Construir la ruta a la carpeta de plataformas
platforms_path = os.path.join(self.launchbox_path, "Data", "Platforms").replace("\\", "/")
if not os.path.exists(platforms_path):
messagebox.showerror("Error", f"No se encontró la carpeta de plataformas en: {platforms_path}")
return
# Obtener los nombres de las plataformas
platform_names = []
for filename in os.listdir(platforms_path):
if filename.endswith(".xml"):
platform_name = filename[:-4] # Eliminar la extensión .xml
self.platforms[platform_name] = os.path.join(platforms_path, filename).replace("\\", "/")
platform_names.append(platform_name)
if not platform_names:
messagebox.showerror("Error", "No se encontraron archivos XML en la carpeta de plataformas.")
return
# Asignar los valores al Combobox
self.platform_menu["values"] = platform_names
self.platform_menu.current(0) # Seleccionar la primera plataforma por defecto
# Forzar la actualización de la interfaz
self.platform_menu.update_idletasks()
# Cargar los datos de la plataforma seleccionada
self.on_platform_select()
def hide_all_si(self, game_dict=None):
if game_dict is None:
selected_platform = self.platform_combobox.get()
if not selected_platform:
return
xml_path = self.platforms[selected_platform]
tree = ET.parse(xml_path)
root = tree.getroot()
games = root.findall("Game")
game_dict = {}
for game in games:
title_element = game.find("Title")
title = title_element.text if title_element is not None else None
app_path_element = game.find("ApplicationPath")
if app_path_element is not None and app_path_element.text:
app_filename = os.path.basename(app_path_element.text)
app_name = os.path.splitext(app_filename)[0]
else:
app_name = None
if title:
game_dict[title] = [title]
if app_name and app_name != title:
game_dict[title].append(app_name)
# Filtrar juegos que tienen "SI" en todas las celdas
filtered_game_dict = self.filter_all_si(game_dict)
# Actualizar la tabla con los juegos filtrados
selected_platform = self.platform_combobox.get()
self.load_filter_file(selected_platform, filtered_game_dict)
def on_platform_select(self, *args):
selected_platform = self.platform_combobox.get()
if not selected_platform:
return
try:
xml_path = self.platforms[selected_platform]
tree = ET.parse(xml_path)
root = tree.getroot()
if root.tag != "LaunchBox":
messagebox.showerror("Error", f"El archivo XML no tiene la estructura esperada. Elemento raíz: {root.tag}")
return
games = root.findall("Game")
game_dict = {}
for game in games:
title_element = game.find("Title")
title = title_element.text if title_element is not None else None
app_path_element = game.find("ApplicationPath")
if app_path_element is not None and app_path_element.text:
app_filename = os.path.basename(app_path_element.text)
app_name = os.path.splitext(app_filename)[0]
else:
app_name = None
if title:
game_dict[title] = [title]
if app_name and app_name != title:
game_dict[title].append(app_name)
sorted_game_dict = dict(sorted(game_dict.items(), key=lambda item: (self.natural_sort_key(item[0]), item[0])))
self.load_filter_file(selected_platform, sorted_game_dict)
except Exception as e:
messagebox.showerror("Error", f"No se pudo leer el archivo XML: {e}")
def generate_all_cache(self):
selected_platform = self.platform_combobox.get()
if not selected_platform:
return
self.progress_label = tk.Label(self.root, text="Generando Caché...")
self.progress_label.place(relx=0.5, rely=0.5, anchor="center")
self.root.update()
original_page = self.current_page
for page in range(self.total_pages):
self.current_page = page
self.on_platform_select()
self.progress_label.config(text=f"Generando página {page + 1} de {self.total_pages}")
self.root.update()
self.current_page = original_page
self.on_platform_select()
self.progress_label.destroy()
def open_settings(self):
current_dir = os.path.dirname(os.path.abspath(__file__))
settings_path = os.path.join(current_dir, "Settings.py")
subprocess.run(["python", settings_path], check=True)
# Volver a cargar la configuración después de guardar los cambios
self.load_launchbox_path()
# Actualizar la visibilidad de los botones de caché
self.update_cache_buttons_visibility()
# Actualizar otros elementos de la interfaz si es necesario
self.on_platform_select() # Esto actualiza la tabla con los nuevos valores
def load_launchbox_path(self):
config_file = os.path.join(os.path.dirname(__file__), "config.txt").replace("\\", "/")
if not os.path.exists(config_file):
with open(config_file, "w") as file:
file.write("path=\n")
file.write("filters=\n")
file.write("image_cache_ratio=150x150\n")
file.write("alternative_path=\n")
file.write("image_cell=150x150\n")
file.write("text_cell=150x50\n")
file.write("max_games_per_page_imagemode=100\n")
file.write("color_media_yes_title=#0073E6\n")
file.write("color_media_yes_rom=#00E673\n")
file.write("color_media_both=#E67300\n")
file.write("color_media_no=#E60073\n")
file.write("color_no_trans=#C0C0C0\n")
file.write("make_cache=true\n")
print("Archivo config.txt creado con la estructura básica.")
return False
with open(config_file, "r") as file:
config_lines = file.readlines()
for line in config_lines:
if line.startswith("path="):
self.launchbox_path = line.strip().split('=')[1]
if line.startswith("filters="):
filters_line = line.strip().split('=')[1]
self.filters = [f.strip().strip('"') for f in filters_line.split(',')]
if line.startswith("image_cache_ratio="):
self.image_cache_ratio = line.strip().split('=')[1]
if line.startswith("alternative_path="):
self.alternative_path = line.strip().split('=')[1]
if line.startswith("image_cell="):
try:
width, height = line.strip().split('=')[1].split('x')
self.cell_size_image = (int(width), int(height))
except ValueError:
print(
"Error: El valor de image_cell no tiene el formato correcto. Usando valores predeterminados (200x200).")
self.cell_size_image = (200, 200)
if line.startswith("text_cell="):
try:
width, height = line.strip().split('=')[1].split('x')
self.cell_size_text = (int(width), int(height))
except ValueError:
print(
"Error: El valor de text_cell no tiene el formato correcto. Usando valores predeterminados (100x50).")
self.cell_size_text = (100, 50)
if line.startswith("max_games_per_page_imagemode="):
try:
self.max_games_per_page_imagemode = int(line.strip().split('=')[1])
except ValueError:
print(
"Error: El valor de max_games_per_page_imagemode no es un número válido. Usando valor predeterminado (20).")
self.max_games_per_page_imagemode = 20
if line.startswith("color_media_yes_title="):
self.color_media_yes_title = line.strip().split('=')[1]
if line.startswith("color_media_yes_rom="):
self.color_media_yes_rom = line.strip().split('=')[1]
if line.startswith("color_media_both="):
self.color_media_both = line.strip().split('=')[1]
if line.startswith("color_media_no="):
self.color_media_no = line.strip().split('=')[1]
if line.startswith("color_no_trans="):
self.color_no_trans = line.strip().split('=')[1]
if line.startswith("color_media_both="):
self.color_media_both = line.strip().split('=')[1]
if line.startswith("make_cache="): # Nuevo parámetro
self.make_cache = line.strip().split('=')[1].lower() == "true"
return True
def load_platforms(self):
self.platform_combobox.set("") # Limpiar la selección actual
self.platforms.clear() # Limpiar el diccionario de plataformas
# Construir la ruta a la carpeta de plataformas
platforms_path = os.path.join(self.launchbox_path, "Data", "Platforms").replace("\\", "/")
if not os.path.exists(platforms_path):
messagebox.showerror("Error", f"No se encontró la carpeta de plataformas en: {platforms_path}")
return
# Obtener los nombres de las plataformas
platform_names = []
for filename in os.listdir(platforms_path):
if filename.endswith(".xml"):
platform_name = filename[:-4] # Eliminar la extensión .xml
self.platforms[platform_name] = os.path.join(platforms_path, filename).replace("\\", "/")
platform_names.append(platform_name)
if not platform_names:
messagebox.showerror("Error", "No se encontraron archivos XML en la carpeta de plataformas.")
return
# Asignar los valores al Combobox
self.platform_menu["values"] = platform_names
self.platform_menu.current(0) # Seleccionar la primera plataforma por defecto
# Forzar la actualización de la interfaz
self.platform_menu.update_idletasks()
# Cargar los datos de la plataforma seleccionada
self.on_platform_select()
def on_platform_select(self, *args):
selected_platform = self.platform_combobox.get()
if not selected_platform:
return
try:
xml_path = self.platforms[selected_platform]
tree = ET.parse(xml_path)
root = tree.getroot()
if root.tag != "LaunchBox":
messagebox.showerror("Error", f"El archivo XML no tiene la estructura esperada. Elemento raíz: {root.tag}")
return
games = root.findall("Game")
game_dict = {}
for game in games:
title_element = game.find("Title")
title = title_element.text if title_element is not None else None
app_path_element = game.find("ApplicationPath")
if app_path_element is not None and app_path_element.text:
app_filename = os.path.basename(app_path_element.text)
app_name = os.path.splitext(app_filename)[0]
else:
app_name = None
if title:
game_dict[title] = [title]
if app_name and app_name != title:
game_dict[title].append(app_name)
sorted_game_dict = dict(sorted(game_dict.items(), key=lambda item: (self.natural_sort_key(item[0]), item[0])))
self.load_filter_file(selected_platform, sorted_game_dict)
except Exception as e:
messagebox.showerror("Error", f"No se pudo leer el archivo XML: {e}")
def load_filter_file(self, platform, game_dict):
filters_to_use = self.filters[:]
if self.videos_enabled:
filters_to_use.append("Videos")
if self.manual_enabled:
filters_to_use.append("Manual")
self.columns = len(filters_to_use) + 1 # Definir self.columns
self.total_pages = (len(game_dict) + self.max_games_per_page_imagemode - 1) // self.max_games_per_page_imagemode
self.update_pagination_controls()
start_index = self.current_page * self.max_games_per_page_imagemode
end_index = start_index + self.max_games_per_page_imagemode
games_to_show = list(game_dict.items())[start_index:end_index]
self.canvas.delete("all")
self.image_references = {}
self.draw_table(games_to_show, filters_to_use)
total_width = sum(
self.cell_size_image[0] if self.show_images else self.cell_size_text[0] for _ in range(self.columns))
total_height = self.rows * (self.cell_size_image[1] if self.show_images else self.cell_size_text[1])
self.canvas.config(scrollregion=(0, 0, total_width, total_height))
def prev_page(self):
if self.current_page > 0:
self.current_page -= 1
self.update_pagination_controls()
self.on_platform_select()
def next_page(self):
if self.current_page < self.total_pages - 1:
self.current_page += 1
self.update_pagination_controls()
self.on_platform_select()
def update_pagination_controls(self):
self.page_label.config(text=f"Página {self.current_page + 1} de {self.total_pages}")
self.prev_button.config(state="normal" if self.current_page > 0 else "disabled")
self.next_button.config(state="normal" if self.current_page < self.total_pages - 1 else "disabled")
def toggle_view_mode(self):
self.show_images = self.switch_var.get()
if self.show_images:
self.header_canvas.config(height=self.cell_size_image[1])
else:
self.header_canvas.config(height=self.cell_size_text[1])
self.on_platform_select()
def search_image_in_folder(self, folder, search_names, extensions):
"""
Busca imágenes en una carpeta y sus subcarpetas de manera recursiva.
Retorna la ruta de la imagen si la encuentra, o None si no la encuentra.
"""
for root, dirs, files in os.walk(folder):
for file in files:
# Verificar si el archivo coincide con alguno de los nombres y extensiones
for search_name in search_names:
for ext in extensions:
if file.startswith(search_name) and file.endswith(ext):
return os.path.join(root, file), search_name
return None, None
def draw_table(self, games_to_show, filters):
self.rows = len(games_to_show)
if self.show_images:
cell_width, cell_height = self.cell_size_image
else:
cell_width, cell_height = self.cell_size_text
total_width = sum(cell_width for _ in range(self.columns))
self.header_canvas.config(width=total_width, height=cell_height)
self.header_canvas.delete("all")
for col, filter_name in enumerate(["Título"] + filters):
self.draw_cell(0, col, filter_name, header=True, canvas=self.header_canvas)
self.header_canvas.tag_bind(f"header_{col}", "<Button-1>", lambda event, col=col: self.on_header_click(col))
for row, (title, search_names) in enumerate(games_to_show, start=0):
self.draw_cell(row, 0, title)
for col, filter_name in enumerate(filters, start=1):
if filter_name == "Videos":
# Definir las rutas principales y alternativas
main_folder_path = os.path.join(self.launchbox_path, "Videos",
self.platform_combobox.get()).replace("\\", "/")
alternative_folder_path = os.path.join(self.alternative_path, self.platform_combobox.get(),
"Videos").replace("\\",
"/") if self.alternative_path else None
file_found = False
match_type = None
file_path = None
# Buscar en la ruta alternativa primero (si existe)
if alternative_folder_path and os.path.exists(alternative_folder_path):
# Buscar <Title>-01.mp4
file_path = os.path.join(alternative_folder_path, f"{search_names[0]}-01.mp4").replace("\\",
"/")
if os.path.isfile(file_path):
file_found = True
match_type = 'title'
else:
# Buscar <ApplicationPath>.mp4 (sin -01)
if len(search_names) > 1:
file_path = os.path.join(alternative_folder_path, f"{search_names[1]}.mp4").replace(
"\\", "/")
if os.path.isfile(file_path):
file_found = True
match_type = 'rom'
else:
# Si <Title> y <ApplicationPath> son iguales, buscar <Title>.mp4 (sin -01)
file_path = os.path.join(alternative_folder_path, f"{search_names[0]}.mp4").replace(
"\\", "/")
if os.path.isfile(file_path):
file_found = True
match_type = 'rom'
# Si no se encontró en la ruta alternativa, buscar en la ruta principal
if not file_found and os.path.exists(main_folder_path):
# Buscar <Title>-01.mp4
file_path = os.path.join(main_folder_path, f"{search_names[0]}-01.mp4").replace("\\", "/")
if os.path.isfile(file_path):
file_found = True
match_type = 'title'
else:
# Buscar <ApplicationPath>.mp4 (sin -01)
if len(search_names) > 1:
file_path = os.path.join(main_folder_path, f"{search_names[1]}.mp4").replace("\\", "/")
if os.path.isfile(file_path):
file_found = True
match_type = 'rom'
else:
# Si <Title> y <ApplicationPath> son iguales, buscar <Title>.mp4 (sin -01)
file_path = os.path.join(main_folder_path, f"{search_names[0]}.mp4").replace("\\", "/")
if os.path.isfile(file_path):
file_found = True
match_type = 'rom'
# Mostrar el resultado en la tabla
if self.show_images:
if file_found:
self.draw_cell(row, col, "SI",
cell_color=self.color_media_yes_title if match_type == 'title' else self.color_media_yes_rom)
else:
self.draw_cell(row, col, "NO", cell_color=self.color_media_no)
else:
cell_value = "SI (Title)" if match_type == 'title' else "SI (Rom)" if match_type == 'rom' else "NO"
cell_color = self.color_media_yes_title if match_type == 'title' else self.color_media_yes_rom if match_type == 'rom' else self.color_media_no
self.draw_cell(row, col, cell_value, cell_color=cell_color, is_text=True)
elif filter_name == "Manual":
# Definir las rutas principales y alternativas
main_folder_path = os.path.join(self.launchbox_path, "Manuals",
self.platform_combobox.get()).replace("\\", "/")
alternative_folder_path = os.path.join(self.alternative_path, self.platform_combobox.get(),
"Manuals").replace("\\",
"/") if self.alternative_path else None
file_found = False
match_type = None
file_path = None
# Buscar en la ruta alternativa primero (si existe)
if alternative_folder_path and os.path.exists(alternative_folder_path):
# Buscar <Title>-01.pdf
file_path = os.path.join(alternative_folder_path, f"{search_names[0]}-01.pdf").replace("\\",
"/")
if os.path.isfile(file_path):
file_found = True
match_type = 'title'
else:
# Buscar <ApplicationPath>.pdf (sin -01)
if len(search_names) > 1:
file_path = os.path.join(alternative_folder_path, f"{search_names[1]}.pdf").replace(
"\\", "/")
if os.path.isfile(file_path):
file_found = True
match_type = 'rom'
else:
# Si <Title> y <ApplicationPath> son iguales, buscar <Title>.pdf (sin -01)
file_path = os.path.join(alternative_folder_path, f"{search_names[0]}.pdf").replace(
"\\", "/")
if os.path.isfile(file_path):
file_found = True
match_type = 'rom'
# Si no se encontró en la ruta alternativa, buscar en la ruta principal
if not file_found and os.path.exists(main_folder_path):
# Buscar <Title>-01.pdf
file_path = os.path.join(main_folder_path, f"{search_names[0]}-01.pdf").replace("\\", "/")
if os.path.isfile(file_path):
file_found = True
match_type = 'title'
else:
# Buscar <ApplicationPath>.pdf (sin -01)
if len(search_names) > 1:
file_path = os.path.join(main_folder_path, f"{search_names[1]}.pdf").replace("\\", "/")
if os.path.isfile(file_path):
file_found = True
match_type = 'rom'
else:
# Si <Title> y <ApplicationPath> son iguales, buscar <Title>.pdf (sin -01)
file_path = os.path.join(main_folder_path, f"{search_names[0]}.pdf").replace("\\", "/")
if os.path.isfile(file_path):
file_found = True
match_type = 'rom'
# Mostrar el resultado en la tabla
if self.show_images:
if file_found:
self.draw_cell(row, col, "SI",
cell_color=self.color_media_yes_title if match_type == 'title' else self.color_media_yes_rom)
else:
self.draw_cell(row, col, "NO", cell_color=self.color_media_no)
else:
cell_value = "SI (Title)" if match_type == 'title' else "SI (Rom)" if match_type == 'rom' else "NO"
cell_color = self.color_media_yes_title if match_type == 'title' else self.color_media_yes_rom if match_type == 'rom' else self.color_media_no
self.draw_cell(row, col, cell_value, cell_color=cell_color, is_text=True)
else:
image_folder = os.path.join(self.launchbox_path, "Images", self.platform_combobox.get(),
filter_name).replace("\\", "/")
image_found = False
image_path = None
match_type = None
title_found = False
rom_found = False
title_path = None
rom_path = None
if os.path.exists(image_folder):
# Buscar <Title>-01.ext de forma recursiva
for ext in ["png", "jpg"]:
for root, dirs, files in os.walk(image_folder):
for file in files:
if file.startswith(f"{search_names[0]}-01") and file.endswith(ext):
title_path = os.path.join(root, file)
title_found = True
break
if title_found:
break
if title_found:
break
# Buscar <ApplicationPath>.ext de forma no recursiva
if len(search_names) > 1:
for ext in ["png", "jpg"]:
rom_path = os.path.join(image_folder, f"{search_names[1]}.{ext}").replace("\\", "/")
if os.path.isfile(rom_path):
rom_found = True
break
else:
# Si <Title> y <ApplicationPath> son iguales, buscar <Title>.ext (sin -01)
for ext in ["png", "jpg"]:
rom_path = os.path.join(image_folder, f"{search_names[0]}.{ext}").replace("\\", "/")
if os.path.isfile(rom_path):
rom_found = True
break
# Determinar el tipo de coincidencia
if title_found and rom_found:
match_type = 'title/rom'
image_path = title_path # Usar la imagen de Title para mostrar
elif title_found:
match_type = 'title'
image_path = title_path
elif rom_found:
match_type = 'rom'
image_path = rom_path
if self.show_images:
if image_path:
self.draw_image_cell(row, col, image_path, match_type)
else:
self.draw_cell(row, col, "NO", cell_color=self.color_media_no)
else:
if match_type == 'title/rom':
cell_value = "SI (Title/Rom)"
cell_color = self.color_media_both
elif match_type == 'title':
cell_value = "SI (Title)"
cell_color = self.color_media_yes_title
elif match_type == 'rom':
cell_value = "SI (Rom)"
cell_color = self.color_media_yes_rom
else:
cell_value = "NO"
cell_color = self.color_media_no
self.draw_cell(row, col, cell_value, cell_color=cell_color, is_text=True)
total_width = sum(
self.cell_size_image[0] if self.show_images else self.cell_size_text[0] for _ in range(self.columns))
total_height = len(games_to_show) * (self.cell_size_image[1] if self.show_images else self.cell_size_text[1])
self.canvas.config(scrollregion=(0, 0, total_width, total_height))
self.header_canvas.config(scrollregion=(0, 0, total_width, cell_height))
# Forzar el foco al Canvas para que reciba eventos de la rueda del ratón
self.canvas.focus_set()
def on_header_click(self, col):
# Llamar al método para ordenar los datos
self.sort_by_column(col)
def draw_cell(self, row, col, value, header=False, cell_color=None, is_text=False, canvas=None):
if canvas is None:
canvas = self.canvas
if header:
if self.show_images:
cell_width, cell_height = self.cell_size_image
else:
cell_width, cell_height = self.cell_size_text
elif is_text or not self.show_images:
cell_width, cell_height = self.cell_size_text
else:
cell_width, cell_height = self.cell_size_image
x1 = col * cell_width
y1 = row * cell_height
x2 = x1 + cell_width
y2 = y1 + cell_height
if value == "SI (Title)":
fill_color = self.color_media_yes_title
elif value == "SI (Rom)":
fill_color = self.color_media_yes_rom
elif value == "NO":
fill_color = self.color_media_no
else:
fill_color = cell_color or "white"
# Dibujar la celda
canvas.create_rectangle(x1, y1, x2, y2, fill=fill_color, outline="black",
tags=f"header_{col}" if header else f"cell_{row}_{col}")
font_style = ("Arial", 10, "bold") if header else ("Arial", 10)
wrapped_text = self.wrap_text(value, cell_width - 10)
text_y = y1 + (cell_height - len(wrapped_text) * 12) / 2
for line in wrapped_text:
canvas.create_text(x1 + 5, text_y, anchor="w", text=line, font=font_style,
tags=f"header_{col}" if header else f"cell_{row}_{col}")
text_y += 12
def wrap_text(self, text, max_width):
import textwrap
return textwrap.wrap(text, width=max_width // 7)
def draw_image_cell(self, row, col, image_path, match_type):
try:
cell_width, cell_height = self.cell_size_image
if self.make_cache:
# Cargar desde la caché
cache_folder = os.path.join(os.path.dirname(__file__), "cache", self.platform_combobox.get(),
self.filters[col - 1])
os.makedirs(cache_folder, exist_ok=True)
cache_image_path = os.path.join(cache_folder,
os.path.basename(image_path).replace(".png", ".jpg").replace(".PNG",
".jpg"))
width, height = map(int, self.image_cache_ratio.split("x"))
if not os.path.exists(cache_image_path):
try:
file_size = os.path.getsize(image_path)
if file_size > 100 * 1024 * 1024:
raise ValueError(
f"El archivo {image_path} es demasiado grande ({file_size / (1024 * 1024):.2f} MB)")
with Image.open(image_path) as image:
if image.mode in ("P", "1", "L", "LA"):
image = image.convert("RGBA")
if image.mode == "RGBA":
background = Image.new("RGB", image.size, self.color_no_trans)
background.paste(image, mask=image.split()[-1])
image = background
image.thumbnail((width, height))
image.save(cache_image_path, "JPEG")
except Exception as e:
print(f"Error al procesar la imagen {image_path}: {e}")
with open("imagenes_con_error.txt", "a") as error_file:
error_file.write(f"{image_path}\n")
self.draw_cell(row, col, "NO", cell_color=self.color_media_no)
return
with Image.open(cache_image_path) as image:
tk_image = ImageTk.PhotoImage(image)
self.image_references[(row, col)] = tk_image
else:
# Cargar directamente desde la fuente
with Image.open(image_path) as image:
if image.mode in ("P", "1", "L", "LA"):
image = image.convert("RGBA")
if image.mode == "RGBA":
background = Image.new("RGB", image.size, self.color_no_trans)
background.paste(image, mask=image.split()[-1])
image = background
image.thumbnail((cell_width, cell_height))
tk_image = ImageTk.PhotoImage(image)
self.image_references[(row, col)] = tk_image
x1 = col * self.cell_size_image[0]
y1 = row * self.cell_size_image[1]
bg_color = self.color_media_yes_title if match_type == 'title' else self.color_media_yes_rom
self.canvas.create_rectangle(x1, y1, x1 + cell_width, y1 + cell_height, fill=bg_color, outline="black")
self.canvas.create_image(x1 + cell_width // 2, y1 + cell_height // 2, anchor="center", image=tk_image)
except Exception as e:
print(f"Error inesperado al procesar la imagen {image_path}: {e}")
self.draw_cell(row, col, "NO", cell_color=self.color_media_no)
with open("imagenes_con_error.txt", "a") as error_file:
error_file.write(f"{image_path}\n")
def regenerate_cache(self):
selected_platform = self.platform_combobox.get()
if not selected_platform:
return
self.progress_label = tk.Label(self.root, text="Generando Caché...")
self.progress_label.place(relx=0.5, rely=0.5, anchor="center")
self.root.update()
current_platform = self.platform_combobox.get()
cache_folder = os.path.join(os.path.dirname(__file__), "cache", current_platform)
if os.path.exists(cache_folder):
shutil.rmtree(cache_folder)
self.load_platforms()
self.platform_combobox.set(current_platform)
self.on_platform_select()
self.progress_label.destroy()
def natural_sort_key(self, text):
import re
return [int(text) if text.isdigit() else text.lower() for text in re.split(r'(\d+)', text)]
def sort_by_column(self, col):
selected_platform = self.platform_combobox.get()
if not selected_platform:
return
xml_path = self.platforms[selected_platform]
tree = ET.parse(xml_path)
root = tree.getroot()
games = root.findall("Game")
game_dict = {}
for game in games:
title_element = game.find("Title")
title = title_element.text if title_element is not None else None
app_path_element = game.find("ApplicationPath")
if app_path_element is not None and app_path_element.text:
app_filename = os.path.basename(app_path_element.text)
app_name = os.path.splitext(app_filename)[0]
else:
app_name = None
if title:
game_dict[title] = [title]
if app_name and app_name != title:
game_dict[title].append(app_name)
# Ordenar los juegos
if col == 0:
# Ordenar por título
sorted_game_dict = dict(
sorted(game_dict.items(), key=lambda item: (self.natural_sort_key(item[0]), item[0])))
else:
# Ordenar por la columna seleccionada
filter_name = self.filters[col - 1]
sorted_game_dict = dict(
sorted(game_dict.items(), key=lambda item: self.get_filter_value(item[1], filter_name)))
# Alternar entre orden ascendente y descendente
if self.last_sorted_column == col:
self.sort_ascending = not self.sort_ascending
if not self.sort_ascending:
sorted_game_dict = dict(reversed(list(sorted_game_dict.items())))
else:
self.sort_ascending = True
self.last_sorted_column = col
# Si el filtro "Ocultar todo SI" está activado, aplicar el filtro después de ordenar
if self.hide_all_si_var.get():
sorted_game_dict = self.filter_all_si(sorted_game_dict)
# Actualizar la interfaz gráfica con los juegos ordenados (y filtrados si es necesario)
self.load_filter_file(selected_platform, sorted_game_dict)
def filter_all_si(self, game_dict):
"""
Filtra los juegos que tienen "SI" en todas las celdas.
"""
filtered_game_dict = {}
for title, search_names in game_dict.items():
all_si = True
for filter_name in self.filters:
if filter_name == "Videos":
# Lógica para videos
main_folder_path = os.path.join(self.launchbox_path, "Videos",
self.platform_combobox.get()).replace("\\", "/")
alternative_folder_path = os.path.join(self.alternative_path, self.platform_combobox.get(),
"Videos").replace("\\",
"/") if self.alternative_path else None
file_found = False
if alternative_folder_path and os.path.exists(alternative_folder_path):
file_path = os.path.join(alternative_folder_path, f"{search_names[0]}-01.mp4").replace("\\",
"/")
if os.path.isfile(file_path):
file_found = True
else:
if len(search_names) > 1:
file_path = os.path.join(alternative_folder_path, f"{search_names[1]}.mp4").replace(
"\\", "/")
if os.path.isfile(file_path):
file_found = True
if not file_found and os.path.exists(main_folder_path):
file_path = os.path.join(main_folder_path, f"{search_names[0]}-01.mp4").replace("\\", "/")
if os.path.isfile(file_path):
file_found = True
else:
if len(search_names) > 1:
file_path = os.path.join(main_folder_path, f"{search_names[1]}.mp4").replace("\\", "/")
if os.path.isfile(file_path):
file_found = True
if not file_found:
all_si = False
break
elif filter_name == "Manual":
# Lógica para manuales
main_folder_path = os.path.join(self.launchbox_path, "Manuals",
self.platform_combobox.get()).replace("\\", "/")
alternative_folder_path = os.path.join(self.alternative_path, self.platform_combobox.get(),
"Manuals").replace("\\",
"/") if self.alternative_path else None
file_found = False
if alternative_folder_path and os.path.exists(alternative_folder_path):
file_path = os.path.join(alternative_folder_path, f"{search_names[0]}-01.pdf").replace("\\",
"/")
if os.path.isfile(file_path):
file_found = True
else:
if len(search_names) > 1:
file_path = os.path.join(alternative_folder_path, f"{search_names[1]}.pdf").replace(
"\\", "/")
if os.path.isfile(file_path):
file_found = True
if not file_found and os.path.exists(main_folder_path):
file_path = os.path.join(main_folder_path, f"{search_names[0]}-01.pdf").replace("\\", "/")
if os.path.isfile(file_path):
file_found = True
else:
if len(search_names) > 1:
file_path = os.path.join(main_folder_path, f"{search_names[1]}.pdf").replace("\\", "/")
if os.path.isfile(file_path):
file_found = True
if not file_found:
all_si = False
break
else:
# Lógica para imágenes
image_folder = os.path.join(self.launchbox_path, "Images", self.platform_combobox.get(),
filter_name).replace("\\", "/")
image_found = False
if os.path.exists(image_folder):
for ext in ["png", "jpg"]:
image_path = os.path.join(image_folder, f"{search_names[0]}-01.{ext}").replace("\\", "/")
if os.path.isfile(image_path):
image_found = True
break
if len(search_names) > 1:
image_path = os.path.join(image_folder, f"{search_names[1]}.{ext}").replace("\\", "/")
if os.path.isfile(image_path):
image_found = True
break
if not image_found:
all_si = False
break
if not all_si:
filtered_game_dict[title] = search_names
return filtered_game_dict
def get_filter_value(self, search_names, filter_name):
image_folder = os.path.join(self.launchbox_path, "Images", self.platform_combobox.get(), filter_name).replace(
"\\", "/")
if os.path.exists(image_folder):
for search_name in search_names:
for ext in ["png", "jpg"]:
image_path = os.path.join(image_folder, f"{search_name}.{ext}").replace("\\", "/")
if os.path.isfile(image_path):
return search_name
return ""
if __name__ == "__main__":
try:
root = tk.Tk()
app = LaunchBoxManager(root)
root.mainloop()
except MemoryError:
print("Error de memoria grave. El programa debe cerrarse.")
except Exception as e:
print(f"Error inesperado: {e}")
messagebox.showerror("Error", f"Se produjo un error inesperado: {e}")
import os
import tkinter as tk
from tkinter import filedialog, messagebox, colorchooser
class SettingsWindow:
def __init__(self, root, config_file):
self.root = root
self.config_file = config_file
self.root.title("Configuración")
# Crear un Canvas y Scrollbars
self.canvas = tk.Canvas(self.root)
self.scrollbar_y = tk.Scrollbar(self.root, orient="vertical", command=self.canvas.yview)
self.scrollbar_x = tk.Scrollbar(self.root, orient="horizontal", command=self.canvas.xview)
# Configurar el Canvas para que sea desplazable
self.scrollable_frame = tk.Frame(self.canvas)
self.scrollable_frame.bind(
"<Configure>",
lambda e: self.canvas.configure(
scrollregion=self.canvas.bbox("all")
)
)
self.canvas.create_window((0, 0), window=self.scrollable_frame, anchor="nw")
self.canvas.configure(yscrollcommand=self.scrollbar_y.set, xscrollcommand=self.scrollbar_x.set)
# Configurar el evento de la rueda del ratón
self.canvas.bind_all("<MouseWheel>", self._on_mousewheel)
# Colocar el Canvas y los Scrollbars en la ventana
self.canvas.grid(row=0, column=0, sticky="nsew")
self.scrollbar_y.grid(row=0, column=1, sticky="ns")
self.scrollbar_x.grid(row=1, column=0, sticky="ew")
# Configurar la expansión de la ventana
self.root.grid_rowconfigure(0, weight=1)
self.root.grid_columnconfigure(0, weight=1)
# Variables para almacenar los valores
self.launchbox_path = tk.StringVar(value="C:/LaunchBox")
self.alternative_path = tk.StringVar(value="D:/LaunchBox")
self.image_cache_ratio_w = tk.StringVar(value="200")
self.image_cache_ratio_h = tk.StringVar(value="200")
self.text_cell_w = tk.StringVar(value="150")
self.text_cell_h = tk.StringVar(value="50")
self.image_cell_w = tk.StringVar(value="200")
self.image_cell_h = tk.StringVar(value="200")
self.max_games_per_page_imagemode = tk.StringVar(value="200")
self.color_media_yes_title = tk.StringVar(value="#80FF00")
self.color_media_yes_rom = tk.StringVar(value="#80AA00")
self.color_media_both= tk.StringVar(value="#DBDBDB")
self.color_media_no = tk.StringVar(value="#FF0000")
self.color_no_trans = tk.StringVar(value="#DBDBDB")
self.selected_filters = ["Box - Front", "Clear Logo"]
self.make_cache = tk.BooleanVar()
# Cargar los valores actuales del archivo config.txt
self.load_config()
# Interfaz gráfica
self.setup_ui()
# Ajustar tamaño de la ventana al contenido más lejano sin margen adicional
self.root.update_idletasks()
width = self.scrollable_frame.winfo_reqwidth()
height = self.scrollable_frame.winfo_reqheight()
self.root.geometry(f"{width}x{height}")
# Vincular el evento de redimensionamiento de la ventana
self.root.bind("<Configure>", self._on_window_resize)
def _on_mousewheel(self, event):
"""Desplaza el Canvas con la rueda del ratón."""
if event.delta:
self.canvas.yview_scroll(int(-1 * (event.delta / 120)), "units")
def _on_window_resize(self, event):
"""Actualiza la región de desplazamiento cuando la ventana se redimensiona."""
self.canvas.configure(scrollregion=self.canvas.bbox("all"))
def setup_ui(self):
"""Configura los elementos de la interfaz gráfica."""
main_frame = tk.Frame(self.scrollable_frame)
main_frame.pack(fill="both", expand=True, padx=10, pady=10)
# Ruta de LaunchBox
tk.Label(main_frame, text="Ruta de LaunchBox:").grid(row=0, column=0, sticky="w")
self.path_entry = tk.Entry(main_frame, textvariable=self.launchbox_path, width=40)
self.path_entry.grid(row=0, column=1, sticky="w")
tk.Button(main_frame, text="Buscar", command=self.browse_launchbox_path).grid(row=0, column=2)
# Filtros seleccionados
tk.Label(main_frame, text="Filtros Seleccionados:").grid(row=1, column=0, sticky="w", pady=(10, 0))
self.filters = [
"Arcade - Marquee", "Banner", "Advertisement Flyer - Back", "Advertisement Flyer - Front", "Box - Front",
"Box - Front - Reconstructed", "Fanart - Box - Front", "Box - 3D", "Box - Back",
"Box - Back - Reconstructed", "Fanart - Box - Back", "Box - Spine", "Box - Full", "Clear Logo",
"Cart - Front", "Disc", "Fanart - Cart - Front", "Fanart - Disc", "Cart - Back", "Fanart - Cart - Back",
"Cart - 3D", "Fanart - Background", "Screenshot - Game Over", "Screenshot - Game Select",
"Screenshot - Game Title", "Screenshot - Gameplay", "Screenshot - High Scores", "Epic Games Background",
"Epic Games Poster", "Epic Games Screenshot", "GOG Poster", "GOG Screenshot", "Origin Background",
"Origin Poster", "Origin Screenshot", "Steam Banner", "Steam Poster", "Steam Screenshot",
"Uplay Background", "Uplay Thumbnail", "Arcade - Cabinet", "Arcade - Circuit Board",
"Arcade - Control Panel", "Arcade - Controls Information", "Amazon Background", "Amazon Poster",
"Amazon Screenshot", "Videos", "Manual"
]
self.filter_vars = []
num_columns = 3 # Número de columnas para los filtros
max_filters_per_column = (len(self.filters) // num_columns) + (1 if len(self.filters) % num_columns != 0 else 0)
for i, filter_name in enumerate(self.filters):
var = tk.BooleanVar(value=(filter_name in self.selected_filters))
row = 2 + (i % max_filters_per_column)
column = i // max_filters_per_column
tk.Checkbutton(main_frame, text=filter_name, variable=var).grid(row=row, column=column, sticky="w")
self.filter_vars.append(var)
# Ajustar el ancho del scrollable_frame al contenido más ancho
self.scrollable_frame.update_idletasks()
self.canvas.configure(scrollregion=self.canvas.bbox("all"))
row_offset = 2 + max_filters_per_column
# Añadir salto de línea
tk.Label(main_frame, text="").grid(row=row_offset, column=0)
row_offset += 1
# Máximo de juegos listados por página
tk.Label(main_frame, text="Máximo de juegos listados por página:").grid(row=row_offset, column=0, sticky="w")
self.max_games_entry = tk.Entry(main_frame, textvariable=self.max_games_per_page_imagemode, width=5)
self.max_games_entry.grid(row=row_offset, column=1, padx=(0, 0))
row_offset += 1
# Añadir salto de línea
tk.Label(main_frame, text="").grid(row=row_offset, column=0)
row_offset += 1
# Tamaño de las celdas en modo Texto
self.create_size_input(main_frame, "Tamaño de las Celdas en modo Texto", row_offset, self.text_cell_w,
self.text_cell_h)
row_offset += 1
# Tamaño de las celdas en modo Imagen
self.create_size_input(main_frame, "Tamaño de las Celdas en modo Imagen", row_offset, self.image_cell_w,
self.image_cell_h)
row_offset += 1
# Añadir doble salto de línea
tk.Label(main_frame, text="").grid(row=row_offset, column=0)
tk.Label(main_frame, text="").grid(row=row_offset + 1, column=0)
row_offset += 2
# Ruta alternativa a la Media (Video/Manuals)
tk.Label(main_frame, text="Ruta alternativa a la Media (Videos/Manuals):").grid(row=row_offset, column=0, sticky="w")
self.alt_path_entry = tk.Entry(main_frame, textvariable=self.alternative_path, width=40)
self.alt_path_entry.grid(row=row_offset, column=1, sticky="w")
tk.Button(main_frame, text="Browse", command=self.browse_alternative_path).grid(row=row_offset, column=2)
# Solo para Videos y Manuals, con el formato [Platforma]/Video/[Nombre del juego].mp4 y [Platforma]/Manuals/[Nombe del juego].pdf
tk.Label(main_frame,
text="Solo para Videos y Manuals, con el formato [Platforma]/Videos/[Nombre del juego].mp4 y [Platforma]/Manuals/[Nombe del juego].pdf").grid(
row=row_offset + 1, column=0, columnspan=3, sticky="w")
row_offset += 2
# Añadir doble salto de línea
tk.Label(main_frame, text="").grid(row=row_offset, column=0)
tk.Label(main_frame, text="").grid(row=row_offset + 1, column=0)
row_offset += 2
# Selectores de color
# Color Media encontrada Title
tk.Label(main_frame, text="Color Media encontrada con Title:").grid(row=row_offset, column=0, sticky="w")
self.color_media_yes_title_display = tk.Label(main_frame, textvariable=self.color_media_yes_title, width=10,
bg=self.color_media_yes_title.get())
self.color_media_yes_title_display.grid(row=row_offset, column=1, sticky="w", padx=(0, 0))
self.color_media_yes_title_button = tk.Button(main_frame, text="Seleccionar color",
command=self.choose_color_media_yes_title)
self.color_media_yes_title_button.grid(row=row_offset, column=1, sticky="e")
row_offset += 1
# Color Media encontrada Rom
tk.Label(main_frame, text="Color Media encontrada con RomName:").grid(row=row_offset, column=0, sticky="w")
self.color_media_yes_rom_display = tk.Label(main_frame, textvariable=self.color_media_yes_rom, width=10,
bg=self.color_media_yes_rom.get())
self.color_media_yes_rom_display.grid(row=row_offset, column=1, sticky="w", padx=(0, 0))
self.color_media_yes_rom_button = tk.Button(main_frame, text="Seleccionar color",
command=self.choose_color_media_yes_rom)
self.color_media_yes_rom_button.grid(row=row_offset, column=1, sticky="e")
row_offset += 1
# Color Media encontrada Ambos
tk.Label(main_frame, text="Color Media encontrada con ambos:").grid(row=row_offset, column=0, sticky="w")
self.color_media_both_display = tk.Label(main_frame, textvariable=self.color_media_both, width=10,
bg=self.color_media_both.get())
self.color_media_both_display.grid(row=row_offset, column=1, sticky="w", padx=(0, 0))
self.color_media_both_button = tk.Button(main_frame, text="Seleccionar color",
command=self.choose_color_media_both)
self.color_media_both_button.grid(row=row_offset, column=1, sticky="e")
row_offset += 1
# Color Media NO encontrada
tk.Label(main_frame, text="Color Media NO encontrada:").grid(row=row_offset, column=0, sticky="w")
self.color_media_no_display = tk.Label(main_frame, textvariable=self.color_media_no, width=10,
bg=self.color_media_no.get())
self.color_media_no_display.grid(row=row_offset, column=1, sticky="w", padx=(0, 30))
self.color_media_no_button = tk.Button(main_frame, text="Seleccionar color", command=self.choose_color_media_no)
self.color_media_no_button.grid(row=row_offset, column=1, sticky="e")
row_offset += 1
# Color fondo imagenes
tk.Label(main_frame, text="Color fondo imagenes:").grid(row=row_offset, column=0, sticky="w")
self.color_no_trans_display = tk.Label(main_frame, textvariable=self.color_no_trans, width=10,
bg=self.color_no_trans.get())
self.color_no_trans_display.grid(row=row_offset, column=1, sticky="w", padx=(0, 30))
self.color_no_trans_button = tk.Button(main_frame, text="Seleccionar color", command=self.choose_color_no_trans)
self.color_no_trans_button.grid(row=row_offset, column=1, sticky="e")
row_offset += 1
# Añadir doble salto de línea
tk.Label(main_frame, text="").grid(row=row_offset, column=0)
tk.Label(main_frame, text="").grid(row=row_offset + 1, column=0)
row_offset += 2
# Opción de hacer caché
tk.Checkbutton(main_frame, text="Hacer caché Local (Mejor rendimiento)", variable=self.make_cache, onvalue=True,
offvalue=False).grid(row=row_offset, column=0, sticky="w")
row_offset += 1
# Ratio de imágenes en caché
self.create_size_input(main_frame, "Ratio de las imágenes en caché", row_offset, self.image_cache_ratio_w,
self.image_cache_ratio_h)
row_offset += 1
# Botón Guardar
tk.Button(main_frame, text="Guardar", command=self.save_config).grid(row=row_offset, column=0, columnspan=3,
pady=(20, 0))
def create_size_input(self, frame, label_text, row, var_w, var_h):
"""Crea una fila con una etiqueta, dos entradas y una 'x' en el medio, con espaciado ajustado."""
tk.Label(frame, text=label_text).grid(row=row, column=0, sticky="w")
tk.Entry(frame, textvariable=var_w, width=5).grid(row=row, column=1, padx=(0, 0)) # Eliminar padding derecho
tk.Label(frame, text="x").grid(row=row, column=1, padx=(32, 0)) # Eliminar padding
tk.Entry(frame, textvariable=var_h, width=5).grid(row=row, column=1, padx=(80, 0)) # Eliminar padding izquierdo
def browse_launchbox_path(self):
folder_path = filedialog.askdirectory(title="Selecciona la carpeta de LaunchBox")
if folder_path:
self.launchbox_path.set(folder_path)
def browse_alternative_path(self):
folder_path = filedialog.askdirectory(title="Selecciona la carpeta alternativa de Media")
if folder_path:
self.alternative_path.set(folder_path)
def choose_color_media_yes_title(self):
color_code = colorchooser.askcolor(title="Selecciona un color")
if color_code[1]: # Verifica si se seleccionó un color (color_code[1] es el código hexadecimal)
self.color_media_yes_title.set(color_code[1])
self.color_media_yes_title_display.config(bg=color_code[1])
def choose_color_media_yes_rom(self):
color_code = colorchooser.askcolor(title="Selecciona un color")
if color_code[1]: # Verifica si se seleccionó un color
self.color_media_yes_rom.set(color_code[1])
self.color_media_yes_rom_display.config(bg=color_code[1])
def choose_color_media_both(self):
color_code = colorchooser.askcolor(title="Selecciona un color")
if color_code[1]: # Verifica si se seleccionó un color
self.color_media_both.set(color_code[1])
self.color_media_both_display.config(bg=color_code[1])
def choose_color_media_no(self):
color_code = colorchooser.askcolor(title="Selecciona un color")
if color_code[1]: # Verifica si se seleccionó un color
self.color_media_no.set(color_code[1])
self.color_media_no_display.config(bg=color_code[1])
def choose_color_no_trans(self):
color_code = colorchooser.askcolor(title="Selecciona un color")
if color_code[1]: # Verifica si se seleccionó un color
self.color_no_trans.set(color_code[1])
self.color_no_trans_display.config(bg=color_code[1])
def load_config(self):
"""Carga los valores actuales del archivo config.txt."""
if os.path.exists(self.config_file):
with open(self.config_file, "r") as file:
for line in file:
key, value = line.strip().split('=', 1)
value = value.strip('"')
if key == "path":
self.launchbox_path.set(value)
elif key == "filters":
self.selected_filters = value.split(',')
elif key == "image_cache_ratio":
try:
w, h = value.split('x')
self.image_cache_ratio_w.set(w)
self.image_cache_ratio_h.set(h)
except ValueError:
print(f"Error: el valor '{value}' no está en el formato 'ancho x alto'")
# Aquí puedes definir un valor por defecto o manejar el error de otra forma
self.image_cache_ratio_w.set(0)
self.image_cache_ratio_h.set(0)
elif key == "text_cell":
try:
w, h = value.split('x')
self.text_cell_w.set(w)
self.text_cell_h.set(h)
except ValueError:
print(f"Error: el valor '{value}' no está en el formato 'ancho x alto'")
# Aquí puedes definir un valor por defecto o manejar el error de otra forma
self.text_cell_w.set(0)
self.text_cell_h.set(0)
elif key == "image_cell":
try:
try:
w, h = value.split('x')
self.image_cell_w.set(w)
self.image_cell_h.set(h)
except ValueError:
print(f"Error: el valor '{value}' no está en el formato 'ancho x alto'")
# Aquí puedes definir un valor por defecto o manejar el error de otra forma
self.image_cell_w.set(0)
self.image_cell_h.set(0)
except ValueError:
print(f"Error: el valor '{value}' no está en el formato 'ancho x alto'")
# Aquí puedes definir un valor por defecto o manejar el error de otra forma
self.image_cell_w.set(0)
self.image_cell_h.set(0)
elif key == "alternative_path":
self.alternative_path.set(value)
elif key == "max_games_per_page_imagemode":
self.max_games_per_page_imagemode.set(value)
elif key == "color_media_yes_title":
self.color_media_yes_title.set(value if value else "#80FF00")
elif key == "color_media_yes_rom":
self.color_media_yes_rom.set(value if value else "80AA00")
elif key == "color_media_both":
self.color_media_both.set(value if value else "#DBDBDB")
elif key == "color_media_no":
self.color_media_no.set(value if value else "#FF0000")
elif key == "color_media_yes_rom":
self.color_media_yes_rom.set(value if value else "80AA00")
elif key == "color_no_trans":
self.color_no_trans.set(value if value else "#DBDBDB")
elif key == "make_cache":
self.make_cache.set(value.lower() == "true")
def save_config(self):
"""Guarda los valores seleccionados en el archivo config.txt."""
selected_filters = [self.filters[i] for i, var in enumerate(self.filter_vars) if var.get()]
with open(self.config_file, "w") as file:
file.write(f"path={self.launchbox_path.get()}\n")
file.write(f'filters="{",".join(selected_filters)}"\n')
file.write(f"image_cache_ratio={self.image_cache_ratio_w.get()}x{self.image_cache_ratio_h.get()}\n")
file.write(f"text_cell={self.text_cell_w.get()}x{self.text_cell_h.get()}\n")
file.write(f"image_cell={self.image_cell_w.get()}x{self.image_cell_h.get()}\n")
file.write(f"alternative_path={self.alternative_path.get()}\n")
file.write(f"max_games_per_page_imagemode={self.max_games_per_page_imagemode.get()}\n")
file.write(f"color_media_yes_title={self.color_media_yes_title.get()}\n")
file.write(f"color_media_yes_rom={self.color_media_yes_rom.get()}\n")
file.write(f"color_media_both={self.color_media_both.get()}\n")
file.write(f"color_media_no={self.color_media_no.get()}\n")
file.write(f"color_no_trans={self.color_no_trans.get()}\n")
file.write(f"make_cache={self.make_cache.get()}\n")
messagebox.showinfo("Guardar", "Configuración guardada correctamente.")
self.root.destroy()
if __name__ == "__main__":
root = tk.Tk()
config_file = os.path.join(os.path.dirname(__file__), "config.txt")
app = SettingsWindow(root, config_file)
root.mainloop()
pip install pillow
SirAzraelGrotesque escribió:@Messiahs Impresionante que hayas logrado algo así sin saber programar, aunque sea con IA. O no sé si solamente es que no sabes programar en Python pero sí en otros lenguajes, no me ha quedado claro.
Messiahs escribió:SirAzraelGrotesque escribió:@Messiahs Impresionante que hayas logrado algo así sin saber programar, aunque sea con IA. O no sé si solamente es que no sabes programar en Python pero sí en otros lenguajes, no me ha quedado claro.
Na no sé programar en ninguno, lo más que hice algún script con Autohotkey para algún juego online xD de pulsar teclas cada x tiempo y tal, pero con las IA está tirado, empizas poco a poco y vas mejorando, al principio way por qué el código es pequeño y te lo dan entero cuando empiezan a darte solo cachos y tienes que pegarlo viene el problema, hasta que me di cuenta lo sensible que era a los espacios el python no veas yo todo el rato que no que n iba y le pedía el código completo entonces funcionaba...
Habré tardado unas 8h o así aún siendo con la IA no lo hice en 10 minutos, pero me ponía a ratos y lo iba "ampliando".