""" Date: 16th/July/2026 Created by: Jap-Slappa NFO Metadata Updater+PSG v4.0 Customised By: Jap-Slappa Base Code By Gemini Completed: 17th/July/2026 """ import PySimpleGUI as sg import traceback # import logging import sys import time from pathlib import Path # Tell Python to look in your C-Drive helper folder # Use raw strings (r"...") to handle Windows backslashes properly # sys.path.append(r"C:\My_Python_Helper_Modules") # OG Code sys.path.append(r"\\DS918-ms\usbshare1\My_Python_Helper_Modules") import Get_Date_and_Time__v1 as Get_Date_and_Time # Get_Date_and_Time.Fav_Day_date_and_time_string() def basic_custom_error(): """Print Minimal Custom info about basic Python stack trace --- Code Example: >>> except: my_custom_error() --- NOTE: Module Name or Function name where the error occurred... | Are NOT required, as ARGs - Nor Provided at print-out! """ print() print(' ============= An exception occurred =============') print(' =================================================== ') traceback.print_exc(limit=1, file=sys.stdout) print(' =================================================== ') print() time.sleep(2.0) #? === End Of Function == # Paths to my main Anime TV Shows Libraries/Folders (On My Portable Drive: G) # main_folder_paths_list = [ # r'G:\Kodi Builds - 2024\_Kodi 21 Builds_NEW_2025\02. TMM - Anime STRM Files\COMPLTED - TMM Anime STRMs\01. Anime TV Shows\01. Anime-TV 2026\__Currently-Watching--Amine-2025+', # r'G:\Kodi Builds - 2024\_Kodi 21 Builds_NEW_2025\02. TMM - Anime STRM Files\COMPLTED - TMM Anime STRMs\01. Anime TV Shows\01. Anime-TV 2026\__Discovered-NEW-Anime_TV--2026', # r'G:\Kodi Builds - 2024\_Kodi 21 Builds_NEW_2025\02. TMM - Anime STRM Files\COMPLTED - TMM Anime STRMs\01. Anime TV Shows\01. Anime-TV 2026\__Old_Fav_Anime', # ] # Main_Anime_Lib_Folder = r'G:\Kodi Builds - 2024\_Kodi 21 Builds_NEW_2025\02. TMM - Anime STRM Files\COMPLTED - TMM Anime STRMs\01. Anime TV Shows\01. Anime-TV 2026' #todo: [TS-vpn] Paths to my main Anime TV Shows Libraries/Folders (On My DS918 Web Server) main_folder_paths_list = [ r'\\100.75.195.49\web\00.My_Watclists_4_Kodi\Discovered-NEW-Anime_TV--2026', r'\\100.75.195.49\web\00.My_Watclists_4_Kodi\Currently-Watching-Anime_TV--2025+', r'\\100.75.195.49\web\00.My_Watclists_4_Kodi\__Old_Fav_Anime', ] # Main_Anime_Lib_Folder = r'\\100.75.195.49\web\00.My_Watclists_4_Kodi' #todo: [DS918] Paths to my main Anime TV Shows Libraries/Folders (On My DS918 Web Server) # main_folder_paths_list = [ # r'\\192.168.4.212\web\00.My_Watclists_4_Kodi\Discovered-NEW-Anime_TV--2026', # r'\\192.168.4.212\web\00.My_Watclists_4_Kodi\Discovered-NEW-Anime_TV--2026', # r'\\192.168.4.212\web\00.My_Watclists_4_Kodi\__Old_Fav_Anime', # ] # Main_Anime_Lib_Folder = r'\\192.168.4.212\web\00.My_Watclists_4_Kodi' def open_folder_dialog(My_default_folder_path): try: select_folder = sg.filedialog.askdirectory(initialdir=My_default_folder_path) if select_folder: return select_folder else: return None except Exception as Error: basic_custom_error() print(f"(Error: {Error})") #? === End Of Function == def Custom_Popup_Get_Folder(My_default_folder_path:Path): """Creates the initial window layout to select a folder and scan.""" try: Anime_Folder_Name, Show_Name, Season_Folder = path_splitter(My_default_folder_path) layout = [ [ sg.Text("Custom Popup + Get The *Season* Folder", font=("Helvetica", 14, "bold") ) ], #todo: === Update Anime Folder Selection === [ sg.Text(f'Anime Folder Name = {Anime_Folder_Name}', font=("Helvetica", 12, "bold"), key='-ANIME_FOLDER_NAME-', ) ], [ sg.Text(f'Show + Season = {Show_Name}: {Season_Folder}', font=("Helvetica", 12, "bold"), key='-SHOW_AND_SEASON-', ) ], #todo: === Update Anime Folder Selection === [sg.HSeparator(pad=(0, 15))], [ sg.Text("Select the directory containing your .nfo files:", font=("Helvetica", 10, "bold"), ), ], [sg.HSeparator(pad=(0, 15))], [ sg.Multiline(str(My_default_folder_path), size=(100, 3), key='-UPDATE_FOLDER_PATH-', font=("Helvetica", 12, "bold"), ), # (width, height) sg.Button("Search For\n Your Season Folder", key="-BROWSE_FOLDER-", font=("Helvetica", 11, "bold"), size=(20, 2), ), # I can't get this (sg.filedialog.askdirectory) working here yet! # sg.filedialog.askdirectory(initialdir=str(My_default_folder_path)), ], [sg.HSeparator(pad=(0, 15))], [ sg.Button("Close Window", key="-EXIT-", font=("Helvetica", 12, "bold"), ), sg.Push(), # sg.Button("Start Folder Scan...", # key="-SEARCH_FOLDER-", # font=("Helvetica", 11, "bold"), # ), sg.Button("Save Folder Path... For Scanning (Win-03)", key="-SEARCH_FOLDER-", font=("Helvetica", 11, "bold"), ), ] ] return layout except Exception as Error: basic_custom_error() print(f"(Error: {Error})") #? === End Of Function == def path_splitter(the_path:str): # Explicitly make a PathLib Object PathLib_Path = Path(the_path) Anime_Folder_Name = PathLib_Path.parts[-3] Show_Name = PathLib_Path.parts[-2] Season_Folder = PathLib_Path.name return Anime_Folder_Name, Show_Name, Season_Folder #? === End Of Function == def PSG_get_nfo_folder_path(My_default_folder_path): """ Win-02: Creates My Custom-Win To.. Get a Season Folder """ try: # Open the folder scanning configuration window Season_Folder_window = sg.Window("Win-02: Custom Popup Window (Get Season Folder)", Custom_Popup_Get_Folder(My_default_folder_path), finalize=True, ) while True: event, values = Season_Folder_window.read() if event in (sg.WIN_CLOSED, "-EXIT-"): break elif event == "-BROWSE_FOLDER-": print('Event (Button Pressed) = BROWSE_FOLDER') # Select The Anime Season Folder... To Be Scanned selected_folder_2 = open_folder_dialog(app.Main_folders_dir) # Get the wanted "Parts"/Folder names in the selected Path Anime_Folder_Name, Show_Name, Season_Folder = path_splitter(selected_folder_2) Season_Folder_window['-ANIME_FOLDER_NAME-'].update(f'Anime Folder Name = {Anime_Folder_Name}') Season_Folder_window['-SHOW_AND_SEASON-'].update(f'Show + Season = {Show_Name} >> {Season_Folder}') Season_Folder_window['-UPDATE_FOLDER_PATH-'].update(selected_folder_2) print('selected_folder_2 = ', selected_folder_2) print() elif event == "-SEARCH_FOLDER-": selected_folder = values.get("-UPDATE_FOLDER_PATH-", False) # This Works print('Event = SEARCH_FOLDER Button Was Pressed!!') print('selected_folder = ', selected_folder) print() try: if selected_folder: PathLib_selected_folder_Path = Path(selected_folder) TV_Show_Folder_Name = PathLib_selected_folder_Path.parts[-2] print('') print('==== PSG_get_nfo_folder_path() ====') print('Your Selected TV SHow Folder To... "SCAN"') print('---') print('PathLib_selected_folder_Path [Name]: ', f'{TV_Show_Folder_Name}: {PathLib_selected_folder_Path.name}') print('PathLib_selected_folder_Path [FULL]: \n >>', PathLib_selected_folder_Path) print('') # Store The selected Season Folder app.selected_season_folder = PathLib_selected_folder_Path print('app.selected_season_folder = ', app.selected_season_folder) print('') break # return PathLib_selected_folder_Path elif selected_folder is None: break # return except Exception as Error: basic_custom_error() print(f"PSG_get_nfo_folder_path() \n(Error: {Error})") Season_Folder_window.close() except Exception as Error: basic_custom_error() print(f"(Error: {Error})") #? === End Of Function == ############################################################## #------------------------------------------------------------ #todo: NFO Updater (Class) #------------------------------------------------------------ ############################################################## class NfoUpdaterApp: def __init__(self): # Set a clean, modern dark theme sg.theme("DarkBlue") self.target_dir = '' # Default fallback path self.nfo_files:list[Path] = [] # Stores Path objects #? Main_folders_dir: Path is created in... "Select_Main_Folder_Window()" Function self.Main_folders_dir:Path = '' # Folder with my TV Show Folder self.Main_folders_list:list[Path] = main_folder_paths_list # Stores the Main Folders Path objects self.Main_folders_window = None self.selected_season_folder = '' self.file_summery_rows_complete = '' self.main_window = None self.list_window = None def Win_03_layout_Scan_Season_Folder(self): """Creates the initial window layout to select a folder and scan.""" Anime_Libray_Name = self.target_dir.parts[-3] Anime_Show_Name = self.target_dir.parts[-2] Anime_Season_Folder = self.target_dir.name layout = [ [ sg.Text("Kodi NFO Watched Status Updater", font=("Helvetica", 14, "bold") ) ], [ sg.Text(f'Anime_Libray_Name = {Anime_Libray_Name}', font=("Helvetica", 12, "bold") ) ], [ sg.Text(f'Target = {Anime_Show_Name}: {Anime_Season_Folder}', font=("Helvetica", 12, "bold") ) ], [sg.Text("Select the directory containing your media and .nfo files:")], [ # sg.Input(str(self.target_dir), # key="-FOLDER-", # size=(100, 4)), # (width, height) sg.Multiline(str(self.target_dir), size=(80, 2), key='-FOLDER-', font=("Helvetica", 12, "bold"), justification='center', disabled=True, ), # (width, height) sg.Button('Select Your\n Season Folder', key="-CHANGE_SEASON_FOLDER-", font=("Helvetica", 11, "bold"), # pad=(10, 15), size=(15, 2), button_color=("black", "#D7C605"), ), # sg.popup_get_folder(message='Select Your\n Season Folder', # key='-CHANGE_SEASON_FOLDER-', # initial_folder=str(self.target_dir), # font=("Helvetica", 11, "bold"), # size=(15, 2), # button_color='#2A8C82', # ), #^ sg.FolderBrowse >> Does NOT Work Here!! # sg.FolderBrowse(button_text='Select Your\n Season Folder', # key='-CHANGE_SEASON_FOLDER-', # initial_folder=str(self.target_dir), # font=("Helvetica", 11, "bold"), # size=(15, 2), # button_color='#2A8C82', # ) ], [sg.HSeparator(pad=(0, 15))], [ sg.Button("Exit", key="-EXIT-", font=("Helvetica", 10, "bold"), pad=(10, 15), button_color=("yellow", "#BE5454"), ), sg.Button("Close Window & Reselect Season", key="-CLOSE_WINDOW-", font=("Helvetica", 10, "bold"), pad=(10, 15), button_color=("black", "#D7C605"), ), sg.Push(), sg.Button("Scan The *Season* Folder...", key="-SCAN-", font=("Helvetica", 12, "bold"), pad=(10, 15), size=(30, 2), button_color=("white", "#2A8C82"), bind_return_key=True ), ] ] return layout #? === End Of Method/Function == def create_MAIN_Folder_list_layout(self): """Dynamically generates a scrollable list of found Folders with checkboxes on the right.""" folder_rows_List:list[str] = [] try: # LOOP: To Build individual rows for each Main folder path, in my List for idx, folder_path in enumerate(self.Main_folders_list, start=1): idx_num = str(idx).zfill(2) #todo: Explicitly make PathLib Obj pathlib_folder_path = Path(folder_path) #^ Individual row construction + Add to the Rows list folder_rows_List.append( [ # Display the foldername (relative to parent to keep it short and clean) sg.Text(f'{idx_num}-{pathlib_folder_path.name}', font=("Helvetica", 11, "bold"), size=(40, 2), tooltip=str(pathlib_folder_path)), sg.Push(), # Pushes the checkbox all the way to the right # Key is indexed so we can match it back to the Path list later sg.Checkbox("", default=False, font=("Helvetica", 20), size=(40, 2), expand_x=True, expand_y=True, enable_events=True, key=f"-CHECK_MAIN{idx}-", ) ] ) # Pack the rows into a scrollable column scrollable_column_2 = sg.Column( #^ Input the completed rows list here folder_rows_List, # scrollable=True, vertical_scroll_only=True, # size=(550, 300), size=(600, 250), # (width, height) key="-SCROLLABLE_COL_MAIN-", expand_x=True, expand_y=True ) #todo: TV Show folder Layout layout_msg_01 = (f"Found {len(self.Main_folders_list)} " f"TV Show folders in: {Path(self.Main_folders_dir).name}") except Exception as Error: basic_custom_error() print(f"(Error: {Error})") layout = [ [sg.Text("Select TV Show folder to Update", font=("Helvetica", 14, "bold"))], [ sg.Text(layout_msg_01, text_color="#A4E3DB", font=("Helvetica", 11, "bold"), ) ], [sg.HSeparator(pad=(0, 10))], #^ Input the scrollable_column_2, with the completed rows list here [scrollable_column_2], [sg.HSeparator(pad=(0, 15))], [ sg.Button("Close", key="-MAIN_CLOSE-"), sg.Push(), sg.Button("Select Folder", key="-MAIN_FOLDER_SELECT-", button_color=("white", "#2A8C82")), ] ] return layout #? === End Of Method/Function == def Check_If_Watched_In_NFO(self, found_nfo_file): pathlib_found_nfo = Path(found_nfo_file) content = pathlib_found_nfo.read_text(encoding='utf-8', errors='ignore') # Swap if "false" in content: return 'NOT', True elif "true" in content: return '', False #? === End Of Method/Function == def create_list_layout(self): """Dynamically generates a scrollable list of found .nfo files with checkboxes on the right.""" file_rows:list[str] = [] file_summery_rows:list[str] = [] try: # Build individual rows for each NFO file found for idx, file_path in enumerate(self.nfo_files): watched_status_txt, bool_status = self.Check_If_Watched_In_NFO(file_path) idx_num = str(idx).zfill(2) idx_sum = str(idx+1).zfill(2) file_summery_txt = f'[{idx_sum}] Status: {watched_status_txt} | {file_path.name} ' self.file_summery_rows_complete += f'\n{file_summery_txt}' file_summery_rows.append(file_summery_txt) file_rows.append([ # Display the filename (relative to parent to keep it short and clean) sg.Text(f'{idx_num}-{file_path.name}', size=(50, 1), font=("Helvetica", 10, "bold"), tooltip=str(file_path)), sg.Push(), # Pushes the checkbox all the way to the right # Key is indexed so we can match it back to the Path list later sg.Checkbox("", default=bool_status, key=f"-CHECK_{idx}-"), sg.Text( f'Status: {watched_status_txt}', font=("Helvetica", 10, "bold"), ) ]) # Pack the rows into a scrollable column scrollable_column = sg.Column( file_rows, scrollable=True, vertical_scroll_only=True, # size=(550, 300), size=(800, 450), # (width, height) key="-SCROLLABLE_COL-", expand_x=True, expand_y=True ) _, Show_Name, Season_Folder = path_splitter(self.target_dir) layout = [ [sg.Text("Select NFO Files to Update", font=("Helvetica", 14, "bold"))], [ sg.Text((f"Found {len(self.nfo_files)} .nfo files " f"in >> {Show_Name} / {Season_Folder}"), text_color="#A4E3DB" ) ], [sg.HSeparator(pad=(0, 10))], [scrollable_column], [sg.HSeparator(pad=(0, 15))], [ # Bottom Row of Buttons sg.Button("Select All", key="-SELECT_ALL-"), sg.Button("Select None", key="-SELECT_NONE-"), sg.Push(), sg.Button("Update NFO", key="-UPDATE-", button_color=("white", "#2A8C82")), sg.Button("Close", key="-CLOSE-") ] ] except Exception as Error: basic_custom_error() print(f"(Error: {Error})") return layout #? === End Of Method/Function == def scan_directory(self, path_str): """Scans the selected path using Pathlib for .nfo files.""" try: self.target_dir = Path(path_str) if not self.target_dir.exists() or not self.target_dir.is_dir(): sg.popup_error("Error", "Selected directory does not exist or is invalid.", keep_on_top=True) return False # self.nfo_files = list(self.target_dir.rglob("*.nfo")) # OG Gemini Code # Scan the Selected Folder ONLY self.nfo_files = list(self.target_dir.glob("*.nfo")) # Me if not self.nfo_files: sg.popup_annoying("No Files Found", "No .nfo files were found in the selected folder tree.", keep_on_top=True) return False except Exception as Error: basic_custom_error() print(f"(Error: {Error})") return True #? === End Of Method/Function == def process_nfo_files(self, values): """Performs the watched-status updates on selected files.""" changed_files = [] unchanged_files = [] try: for idx, file_path in enumerate(self.nfo_files): # Check if this specific file's checkbox was ticked is_selected = values.get(f"-CHECK_{idx}-", False) if not is_selected: unchanged_files.append(f"{file_path.name} (Skipped)") continue try: watch_false = "false" watch_true = "true" playcount_zero = "0" playcount_one = "1" # Read content using UTF-8 safely content = file_path.read_text(encoding='utf-8', errors='ignore') modified = False # Swap if "false" in content: if watch_false == "false": content = content.replace("false", "true") modified = True elif watch_true == "true": modified = False continue # Swap if "0" in content: if playcount_zero == "0": content = content.replace("0", "1") modified = True elif playcount_one == "1": modified = False continue if modified: file_path.write_text(content, encoding='utf-8') changed_files.append(f"{file_path.name} (Updated)") else: unchanged_files.append(file_path.name) except Exception as e: unchanged_files.append(f"{file_path.name} (Error: {e})") # Generate a detailed summary window self.show_summary_popup(changed_files, unchanged_files) except Exception as Error: basic_custom_error() print(f"(Error: {Error})") #? === End Of Method/Function == def show_summary_popup(self, changed, unchanged): """Creates a readable, scrollable text summary popup of the operation.""" try: Anime_Folder_Name, Show_Name, Season_Folder = path_splitter(self.target_dir) summary_text = ( f"======================\n" f"=== UPDATE SUMMARY ===\n" f"======================\n" f"----------------------\n" f"....Date + Time: {Get_Date_and_Time.Fav_Day_date_and_time_string()}\n" f"----------------------\n" f"..Anime TV Show: {Show_Name}\n" f".TV Show Season: {Season_Folder}\n" f"----------------------\n" f"Total Files Found: {len(self.nfo_files)}\n" f"Files Updated: {len(changed)}\n" f"Files Unchanged / Skipped: {len(unchanged)}\n\n" ) if changed: # OG Code # summary_text += "UPDATED FILES:\n" + "\n".join([f" [✓] {f}" for f in changed]) + "\n\n" # New Code (Coz this... [✓], caused an error. I changed to.. [+]) changed_reformated = [] for idx, f_Y in enumerate(changed, start=1): idx_num_Y = str(idx).zfill(2) reformated_item_Y = f" [+] {idx_num_Y}. {f_Y}" changed_reformated.append(reformated_item_Y) # summary_text += "UPDATED FILES:\n" + "\n".join([f" [+] {f}" for f in changed]) + "\n\n" summary_text += "UPDATED FILES:\n" + "\n".join([f"{f}" for f in changed_reformated]) + "\n\n" else: summary_text += "UPDATED FILES:\n None\n\n" if unchanged: unchanged_reformated = [] for idx, f in enumerate(unchanged, start=1): idx_num = str(idx).zfill(2) reformated_item = f" [-] {idx_num}. {f}" unchanged_reformated.append(reformated_item) # summary_text += "UNCHANGED / SKIPPED FILES:\n" + "\n".join([f" [-] {f}" for f in unchanged]) + "\n" # OG Code summary_text += "UNCHANGED / SKIPPED FILES:\n" + "\n".join([f"{f}" for f in unchanged_reformated]) + "\n" sg.popup_scrolled( summary_text, title="Results", # size=(80, 20), # (width, height) size=(120, 40), # (width, height) font=("Consolas", 13, 'bold'), non_blocking=False ) print('') print(f'{summary_text}') # Re-Build the Watched info BEFORE writing into the Summery File self.get_watched_summery() # https://stackoverflow.com/questions/68418413/calling-an-object-of-a-method-of-a-class-within-another-method-of-the-same-class self.write_summery_file(summary_text) print('') except Exception as Error: basic_custom_error() print(f"(Error: {Error})") #? === End Of Method/Function == def write_summery_file(self, complete_summary_text): """ Writes the summery text into a file, then saves into the TV Show Folder """ try: _, Show_Name, Season_Folder = path_splitter(self.target_dir) file_name_and_Ext = f'{Show_Name}_{Season_Folder} - Watched Episodes Summery.md' # Build Pathlib Path to write the file Abs_path_and_file = Path(self.target_dir).parent / file_name_and_Ext date_time = f"Date + Time: {Get_Date_and_Time.Fav_Day_date_and_time_string()}\n" watched_title = 'Watched Episodes Summery' with open(Abs_path_and_file, 'w', encoding='utf-8', errors='ignore') as sum_file: sum_file.write(complete_summary_text) sum_file.write('\n\n') sum_file.write('*' *100) sum_file.write('\n') sum_file.write(f'{watched_title:*^100}') # Centered Text (^) sum_file.write('\n') sum_file.write(date_time) sum_file.write('\n') sum_file.write('*' *100) sum_file.write('\n') sum_file.write(self.file_summery_rows_complete) except Exception as Error: basic_custom_error() print(f"(Error: {Error})") #? === End Of Method/Function == def get_watched_summery(self): try: #^ IMPORTANT!! - Clear-Out/Delete all current data stored in the Class Var self.file_summery_rows_complete = '' # Build individual rows for each NFO file found for idx, file_path in enumerate(self.nfo_files): watched_status_txt, _ = self.Check_If_Watched_In_NFO(file_path) idx_Plus_1 = str(idx+1).zfill(2) file_summery_txt = f'[{idx_Plus_1}] Status: {watched_status_txt} | {file_path.name} ' # Add New updated data (Re-Build thw Watched Data) self.file_summery_rows_complete += f'\n{file_summery_txt}' except Exception as Error: basic_custom_error() print(f"(Error: {Error})") ############################################################### #todo: This Works! def get_main_folder_path(self, values): """Performs the watched-status updates on selected files.""" selected = False try: # Loop thru the checkboxes, to find the selected chkBox for idx, file_path in enumerate(self.Main_folders_list, start=1): # Check if this specific file's checkbox was ticked is_selected = values.get(f"-CHECK_MAIN{idx}-", False) if is_selected: selected = True selected_Anime_Lib_Folder = Path(file_path) print(f'') print(f'Your Selected Anime Lib Folder Is...') print('.....Name: ', f'{selected_Anime_Lib_Folder.parts[-2]}: {selected_Anime_Lib_Folder.name}') print('Full Path: \n >>', selected_Anime_Lib_Folder) print(f'') return selected_Anime_Lib_Folder elif not is_selected: selected = False # unchanged_files.append(f"{file_path.name} (Skipped)") continue except Exception as Error: basic_custom_error() print(f"(Error: {Error})") # Check if a ChkBx was selected. If no, inform user + stop this script here. # This stops further errors in the script, # where other functions depend on the output data from this function if selected == False: sg.popup_error("Function Error", "Critical Error In >> get_main_folder_path()", "No Target/*Season* Folder Was Selected!", "This App Will Now Shut Down...", font=("Helvetica", 12, "bold"), keep_on_top=True, ) # Programmatically stop execution of python script sys.exit("Critical Error: Now Stopping This Script!!") #? === End Of Method/Function == #todo: This Works! def Select_Main_Folder_Window(self): """ Win-01: Creates My Custom-Win For.. Main Anime Folders Selector """ try: # Open the folder scanning configuration window self.Main_folders_window = sg.Window("Win-01: Select Your Main Anime Library Folder", self.create_MAIN_Folder_list_layout(), finalize=True, ) while True: foldr_event, foldr_values = self.Main_folders_window.read() if foldr_event in (sg.WIN_CLOSED, "-MAIN_CLOSE-"): break elif foldr_event == "-MAIN_FOLDER_SELECT-": # Process only what's checked app.Main_folders_dir = Path(self.get_main_folder_path(foldr_values)) if app.Main_folders_dir != None or '': break # Close selection list after running updates else: sg.popup_error("Error", "No Target *MAIN Anime Library* Folder Was Selected!", font=("Helvetica", 12, "bold"), keep_on_top=True, ) break # Close selection list after running updates self.Main_folders_window.close() except Exception as Error: basic_custom_error() print(f"===== Select_Main_Folder_Window(self) | Function =====") print(f"(Error: {Error})") print(f"\n") #? === End Of Method/Function == ############################################################### def run(self): # Open the folder scanning configuration window self.main_window = sg.Window("Win-03: Season Folder Scanner Window", self.Win_03_layout_Scan_Season_Folder(), size=(1100, 350), # (width, height) finalize=True, ) try: while True: event, values = self.main_window.read() if event in (sg.WIN_CLOSED, "-EXIT-"): break if event == "-CLOSE_WINDOW-": print("User Selected: Close Window & Reselect Season") break # self.main_window.hide() if event == "-CHANGE_SEASON_FOLDER-": print("User Selected: CHANGE_SEASON_FOLDER") # Select The Anime Season Folder... To Be Scanned NEW_selected_folder = open_folder_dialog(app.target_dir) print("NEW_selected_folder: ", NEW_selected_folder) print() # NEW_selected_folder = values.get("-CHANGE_SEASON_FOLDER-", False) self.main_window['-FOLDER-'].update(NEW_selected_folder) if event == "-SCAN-": folder_path = values["-FOLDER-"] if self.scan_directory(folder_path): # Hide main window and open the selection list window self.main_window.hide() self.list_window = sg.Window("Win-04: NFO Selector", self.create_list_layout(), modal=True, resizable=True, finalize=True, ) # Sub-loop for managing the list window selection while True: list_event, list_values = self.list_window.read() if list_event in (sg.WIN_CLOSED, "-CLOSE-"): break elif list_event == "-SELECT_ALL-": for idx in range(len(self.nfo_files)): self.list_window[f"-CHECK_{idx}-"].update(True) elif list_event == "-SELECT_NONE-": for idx in range(len(self.nfo_files)): self.list_window[f"-CHECK_{idx}-"].update(False) elif list_event == "-UPDATE-": # Process only what's checked self.process_nfo_files(list_values) break # Close selection list after running updates self.list_window.close() self.list_window = None self.main_window.un_hide() # Restore the main setup window self.main_window.close() except Exception as Error: basic_custom_error() print(f"(Error: {Error})") #? === End Of Method/Function == #todo ============ End Of The Whole Class ============ def App_Controller_Layout(): """Creates a main window layout to Control all other windows.""" button_txt_size = 12 try: App_Control_layout = [ [ sg.Text("App Controller Window", font=("Helvetica", 14, "bold"), ), ], [sg.HSeparator(pad=(0, 15))], #todo: === Update Anime Folder Selection === [ sg.Button("Win-01: Select Main Folder (Window)", key="-Select_Main_Folder_Window-", font=("Helvetica", button_txt_size, "bold"), pad=(10, 10), expand_x=True, ), # sg.Push(), ], [ sg.Multiline(str('Main Library >> NOT Selected!'), size=(50, 1), key='-APP_SELECTED_LIBRARY_FOLDER-', font=("Helvetica", 12, "bold"), justification='center', disabled=True, ), # (width, height) ], [sg.HSeparator(pad=(0, 15))], [ sg.Button("Win-02: Get Season Folder Path (Window)", key="-PSG_get_nfo_folder_path-", font=("Helvetica", button_txt_size, "bold"), pad=(10, 10), expand_x=True, ), ], [sg.HSeparator(pad=(0, 15))], [ sg.Text("Selected Folder To Scan...", font=("Helvetica", button_txt_size, "bold"), justification='center', expand_x=True, ) ], # [ # window.refresh() # sg.Text("Selected Folder To Scan...", # font=("Helvetica", button_txt_size, "bold"), # expand_x=True, # ) # ], [ sg.Multiline(str('NOT Selected!'), size=(50, 1), key='-APP_SELECTED_SEASON_FOLDER-', font=("Helvetica", 12, "bold"), justification='center', disabled=True, ), # (width, height) ], [sg.HSeparator(pad=(0, 15))], [ sg.Text("Run The Main App To Scan Selected Folder (app.run)", font=("Helvetica", button_txt_size, "bold"), expand_x=True, ) ], [ sg.Button("Win-03: app.run() >> Open Scan Folder Window...", key="-APP_DOT_RUN-", font=("Helvetica", button_txt_size, "bold"), expand_x=True, ), ], #todo: === Update Anime Folder Selection === [sg.HSeparator(pad=(0, 15))], [ sg.Button("Exit", key="-APP_CON_CLOSE-", font=("Helvetica", button_txt_size, "bold"), size=(20, 1) ), # sg.Push(), # sg.Button("Start Folder Scan...", key="-SEARCH_FOLDER-"), ], ] return App_Control_layout except Exception as Error: basic_custom_error() print(f"(Error: {Error})") #? === End Of Function == #todo: This Works! def App_Controller__Window(): """ Win-01: Creates My Custom-Win For.. Main Anime Folders Selector """ try: # Open the folder scanning configuration window App_Controller__window = sg.Window("Win-00: App Controller Window", App_Controller_Layout(), size=(550, 650), # (width, height) finalize=True, ) while True: App_Con_event, App_Con_values = App_Controller__window.read() if App_Con_event in (sg.WIN_CLOSED, "-APP_CON_CLOSE-"): print(f"===== App_Controller__Window =====") print(f"======= Closed By The User =======") break #todo: Select the Anime Library Folder elif App_Con_event == "-Select_Main_Folder_Window-": print('Event = Select_Main_Folder_Window >>> Button Was Pressed!!') # Creates My Custom-Win For.. Main Anime Folders Selector # Call-1 >> self.create_MAIN_Folder_list_layout() # Call-2 >> self.get_main_folder_path(App_Con_values) app.Select_Main_Folder_Window() # THIS if app.Main_folders_dir: pathlib_app_Main_folders_dir = Path(app.Main_folders_dir) App_Controller__window['-APP_SELECTED_LIBRARY_FOLDER-'].update(pathlib_app_Main_folders_dir.name) print('Data returned from Function... Select_Main_Folder_Window()') print('app.Main_folders_dir: ', app.Main_folders_dir) print("") lib_folder_msg = ('Step-2: Get The Season Folder \n' 'Click Button... \n' 'Win-02: Get Season Folder Path (Window)') sg.popup_ok(lib_folder_msg, title='Reminder Message', font=("Helvetica", 14, "bold"), ) #todo: Select the Season Folder elif App_Con_event == "-PSG_get_nfo_folder_path-": print('Event = PSG_get_nfo_folder_path >>> Button Was Pressed!!') print('app.Main_folders_dir: ', app.Main_folders_dir) if app.Main_folders_dir != None or '': # my_selected_path = PSG_get_nfo_folder_path(app.Main_folders_dir) # OG Code # Get the season Folder Path, then save to.. app.selected_season_folder # Win-02: PSG_get_nfo_folder_path(app.Main_folders_dir) # THIS my_selected_path = Path(app.selected_season_folder) Anime_Folder_Name, Show_Name, Season_Folder = path_splitter(my_selected_path) selected_show_season = f'{Show_Name} >> Season_Folder' App_Controller__window['-APP_SELECTED_SEASON_FOLDER-'].update(selected_show_season) print('Data returned from Function... PSG_get_nfo_folder_path()') print('my_selected_path: ', my_selected_path) print('app.selected_season_folder: ', app.selected_season_folder) print("") lib_folder_msg = ('Step-3: Scan The Season Folder \n' 'Click Button... \n' 'Win-03: app.run()') sg.popup_ok(lib_folder_msg, title='Reminder Message', font=("Helvetica", 14, "bold"), ) else: sg.popup_error("Error", "No Target/*Season* Folder Was Selected!", font=("Helvetica", 14, "bold"), keep_on_top=True, ) #todo: Scan the Season Folder + Update the NFO Files elif App_Con_event == "-APP_DOT_RUN-": print('Event = APP_DOT_RUN >>> Button Was Pressed!!') print('app.selected_season_folder: ', app.selected_season_folder) print("") if app.selected_season_folder: app.target_dir = Path(app.selected_season_folder) # THIS # Runs the Main function in my Class/app instance app.run() else: sg.popup_error("Error", "No Target/*Season* Folder Was Selected!", font=("Helvetica", 14, "bold"), keep_on_top=True, ) App_Controller__window.close() except Exception as Error: basic_custom_error() print(f"===== Select_Main_Folder_Window(self) | Function =====") print(f"(Error: {Error})") print(f"\n") #? === End Of Method/Function == ########################################################### if __name__ == "__main__": try: # Create an app instance #^ LEAVE THIS HERE! app = NfoUpdaterApp() # THIS App_Controller__Window() ##################################################### # Creates My Custom-Win For.. Main Anime Folders Selector # app.Select_Main_Folder_Window() #? THIS ##### # Call-1 >> self.create_MAIN_Folder_list_layout() # Call-2 >> self.get_main_folder_path(foldr_values) ##################################################### # if app.Main_folders_dir != None or '': # # Main_folders_dir / Path(my_selected_path) # my_selected_path = PSG_get_nfo_folder_path(app.Main_folders_dir) #? THIS # else: # sg.popup_error("Error", "No Target/*Season* Folder Was Selected!", # font=("Helvetica", 14, "bold"), # keep_on_top=True, # ) ##################################################### # if my_selected_path: # app.target_dir = Path(my_selected_path) # THIS # # Runs the Main function in my Class/app instance # app.run() # else: # sg.popup_error("Error", "No Target/*Season* Folder Was Selected!", # font=("Helvetica", 14, "bold"), # keep_on_top=True, # ) except Exception as Error: print(f"\n") print(f"===== Main Code Execution Block =====") print(f"(Error: {Error})") print(f"===== Main Code Execution Block =====") basic_custom_error()