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
This commit is contained in:
frarol96 2023-10-05 22:09:17 +00:00
parent ac4894f318
commit 16b60efe0b

View File

@ -14,19 +14,142 @@ import platform
import urllib.request import urllib.request
import re import re
from bs4 import BeautifulSoup 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 # Constants
v2cversion1 = 1 v2cv = [1, 2, 0]
v2cversion2 = 1 v2cversion1 = v2cv[0]
v2cversion3 = 0 v2cversion2 = v2cv[1]
v2cversion3 = v2cv[2]
v2cversion = f"{v2cversion1}.{v2cversion2}.{v2cversion3}" v2cversion = f"{v2cversion1}.{v2cversion2}.{v2cversion3}"
frame_step = 100
value_threshold = 11.0
SAVE = True
SHOW = True
processing = False 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 # Define the RSS feed URL
rss_feed_url = "https://git.rolfsvaag.no/frarol96/Video2Crops/releases.rss" 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 # Create the root window
root = TkinterDnD.Tk() root = TkinterDnD.Tk()
root.title("Video2Crops") 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(): def open_changelog_window():
debug('DEBUG', 'Loading changelog window')
changelog_window = tk.Toplevel(root) changelog_window = tk.Toplevel(root)
changelog_window.title("Video2Crops Changelog") changelog_window.title("Video2Crops Changelog")
try: try:
debug('DEBUG', f"Reading the RSS feed from: {rss_feed_url}")
# Parse the RSS feed # Parse the RSS feed
feed = feedparser.parse(rss_feed_url) 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 = scrolledtext.ScrolledText(changelog_window, wrap=tk.WORD)
scrolled_text.pack(fill=tk.BOTH, expand=True) scrolled_text.pack(fill=tk.BOTH, expand=True)
debug('DEBUG', f"Done collecting RSS data")
entry_num = 0 entry_num = 0
for entry in feed.entries: for entry in feed.entries:
entry_num += 1 entry_num += 1
@ -82,10 +214,13 @@ def open_changelog_window():
changelog_text = version_info + changelog_md + f"\n--------------------\n" changelog_text = version_info + changelog_md + f"\n--------------------\n"
scrolled_text.insert(tk.END, changelog_text) scrolled_text.insert(tk.END, changelog_text)
debug('DEBUG', f"Finished processing changelog")
except Exception as e: except Exception as e:
scrolled_text = scrolledtext.ScrolledText(changelog_window, wrap=tk.WORD) scrolled_text = scrolledtext.ScrolledText(changelog_window, wrap=tk.WORD)
scrolled_text.pack(fill=tk.BOTH, expand=True) scrolled_text.pack(fill=tk.BOTH, expand=True)
scrolled_text.insert(tk.END, f"An error occurred: {str(e)}") 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): def extract_version(title):
# Use regular expressions to extract the version number from the title # Use regular expressions to extract the version number from the title
@ -115,32 +250,33 @@ def extract_changelog(description):
# Function for updating Video2Crops # Function for updating Video2Crops
def check_for_updates(): def check_for_updates():
debug('DEBUG', f"Checking for updates")
try: try:
# Parse the RSS feed # Parse the RSS feed
feed = feedparser.parse(rss_feed_url) feed = feedparser.parse(rss_feed_url)
if not feed.entries: if not feed.entries and not STARTING:
response = tk.messagebox.showinfo( response = tk.messagebox.showinfo(
"No updates found", "No updates found",
f"Latest version: ({v2cversion})\n" f"Latest version: ({v2cversion})\n"
f"Current version: ({v2cversion})\n" f"Current version: ({v2cversion})\n"
) )
debug('DEBUG', f"No updates found")
return return
# Get the latest release # Get the latest release
latest_release = feed.entries[0] latest_release = feed.entries[0]
latest_version_str = extract_version(latest_release.link) 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( tk.messagebox.showinfo(
"No update Available", "No update Available",
f"Latest version: ({v2cversion})\n" f"Latest version: ({v2cversion})\n"
f"Current version: ({v2cversion})\n" f"Current version: ({v2cversion})\n"
) )
debug('DEBUG', f"No updates available")
return return
print(f"RSS Version: {latest_version_str}")
# Extract version numbers from the latest release title # Extract version numbers from the latest release title
latest_version = tuple(map(int, latest_version_str.split('.'))) latest_version = tuple(map(int, latest_version_str.split('.')))
@ -153,46 +289,66 @@ def check_for_updates():
f"Current version: ({v2cversion})\n" f"Current version: ({v2cversion})\n"
"Do you want to update?" "Do you want to update?"
) )
debug('DEBUG', f"Found new update, prompting user")
if response: if response:
debug('DEBUG', f"User chose to update ...")
# Determine the platform # Determine the platform
current_platform = platform.system().lower() current_platform = platform.system().lower()
if current_platform == "windows": if current_platform == "windows":
debug('DEBUG', f"Detected Windows system, grabbing installer")
# On Windows, download and run the 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) installer_url = "https://git.rolfsvaag.no/frarol96/Video2Crops/releases/download/v{}/Video2Crops-windows-installer.exe".format(latest_version_str)
try: try:
urllib.request.urlretrieve(installer_url, "Video2Crops-windows-installer.exe") urllib.request.urlretrieve(installer_url, "Video2Crops-windows-installer.exe")
subprocess.Popen("Video2Crops-windows-installer.exe", shell=True) subprocess.Popen("Video2Crops-windows-installer.exe", shell=True)
debug('DEBUG', f"Downloaded new installer, running it ...")
root.quit() root.quit()
except: except:
tk.messagebox.showinfo( tk.messagebox.showinfo(
"Installer not found", "Installer not found",
f"The installer file was not found.\nPlease check your antivirus and network connectivity" 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": elif current_platform == "linux":
debug('DEBUG', f"Detected Linux system, grabbing executable")
# On Linux, download and run the 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) executable_url = "https://git.rolfsvaag.no/frarol96/Video2Crops/releases/download/v{}/Video2Crops-linux".format(latest_version_str)
try: try:
urllib.request.urlretrieve(executable_url, "Video2Crops-linux") urllib.request.urlretrieve(executable_url, "Video2Crops-linux")
os.chmod("Video2Crops-linux", 0o755) os.chmod("Video2Crops-linux", 0o755)
subprocess.Popen("./Video2Crops-linux", shell=True) subprocess.Popen("./Video2Crops-linux", shell=True)
debug('DEBUG', f"Downloaded new executable, running it ...")
root.quit() root.quit()
except: except:
tk.messagebox.showinfo( tk.messagebox.showinfo(
"Executable not found", "Executable not found",
f"The executable file was not found.\nPlease check your antivirus and network connectivity" 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: else:
tk.messagebox.showinfo( if not STARTING:
"No update Available", tk.messagebox.showinfo(
f"Latest version: ({v2cversion})\n" "No update Available",
f"Current version: ({v2cversion})\n" f"Latest version: ({v2cversion})\n"
) f"Current version: ({v2cversion})\n"
)
debug('DEBUG', f"No update available")
except Exception as e: except Exception as e:
tk.messagebox.showerror("Error", f"An error occurred: {str(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 # Function to create the output directory and return its path
def create_output_directory(video_filename): 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 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)) video_filename_base, _ = os.path.splitext(os.path.basename(video_filename))
output_folder_path = os.path.join(base_directory, video_filename_base) 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): if not os.path.exists(output_folder_path):
os.makedirs(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 return output_folder_path
# Function to calculate ROI position based on alignment and size settings # 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 # Create a function to process video files
def process_video(video_filenames, main_window): def process_video(video_filenames, main_window):
debug('DEBUG', f"Starting video processing ...")
global processing global processing
processing = True debug('DEBUG', f"Processing state: {processing}")
def write_log(message): def write_log(message):
logfile_path = os.path.join(output_folder, 'logfile.txt') logfile_path = os.path.join(output_folder, 'logfile.txt')
with open(logfile_path, 'a') as logfile: with open(logfile_path, 'a') as logfile:
log_message = f"{datetime.now().strftime('%m-%d_%H:%M:%S')} - {message}\n" log_message = f"{datetime.now().strftime('%m-%d_%H:%M:%S')} - {message}\n"
logfile.write(log_message) logfile.write(log_message)
print(log_message) debug('LOG EVENT', f"{log_message}")
def calc_value(image): def calc_value(image):
grayscale = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) grayscale = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
return np.std(grayscale) 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) corner1 = (-1, -1)
corner2 = (-1, -1) corner2 = (-1, -1)
@ -249,6 +424,10 @@ def process_video(video_filenames, main_window):
cropped_image = None cropped_image = None
for videofilename in video_filenames: 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") main_window.title(f"Video Processing App")
@ -283,6 +462,7 @@ def process_video(video_filenames, main_window):
vertical_alignment = vertical_alignment_var.get() vertical_alignment = vertical_alignment_var.get()
horizontal_alignment = horizontal_alignment_var.get() horizontal_alignment = horizontal_alignment_var.get()
debug('DEBUG', f"Displaying video player window: {SHOW}")
if SHOW: if SHOW:
vp_title = "Video2Crops Video Player" vp_title = "Video2Crops Video Player"
# Get the screen resolution # Get the screen resolution
@ -297,6 +477,9 @@ def process_video(video_filenames, main_window):
cv2.namedWindow(vp_title, cv2.WINDOW_NORMAL) cv2.namedWindow(vp_title, cv2.WINDOW_NORMAL)
cv2.resizeWindow(vp_title, initial_width, initial_height) 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: while True:
ret, frame = cap.read() ret, frame = cap.read()
@ -324,7 +507,8 @@ def process_video(video_filenames, main_window):
key = cv2.waitKey(10) 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 break
if cropped_image is not None and frame_cnt % frame_step == 0: 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.namedWindow(ci_title, cv2.WINDOW_NORMAL)
cv2.imshow(ci_title, cropped_image) 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) 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" cropped_image_status = "Accepted"
filename = videofilename_base + '_' + str(counter).zfill(4) + ".png" filename = videofilename_base + '_' + str(counter).zfill(4) + ".png"
@ -343,7 +529,7 @@ def process_video(video_filenames, main_window):
if SAVE: if SAVE:
cv2.imwrite(image_filename, cropped_image) 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) write_log(cropped_image_info)
counter += 1 counter += 1
else: else:
@ -352,14 +538,20 @@ def process_video(video_filenames, main_window):
if SAVE: if SAVE:
cv2.imwrite(image_filename, cropped_image) 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) write_log(cropped_image_info)
discarded_frames += 1 discarded_frames += 1
frame_cnt += 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() cap.release()
cv2.destroyAllWindows() cv2.destroyAllWindows()
debug('DEBUG', f"Destroyed all current CV2 windows")
write_log(f"Video2Crops run complete!\nSaved {counter} frames.\nDiscarded {discarded_frames} frames.") write_log(f"Video2Crops run complete!\nSaved {counter} frames.\nDiscarded {discarded_frames} frames.")
@ -372,35 +564,75 @@ def add_files():
if file_paths: if file_paths:
for file_path in file_paths: for file_path in file_paths:
selected_files_listbox.insert(tk.END, file_path) 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 # Create a button to remove the selected file from the Listbox
def remove_file(): def remove_file():
selected_index = selected_files_listbox.curselection() selected_index = selected_files_listbox.curselection()
if selected_index: if selected_index:
selected_files_listbox.delete(selected_index) selected_files_listbox.delete(selected_index)
debug('DEBUG', f"Removed file from queue: {selected_index}")
# Create a function to clear the queue # Create a function to clear the queue
def clear_queue(): def clear_queue():
selected_files_listbox.delete(0, tk.END) selected_files_listbox.delete(0, tk.END)
debug('DEBUG', f"Cleared the queue")
# Create a function to cancel processing # Create a function to cancel processing
def cancel_processing(): def cancel_processing():
global processing global processing
debug('DEBUG', f"Attempting cancelation ...")
if processing: if processing:
processing = False 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.") 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 # Create a button to process the selected files
def process_files(): def process_files():
global processing debug('DEBUG', f"Starting file processing ...")
global processing, processing_thread
if not processing: if not processing:
processing = True
selected_files = selected_files_listbox.get(0, tk.END) selected_files = selected_files_listbox.get(0, tk.END)
for file_path in selected_files: processing_thread = threading.Thread(target=process_video, args=(selected_files, root))
process_video([file_path], 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 # Create a function to open the preferences window
def open_preferences(): def open_preferences():
debug('DEBUG', f"Opening preferences window")
preferences_window = tk.Toplevel(root) preferences_window = tk.Toplevel(root)
preferences_window.title("Preferences") preferences_window.title("Preferences")
@ -411,73 +643,116 @@ def open_preferences():
frame_step_var.set(str(new_frame_step)) frame_step_var.set(str(new_frame_step))
global frame_step global frame_step
frame_step = new_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: if not processing:
new_value_threshold = simpledialog.askfloat("Value Threshold", "Enter the new value threshold:") new_contrast_threshold = simpledialog.askfloat("Contrast Threshold", "Enter the new value threshold:")
if new_value_threshold is not None: if new_contrast_threshold is not None:
value_threshold_var.set(str(new_value_threshold)) 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(): def toggle_save():
if not processing: if not processing:
global SAVE global SAVE
SAVE = not 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(): def toggle_show():
if not processing: if not processing:
global SHOW global SHOW
SHOW = not 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(): def apply_roi_size():
if not processing: if not processing:
new_roi_size = simpledialog.askinteger("ROI Size", "Enter the new ROI size:") new_roi_size = simpledialog.askinteger("ROI Size", "Enter the new ROI size:")
if new_roi_size is not None: if new_roi_size is not None:
roi_size_var.set(str(new_roi_size)) 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 # Create preference fields
frame_step_label = tk.Label(preferences_window, text="Skip n frames:") 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 = 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 = 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:") contrast_threshold_label = tk.Label(preferences_window, text="Contrast Threshold:")
value_threshold_label.grid(row=1, column=0, padx=20, pady=10) contrast_threshold_label.grid(row=1, column=0, padx=20, pady=5)
value_threshold_value = tk.Label(preferences_window, textvariable=value_threshold_var) contrast_threshold_value = tk.Label(preferences_window, textvariable=contrast_threshold_var)
value_threshold_value.grid(row=1, column=1, padx=20, pady=10) contrast_threshold_value.grid(row=1, column=1, padx=20, pady=5)
value_threshold_button = tk.Button(preferences_window, text="Edit", command=change_value_threshold) contrast_threshold_button = tk.Button(preferences_window, text="Edit", command=change_contrast_threshold)
value_threshold_button.grid(row=1, column=2, padx=20, pady=10) 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 = 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 = 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 # Section divider
divider_label = tk.Label(preferences_window, text="ROI Settings", font=("Helvetica", 12, "bold")) 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 # Create ROI fields
vertical_alignment_label = tk.Label(preferences_window, text="Vertical Alignment:") 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_var.set("Center") # Default alignment
vertical_alignment_menu = tk.OptionMenu(preferences_window, vertical_alignment_var, "Top", "Center", "Bottom") 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 = 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_var.set("Center") # Default alignment
horizontal_alignment_menu = tk.OptionMenu(preferences_window, horizontal_alignment_var, "Left", "Center", "Right") 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 = 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 = 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 = 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 # Create a menu
menu = tk.Menu(root) menu = tk.Menu(root)
@ -485,7 +760,8 @@ root.config(menu=menu)
# Variables for preferences # Variables for preferences
frame_step_var = tk.StringVar() frame_step_var = tk.StringVar()
value_threshold_var = tk.StringVar() contrast_threshold_var = tk.StringVar()
duplicate_threshold_var = tk.StringVar()
save_var = tk.BooleanVar() save_var = tk.BooleanVar()
show_var = tk.BooleanVar() show_var = tk.BooleanVar()
@ -493,13 +769,14 @@ show_var = tk.BooleanVar()
vertical_alignment_var = tk.StringVar() vertical_alignment_var = tk.StringVar()
horizontal_alignment_var = tk.StringVar() horizontal_alignment_var = tk.StringVar()
roi_size_var = tk.StringVar() roi_size_var = tk.StringVar()
roi_size_var.set("400")
# Set initial values for preference variables # Set initial values for preference variables
frame_step_var.set(str(frame_step)) 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) save_var.set(SAVE)
show_var.set(SHOW) show_var.set(SHOW)
roi_size_var.set(roi_size)
# Create a "Queue" submenu # Create a "Queue" submenu
queue_menu = tk.Menu(menu, tearoff=0) 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="Add file", command=add_files)
queue_menu.add_command(label="Clear queue", command=clear_queue) queue_menu.add_command(label="Clear queue", command=clear_queue)
queue_menu.add_separator() queue_menu.add_separator()
queue_menu.add_command(label="Cancel", command=cancel_processing) queue_menu.add_command(label="Cancel", state="disabled", command=cancel_processing)
queue_menu.add_command(label="Cancel & Clear", command=lambda: [cancel_processing(), clear_queue()]) queue_menu.add_command(label="Cancel & Clear", state="disabled", command=lambda: [cancel_processing(), clear_queue()])
# Create a "Preferences" submenu # Create a "Preferences" submenu
menu.add_command(label="Preferences", command=open_preferences) menu.add_command(label="Preferences", command=open_preferences)
# Create an "Info" submenu # 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) menu.add_cascade(label="Info", menu=info_menu)
info_menu.add_command(label="Project Folder", command=lambda: webbrowser.open(os.getcwd())) info_menu.add_command(label="Project Folder", command=lambda: webbrowser.open(os.getcwd()))
info_menu.add_separator() 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="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"Check for updates", command=check_for_updates)
info_menu.add_command(label=f"View Changelog", command=open_changelog_window) 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 # Create a label for selected files
label = tk.Label(root, text="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 # Create a Listbox to display the selected files
selected_files_listbox = tk.Listbox(root, selectmode=tk.SINGLE, exportselection=0) 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 # 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 = 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 # 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 = 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 # Create a button to process the selected files
process_files_button = tk.Button(root, text="Process Files", command=process_files) processing_button = tk.Button(root, text="Process Files", command=process_cancel_toggle)
process_files_button.pack(padx=20, pady=10) 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()