From 16b60efe0b7766a08def7658265f7fca757cbd8a Mon Sep 17 00:00:00 2001 From: frarol96 Date: Thu, 5 Oct 2023 22:09:17 +0000 Subject: [PATCH] Start on 1.2.0 - + .ini configuration file read/write - + .ini configuration file verification and reset function - + Frame de-duplication function (discard similar frames based on MSE - + Minor QoL changes/improvements - - "Cancel" button - - "Cancel & Clear" button --- Video2Crops.py | 413 +++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 348 insertions(+), 65 deletions(-) diff --git a/Video2Crops.py b/Video2Crops.py index 7e19df0..63c8208 100644 --- a/Video2Crops.py +++ b/Video2Crops.py @@ -14,19 +14,142 @@ import platform import urllib.request import re from bs4 import BeautifulSoup +import threading +import configparser -print(f"New start\n") +DEBUG = True +STARTING = True +INVALID_INI = False + +# Debug function +def debug(state, message): + if DEBUG: + allowed_states = ['LOG EVENT', 'DEBUG', 'ERROR', 'INFO'] + if state in allowed_states: + debug_message = f"[{state}] {message}" + print(f"{debug_message}") + debugfile_path = os.path.join(os.getcwd(), 'debugfile.txt') + with open(debugfile_path, 'a') as debugfile: + log_debug_message = f"{datetime.now().strftime('%m-%d_%H:%M:%S')} | [{state}] {message}\n" + debugfile.write(log_debug_message) + else: + raise ValueError(f"Invalid state '{state}'. Please choose from {', '.join(allowed_states)}.") + +def fetch_ini(): + global INVALID_INI + try: + with open(config_file, 'w') as file: + config_url = 'https://git.rolfsvaag.no/frarol96/Video2Crops/raw/branch/main/config.ini' + with urllib.request.urlopen(config_url) as config_template: + config_template = config_template.read().decode("utf-8") + file.write(config_template) + tk.messagebox.showinfo( + "Reset config file", + f"Config file has been reset" + ) + debug('DEBUG', f"Config file was reset to default") + INVALID_INI = False + except: + print(f"[ERROR] Unable to fetch the missing config.ini file!\nCheck directory permissions and network connectivity!") + exit + +# Setup config file +config_file = 'config.ini' +config = configparser.ConfigParser() +if not os.path.exists(config_file): + fetch_ini() + +config.read(config_file) + +def checkinifile(): + + # Check for the presence of sections and keys + expected_sections = ['settings', 'debug'] + expected_settings_keys = { + 'frame_step': int, + 'contrast_threshold': float, + 'duplicate_threshold': int, + 'save_output': bool, + 'show_playback': bool, + 'roi_size': int, + 'window_size': str + } + expected_debug_keys = { + 'debug_output': bool + } + + CHECK_SECTIONS = True + CHECK_KEYS = True + CHECK_VALUES = False + global INVALID_INI + + # Check for the presence of sections + if CHECK_SECTIONS: + for section in expected_sections: + if section not in config.sections(): + INVALID_INI = True + print(f"Missing section: [{section}]") + + # Check for the presence of keys and their types in the 'settings' section + if 'settings' in config.sections(): + for key, expected_type in expected_settings_keys.items(): + if key not in config['settings']: + if CHECK_KEYS: + INVALID_INI = True + print(f"Missing key '{key}' in [settings] section") + else: + value = config.get('settings', key) + if not isinstance(value, expected_type): + if CHECK_VALUES: + INVALID_INI = True + print(f"Value of '{key}' in [settings] section is not of expected type {expected_type}") + + # Check for the presence of keys and their types in the 'debug' section + if 'debug' in config.sections(): + for key, expected_type in expected_debug_keys.items(): + if key not in config['debug']: + if CHECK_KEYS: + INVALID_INI = True + print(f"Missing key '{key}' in [debug] section") + else: + value = config.get('debug', key) + if not isinstance(value, expected_type): + if CHECK_VALUES: + INVALID_INI = True + print(f"Value of '{key}' in [debug] section is not of expected type {expected_type}") + if INVALID_INI: + fetch_ini() + +checkinifile() + +debug('INFO', "Video2Crops starting ...") + +# START INITIATING PROGRAM VARIABLES FROM HERE # Constants -v2cversion1 = 1 -v2cversion2 = 1 -v2cversion3 = 0 +v2cv = [1, 2, 0] +v2cversion1 = v2cv[0] +v2cversion2 = v2cv[1] +v2cversion3 = v2cv[2] v2cversion = f"{v2cversion1}.{v2cversion2}.{v2cversion3}" -frame_step = 100 -value_threshold = 11.0 -SAVE = True -SHOW = True processing = False +processing_button_state = "start" +# Load ini settings +frame_step = config.get('settings', 'frame_step') +contrast_threshold = config.get('settings', 'contrast_threshold') +duplicate_threshold = config.get('settings', 'duplicate_threshold') +SAVE = config.get('settings', 'save_output') +SHOW = config.get('settings', 'show_playback') +roi_size = config.get('settings', 'roi_size') +tksize = config.get('settings', 'window_size') +DEBUG = config.get('debug', 'debug_output') + +def write2ini(section, variable, value): + config.set(section, str(variable), str(value)) + + # Save the changes to the INI file + with open(config_file, 'w') as configfile: + config.write(configfile) # Define the RSS feed URL rss_feed_url = "https://git.rolfsvaag.no/frarol96/Video2Crops/releases.rss" @@ -34,13 +157,20 @@ rss_feed_url = "https://git.rolfsvaag.no/frarol96/Video2Crops/releases.rss" # Create the root window root = TkinterDnD.Tk() root.title("Video2Crops") -root.geometry("300x450") +root.geometry(tksize) + +# Function to clear the queue +def clear_queue(): + selected_files_listbox.delete(0, tk.END) + debug('DEBUG', 'Cleared work queue') def open_changelog_window(): + debug('DEBUG', 'Loading changelog window') changelog_window = tk.Toplevel(root) changelog_window.title("Video2Crops Changelog") try: + debug('DEBUG', f"Reading the RSS feed from: {rss_feed_url}") # Parse the RSS feed feed = feedparser.parse(rss_feed_url) @@ -54,6 +184,8 @@ def open_changelog_window(): scrolled_text = scrolledtext.ScrolledText(changelog_window, wrap=tk.WORD) scrolled_text.pack(fill=tk.BOTH, expand=True) + debug('DEBUG', f"Done collecting RSS data") + entry_num = 0 for entry in feed.entries: entry_num += 1 @@ -82,10 +214,13 @@ def open_changelog_window(): changelog_text = version_info + changelog_md + f"\n--------------------\n" scrolled_text.insert(tk.END, changelog_text) + debug('DEBUG', f"Finished processing changelog") + except Exception as e: scrolled_text = scrolledtext.ScrolledText(changelog_window, wrap=tk.WORD) scrolled_text.pack(fill=tk.BOTH, expand=True) scrolled_text.insert(tk.END, f"An error occurred: {str(e)}") + debug('ERROR', f"An error occured while processing changelog:\n{str(e)}\n") def extract_version(title): # Use regular expressions to extract the version number from the title @@ -115,32 +250,33 @@ def extract_changelog(description): # Function for updating Video2Crops def check_for_updates(): + debug('DEBUG', f"Checking for updates") try: # Parse the RSS feed feed = feedparser.parse(rss_feed_url) - if not feed.entries: + if not feed.entries and not STARTING: response = tk.messagebox.showinfo( "No updates found", f"Latest version: ({v2cversion})\n" f"Current version: ({v2cversion})\n" ) + debug('DEBUG', f"No updates found") return # Get the latest release latest_release = feed.entries[0] latest_version_str = extract_version(latest_release.link) - if latest_version_str is None: + if latest_version_str is None and not STARTING: tk.messagebox.showinfo( "No update Available", f"Latest version: ({v2cversion})\n" f"Current version: ({v2cversion})\n" ) + debug('DEBUG', f"No updates available") return - print(f"RSS Version: {latest_version_str}") - # Extract version numbers from the latest release title latest_version = tuple(map(int, latest_version_str.split('.'))) @@ -153,46 +289,66 @@ def check_for_updates(): f"Current version: ({v2cversion})\n" "Do you want to update?" ) + debug('DEBUG', f"Found new update, prompting user") if response: + debug('DEBUG', f"User chose to update ...") # Determine the platform current_platform = platform.system().lower() if current_platform == "windows": + debug('DEBUG', f"Detected Windows system, grabbing installer") # On Windows, download and run the installer installer_url = "https://git.rolfsvaag.no/frarol96/Video2Crops/releases/download/v{}/Video2Crops-windows-installer.exe".format(latest_version_str) try: urllib.request.urlretrieve(installer_url, "Video2Crops-windows-installer.exe") subprocess.Popen("Video2Crops-windows-installer.exe", shell=True) + debug('DEBUG', f"Downloaded new installer, running it ...") root.quit() except: tk.messagebox.showinfo( "Installer not found", f"The installer file was not found.\nPlease check your antivirus and network connectivity" ) + debug('ERROR', f"Windows installer not found") elif current_platform == "linux": + debug('DEBUG', f"Detected Linux system, grabbing executable") # On Linux, download and run the executable executable_url = "https://git.rolfsvaag.no/frarol96/Video2Crops/releases/download/v{}/Video2Crops-linux".format(latest_version_str) try: urllib.request.urlretrieve(executable_url, "Video2Crops-linux") os.chmod("Video2Crops-linux", 0o755) subprocess.Popen("./Video2Crops-linux", shell=True) + debug('DEBUG', f"Downloaded new executable, running it ...") root.quit() except: tk.messagebox.showinfo( "Executable not found", f"The executable file was not found.\nPlease check your antivirus and network connectivity" ) + debug('ERROR', f"Linux executable not found") + else: + debug('DEBUG', f"Non-supported platform found: {current_platform}") + tk.messagebox.showinfo( + "Non-supported platform", + f"Video2Crops detected non-supported platform: {current_platform}.\nCurrently, only Windows and Linux are supported.\n\nYou can build from source if you need to run Video2Crops on other platforms." + ) + else: + debug('INFO', 'User closed prompt window') else: - tk.messagebox.showinfo( - "No update Available", - f"Latest version: ({v2cversion})\n" - f"Current version: ({v2cversion})\n" - ) + if not STARTING: + tk.messagebox.showinfo( + "No update Available", + f"Latest version: ({v2cversion})\n" + f"Current version: ({v2cversion})\n" + ) + debug('DEBUG', f"No update available") except Exception as e: tk.messagebox.showerror("Error", f"An error occurred: {str(e)}") + debug('ERROR', f"An error occured while checking for updates:\n{str(e)}\n") # Function to create the output directory and return its path def create_output_directory(video_filename): + debug('DEBUG', f"Creating output directory") base_directory = os.getcwd() # You can change this to specify a different base directory video_filename_base, _ = os.path.splitext(os.path.basename(video_filename)) output_folder_path = os.path.join(base_directory, video_filename_base) @@ -201,6 +357,8 @@ def create_output_directory(video_filename): if not os.path.exists(output_folder_path): os.makedirs(output_folder_path) + debug('DEBUG', f"Processing file: {video_filename}") + debug('DEBUG', f"Created output directory at: {output_folder_path}") return output_folder_path # Function to calculate ROI position based on alignment and size settings @@ -229,19 +387,36 @@ def calculate_roi(video_width, video_height, vertical_alignment, horizontal_alig # Create a function to process video files def process_video(video_filenames, main_window): + debug('DEBUG', f"Starting video processing ...") global processing - processing = True + debug('DEBUG', f"Processing state: {processing}") def write_log(message): logfile_path = os.path.join(output_folder, 'logfile.txt') with open(logfile_path, 'a') as logfile: log_message = f"{datetime.now().strftime('%m-%d_%H:%M:%S')} - {message}\n" logfile.write(log_message) - print(log_message) + debug('LOG EVENT', f"{log_message}") def calc_value(image): grayscale = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) return np.std(grayscale) + + def check_duplicate(ref_frame, frame): + frame_is_duplicate = True + # Calculate the Absolute Difference between the frames + frame_diff = cv2.absdiff(ref_frame, frame) + + # Calculate the Mean Squared Error (MSE) between the frames + mse = np.mean(frame_diff) + + if mse > duplicate_threshold: + # The frame is significantly different, store it + ref_frame = frame.copy() + # Process the frame here or store it for later processing + frame_is_duplicate = False + + return frame_is_duplicate, mse, ref_frame corner1 = (-1, -1) corner2 = (-1, -1) @@ -249,6 +424,10 @@ def process_video(video_filenames, main_window): cropped_image = None for videofilename in video_filenames: + debug('DEBUG', f"Processing video file: {videofilename}") + if not processing: + debug('DEBUG', f"Breaking loop due to processing = {processing}") + break main_window.title(f"Video Processing App") @@ -283,6 +462,7 @@ def process_video(video_filenames, main_window): vertical_alignment = vertical_alignment_var.get() horizontal_alignment = horizontal_alignment_var.get() + debug('DEBUG', f"Displaying video player window: {SHOW}") if SHOW: vp_title = "Video2Crops Video Player" # Get the screen resolution @@ -297,6 +477,9 @@ def process_video(video_filenames, main_window): cv2.namedWindow(vp_title, cv2.WINDOW_NORMAL) cv2.resizeWindow(vp_title, initial_width, initial_height) + # Store the first frame as reference for the duplicate check + ret, reference_frame = cap.read() + while True: ret, frame = cap.read() @@ -324,7 +507,8 @@ def process_video(video_filenames, main_window): key = cv2.waitKey(10) - if key == ord('q'): + if key == ord('q') or (not processing): + debug('DEBUG', f"Breaking loop due to cancel event") break if cropped_image is not None and frame_cnt % frame_step == 0: @@ -333,8 +517,10 @@ def process_video(video_filenames, main_window): cv2.namedWindow(ci_title, cv2.WINDOW_NORMAL) cv2.imshow(ci_title, cropped_image) + frame_is_duplicate, frame_duplicate_mse, reference_frame = check_duplicate(reference_frame, cropped_image) frame_value = calc_value(cropped_image) - if frame_value >= value_threshold: + + if frame_value >= contrast_threshold and not frame_is_duplicate: cropped_image_status = "Accepted" filename = videofilename_base + '_' + str(counter).zfill(4) + ".png" @@ -343,7 +529,7 @@ def process_video(video_filenames, main_window): if SAVE: cv2.imwrite(image_filename, cropped_image) - cropped_image_info = f"Image on frame {frame_cnt} ({cropped_image_status}) - {frame_value} / {value_threshold} - image nr: {str(str(counter)).zfill(4)}" + cropped_image_info = f"Image on frame {frame_cnt} ({cropped_image_status} | {frame_duplicate_mse}) - {frame_value} / {contrast_threshold} - image nr: {str(str(counter)).zfill(4)}" write_log(cropped_image_info) counter += 1 else: @@ -352,14 +538,20 @@ def process_video(video_filenames, main_window): if SAVE: cv2.imwrite(image_filename, cropped_image) - cropped_image_info = f"Image on frame {frame_cnt} ({cropped_image_status}) - {frame_value} / {value_threshold} - image nr: {str(str(counter)).zfill(4)}" + cropped_image_info = f"Image on frame {frame_cnt} ({cropped_image_status} | {frame_duplicate_mse}) - {frame_value} / {contrast_threshold} - image nr: {str(str(counter)).zfill(4)}" write_log(cropped_image_info) discarded_frames += 1 frame_cnt += 1 + if frame_cnt == 1: + debug('DEBUG', f"Video size: {video_width}x{video_height}") + debug('DEBUG', f"ROI location: {vertical_alignment}-{horizontal_alignment}") + debug('DEBUG', f"ROI size: {roi_size}") + debug('DEBUG', f"ROI position (left, top, right, bottom): {x1}, {y1}, {x2}, {y2}") cap.release() cv2.destroyAllWindows() + debug('DEBUG', f"Destroyed all current CV2 windows") write_log(f"Video2Crops run complete!\nSaved {counter} frames.\nDiscarded {discarded_frames} frames.") @@ -372,35 +564,75 @@ def add_files(): if file_paths: for file_path in file_paths: selected_files_listbox.insert(tk.END, file_path) + debug('DEBUG', f"Queued video file: {file_path}") # Create a button to remove the selected file from the Listbox def remove_file(): selected_index = selected_files_listbox.curselection() if selected_index: selected_files_listbox.delete(selected_index) + debug('DEBUG', f"Removed file from queue: {selected_index}") # Create a function to clear the queue def clear_queue(): selected_files_listbox.delete(0, tk.END) + debug('DEBUG', f"Cleared the queue") # Create a function to cancel processing def cancel_processing(): global processing + debug('DEBUG', f"Attempting cancelation ...") if processing: processing = False + if processing_thread.is_alive(): + processing_thread.join() # Wait for the processing thread to finish cleanly messagebox.showinfo("Processing Canceled", "Video processing has been canceled.") + elif not processing: + processing = False + messagebox.showinfo("Nothing to cancel", "Video2Crops isn't processing anything.") + debug('DEBUG', f"Cancel complete. Processing state: {processing}") # Create a button to process the selected files def process_files(): - global processing + debug('DEBUG', f"Starting file processing ...") + global processing, processing_thread if not processing: + processing = True selected_files = selected_files_listbox.get(0, tk.END) - for file_path in selected_files: - process_video([file_path], root) + processing_thread = threading.Thread(target=process_video, args=(selected_files, root)) + processing_thread.daemon = True # Set the thread as a daemon so it terminates when the main program exits + processing_thread.start() + else: + messagebox.showinfo("Info", "Video2Crops is currently processing files!") + debug('DEBUG', f"File processing done") + + +# Define dynamic process/cancel button +def process_cancel_toggle(): + debug('DEBUG', f"Toggling process/cancel button state") + global processing, processing_thread, processing_button_state + if processing_button_state == "start": + if not processing: + processing = True + selected_files = selected_files_listbox.get(0, tk.END) + processing_thread = threading.Thread(target=process_video, args=(selected_files, root)) + processing_thread.daemon = True + processing_thread.start() + processing_button.config(text="Cancel") + processing_button_state = "cancel" + elif processing_button_state == "cancel": + if processing: + processing = False + if processing_thread.is_alive(): + processing_thread.join() + processing_button.config(text="Process Files") + processing_button_state = "start" + debug('DEBUG', f"Processing/Cancel button state switch finished") + # Create a function to open the preferences window def open_preferences(): - + debug('DEBUG', f"Opening preferences window") preferences_window = tk.Toplevel(root) preferences_window.title("Preferences") @@ -411,73 +643,116 @@ def open_preferences(): frame_step_var.set(str(new_frame_step)) global frame_step frame_step = new_frame_step + write2ini('settings', 'frame_step', new_frame_step) + debug('DEBUG', f"Set frame stepping to {frame_step}") + else: + messagebox.showinfo("Info", "Video2Crops is currently processing files!") - def change_value_threshold(): + def change_contrast_threshold(): if not processing: - new_value_threshold = simpledialog.askfloat("Value Threshold", "Enter the new value threshold:") - if new_value_threshold is not None: - value_threshold_var.set(str(new_value_threshold)) + new_contrast_threshold = simpledialog.askfloat("Contrast Threshold", "Enter the new value threshold:") + if new_contrast_threshold is not None: + contrast_threshold_var.set(str(new_contrast_threshold)) + write2ini('settings', 'contrast_threshold', new_contrast_threshold) + debug('DEBUG', f"Set contrast discard threshold to {new_contrast_threshold}") + else: + messagebox.showinfo("Info", "Video2Crops is currently processing files!") + + def change_duplicate_threshold(): + if not processing: + new_duplicate_threshold = simpledialog.askfloat("Duplicate Threshold", "Enter the new value threshold:") + if new_duplicate_threshold is not None: + duplicate_threshold_var.set(str(new_duplicate_threshold)) + write2ini('settings', 'duplicate_threshold', new_duplicate_threshold) + debug('DEBUG', f"Set duplicate discard threshold to {new_duplicate_threshold}") + else: + messagebox.showinfo("Info", "Video2Crops is currently processing files!") def toggle_save(): if not processing: global SAVE SAVE = not SAVE + write2ini('settings', 'save_output', SAVE) + debug('DEBUG', f"Saving output: {SAVE}") + else: + messagebox.showinfo("Info", "Video2Crops is currently processing files!") def toggle_show(): if not processing: global SHOW SHOW = not SHOW + write2ini('settings', 'show_playback', SHOW) + debug('DEBUG', f"Displaying output: {SHOW}") + else: + messagebox.showinfo("Info", "Video2Crops is currently processing files!") def apply_roi_size(): if not processing: new_roi_size = simpledialog.askinteger("ROI Size", "Enter the new ROI size:") if new_roi_size is not None: roi_size_var.set(str(new_roi_size)) + write2ini('settings', 'roi_size', new_roi_size) + debug('DEBUG', f"Set new ROI size to {new_roi_size}") + else: + messagebox.showinfo("Info", "Video2Crops is currently processing files!") + + debug('DEBUG', f"Creating preference fields ...") # Create preference fields frame_step_label = tk.Label(preferences_window, text="Skip n frames:") - frame_step_label.grid(row=0, column=0, padx=20, pady=10) + frame_step_label.grid(row=0, column=0, padx=20, pady=5) frame_step_value = tk.Label(preferences_window, textvariable=frame_step_var) - frame_step_value.grid(row=0, column=1, padx=20, pady=10) + frame_step_value.grid(row=0, column=1, padx=20, pady=5) frame_step_button = tk.Button(preferences_window, text="Edit", command=change_frame_step) - frame_step_button.grid(row=0, column=2, padx=20, pady=10) + frame_step_button.grid(row=0, column=2, padx=20, pady=5) - value_threshold_label = tk.Label(preferences_window, text="Discard Threshold:") - value_threshold_label.grid(row=1, column=0, padx=20, pady=10) - value_threshold_value = tk.Label(preferences_window, textvariable=value_threshold_var) - value_threshold_value.grid(row=1, column=1, padx=20, pady=10) - value_threshold_button = tk.Button(preferences_window, text="Edit", command=change_value_threshold) - value_threshold_button.grid(row=1, column=2, padx=20, pady=10) + contrast_threshold_label = tk.Label(preferences_window, text="Contrast Threshold:") + contrast_threshold_label.grid(row=1, column=0, padx=20, pady=5) + contrast_threshold_value = tk.Label(preferences_window, textvariable=contrast_threshold_var) + contrast_threshold_value.grid(row=1, column=1, padx=20, pady=5) + contrast_threshold_button = tk.Button(preferences_window, text="Edit", command=change_contrast_threshold) + contrast_threshold_button.grid(row=1, column=2, padx=20, pady=5) + + duplicate_threshold_label = tk.Label(preferences_window, text="Duplicate Threshold:") + duplicate_threshold_label.grid(row=2, column=0, padx=20, pady=5) + duplicate_threshold_value = tk.Label(preferences_window, textvariable=duplicate_threshold_var) + duplicate_threshold_value.grid(row=2, column=1, padx=20, pady=5) + duplicate_threshold_button = tk.Button(preferences_window, text="Edit", command=change_duplicate_threshold) + duplicate_threshold_button.grid(row=2, column=2, padx=20, pady=5) save_checkbox = tk.Checkbutton(preferences_window, text="Save Output Images", variable=save_var, command=toggle_save) - save_checkbox.grid(row=2, column=0, columnspan=3, padx=20, pady=10) + save_checkbox.grid(row=3, column=0, columnspan=3, padx=20, pady=5) show_checkbox = tk.Checkbutton(preferences_window, text="Display Playback", variable=show_var, command=toggle_show) - show_checkbox.grid(row=3, column=0, columnspan=3, padx=20, pady=10) + show_checkbox.grid(row=4, column=0, columnspan=3, padx=20, pady=5) # Section divider divider_label = tk.Label(preferences_window, text="ROI Settings", font=("Helvetica", 12, "bold")) - divider_label.grid(row=4, column=0, columnspan=3, padx=20, pady=(20, 10)) + divider_label.grid(row=5, column=0, columnspan=3, padx=20, pady=(20, 10)) # Create ROI fields vertical_alignment_label = tk.Label(preferences_window, text="Vertical Alignment:") - vertical_alignment_label.grid(row=5, column=0, padx=20, pady=10) + vertical_alignment_label.grid(row=6, column=0, padx=20, pady=5) vertical_alignment_var.set("Center") # Default alignment vertical_alignment_menu = tk.OptionMenu(preferences_window, vertical_alignment_var, "Top", "Center", "Bottom") - vertical_alignment_menu.grid(row=5, column=1, padx=20, pady=10) + vertical_alignment_menu.grid(row=6, column=1, padx=20, pady=5) horizontal_alignment_label = tk.Label(preferences_window, text="Horizontal Alignment:") - horizontal_alignment_label.grid(row=6, column=0, padx=20, pady=10) + horizontal_alignment_label.grid(row=7, column=0, padx=20, pady=5) horizontal_alignment_var.set("Center") # Default alignment horizontal_alignment_menu = tk.OptionMenu(preferences_window, horizontal_alignment_var, "Left", "Center", "Right") - horizontal_alignment_menu.grid(row=6, column=1, padx=20, pady=10) + horizontal_alignment_menu.grid(row=7, column=1, padx=20, pady=5) roi_size_label = tk.Label(preferences_window, text="ROI Size:") - roi_size_label.grid(row=7, column=0, padx=20, pady=10) + roi_size_label.grid(row=8, column=0, padx=20, pady=5) roi_size_value = tk.Label(preferences_window, textvariable=roi_size_var) - roi_size_value.grid(row=7, column=1, padx=20, pady=10) + roi_size_value.grid(row=8, column=1, padx=20, pady=5) roi_size_button = tk.Button(preferences_window, text="Edit", command=apply_roi_size) - roi_size_button.grid(row=7, column=2, padx=20, pady=10) + roi_size_button.grid(row=8, column=2, padx=20, pady=5) + + debug('DEBUG', f"Done creating preference fields") + +debug('DEBUG', 'Functions defined') # Create a menu menu = tk.Menu(root) @@ -485,7 +760,8 @@ root.config(menu=menu) # Variables for preferences frame_step_var = tk.StringVar() -value_threshold_var = tk.StringVar() +contrast_threshold_var = tk.StringVar() +duplicate_threshold_var = tk.StringVar() save_var = tk.BooleanVar() show_var = tk.BooleanVar() @@ -493,13 +769,14 @@ show_var = tk.BooleanVar() vertical_alignment_var = tk.StringVar() horizontal_alignment_var = tk.StringVar() roi_size_var = tk.StringVar() -roi_size_var.set("400") # Set initial values for preference variables frame_step_var.set(str(frame_step)) -value_threshold_var.set(str(value_threshold)) +contrast_threshold_var.set(str(contrast_threshold)) +duplicate_threshold_var.set(str(duplicate_threshold)) save_var.set(SAVE) show_var.set(SHOW) +roi_size_var.set(roi_size) # Create a "Queue" submenu queue_menu = tk.Menu(menu, tearoff=0) @@ -507,40 +784,46 @@ menu.add_cascade(label="Queue", menu=queue_menu) queue_menu.add_command(label="Add file", command=add_files) queue_menu.add_command(label="Clear queue", command=clear_queue) queue_menu.add_separator() -queue_menu.add_command(label="Cancel", command=cancel_processing) -queue_menu.add_command(label="Cancel & Clear", command=lambda: [cancel_processing(), clear_queue()]) +queue_menu.add_command(label="Cancel", state="disabled", command=cancel_processing) +queue_menu.add_command(label="Cancel & Clear", state="disabled", command=lambda: [cancel_processing(), clear_queue()]) # Create a "Preferences" submenu menu.add_command(label="Preferences", command=open_preferences) # Create an "Info" submenu -info_menu = tk.Menu(menu, tearoff=0) +info_menu = tk.Menu(menu, tearoff=0, disabledforeground="#000") menu.add_cascade(label="Info", menu=info_menu) info_menu.add_command(label="Project Folder", command=lambda: webbrowser.open(os.getcwd())) info_menu.add_separator() info_menu.add_command(label="About", command=lambda: webbrowser.open("https://git.rolfsvaag.no/frarol96/Video2Crops/wiki")) -info_menu.add_command(label=f"Version: {str(v2cversion)}") info_menu.add_command(label=f"Check for updates", command=check_for_updates) info_menu.add_command(label=f"View Changelog", command=open_changelog_window) +info_menu.add_separator() +info_menu.add_command(state="disabled", label=f"Version: {str(v2cversion)}") # Create a label for selected files label = tk.Label(root, text="Selected Files:") -label.pack(padx=20, pady=10) +label.pack(padx=20, pady=5) # Create a Listbox to display the selected files selected_files_listbox = tk.Listbox(root, selectmode=tk.SINGLE, exportselection=0) -selected_files_listbox.pack(fill=tk.BOTH, expand=True, padx=20, pady=10) +selected_files_listbox.pack(fill=tk.BOTH, expand=True, padx=20, pady=5) # Create a button to select and add files to the Listbox select_files_button = tk.Button(root, text="Select Files", command=add_files) -select_files_button.pack(padx=20, pady=10) +select_files_button.pack(padx=20, pady=5) # Create a button to remove the selected file from the Listbox remove_file_button = tk.Button(root, text="Remove File", command=remove_file) -remove_file_button.pack(padx=20, pady=10) +remove_file_button.pack(padx=20, pady=5) # Create a button to process the selected files -process_files_button = tk.Button(root, text="Process Files", command=process_files) -process_files_button.pack(padx=20, pady=10) +processing_button = tk.Button(root, text="Process Files", command=process_cancel_toggle) +processing_button.pack(padx=20, pady=5) -root.mainloop() +debug('DEBUG', 'UI generated, running main loop') +debug('INFO', '... Video2Crops initialized and ready!') + +check_for_updates() +STARTING = False +root.mainloop() \ No newline at end of file