Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- 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: Could not load LaunchBox configuration. Retrying...")
- retries += 1
- if retries == max_retries:
- print("Error: Could not load LaunchBox configuration after several attempts.")
- self.root.destroy()
- self.setup_ui()
- def setup_ui(self):
- # Top frame (for platform selection)
- self.top_frame = tk.Frame(self.root)
- self.top_frame.pack(fill="x", padx=10, pady=10)
- # Label and Combobox for platform selection
- self.platform_label = tk.Label(self.top_frame, text="Platform:")
- 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)
- # Button to load platforms
- self.load_button = tk.Button(self.top_frame, text="Load Platforms", command=self.load_platforms)
- self.load_button.pack(side="left")
- # Frame for Checkbuttons (Image Mode and Hide All YES)
- self.checkbutton_frame = tk.Frame(self.top_frame)
- self.checkbutton_frame.pack(side="left", padx=(10, 0))
- # Checkbutton for image mode
- self.switch_var = tk.BooleanVar()
- self.switch = tk.Checkbutton(self.checkbutton_frame, text="Image Mode", variable=self.switch_var,
- command=self.toggle_view_mode)
- self.switch.pack(anchor="w") # Aligned to the left within the Frame
- # Checkbutton to hide games with "YES" in all cells
- self.hide_all_si_var = tk.BooleanVar(value=False) # Disabled by default
- self.hide_all_si_check = tk.Checkbutton(self.checkbutton_frame, text="Hide All YES",
- variable=self.hide_all_si_var,
- command=self.toggle_hide_all_si)
- self.hide_all_si_check.pack(anchor="w") # Aligned to the left within the Frame
- # Button to open settings (on the right)
- self.settings_button = tk.Button(self.top_frame, text="Settings", command=self.open_settings)
- self.settings_button.pack(side="right", padx=(10, 0))
- # Frame for filters and the table
- self.filter_frame = tk.LabelFrame(self.root, text="Image Status")
- self.filter_frame.pack(fill="both", expand=True, padx=10, pady=10)
- # Canvas and scrollbars for the table
- 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())
- # Bind mouse wheel event to the Canvas
- self.canvas.bind("<MouseWheel>", self.on_mouse_wheel_cells)
- # Frame for pagination and additional buttons
- 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()
- # "Previous" button
- self.prev_button = tk.Button(self.center_frame, text="Previous", command=self.prev_page)
- self.prev_button.pack(side="left")
- # Current page label
- self.page_label = tk.Label(self.center_frame, text="Page 1 of 1")
- self.page_label.pack(side="left", padx=10)
- # "Next" button
- self.next_button = tk.Button(self.center_frame, text="Next", command=self.next_page)
- self.next_button.pack(side="left")
- # "Generate All Cache" button
- self.generate_all_cache_button = tk.Button(self.center_frame, text="Generate All Cache",
- command=self.generate_all_cache)
- self.generate_all_cache_button.pack(side="left", padx=(10, 0))
- # "Regenerate Cache" button
- self.regenerate_cache_button = tk.Button(self.center_frame, text="Regenerate Cache",
- command=self.regenerate_cache)
- self.regenerate_cache_button.pack(side="left", padx=(10, 0))
- # Hide or show cache buttons based on make_cache
- self.update_cache_buttons_visibility()
- def on_mouse_wheel_cells(self, event):
- menu_state = str(self.platform_menu.cget("state")) # Save it as an immutable string
- 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):
- """
- Handles vertical scrolling with the mouse wheel for the platforms list.
- """
- menu_state = self.platform_menu.cget("state")
- if menu_state == "normal": # Only scroll if the menu is expanded
- current_index = self.platform_menu.current()
- if event.delta > 0:
- new_index = max(0, current_index - 1) # Scroll up
- else:
- new_index = min(len(self.platform_menu["values"]) - 1, current_index + 1) # Scroll down
- 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):
- """
- Synchronizes horizontal scrolling between the header_canvas and the main canvas.
- Does not affect vertical scrolling.
- """
- 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):
- # If the Checkbutton is enabled, hide games with "YES" in all cells
- if self.hide_all_si_var.get():
- # Sort games alphabetically before applying the filter
- 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)
- # Sort games alphabetically (from A to Z)
- sorted_game_dict = dict(
- sorted(game_dict.items(), key=lambda item: (self.natural_sort_key(item[0]), item[0])))
- # Apply the "Hide All YES" filter to the sorted list
- self.hide_all_si(sorted_game_dict)
- else:
- # If disabled, show all games again
- self.on_platform_select()
- def update_cache_buttons_visibility(self):
- """Updates the visibility of cache buttons based on the value of 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("") # Clear current selection
- self.platforms.clear() # Clear the platforms dictionary
- # Build the path to the platforms folder
- platforms_path = os.path.join(self.launchbox_path, "Data", "Platforms").replace("\\", "/")
- if not os.path.exists(platforms_path):
- messagebox.showerror("Error", f"Platforms folder not found at: {platforms_path}")
- return
- # Get platform names
- platform_names = []
- for filename in os.listdir(platforms_path):
- if filename.endswith(".xml"):
- platform_name = filename[:-4] # Remove the .xml extension
- self.platforms[platform_name] = os.path.join(platforms_path, filename).replace("\\", "/")
- platform_names.append(platform_name)
- if not platform_names:
- messagebox.showerror("Error", "No XML files found in the platforms folder.")
- return
- # Assign values to the Combobox
- self.platform_menu["values"] = platform_names
- self.platform_menu.current(0) # Select the first platform by default
- # Force UI update
- self.platform_menu.update_idletasks()
- # Load data for the selected platform
- 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)
- # Filter games that have "YES" in all cells
- filtered_game_dict = self.filter_all_si(game_dict)
- # Update the table with the filtered games
- 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"The XML file does not have the expected structure. Root element: {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"Could not read the XML file: {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="Generating Cache... 0%")
- 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"Generating page {page + 1} of {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)
- # Reload configuration after saving changes
- self.load_launchbox_path()
- # Update cache buttons visibility
- self.update_cache_buttons_visibility()
- # Update other UI elements if necessary
- self.on_platform_select() # This updates the table with the new values
- 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("config.txt file created with the basic structure.")
- 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: The value of image_cell is not in the correct format. Using default values (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: The value of text_cell is not in the correct format. Using default values (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: The value of max_games_per_page_imagemode is not a valid number. Using default value (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="): # New parameter
- self.make_cache = line.strip().split('=')[1].lower() == "true"
- return True
- def load_platforms(self):
- self.platform_combobox.set("") # Clear current selection
- self.platforms.clear() # Clear the platforms dictionary
- # Build the path to the platforms folder
- platforms_path = os.path.join(self.launchbox_path, "Data", "Platforms").replace("\\", "/")
- if not os.path.exists(platforms_path):
- messagebox.showerror("Error", f"Platforms folder not found at: {platforms_path}")
- return
- # Get platform names
- platform_names = []
- for filename in os.listdir(platforms_path):
- if filename.endswith(".xml"):
- platform_name = filename[:-4] # Remove the .xml extension
- self.platforms[platform_name] = os.path.join(platforms_path, filename).replace("\\", "/")
- platform_names.append(platform_name)
- if not platform_names:
- messagebox.showerror("Error", "No XML files found in the platforms folder.")
- return
- # Assign values to the Combobox
- self.platform_menu["values"] = platform_names
- self.platform_menu.current(0) # Select the first platform by default
- # Force UI update
- self.platform_menu.update_idletasks()
- # Load data for the selected platform
- 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"The XML file does not have the expected structure. Root element: {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"Could not read the XML file: {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 # Define 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"Page {self.current_page + 1} of {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):
- """
- Searches for images in a folder and its subfolders recursively.
- Returns the image path if found, or None if not found.
- """
- for root, dirs, files in os.walk(folder):
- for file in files:
- # Check if the file matches any of the names and extensions
- 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(["Title"] + 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":
- # Define main and alternative paths
- 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
- # Search in the alternative path first (if it exists)
- if alternative_folder_path and os.path.exists(alternative_folder_path):
- # Search for <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:
- # Search for <ApplicationPath>.mp4 (without -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:
- # If <Title> and <ApplicationPath> are the same, search for <Title>.mp4 (without -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'
- # If not found in the alternative path, search in the main path
- if not file_found and os.path.exists(main_folder_path):
- # Search for <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:
- # Search for <ApplicationPath>.mp4 (without -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:
- # If <Title> and <ApplicationPath> are the same, search for <Title>.mp4 (without -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'
- # Show the result in the table
- if self.show_images:
- if file_found:
- self.draw_cell(row, col, "YES",
- 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 = "YES (Title)" if match_type == 'title' else "YES (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":
- # Define main and alternative paths
- 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
- # Search in the alternative path first (if it exists)
- if alternative_folder_path and os.path.exists(alternative_folder_path):
- # Search for <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:
- # Search for <ApplicationPath>.pdf (without -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:
- # If <Title> and <ApplicationPath> are the same, search for <Title>.pdf (without -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'
- # If not found in the alternative path, search in the main path
- if not file_found and os.path.exists(main_folder_path):
- # Search for <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:
- # Search for <ApplicationPath>.pdf (without -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:
- # If <Title> and <ApplicationPath> are the same, search for <Title>.pdf (without -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'
- # Show the result in the table
- if self.show_images:
- if file_found:
- self.draw_cell(row, col, "YES",
- 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 = "YES (Title)" if match_type == 'title' else "YES (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):
- # Search for <Title>-01.ext recursively
- 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
- # Search for <ApplicationPath>.ext non-recursively
- 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:
- # If <Title> and <ApplicationPath> are the same, search for <Title>.ext (without -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
- # Determine the match type
- if title_found and rom_found:
- match_type = 'title/rom'
- image_path = title_path # Use the Title image to display
- 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 = "YES (Title/Rom)"
- cell_color = self.color_media_both
- elif match_type == 'title':
- cell_value = "YES (Title)"
- cell_color = self.color_media_yes_title
- elif match_type == 'rom':
- cell_value = "YES (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))
- # Force focus to the Canvas to receive mouse wheel events
- self.canvas.focus_set()
- def on_header_click(self, col):
- # Call the method to sort the data
- 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 == "YES (Title)":
- fill_color = self.color_media_yes_title
- elif value == "YES (Rom)":
- fill_color = self.color_media_yes_rom
- elif value == "NO":
- fill_color = self.color_media_no
- else:
- fill_color = cell_color or "white"
- # Draw the cell
- 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:
- # Load from cache
- 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"The file {image_path} is too large ({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 processing image {image_path}: {e}")
- with open("images_with_errors.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:
- # Load directly from the source
- 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"Unexpected error processing image {image_path}: {e}")
- self.draw_cell(row, col, "NO", cell_color=self.color_media_no)
- with open("images_with_errors.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="Generating Cache... 0%")
- 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)
- # Sort games
- if col == 0:
- # Sort by title
- sorted_game_dict = dict(
- sorted(game_dict.items(), key=lambda item: (self.natural_sort_key(item[0]), item[0])))
- else:
- # Sort by the selected column
- 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)))
- # Toggle between ascending and descending order
- 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
- # If the "Hide All YES" filter is enabled, apply the filter after sorting
- if self.hide_all_si_var.get():
- sorted_game_dict = self.filter_all_si(sorted_game_dict)
- # Update the GUI with the sorted (and filtered if necessary) games
- self.load_filter_file(selected_platform, sorted_game_dict)
- def filter_all_si(self, game_dict):
- """
- Filters games that have "YES" in all cells.
- """
- filtered_game_dict = {}
- for title, search_names in game_dict.items():
- all_si = True
- for filter_name in self.filters:
- if filter_name == "Videos":
- # Logic for 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":
- # Logic for manuals
- 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:
- # Logic for images
- 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("Severe memory error. The program must close.")
- except Exception as e:
- print(f"Unexpected error: {e}")
- messagebox.showerror("Error", f"An unexpected error occurred: {e}")
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement