Video2Crops/Video2Crops.py
2023-09-30 11:37:14 +00:00

378 lines
14 KiB
Python

import cv2
import os
import numpy as np
import tkinter as tk
from tkinterdnd2 import DND_FILES, TkinterDnD
from tkinter import filedialog, simpledialog, messagebox
from datetime import datetime
import sys
import webbrowser
import screeninfo
# Constants
v2cversion1 = 1
v2cversion2 = 0
v2cversion3 = 0
v2cversion = f"{v2cversion1}.{v2cversion2}.{v2cversion3}"
frame_step = 100
value_threshold = 11.0
SAVE = True
SHOW = True
processing = False
# Create the root window
root = TkinterDnD.Tk()
root.title("Video2Crops")
# Function to create the output directory and return its path
def create_output_directory(video_filename):
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)
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):
global processing
processing = True
def write_log(message):
logfile_path = os.path.join(output_folder, 'logfile.txt')
with open(logfile_path, 'a') as logfile:
log_message = f"{datetime.now().strftime('%m-%d_%H:%M:%S')} - {message}\n"
logfile.write(log_message)
print(log_message)
def calc_value(image):
grayscale = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
return np.std(grayscale)
corner1 = (-1, -1)
corner2 = (-1, -1)
drawing = False
cropped_image = None
for videofilename in video_filenames:
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()
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 SHOW:
cv2.imshow(vp_title, test)
key = cv2.waitKey(10)
if key == ord('q'):
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_value = calc_value(cropped_image)
if frame_value >= value_threshold:
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_value} / {value_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_value} / {value_threshold} - image nr: {str(str(counter)).zfill(4)}"
write_log(cropped_image_info)
discarded_frames += 1
frame_cnt += 1
cap.release()
cv2.destroyAllWindows()
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():
file_paths = filedialog.askopenfilenames(filetypes=[("Video Files", "*.mp4 *.avi *.mov")])
if file_paths:
for file_path in file_paths:
selected_files_listbox.insert(tk.END, file_path)
# Create a button to remove the selected file from the Listbox
def remove_file():
selected_index = selected_files_listbox.curselection()
if selected_index:
selected_files_listbox.delete(selected_index)
# Create a function to clear the queue
def clear_queue():
selected_files_listbox.delete(0, tk.END)
# Create a function to cancel processing
def cancel_processing():
global processing
if processing:
processing = False
messagebox.showinfo("Processing Canceled", "Video processing has been canceled.")
# Create a button to process the selected files
def process_files():
global processing
if not processing:
selected_files = selected_files_listbox.get(0, tk.END)
for file_path in selected_files:
process_video([file_path], root)
# Create a function to open the preferences window
def open_preferences():
preferences_window = tk.Toplevel(root)
preferences_window.title("Preferences")
def change_frame_step():
if not processing:
new_frame_step = simpledialog.askinteger("Frame Step", "Enter the new frame step:")
if new_frame_step is not None:
frame_step_var.set(str(new_frame_step))
global frame_step
frame_step = new_frame_step
def change_value_threshold():
if not processing:
new_value_threshold = simpledialog.askfloat("Value Threshold", "Enter the new value threshold:")
if new_value_threshold is not None:
value_threshold_var.set(str(new_value_threshold))
def toggle_save():
if not processing:
global SAVE
SAVE = not SAVE
def toggle_show():
if not processing:
global SHOW
SHOW = not SHOW
def apply_roi_size():
if not processing:
new_roi_size = simpledialog.askinteger("ROI Size", "Enter the new ROI size:")
if new_roi_size is not None:
roi_size_var.set(str(new_roi_size))
# Create preference fields
frame_step_label = tk.Label(preferences_window, text="Skip n frames:")
frame_step_label.grid(row=0, column=0, padx=20, pady=10)
frame_step_value = tk.Label(preferences_window, textvariable=frame_step_var)
frame_step_value.grid(row=0, column=1, padx=20, pady=10)
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)
value_threshold_label = tk.Label(preferences_window, text="Discard Threshold:")
value_threshold_label.grid(row=1, column=0, padx=20, pady=10)
value_threshold_value = tk.Label(preferences_window, textvariable=value_threshold_var)
value_threshold_value.grid(row=1, column=1, padx=20, pady=10)
value_threshold_button = tk.Button(preferences_window, text="Edit", command=change_value_threshold)
value_threshold_button.grid(row=1, column=2, padx=20, pady=10)
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)
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)
# Section divider
divider_label = tk.Label(preferences_window, text="ROI Settings", font=("Helvetica", 12, "bold"))
divider_label.grid(row=4, column=0, columnspan=3, padx=20, pady=(20, 10))
# Create ROI fields
vertical_alignment_label = tk.Label(preferences_window, text="Vertical Alignment:")
vertical_alignment_label.grid(row=5, column=0, padx=20, pady=10)
vertical_alignment_var.set("Center") # Default alignment
vertical_alignment_menu = tk.OptionMenu(preferences_window, vertical_alignment_var, "Top", "Center", "Bottom")
vertical_alignment_menu.grid(row=5, column=1, padx=20, pady=10)
horizontal_alignment_label = tk.Label(preferences_window, text="Horizontal Alignment:")
horizontal_alignment_label.grid(row=6, column=0, padx=20, pady=10)
horizontal_alignment_var.set("Center") # Default alignment
horizontal_alignment_menu = tk.OptionMenu(preferences_window, horizontal_alignment_var, "Left", "Center", "Right")
horizontal_alignment_menu.grid(row=6, column=1, padx=20, pady=10)
roi_size_label = tk.Label(preferences_window, text="ROI Size:")
roi_size_label.grid(row=7, column=0, padx=20, pady=10)
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_button = tk.Button(preferences_window, text="Edit", command=apply_roi_size)
roi_size_button.grid(row=7, column=2, padx=20, pady=10)
# Create a menu
menu = tk.Menu(root)
root.config(menu=menu)
# Variables for preferences
frame_step_var = tk.StringVar()
value_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()
roi_size_var.set("400")
# Set initial values for preference variables
frame_step_var.set(str(frame_step))
value_threshold_var.set(str(value_threshold))
save_var.set(SAVE)
show_var.set(SHOW)
# 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", command=cancel_processing)
queue_menu.add_command(label="Cancel & Clear", 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)
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("about.md"))
info_menu.add_command(label=f"Version: {str(v2cversion)}")
# Create a label for selected files
label = tk.Label(root, text="Selected Files:")
label.pack(padx=20, pady=10)
# Create a Listbox to display the selected files
selected_files_listbox = tk.Listbox(root, selectmode=tk.SINGLE, exportselection=0)
selected_files_listbox.pack(fill=tk.BOTH, expand=True, padx=20, pady=10)
# Create a button to select and add files to the Listbox
select_files_button = tk.Button(root, text="Select Files", command=add_files)
select_files_button.pack(padx=20, pady=10)
# Create a button to remove the selected file from the Listbox
remove_file_button = tk.Button(root, text="Remove File", command=remove_file)
remove_file_button.pack(padx=20, pady=10)
# Create a button to process the selected files
process_files_button = tk.Button(root, text="Process Files", command=process_files)
process_files_button.pack(padx=20, pady=10)
root.mainloop()