2023-10-09 23:14:31 +00:00
import cv2 , os , sys , re , threading , webbrowser , screeninfo
2023-09-30 10:12:06 +00:00
import numpy as np
import tkinter as tk
from tkinterdnd2 import DND_FILES , TkinterDnD
2023-10-01 13:10:23 +00:00
from tkinter import filedialog , simpledialog , messagebox , scrolledtext
2023-09-30 10:12:06 +00:00
from datetime import datetime
2023-10-01 13:10:23 +00:00
import feedparser
import subprocess
import platform
import urllib . request
2023-10-09 23:14:31 +00:00
from v2cmodules . toolbox import extract_version , debug , checkinifile , write2ini , extract_changelog , toggle_save , toggle_show
from v2cmodules . common import *
2023-10-05 22:09:17 +00:00
checkinifile ( )
debug ( ' INFO ' , " Video2Crops starting ... " )
2023-09-30 10:12:06 +00:00
# Create the root window
root = TkinterDnD . Tk ( )
root . title ( " Video2Crops " )
2023-10-05 22:09:17 +00:00
root . geometry ( tksize )
2023-10-01 13:10:23 +00:00
def open_changelog_window ( ) :
2023-10-05 22:09:17 +00:00
debug ( ' DEBUG ' , ' Loading changelog window ' )
2023-10-01 13:10:23 +00:00
changelog_window = tk . Toplevel ( root )
changelog_window . title ( " Video2Crops Changelog " )
try :
2023-10-09 23:14:31 +00:00
debug ( ' DEBUG ' , f " Reading the RSS feed from: { RSS_FEED_URL } " )
2023-10-01 13:10:23 +00:00
# Parse the RSS feed
2023-10-09 23:14:31 +00:00
feed = feedparser . parse ( RSS_FEED_URL )
2023-10-01 13:10:23 +00:00
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 )
2023-10-05 22:09:17 +00:00
debug ( ' DEBUG ' , f " Done collecting RSS data " )
2023-10-01 13:10:23 +00:00
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 )
2023-10-09 23:14:31 +00:00
if str ( version_str ) == str ( V2CVERSION ) and entry_num == 1 :
2023-10-01 13:10:23 +00:00
version_info + = " (up to date) "
2023-10-09 23:14:31 +00:00
elif str ( version_str ) == str ( V2CVERSION ) :
2023-10-01 13:10:23 +00:00
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 )
2023-10-05 22:09:17 +00:00
debug ( ' DEBUG ' , f " Finished processing changelog " )
2023-10-01 13:10:23 +00:00
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 ) } " )
2023-10-05 22:09:17 +00:00
debug ( ' ERROR ' , f " An error occured while processing changelog: \n { str ( e ) } \n " )
2023-10-01 13:10:23 +00:00
# Function for updating Video2Crops
def check_for_updates ( ) :
2023-10-05 22:09:17 +00:00
debug ( ' DEBUG ' , f " Checking for updates " )
2023-10-01 13:10:23 +00:00
try :
# Parse the RSS feed
2023-10-09 23:14:31 +00:00
feed = feedparser . parse ( RSS_FEED_URL )
2023-10-01 13:10:23 +00:00
2023-10-05 22:09:17 +00:00
if not feed . entries and not STARTING :
2023-10-01 13:10:23 +00:00
response = tk . messagebox . showinfo (
" No updates found " ,
2023-10-09 23:14:31 +00:00
f " Latest version: ( { V2CVERSION } ) \n "
f " Current version: ( { V2CVERSION } ) \n "
2023-10-01 13:10:23 +00:00
)
2023-10-05 22:09:17 +00:00
debug ( ' DEBUG ' , f " No updates found " )
2023-10-01 13:10:23 +00:00
return
# Get the latest release
latest_release = feed . entries [ 0 ]
latest_version_str = extract_version ( latest_release . link )
2023-10-05 22:09:17 +00:00
if latest_version_str is None and not STARTING :
2023-10-01 13:10:23 +00:00
tk . messagebox . showinfo (
" No update Available " ,
2023-10-09 23:14:31 +00:00
f " Latest version: ( { V2CVERSION } ) \n "
f " Current version: ( { V2CVERSION } ) \n "
2023-10-01 13:10:23 +00:00
)
2023-10-05 22:09:17 +00:00
debug ( ' DEBUG ' , f " No updates available " )
2023-10-01 13:10:23 +00:00
return
# Extract version numbers from the latest release title
latest_version = tuple ( map ( int , latest_version_str . split ( ' . ' ) ) )
# Compare the versions
2023-10-09 23:14:31 +00:00
if latest_version > ( V2CV [ 0 ] , V2CV [ 1 ] , V2CV [ 2 ] ) :
2023-10-01 13:10:23 +00:00
# Prompt the user for an update
response = tk . messagebox . askyesno (
" Update Available " ,
f " A newer version ( { latest_version_str } ) is available. \n "
2023-10-09 23:14:31 +00:00
f " Current version: ( { V2CVERSION } ) \n "
2023-10-01 13:10:23 +00:00
" Do you want to update? "
)
2023-10-05 22:09:17 +00:00
debug ( ' DEBUG ' , f " Found new update, prompting user " )
2023-10-01 13:10:23 +00:00
if response :
2023-10-05 22:09:17 +00:00
debug ( ' DEBUG ' , f " User chose to update ... " )
2023-10-01 13:10:23 +00:00
# Determine the platform
current_platform = platform . system ( ) . lower ( )
if current_platform == " windows " :
2023-10-05 22:09:17 +00:00
debug ( ' DEBUG ' , f " Detected Windows system, grabbing installer " )
2023-10-01 13:10:23 +00:00
# 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 )
2023-10-05 22:09:17 +00:00
debug ( ' DEBUG ' , f " Downloaded new installer, running it ... " )
2023-10-01 13:10:23 +00:00
root . quit ( )
except :
tk . messagebox . showinfo (
" Installer not found " ,
f " The installer file was not found. \n Please check your antivirus and network connectivity "
)
2023-10-05 22:09:17 +00:00
debug ( ' ERROR ' , f " Windows installer not found " )
2023-10-01 13:10:23 +00:00
elif current_platform == " linux " :
2023-10-05 22:09:17 +00:00
debug ( ' DEBUG ' , f " Detected Linux system, grabbing executable " )
2023-10-01 13:10:23 +00:00
# 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 )
2023-10-05 22:09:17 +00:00
debug ( ' DEBUG ' , f " Downloaded new executable, running it ... " )
2023-10-01 13:10:23 +00:00
root . quit ( )
except :
tk . messagebox . showinfo (
" Executable not found " ,
f " The executable file was not found. \n Please check your antivirus and network connectivity "
)
2023-10-05 22:09:17 +00:00
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 } . \n Currently, only Windows and Linux are supported. \n \n You can build from source if you need to run Video2Crops on other platforms. "
)
else :
debug ( ' INFO ' , ' User closed prompt window ' )
2023-10-01 13:10:23 +00:00
else :
2023-10-05 22:09:17 +00:00
if not STARTING :
tk . messagebox . showinfo (
" No update Available " ,
2023-10-09 23:14:31 +00:00
f " Latest version: ( { V2CVERSION } ) \n "
f " Current version: ( { V2CVERSION } ) \n "
2023-10-05 22:09:17 +00:00
)
debug ( ' DEBUG ' , f " No update available " )
2023-10-01 13:10:23 +00:00
except Exception as e :
tk . messagebox . showerror ( " Error " , f " An error occurred: { str ( e ) } " )
2023-10-05 22:09:17 +00:00
debug ( ' ERROR ' , f " An error occured while checking for updates: \n { str ( e ) } \n " )
2023-09-30 10:12:06 +00:00
# Function to create the output directory and return its path
def create_output_directory ( video_filename ) :
2023-10-05 22:09:17 +00:00
debug ( ' DEBUG ' , f " Creating output directory " )
2023-09-30 10:12:06 +00:00
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 )
2023-10-05 22:09:17 +00:00
debug ( ' DEBUG ' , f " Processing file: { video_filename } " )
debug ( ' DEBUG ' , f " Created output directory at: { output_folder_path } " )
2023-09-30 10:12:06 +00:00
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 ) :
2023-10-05 22:09:17 +00:00
debug ( ' DEBUG ' , f " Starting video processing ... " )
2023-09-30 10:12:06 +00:00
global processing
2023-10-05 22:09:17 +00:00
debug ( ' DEBUG ' , f " Processing state: { processing } " )
2023-09-30 10:12:06 +00:00
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 )
2023-10-05 22:09:17 +00:00
debug ( ' LOG EVENT ' , f " { log_message } " )
2023-09-30 10:12:06 +00:00
def calc_value ( image ) :
grayscale = cv2 . cvtColor ( image , cv2 . COLOR_BGR2GRAY )
return np . std ( grayscale )
2023-10-05 22:09:17 +00:00
def check_duplicate ( ref_frame , frame ) :
2023-10-09 23:14:31 +00:00
global first_frame
2023-10-11 17:13:32 +00:00
min_possible_mse = 0
# Calculate the maximum possible MSE (when images are completely different)
max_possible_mse = 255 * * 2 * roi_size * roi_size
2023-10-09 23:14:31 +00:00
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 )
2023-10-11 17:13:32 +00:00
normalized_mse = 100 * ( 1 - ( mse - min_possible_mse ) / ( max_possible_mse - min_possible_mse ) )
2023-10-09 23:14:31 +00:00
2023-10-11 17:13:32 +00:00
if normalized_mse > duplicate_threshold :
2023-10-09 23:14:31 +00:00
# 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
2023-10-11 17:13:32 +00:00
debug ( ' DEBUG ' , f " Frame is duplicate: { frame_is_duplicate } , MSE: { mse } , Normalized MSE: { normalized_mse } " )
2023-10-09 23:14:31 +00:00
return frame_is_duplicate , mse , ref_frame
else :
2023-10-11 17:13:32 +00:00
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
2023-09-30 10:12:06 +00:00
corner1 = ( - 1 , - 1 )
corner2 = ( - 1 , - 1 )
drawing = False
cropped_image = None
for videofilename in video_filenames :
2023-10-05 22:09:17 +00:00
debug ( ' DEBUG ' , f " Processing video file: { videofilename } " )
if not processing :
debug ( ' DEBUG ' , f " Breaking loop due to processing = { processing } " )
break
2023-09-30 10:12:06 +00:00
2023-10-09 23:14:31 +00:00
global first_frame
first_frame = True
2023-09-30 10:12:06 +00:00
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 ( )
2023-10-05 22:09:17 +00:00
debug ( ' DEBUG ' , f " Displaying video player window: { SHOW } " )
2023-09-30 10:12:06 +00:00
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 ]
2023-10-09 23:14:31 +00:00
if first_frame :
reference_frame = cropped_image
2023-09-30 10:12:06 +00:00
if SHOW :
cv2 . imshow ( vp_title , test )
key = cv2 . waitKey ( 10 )
2023-10-05 22:09:17 +00:00
if key == ord ( ' q ' ) or ( not processing ) :
debug ( ' DEBUG ' , f " Breaking loop due to cancel event " )
2023-09-30 10:12:06 +00:00
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 )
2023-10-05 22:09:17 +00:00
frame_is_duplicate , frame_duplicate_mse , reference_frame = check_duplicate ( reference_frame , cropped_image )
2023-09-30 10:12:06 +00:00
frame_value = calc_value ( cropped_image )
2023-10-05 22:09:17 +00:00
2023-10-09 23:14:31 +00:00
if ( frame_value > = contrast_threshold or contrast_threshold == 0 ) and not frame_is_duplicate :
2023-09-30 10:12:06 +00:00
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 )
2023-10-05 22:09:17 +00:00
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 ) } "
2023-09-30 10:12:06 +00:00
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 )
2023-10-05 22:09:17 +00:00
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 ) } "
2023-09-30 10:12:06 +00:00
write_log ( cropped_image_info )
discarded_frames + = 1
frame_cnt + = 1
2023-10-05 22:09:17 +00:00
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 } " )
2023-09-30 10:12:06 +00:00
cap . release ( )
cv2 . destroyAllWindows ( )
2023-10-05 22:09:17 +00:00
debug ( ' DEBUG ' , f " Destroyed all current CV2 windows " )
2023-09-30 10:12:06 +00:00
write_log ( f " Video2Crops run complete! \n Saved { counter } frames. \n Discarded { discarded_frames } frames. " )
processing = False
# Create a function to add files to the selected files Listbox
def add_files ( ) :
2023-10-11 17:13:32 +00:00
global processing_button
2023-10-11 16:06:51 +00:00
selected_files = selected_files_listbox . get ( 0 , tk . END )
2023-10-11 17:13:32 +00:00
file_paths = filedialog . askopenfilenames ( title = ' Select Video File(s) ' , filetypes = [ ( " Video Files " , FILE_TYPES ) ] )
2023-09-30 10:12:06 +00:00
if file_paths :
for file_path in file_paths :
2023-10-11 16:06:51 +00:00
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 } \' " )
2023-10-11 17:13:32 +00:00
selected_files = selected_files_listbox . get ( 0 , tk . END )
if len ( selected_files ) > 0 :
processing_button . config ( state = ' normal ' )
2023-09-30 10:12:06 +00:00
# Create a button to remove the selected file from the Listbox
def remove_file ( ) :
2023-10-11 17:13:32 +00:00
global processing_button
2023-09-30 10:12:06 +00:00
selected_index = selected_files_listbox . curselection ( )
if selected_index :
selected_files_listbox . delete ( selected_index )
2023-10-05 22:09:17 +00:00
debug ( ' DEBUG ' , f " Removed file from queue: { selected_index } " )
2023-10-11 17:13:32 +00:00
selected_files = selected_files_listbox . get ( 0 , tk . END )
if len ( selected_files ) < = 0 :
processing_button . config ( state = ' disabled ' )
2023-09-30 10:12:06 +00:00
# Create a function to clear the queue
def clear_queue ( ) :
2023-10-11 17:13:32 +00:00
global processing_button
2023-09-30 10:12:06 +00:00
selected_files_listbox . delete ( 0 , tk . END )
2023-10-11 17:13:32 +00:00
processing_button . config ( state = ' disabled ' )
2023-10-05 22:09:17 +00:00
debug ( ' DEBUG ' , f " Cleared the queue " )
2023-09-30 10:12:06 +00:00
# Create a function to cancel processing
def cancel_processing ( ) :
global processing
2023-10-05 22:09:17 +00:00
debug ( ' DEBUG ' , f " Attempting cancelation ... " )
2023-09-30 10:12:06 +00:00
if processing :
processing = False
2023-10-05 22:09:17 +00:00
if processing_thread . is_alive ( ) :
processing_thread . join ( ) # Wait for the processing thread to finish cleanly
2023-09-30 10:12:06 +00:00
messagebox . showinfo ( " Processing Canceled " , " Video processing has been canceled. " )
2023-10-05 22:09:17 +00:00
elif not processing :
processing = False
messagebox . showinfo ( " Nothing to cancel " , " Video2Crops isn ' t processing anything. " )
debug ( ' DEBUG ' , f " Cancel complete. Processing state: { processing } " )
2023-09-30 10:12:06 +00:00
# Create a button to process the selected files
def process_files ( ) :
2023-10-05 22:09:17 +00:00
debug ( ' DEBUG ' , f " Starting file processing ... " )
2023-10-11 16:06:51 +00:00
global processing , processing_thread , selected_files
2023-09-30 10:12:06 +00:00
if not processing :
2023-10-05 22:09:17 +00:00
processing = True
2023-09-30 10:12:06 +00:00
selected_files = selected_files_listbox . get ( 0 , tk . END )
2023-10-05 22:09:17 +00:00
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 " )
2023-10-11 17:13:32 +00:00
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 ( )
2023-10-05 22:09:17 +00:00
debug ( ' DEBUG ' , f " Processing/Cancel button state switch finished " )
2023-10-11 17:13:32 +00:00
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 " )
2023-09-30 10:12:06 +00:00
# Create a function to open the preferences window
def open_preferences ( ) :
2023-10-05 22:09:17 +00:00
debug ( ' DEBUG ' , f " Opening preferences window " )
2023-09-30 10:12:06 +00:00
preferences_window = tk . Toplevel ( root )
preferences_window . title ( " Preferences " )
def change_frame_step ( ) :
2023-10-09 23:14:31 +00:00
global frame_step
2023-09-30 10:12:06 +00:00
if not processing :
2023-10-09 23:14:31 +00:00
new_frame_step = simpledialog . askinteger ( " Frame Step " , " Enter the new frame step: " , initialvalue = frame_step , minvalue = 1 )
2023-09-30 10:12:06 +00:00
if new_frame_step is not None :
frame_step_var . set ( str ( new_frame_step ) )
frame_step = new_frame_step
2023-10-05 22:09:17 +00:00
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! " )
2023-09-30 10:12:06 +00:00
2023-10-05 22:09:17 +00:00
def change_contrast_threshold ( ) :
2023-10-09 23:14:31 +00:00
global contrast_threshold
2023-09-30 10:12:06 +00:00
if not processing :
2023-10-09 23:14:31 +00:00
new_contrast_threshold = simpledialog . askfloat ( " Contrast Threshold " , " Enter the new value threshold: " , initialvalue = contrast_threshold , minvalue = 0 )
2023-10-05 22:09:17 +00:00
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 ( ) :
2023-10-09 23:14:31 +00:00
global duplicate_threshold
2023-10-05 22:09:17 +00:00
if not processing :
2023-10-09 23:14:31 +00:00
new_duplicate_threshold = simpledialog . askfloat ( " Duplicate Threshold " , " Enter the new value threshold: " , initialvalue = duplicate_threshold , minvalue = 0 , maxvalue = 100 )
2023-10-05 22:09:17 +00:00
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! " )
2023-09-30 10:12:06 +00:00
def apply_roi_size ( ) :
2023-10-09 23:14:31 +00:00
global roi_size
2023-09-30 10:12:06 +00:00
if not processing :
2023-10-09 23:14:31 +00:00
new_roi_size = simpledialog . askinteger ( " ROI Size " , " Enter the new ROI size: " , initialvalue = roi_size , minvalue = 1 )
2023-09-30 10:12:06 +00:00
if new_roi_size is not None :
roi_size_var . set ( str ( new_roi_size ) )
2023-10-05 22:09:17 +00:00
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 ... " )
2023-09-30 10:12:06 +00:00
# Create preference fields
frame_step_label = tk . Label ( preferences_window , text = " Skip n frames: " )
2023-10-05 22:09:17 +00:00
frame_step_label . grid ( row = 0 , column = 0 , padx = 20 , pady = 5 )
2023-09-30 10:12:06 +00:00
frame_step_value = tk . Label ( preferences_window , textvariable = frame_step_var )
2023-10-05 22:09:17 +00:00
frame_step_value . grid ( row = 0 , column = 1 , padx = 20 , pady = 5 )
2023-09-30 10:12:06 +00:00
frame_step_button = tk . Button ( preferences_window , text = " Edit " , command = change_frame_step )
2023-10-05 22:09:17 +00:00
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 )
2023-10-09 23:14:31 +00:00
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 )
2023-10-05 22:09:17 +00:00
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 )
2023-10-09 23:14:31 +00:00
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 )
2023-10-05 22:09:17 +00:00
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 )
2023-09-30 10:12:06 +00:00
save_checkbox = tk . Checkbutton ( preferences_window , text = " Save Output Images " , variable = save_var , command = toggle_save )
2023-10-05 22:09:17 +00:00
save_checkbox . grid ( row = 3 , column = 0 , columnspan = 3 , padx = 20 , pady = 5 )
2023-09-30 10:12:06 +00:00
show_checkbox = tk . Checkbutton ( preferences_window , text = " Display Playback " , variable = show_var , command = toggle_show )
2023-10-05 22:09:17 +00:00
show_checkbox . grid ( row = 4 , column = 0 , columnspan = 3 , padx = 20 , pady = 5 )
2023-09-30 10:12:06 +00:00
# Section divider
divider_label = tk . Label ( preferences_window , text = " ROI Settings " , font = ( " Helvetica " , 12 , " bold " ) )
2023-10-05 22:09:17 +00:00
divider_label . grid ( row = 5 , column = 0 , columnspan = 3 , padx = 20 , pady = ( 20 , 10 ) )
2023-09-30 10:12:06 +00:00
# Create ROI fields
vertical_alignment_label = tk . Label ( preferences_window , text = " Vertical Alignment: " )
2023-10-05 22:09:17 +00:00
vertical_alignment_label . grid ( row = 6 , column = 0 , padx = 20 , pady = 5 )
2023-09-30 10:12:06 +00:00
vertical_alignment_var . set ( " Center " ) # Default alignment
vertical_alignment_menu = tk . OptionMenu ( preferences_window , vertical_alignment_var , " Top " , " Center " , " Bottom " )
2023-10-05 22:09:17 +00:00
vertical_alignment_menu . grid ( row = 6 , column = 1 , padx = 20 , pady = 5 )
2023-09-30 10:12:06 +00:00
horizontal_alignment_label = tk . Label ( preferences_window , text = " Horizontal Alignment: " )
2023-10-05 22:09:17 +00:00
horizontal_alignment_label . grid ( row = 7 , column = 0 , padx = 20 , pady = 5 )
2023-09-30 10:12:06 +00:00
horizontal_alignment_var . set ( " Center " ) # Default alignment
horizontal_alignment_menu = tk . OptionMenu ( preferences_window , horizontal_alignment_var , " Left " , " Center " , " Right " )
2023-10-05 22:09:17 +00:00
horizontal_alignment_menu . grid ( row = 7 , column = 1 , padx = 20 , pady = 5 )
2023-09-30 10:12:06 +00:00
roi_size_label = tk . Label ( preferences_window , text = " ROI Size: " )
2023-10-05 22:09:17 +00:00
roi_size_label . grid ( row = 8 , column = 0 , padx = 20 , pady = 5 )
2023-09-30 10:12:06 +00:00
roi_size_value = tk . Label ( preferences_window , textvariable = roi_size_var )
2023-10-05 22:09:17 +00:00
roi_size_value . grid ( row = 8 , column = 1 , padx = 20 , pady = 5 )
2023-09-30 10:12:06 +00:00
roi_size_button = tk . Button ( preferences_window , text = " Edit " , command = apply_roi_size )
2023-10-05 22:09:17 +00:00
roi_size_button . grid ( row = 8 , column = 2 , padx = 20 , pady = 5 )
debug ( ' DEBUG ' , f " Done creating preference fields " )
debug ( ' DEBUG ' , ' Functions defined ' )
2023-09-30 10:12:06 +00:00
# Create a menu
menu = tk . Menu ( root )
root . config ( menu = menu )
# Variables for preferences
frame_step_var = tk . StringVar ( )
2023-10-05 22:09:17 +00:00
contrast_threshold_var = tk . StringVar ( )
duplicate_threshold_var = tk . StringVar ( )
2023-09-30 10:12:06 +00:00
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 ) )
2023-10-05 22:09:17 +00:00
contrast_threshold_var . set ( str ( contrast_threshold ) )
duplicate_threshold_var . set ( str ( duplicate_threshold ) )
2023-09-30 10:12:06 +00:00
save_var . set ( SAVE )
show_var . set ( SHOW )
2023-10-05 22:09:17 +00:00
roi_size_var . set ( roi_size )
2023-09-30 10:12:06 +00:00
# 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 ( )
2023-10-05 22:09:17 +00:00
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 ( ) ] )
2023-09-30 10:12:06 +00:00
# Create a "Preferences" submenu
menu . add_command ( label = " Preferences " , command = open_preferences )
# Create an "Info" submenu
2023-10-05 22:09:17 +00:00
info_menu = tk . Menu ( menu , tearoff = 0 , disabledforeground = " #000 " )
2023-09-30 10:12:06 +00:00
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 ( )
2023-10-09 23:14:31 +00:00
info_menu . add_command ( label = " About " , command = lambda : webbrowser . open ( WIKI_URL ) )
2023-10-01 13:10:23 +00:00
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 )
2023-10-05 22:09:17 +00:00
info_menu . add_separator ( )
2023-10-09 23:14:31 +00:00
info_menu . add_command ( state = " disabled " , label = f " Version: { str ( V2CVERSION ) } " )
2023-09-30 10:12:06 +00:00
# Create a label for selected files
label = tk . Label ( root , text = " Selected Files: " )
2023-10-05 22:09:17 +00:00
label . pack ( padx = 20 , pady = 5 )
2023-09-30 10:12:06 +00:00
# Create a Listbox to display the selected files
selected_files_listbox = tk . Listbox ( root , selectmode = tk . SINGLE , exportselection = 0 )
2023-10-05 22:09:17 +00:00
selected_files_listbox . pack ( fill = tk . BOTH , expand = True , padx = 20 , pady = 5 )
2023-10-11 16:06:51 +00:00
selected_files = selected_files_listbox . get ( 0 , tk . END )
2023-09-30 10:12:06 +00:00
# Create a button to select and add files to the Listbox
select_files_button = tk . Button ( root , text = " Select Files " , command = add_files )
2023-10-05 22:09:17 +00:00
select_files_button . pack ( padx = 20 , pady = 5 )
2023-09-30 10:12:06 +00:00
# Create a button to remove the selected file from the Listbox
remove_file_button = tk . Button ( root , text = " Remove File " , command = remove_file )
2023-10-05 22:09:17 +00:00
remove_file_button . pack ( padx = 20 , pady = 5 )
2023-09-30 10:12:06 +00:00
# Create a button to process the selected files
2023-10-11 17:13:32 +00:00
processing_button = tk . Button ( root , text = " Process Files " , command = process_cancel_toggle , state = ' disabled ' )
2023-10-05 22:09:17 +00:00
processing_button . pack ( padx = 20 , pady = 5 )
debug ( ' DEBUG ' , ' UI generated, running main loop ' )
debug ( ' INFO ' , ' ... Video2Crops initialized and ready! ' )
2023-09-30 10:12:06 +00:00
2023-10-05 22:09:17 +00:00
check_for_updates ( )
STARTING = False
root . mainloop ( )