#ANCHOR - Script DocString """ Created By Jap-Slappa Date: 30th/May/2024 * Max's: Files_2_Folder_PSG v6.0 Date: 30th/May/2024 - v1.0 - Started coding this project - Used ChatGPT to start the code, then I customized & tweaked for my purposes Date: 25th/Mar/2024 - v2.0 Date: 25th/June/2024 - v3.0 - Date: 2nd/July/2024 - v4.0 (Me, Not ChatGPT) - Works great, but I can only do x1 set of file at a time - Updating to: Automatically scan a whole folder & ALL files Date: 5th/July/2024 - v5.0 (Me, Not ChatGPT) Date: 6th/July/2024 - v6.0 (Me, Not ChatGPT) """ """ *Explanation: select_file Function: Opens a file selection dialog and returns the selected file path as a Path object. Returns None if no file is selected. select_folder Function: Opens a folder selection dialog and returns the selected folder path as a Path object. Returns None if no folder is selected. create_folder Function: Creates a folder with the same name as the file (excluding extension) in the same location. Returns the path to the newly created folder. move_file_to_folder Function: Moves a file to the specified folder using shutil.move. get_matching_files Function: Returns a list of files in the specified folder that contain the search name. scan_and_move_files Function: Manages the overall process of selecting files, creating folders, and moving matching files. Uses the previously defined functions to perform specific tasks. This modular approach makes the code easier to read, understand, and maintain. """ import PySimpleGUI as sg import shutil import os import time from icecream import ic from pathlib import Path from typing import List, Optional def cleanup_file_name(full_file_name:str): split_name:str = full_file_name.split('(') clean_file_name:str = split_name[0] # file_name_xtra:str = split_name[1] return clean_file_name #? ==== End Of Function ==== def clean_file_name_func(): """ Removes unwanted parts of the OG file name --- 1. The popup gives the user a last chance to edit the file name before a search for matching files is carried out. Returns: str: File name, unwanted parts removed """ # Get the MP4 file name clean_file_name = cleanup_file_name(My_Vars.GV_mp4_file_abs_path.stem) # User-edited file name for searching edited_file_name = sg.popup_get_text( "Do you want to Edit the file name to search for?...", font=('Arial', 12, 'bold'), text_color='yellow', default_text=clean_file_name) return edited_file_name #? ==== End Of Function ==== def select_folder() -> Optional[Path]: """ Open a folder selection dialog and return the selected folder path as a PathLib Path Object. --- Returns: selected_folder_path - PathLib Path Object """ print("===== select_folder() =====") # Path of the selected folder selected_folder_path:str = sg.popup_get_folder('Please Select a folder', font=('Arial', 12), initial_folder=APP_DIR, no_window=True) if selected_folder_path is not None and selected_folder_path != "": print("Folder path selected = YES.") print() return Path(selected_folder_path) # If the user cancels or does not select a folder path: # Return None else: return None #? ==== End Of Function ==== def check_selected_folder_path(): """ Checks: The user has selected a folder path. --- 1. Returns the Search Folder path which is stored in my variables class or... If no folder was stored: Returns None Returns: Path: Search Folder Abs Path """ # selected_folder_path:Path[str] = Search_Folder_Path # OLD Code # NEW Code (6th/July/2024) # Path of the selected folder to scan for matching files selected_folder_path:Path[str] = My_Vars.GV_Search_Folder_Path print('==== check_selected_folder_path ====') # print(My_Vars.GV_mp4_folder_abs_path) print('selected_folder_path... \n', selected_folder_path) print() if selected_folder_path != False or 'None': return selected_folder_path else: sg.popup("No folder selected.", font=('Arial', 12)) print("===== check_selected_folder_path() =====") print("No Folder selected.") print('selected_folder_path... \n', selected_folder_path) print("") return False #? ==== End Of Function ==== def get_matching_files(folder_path: Path[str], search_name: str) -> List[Path]: """ Get a list of files in the selected folder that contain the search name. --- """ print('==== get_matching_files ====') print('folder_path... \n', folder_path) print('search_name... \n', search_name) print() #NOTE Glob can only be used with PathLib Paths # List of matching files matching_files = list(folder_path.glob(f"*{search_name}*.*")) # File Name #1 matching_file_01:str = matching_files[0] # File Name #2 matching_file_02:str = matching_files[1] return matching_files, matching_file_01, matching_file_02 #? ==== End Of Function ==== def matching_files_info(): selected_folder_path:str = check_selected_folder_path() # Strip un-wanted words from the File Name edited_file_name:str = clean_file_name_func() if edited_file_name != False or 'None': # List of matching files matching_files, matching_file_1, matching_file_2 = get_matching_files( selected_folder_path, edited_file_name) return matching_files, matching_file_1, matching_file_2 else: sg.popup("No file name entered.", font=('Arial', 12)) print("===== matching_files_info() =====") print("No file name entered.") print("") return #? ==== End Of Function ==== def create_folder(file_path: Path) -> Path: """ Create a folder with the same name as the selected file, (without the extension), in the same location as the selected file. --- file_path = My_Vars.GV_mp4_file_abs_path """ # Path for the new folder new_folder_path = file_path.parent / file_path.stem # Save the MP4 Folder path into My_Vars Class My_Vars.GV_mp4_folder_abs_path = new_folder_path # Create a folder, named as the MP4 file (NO Ext) new_folder_path.mkdir(exist_ok=True) return new_folder_path #? ==== End Of Function ==== def ask_user_2_scan_for_leftovers(): # Confirmation Popup dialog confirm = sg.popup_yes_no( f"Do you want to scan the folder " f"\n\nfor left-over files?\n\n" f"Some may have been missed?!.\n\n", font=('Arial', 12), text_color='yellow' ) if confirm == "Yes": sg.popup(f"Now Starting...." f"\n\nfind_and_move_leftover_files() Function\n\n", font=('Arial', 12), text_color='yellow' ) find_and_move_leftover_files() else: sg.popup("OK, All operations are now Completed!!.", font=('Arial', 12), text_color='yellow' ) #? ==== End Of Function ==== def find_and_move_leftover_files(): leftover_file_exts:list[str] = ['.jpg', '.txt'] # Found matching files matching_files:list[str] = [] matching_file_names:list[str] = [] # Found matching files matching_file_paths:list[str] = [] # Selected Search Folder dir_path = My_Vars.GV_Search_Folder_Path # Iterate over files in a directory for item in dir_path.iterdir(): if item.is_file() and item.suffix in leftover_file_exts: matching_file_paths.append(item) matching_files.append(item.stem) matching_file_names.append(item.name) # Number of matching found files in the list num_matching_files:int = len(matching_files) found_folder_name = '' # Scan the folder, then compare the folder with the found files for item in dir_path.iterdir(): if item.is_dir() and item.stem in matching_files: found_folder = item.stem found_folder_name = item.stem print('=====================') print('Matching Folder Path: ', found_folder) print('Matching Folder Name: ', found_folder.stem) print('') print(f"Matching files found (x{num_matching_files}):") print('matching_files (List): ', matching_files) print('matching_file_names (List): ', matching_file_names) print('') print('===== List Comp Print: Fake FILES List[] =====') print('\n'.join([f'{items}' for items in matching_file_paths])) # print('matching_files (Paths): ', matching_file_paths) print('') if num_matching_files >0: # Confirmation Popup dialog confirm = sg.popup_yes_no( f"Matching files found (x{num_matching_files}):" f"\n\n#1 - {matching_file_names[0]}\n\n" f"#2 - {matching_file_names[0]}\n\n" f"Matching Folder Name: {found_folder_name}\n\n" f"Do you want to move these files to the new folder?", font=('Arial', 12), text_color='yellow' ) if confirm == "Yes": for file in matching_file_paths: move_file_to_folder(file, found_folder) sg.popup(f"x{num_matching_files} Files have been moved Now.", font=('Arial', 12), text_color='yellow' ) else: sg.popup("OK, No files were moved.", font=('Arial', 12), text_color='yellow' ) else: sg.popup("NO left-over files where found in the folder!", font=('Arial', 12), text_color='yellow' ) #? ==== End Of Function ==== def move_file_to_folder(file_path: Path, folder_path: Path) -> None: """ Move a file into the user's specified folder. --- """ shutil.move(str(file_path), str(folder_path / file_path.name)) #? ==== End Of Function ==== #todo: ======= scan_and_move_files ====== def scan_and_move_files() -> None: """ Main function to handle file selection, folder creation, and moving matching files. """ # Name + Path, for the new folder new_folder_path:Path = create_folder(My_Vars.GV_mp4_file_abs_path) # Move the MP4 File into the NEW folder for the MP4 files move_file_to_folder(My_Vars.GV_mp4_file_abs_path, new_folder_path) # User Info Popup sg.popup( f"Moved original file to: {new_folder_path}" "\n\n" f"MP4 Folder Name: {new_folder_path.stem}", font=('Arial', 12), text_color='yellow' ) matching_files, matching_file_1, matching_file_2 = matching_files_info() num_matching_files:int = len(matching_files) # In Python, an empty list ([]) evaluates to *False* in a boolean context, # while a NON-empty list ([Bool, Is, True]) evaluates to *True*. if matching_files == False: # This block executes because matching_files is empty # (evaluates to False) sg.popup("No matching files found.", font=('Arial', 12)) return else: # Confirmation dialog confirm = sg.popup_yes_no( f"Matching files found (x{num_matching_files}):" f"\n\n#1 - {matching_file_1.name}\n\n" f"#2 - {matching_file_2.name}\n\n" f"Do you want to move these files to the new folder?", font=('Arial', 12), text_color='yellow' ) if confirm == "Yes": for file in matching_files: move_file_to_folder(file, new_folder_path) sg.popup(f"x{num_matching_files} Files have been moved Now.", font=('Arial', 12), text_color='yellow' ) else: sg.popup("No files were moved.", font=('Arial', 12) ) #? ==== End Of Function ==== def scan_4_mp4_files(): for root, dirs, files in os.walk(Search_Folder_Path): all_folder_files.append(files) leftover_files:list[str] = ['.jpg', '.txt'] print('===== Loop Thru Total List ====') for fake_file in all_folder_files[0]: # Absolute file path to the MP4 File (PathLib Path Object) mp4_file_abs_path:Path[str] = Search_Folder_Path / fake_file # Get all the found files, file ext's file_extension:str = mp4_file_abs_path.suffix # print('file_extension: ', file_extension) if file_extension == '.mp4': print('=== MP4 File ===') print(fake_file) # File Name ONLY (NOT Path) # print() # Set NEW contents into the variable, or Replace old contents My_Vars.GV_mp4_file_abs_path = mp4_file_abs_path print('==== My_Vars.GV_mp4_file_abs_path ====') print(My_Vars.GV_mp4_file_abs_path) print() #? Start moving & processing the MP4 & matching files scan_and_move_files() time.sleep(0.5) # elif file_extension in leftover_files: # print('Not MP4') # # print() # time.sleep(0.5) #? ==== End Of Function ==== #todo: ===== Code Control ===== if __name__ == "__main__": # PathLib: Path To THIS Scripts Location APP_DIR:Path[str] = Path(__file__).resolve().parent # script_path:str = str(APP_DIR) # print('==== APP_DIR ====') # print('APP_DIR: ', APP_DIR) # print('APP_DIR Type: ', type(APP_DIR)) # print() class My_Vars: GV_mp4_file_abs_path:Path[str] = '' GV_mp4_folder_abs_path:Path[str] = '' GV_Search_Folder_Path:Path[str] = '' all_folder_files:list[str] = [] """PathLib Path Object: original_file_path""" Search_Folder_Path:Path[str] = select_folder() # This now works My_Vars.GV_Search_Folder_Path = Search_Folder_Path #? This is now the Main Functions co-ordinator # scan_4_mp4_files() # find_and_move_leftover_files() ask_user_2_scan_for_leftovers()