frarol96
e6aff29a81
The "process files" button now toggles between normal and disabled depending on the presence of files in the work queue
683 lines
30 KiB
Python
683 lines
30 KiB
Python
import cv2, os, sys, re, threading, webbrowser, screeninfo
|
|
import numpy as np
|
|
import tkinter as tk
|
|
from tkinterdnd2 import DND_FILES, TkinterDnD
|
|
from tkinter import filedialog, simpledialog, messagebox, scrolledtext
|
|
from datetime import datetime
|
|
import feedparser
|
|
import subprocess
|
|
import platform
|
|
import urllib.request
|
|
from v2cmodules.toolbox import extract_version, debug, checkinifile, write2ini, extract_changelog, toggle_save, toggle_show
|
|
from v2cmodules.common import *
|
|
|
|
checkinifile()
|
|
|
|
debug('INFO', "Video2Crops starting ...")
|
|
|
|
# Create the root window
|
|
root = TkinterDnD.Tk()
|
|
root.title("Video2Crops")
|
|
root.geometry(tksize)
|
|
|
|
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)
|
|
|
|
if not feed.entries:
|
|
scrolled_text = scrolledtext.ScrolledText(changelog_window, wrap=tk.WORD)
|
|
scrolled_text.pack(fill=tk.BOTH, expand=True)
|
|
scrolled_text.insert(tk.END, "No updates available.")
|
|
return
|
|
|
|
# Create a single scrolled text widget to display all changelog entries
|
|
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
|
|
version_str = extract_version(entry.link)
|
|
changelog_md = extract_changelog(entry.content[0].value)
|
|
|
|
if version_str is not None and changelog_md is not None:
|
|
version_info = f"# v{version_str}"
|
|
print(version_str)
|
|
if str(version_str) == str(V2CVERSION) and entry_num == 1:
|
|
version_info += "(up to date)"
|
|
elif str(version_str) == str(V2CVERSION):
|
|
version_info += "(current)"
|
|
elif entry_num == 1:
|
|
version_info += "(latest)"
|
|
version_info += "\n"
|
|
|
|
date_pattern = r'(\w{3}, \d{2} \w{3} \d{4})'
|
|
pub_date = re.search(date_pattern, entry.published)
|
|
if pub_date:
|
|
rel_date = pub_date.group(1)
|
|
|
|
version_info += f"\n{rel_date}\n\n"
|
|
|
|
# Append the version information and changelog entry to the scrolled text widget
|
|
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")
|
|
|
|
# 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 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 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
|
|
|
|
# Extract version numbers from the latest release title
|
|
latest_version = tuple(map(int, latest_version_str.split('.')))
|
|
|
|
# Compare the versions
|
|
if latest_version > (V2CV[0], V2CV[1], V2CV[2]):
|
|
# Prompt the user for an update
|
|
response = tk.messagebox.askyesno(
|
|
"Update Available",
|
|
f"A newer version ({latest_version_str}) is available.\n"
|
|
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:
|
|
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)
|
|
|
|
# Create the output directory if it doesn't exist
|
|
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
|
|
def calculate_roi(video_width, video_height, vertical_alignment, horizontal_alignment, roi_size):
|
|
y1 = (video_height - roi_size) // 2
|
|
x1 = (video_width - roi_size) // 2
|
|
|
|
if vertical_alignment == "Top":
|
|
y1 = 0
|
|
elif vertical_alignment == "Center":
|
|
y1 = (video_height - roi_size) // 2
|
|
elif vertical_alignment == "Bottom":
|
|
y1 = video_height - roi_size
|
|
|
|
if horizontal_alignment == "Left":
|
|
x1 = 0
|
|
elif horizontal_alignment == "Center":
|
|
x1 = (video_width - roi_size) // 2
|
|
elif horizontal_alignment == "Right":
|
|
x1 = video_width - roi_size
|
|
|
|
x2 = x1 + roi_size
|
|
y2 = y1 + roi_size
|
|
|
|
return x1, y1, x2, y2
|
|
|
|
# Create a function to process video files
|
|
def process_video(video_filenames, main_window):
|
|
debug('DEBUG', f"Starting video processing ...")
|
|
global processing
|
|
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)
|
|
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):
|
|
global first_frame
|
|
|
|
min_possible_mse = 0
|
|
# Calculate the maximum possible MSE (when images are completely different)
|
|
max_possible_mse = 255**2 * roi_size * roi_size
|
|
|
|
if duplicate_threshold > 0 and not first_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)
|
|
normalized_mse = 100 * (1 - (mse - min_possible_mse) / (max_possible_mse - min_possible_mse))
|
|
|
|
if normalized_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
|
|
|
|
debug('DEBUG', f"Frame is duplicate: {frame_is_duplicate}, MSE: {mse}, Normalized MSE: {normalized_mse}")
|
|
return frame_is_duplicate, mse, ref_frame
|
|
else:
|
|
if first_frame:
|
|
debug('DEBUG', f"Frame is the first frame, storing as initial reference")
|
|
first_frame = False
|
|
else:
|
|
debug('DEBUG', f"First Frame Duplicate check is disabled, ignoring!")
|
|
return False, 0, frame
|
|
|
|
corner1 = (-1, -1)
|
|
corner2 = (-1, -1)
|
|
drawing = False
|
|
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
|
|
|
|
global first_frame
|
|
first_frame = True
|
|
|
|
main_window.title(f"Video Processing App")
|
|
|
|
video_file_src = videofilename
|
|
|
|
# Define videofilename_base based on the video file name
|
|
videofilename_base, _ = os.path.splitext(os.path.basename(videofilename))
|
|
|
|
cap = cv2.VideoCapture(video_file_src, 0)
|
|
|
|
if not cap.isOpened():
|
|
write_log(f"Error: Could not open video file: {video_file_src}")
|
|
sys.exit()
|
|
|
|
counter = 0
|
|
frame_cnt = 0
|
|
discarded_frames = 0
|
|
|
|
# Create the output folder early on
|
|
output_folder = create_output_directory(videofilename)
|
|
|
|
# Create the "Discarded Frames" subfolder within the output folder
|
|
discarded_folder = os.path.join(output_folder, 'Discarded Frames')
|
|
if not os.path.exists(discarded_folder) and SAVE:
|
|
os.makedirs(discarded_folder)
|
|
|
|
write_log(f"[DEBUG] Output Folder: {output_folder}\n[DEBUG] Discarded Folder: {discarded_folder}")
|
|
|
|
|
|
# Initialize ROI parameters from preferences
|
|
roi_size = int(roi_size_var.get())
|
|
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
|
|
screen = screeninfo.get_monitors()[0]
|
|
screen_width, screen_height = screen.width, screen.height
|
|
|
|
# Calculate the initial window size as a quarter of the screen resolution
|
|
initial_width = screen_width // 2
|
|
initial_height = screen_height // 2
|
|
|
|
# Create the video player window
|
|
cv2.namedWindow(vp_title, cv2.WINDOW_NORMAL)
|
|
cv2.resizeWindow(vp_title, initial_width, initial_height)
|
|
|
|
while True:
|
|
ret, frame = cap.read()
|
|
|
|
if not ret:
|
|
break
|
|
|
|
# Get the video width and height from the frame
|
|
video_width = frame.shape[1]
|
|
video_height = frame.shape[0]
|
|
|
|
x1, y1, x2, y2 = calculate_roi(video_width, video_height, vertical_alignment, horizontal_alignment, roi_size)
|
|
|
|
cropped_image_status = "Rejected"
|
|
|
|
frame_copy = frame.copy()
|
|
|
|
if drawing:
|
|
cv2.rectangle(frame_copy, corner1, corner2, (0, 255, 0), 3)
|
|
|
|
test = cv2.rectangle(frame.copy(), (x1 - 1, y2 - 1), (x2 + 1, y1 + 1), (255, 0, 0), 4)
|
|
cropped_image = frame[y1:y2, x1:x2]
|
|
|
|
if first_frame:
|
|
reference_frame = cropped_image
|
|
|
|
if SHOW:
|
|
cv2.imshow(vp_title, test)
|
|
|
|
key = cv2.waitKey(10)
|
|
|
|
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:
|
|
if SHOW:
|
|
ci_title = "Video2Crops Cropped Image"
|
|
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 >= contrast_threshold or contrast_threshold == 0) and not frame_is_duplicate:
|
|
cropped_image_status = "Accepted"
|
|
|
|
filename = videofilename_base + '_' + str(counter).zfill(4) + ".png"
|
|
image_filename = os.path.join(output_folder, filename)
|
|
|
|
if SAVE:
|
|
cv2.imwrite(image_filename, cropped_image)
|
|
|
|
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:
|
|
filename = videofilename_base + '_' + str(discarded_frames).zfill(4) + ".png"
|
|
image_filename = os.path.join(discarded_folder, filename)
|
|
if SAVE:
|
|
cv2.imwrite(image_filename, cropped_image)
|
|
|
|
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.")
|
|
processing = False
|
|
|
|
# Create a function to add files to the selected files Listbox
|
|
def add_files():
|
|
global processing_button
|
|
selected_files = selected_files_listbox.get(0, tk.END)
|
|
file_paths = filedialog.askopenfilenames(title='Select Video File(s)', filetypes=[("Video Files", FILE_TYPES)])
|
|
if file_paths:
|
|
for file_path in file_paths:
|
|
if file_path not in selected_files:
|
|
selected_files_listbox.insert(tk.END, file_path)
|
|
debug('DEBUG', f"Queued video file: {file_path}")
|
|
else:
|
|
debug('DEBUG', f"Attempted to queue already-queued file \'{file_path}\'")
|
|
selected_files = selected_files_listbox.get(0, tk.END)
|
|
if len(selected_files) > 0:
|
|
processing_button.config(state='normal')
|
|
|
|
# Create a button to remove the selected file from the Listbox
|
|
def remove_file():
|
|
global processing_button
|
|
selected_index = selected_files_listbox.curselection()
|
|
if selected_index:
|
|
selected_files_listbox.delete(selected_index)
|
|
debug('DEBUG', f"Removed file from queue: {selected_index}")
|
|
selected_files = selected_files_listbox.get(0, tk.END)
|
|
if len(selected_files) <= 0:
|
|
processing_button.config(state='disabled')
|
|
|
|
# Create a function to clear the queue
|
|
def clear_queue():
|
|
global processing_button
|
|
selected_files_listbox.delete(0, tk.END)
|
|
processing_button.config(state='disabled')
|
|
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():
|
|
debug('DEBUG', f"Starting file processing ...")
|
|
global processing, processing_thread, selected_files
|
|
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 # 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, selected_files
|
|
if not processing:
|
|
toggle_process_button(False)
|
|
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()
|
|
elif processing:
|
|
toggle_process_button(True)
|
|
processing = False
|
|
if processing_thread.is_alive():
|
|
processing_thread.join()
|
|
debug('DEBUG', f"Processing/Cancel button state switch finished")
|
|
|
|
async def toggle_process_button(enable=bool):
|
|
"""True = Set state to 'Process Files' | False = Set state to 'Cancel'"""
|
|
global processing_button
|
|
if enable:
|
|
processing_button.config(text="Process Files")
|
|
else:
|
|
processing_button.config(text="Cancel")
|
|
|
|
# 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")
|
|
|
|
def change_frame_step():
|
|
global frame_step
|
|
if not processing:
|
|
new_frame_step = simpledialog.askinteger("Frame Step", "Enter the new frame step:", initialvalue=frame_step, minvalue=1)
|
|
if new_frame_step is not None:
|
|
frame_step_var.set(str(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_contrast_threshold():
|
|
global contrast_threshold
|
|
if not processing:
|
|
new_contrast_threshold = simpledialog.askfloat("Contrast Threshold", "Enter the new value threshold:", initialvalue=contrast_threshold, minvalue=0)
|
|
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():
|
|
global duplicate_threshold
|
|
if not processing:
|
|
new_duplicate_threshold = simpledialog.askfloat("Duplicate Threshold", "Enter the new value threshold:", initialvalue=duplicate_threshold, minvalue=0, maxvalue=100)
|
|
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 apply_roi_size():
|
|
global roi_size
|
|
if not processing:
|
|
new_roi_size = simpledialog.askinteger("ROI Size", "Enter the new ROI size:", initialvalue=roi_size, minvalue=1)
|
|
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=5)
|
|
frame_step_value = tk.Label(preferences_window, textvariable=frame_step_var)
|
|
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=5)
|
|
|
|
contrast_threshold_label = tk.Label(preferences_window, text="Contrast Threshold:")
|
|
contrast_threshold_label.grid(row=1, column=0, padx=20, pady=5)
|
|
if contrast_threshold == float(0):
|
|
contrast_threshold_value = tk.Label(preferences_window, text="Disabled")
|
|
else:
|
|
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)
|
|
if duplicate_threshold == float(0):
|
|
duplicate_threshold_value = tk.Label(preferences_window, text="Disabled")
|
|
else:
|
|
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=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=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=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=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=6, column=1, padx=20, pady=5)
|
|
|
|
horizontal_alignment_label = tk.Label(preferences_window, text="Horizontal Alignment:")
|
|
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=7, column=1, padx=20, pady=5)
|
|
|
|
roi_size_label = tk.Label(preferences_window, text="ROI Size:")
|
|
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=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=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)
|
|
root.config(menu=menu)
|
|
|
|
# Variables for preferences
|
|
frame_step_var = tk.StringVar()
|
|
contrast_threshold_var = tk.StringVar()
|
|
duplicate_threshold_var = tk.StringVar()
|
|
save_var = tk.BooleanVar()
|
|
show_var = tk.BooleanVar()
|
|
|
|
# Variables for ROI
|
|
vertical_alignment_var = tk.StringVar()
|
|
horizontal_alignment_var = tk.StringVar()
|
|
roi_size_var = tk.StringVar()
|
|
|
|
# Set initial values for preference variables
|
|
frame_step_var.set(str(frame_step))
|
|
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)
|
|
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", 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, 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(WIKI_URL))
|
|
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=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=5)
|
|
selected_files = selected_files_listbox.get(0, tk.END)
|
|
|
|
# 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=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=5)
|
|
|
|
# Create a button to process the selected files
|
|
processing_button = tk.Button(root, text="Process Files", command=process_cancel_toggle, state='disabled')
|
|
processing_button.pack(padx=20, pady=5)
|
|
|
|
debug('DEBUG', 'UI generated, running main loop')
|
|
debug('INFO', '... Video2Crops initialized and ready!')
|
|
|
|
check_for_updates()
|
|
STARTING = False
|
|
root.mainloop() |