Merge pull request 'Video to picture converter added' (#3) from Converter into main
Reviewed-on: http://192.168.178.135:3000/Fabelous/MPENN/pulls/3
This commit is contained in:
commit
34de5ba73e
|
@ -0,0 +1,326 @@
|
|||
import customtkinter as Ctk
|
||||
from tkinter import filedialog
|
||||
import scripts.get_sys_info as system_code
|
||||
import cv2
|
||||
import threading
|
||||
|
||||
|
||||
EPSILON = 1e-9 # choose an appropriate epsilon value
|
||||
|
||||
|
||||
class SharedCounter:
|
||||
def __init__(self):
|
||||
self._value = 0
|
||||
self.lock = threading.Lock()
|
||||
|
||||
@property
|
||||
def value(self):
|
||||
with self.lock:
|
||||
return self._value
|
||||
|
||||
@value.setter
|
||||
def value(self, new_value):
|
||||
with self.lock:
|
||||
self._value = new_value
|
||||
|
||||
class WorkerThread(threading.Thread):
|
||||
def __init__(self, thread_id, thread_count, last_frame, delay, shared_counter, is_running, video_path, output_path, img_name):
|
||||
super(WorkerThread, self).__init__()
|
||||
self.thread_id = thread_id
|
||||
self.thread_count = thread_count
|
||||
self.last_frame = last_frame
|
||||
self.delay = delay
|
||||
self.shared_counter = shared_counter
|
||||
self.is_running = is_running
|
||||
self.video_path = video_path
|
||||
self.output_path = output_path
|
||||
self.img_name = img_name
|
||||
if len(self.img_name) == 0:
|
||||
self.img_name = "Img_seq"
|
||||
|
||||
|
||||
def run(self):
|
||||
video = cv2.VideoCapture(self.video_path)
|
||||
if not video.isOpened():
|
||||
print(f"Error opening video file {self.video_path}")
|
||||
return
|
||||
|
||||
for i in range(self.thread_id, self.last_frame, self.thread_count):
|
||||
video.set(cv2.CAP_PROP_POS_FRAMES, i) # Seek to the correct frame.
|
||||
|
||||
ret, frame = video.read()
|
||||
# Check if the frame was read successfully
|
||||
if not ret:
|
||||
break
|
||||
# Save the frame as an image
|
||||
cv2.imwrite(f'{self.output_path}/{self.img_name}_{i+1}.png', frame)
|
||||
if not self.is_running.value:
|
||||
break
|
||||
with self.shared_counter.lock: # Safely increment the shared counter
|
||||
self.shared_counter._value += 1
|
||||
# Release the video file
|
||||
video.release()
|
||||
|
||||
|
||||
|
||||
|
||||
class Converter(Ctk.CTkFrame):
|
||||
def __init__(self,master, **kwargs):
|
||||
super().__init__(master, **kwargs)
|
||||
# get the threads counts:
|
||||
# create objects for the classes
|
||||
self.shared_counter = SharedCounter()
|
||||
self.is_running = SharedCounter()
|
||||
#system_code.load_json_file()
|
||||
self.thread_count = system_code.used_threads
|
||||
self.continue_offset = 0
|
||||
self.my_font = Ctk.CTkFont(family="Berlin Sans FB", size=22)
|
||||
self.font_entry = Ctk.CTkFont(family="Berlin Sans FB", size=18)
|
||||
stop_btn_txt = ("Stop Convert", "Continue")
|
||||
self.test_var = 0
|
||||
self.input_path = None
|
||||
self.output_path = None
|
||||
self.img_name = None
|
||||
self.total_frames = None
|
||||
self.step_size = None
|
||||
# the layout
|
||||
# input layout
|
||||
self.input_entry = Ctk.CTkEntry(self, placeholder_text="input path", width=350, font=self.my_font)
|
||||
self.input_btn = Ctk.CTkButton(self, text="Browse Input", width=100, command=self.get_video_path, font=self.my_font)
|
||||
|
||||
# output layout
|
||||
self.output_entry = Ctk.CTkEntry(self, placeholder_text="output path", width=350, font=self.my_font)
|
||||
self.output_btn = Ctk.CTkButton(self, text="Browse Output", width=100, command=self.get_folder_path, font=self.my_font)
|
||||
|
||||
# button row + img name selection
|
||||
self.img_naming = Ctk.CTkEntry(self, placeholder_text="Img_seq", width=150, font=self.my_font)
|
||||
# start Button
|
||||
self.start_btn = Ctk.CTkButton(self, text="Start Convert", width=100, command=self.start_threads, font=self.my_font)
|
||||
# stop Button
|
||||
self.stop_btn = Ctk.CTkButton(self, text=stop_btn_txt[0], width=50, command=self.stop_continue_threads, font=self.my_font)
|
||||
self.stop_btn.configure(text='Stop', state=Ctk.DISABLED)
|
||||
self.show_progress()
|
||||
self.progress_bar.set(0)
|
||||
self.align()
|
||||
|
||||
def enable_keybinding(self):
|
||||
self.master.bind("<Return>", self.on_a_press, add="+")
|
||||
|
||||
def disable_keybinding(self):
|
||||
self.master.unbind("<Return>")
|
||||
|
||||
def on_a_press(self, event):
|
||||
# Check if the frame is visible by querying its manager info
|
||||
if self.winfo_manager():
|
||||
self.start_threads()
|
||||
|
||||
|
||||
def get_frame_rate_and_last_frame(self, video_path):
|
||||
"""
|
||||
Determines the frame rate of a video file and returns the index of the last frame using OpenCV.
|
||||
|
||||
:param video_path: The path to the video file.
|
||||
:return: A tuple containing the frame rate and the index of the last frame of the video.
|
||||
"""
|
||||
# Open the video file using OpenCV's VideoCapture class
|
||||
video = cv2.VideoCapture(video_path)
|
||||
|
||||
# Check if the video was opened successfully
|
||||
if not video.isOpened():
|
||||
raise ValueError(f"Failed to open video file at path: {video_path}")
|
||||
|
||||
# Get the total number of frames and the frame rate of the video
|
||||
total_frames = int(video.get(cv2.CAP_PROP_FRAME_COUNT))
|
||||
frame_rate = video.get(cv2.CAP_PROP_FPS)
|
||||
|
||||
# Check if the total number of frames is positive
|
||||
if total_frames <= 0:
|
||||
raise ValueError(f"Invalid total number of frames: {total_frames}")
|
||||
|
||||
# Check if the frame rate is positive
|
||||
if frame_rate <= 0:
|
||||
raise ValueError(f"Invalid frame rate: {frame_rate}")
|
||||
|
||||
# Index of the last frame
|
||||
last_frame_index = total_frames - 1
|
||||
|
||||
# Release the video file
|
||||
video.release()
|
||||
|
||||
return last_frame_index
|
||||
|
||||
|
||||
def show_progress(self):
|
||||
# Progress
|
||||
# progressbar
|
||||
self.progress_bar = Ctk.CTkProgressBar(self,orientation="horizontal", width=500, height=30)
|
||||
# progressinfo
|
||||
self.progress_info = Ctk.CTkLabel(self, text="", width=1, font=self.my_font)
|
||||
# button row
|
||||
self.progress_bar.place(
|
||||
relx=0.5,
|
||||
rely=0.6,
|
||||
anchor="center",
|
||||
relwidth=0.8,
|
||||
relheight=0.03
|
||||
) # Position the converter button to the left
|
||||
self.progress_info.place(
|
||||
relx=0.5,
|
||||
rely=0.7,
|
||||
anchor="center",
|
||||
relwidth=0.35,
|
||||
relheight=0.06
|
||||
) # Position the converter button to the left
|
||||
|
||||
def update_progress(self, total_frames, last_counter_value):
|
||||
current_counter_value = self.shared_counter.value
|
||||
|
||||
if current_counter_value == total_frames:
|
||||
# Disable the stop button and display "Finished! :)"
|
||||
self.progress_bar.set(1)
|
||||
self.stop_btn.configure(state="disabled")
|
||||
self.start_btn.configure(state="enabled")
|
||||
self.input_btn.configure(state="enabled")
|
||||
self.output_btn.configure(state="enabled")
|
||||
self.progress_info.configure(text="Finished! :)")
|
||||
self.is_running.value = False
|
||||
self.stop_threads()
|
||||
elif current_counter_value > last_counter_value:
|
||||
jump = current_counter_value / self.total_frames
|
||||
self.progress_bar.set(jump)
|
||||
self.progress_info.configure(text=f"{int(self.progress_bar.get()*100)} %")
|
||||
self.after(1, lambda: self.update_progress(total_frames, current_counter_value))
|
||||
else:
|
||||
# If the counter value did not change, schedule the next update
|
||||
self.after(1, lambda: self.update_progress(total_frames, current_counter_value))
|
||||
|
||||
def get_video_path(self):
|
||||
video_path = filedialog.askopenfilename(filetypes=[('Video files', '*.mp4 *.avi *.mov')])
|
||||
self.input_entry.delete(0, Ctk.END) # Delete any existing text in the Entry widget
|
||||
self.input_entry.insert(0, video_path) # Insert the new text
|
||||
|
||||
def get_folder_path(self):
|
||||
file_path = filedialog.askdirectory()
|
||||
self.output_entry.delete(0, Ctk.END) # Delete any existing text in the Entry widget
|
||||
self.output_entry.insert(0, file_path) # Insert the new text
|
||||
|
||||
def start_threads(self):
|
||||
self.input_btn.configure(state="disabled")
|
||||
self.output_btn.configure(state="disabled")
|
||||
self.start_btn.configure(state="disabled")
|
||||
self.input_path = self.input_entry.get()
|
||||
self.output_path = self.output_entry.get()
|
||||
self.img_name = self.img_naming.get()
|
||||
self.total_frames = self.get_frame_rate_and_last_frame(self.input_path)
|
||||
self.step_size = 1 / self.total_frames * 100 /2
|
||||
self.progress_bar.configure(determinate_speed=self.step_size)
|
||||
self.progress_bar.set(0)
|
||||
|
||||
self.is_running.value = True
|
||||
self.continue_offset = 0 # Reset the continue offset to start from the beginning
|
||||
self.shared_counter.value = 0 # Reset the counter
|
||||
self.threads = []
|
||||
for i in range(0,self.thread_count):
|
||||
thread = WorkerThread(i, self.thread_count, self.total_frames, 100, self.shared_counter, self.is_running, self.input_path, self.output_path, self.img_name)
|
||||
self.threads.append(thread)
|
||||
thread.start()
|
||||
self.update_progress(self.total_frames, 0)
|
||||
self.start_btn.configure(state=Ctk.DISABLED)
|
||||
self.stop_btn.configure(text='Stop', state=Ctk.NORMAL)
|
||||
|
||||
def stop_threads(self):
|
||||
self.is_running.value = False
|
||||
for thread in self.threads:
|
||||
thread.join()
|
||||
self.continue_offset = self.shared_counter.value # Save the current counter value for continue
|
||||
if self.shared_counter.value != self.total_frames:
|
||||
self.stop_btn.configure(text='Continue', state=Ctk.NORMAL)
|
||||
# Close all OpenCV windows
|
||||
cv2.destroyAllWindows()
|
||||
|
||||
|
||||
def stop_continue_threads(self):
|
||||
self.start_btn.configure(state=Ctk.NORMAL)
|
||||
self.input_btn.configure(state=Ctk.NORMAL)
|
||||
self.output_btn.configure(state=Ctk.NORMAL)
|
||||
if self.is_running.value:
|
||||
self.stop_threads()
|
||||
else:
|
||||
self.continue_threads()
|
||||
|
||||
def continue_threads(self):
|
||||
self.is_running.value = True
|
||||
self.threads = []
|
||||
|
||||
for i in range(self.thread_count):
|
||||
thread = WorkerThread(i, self.thread_count, self.total_frames, 100, self.shared_counter, self.is_running, self.input_path, self.output_path, self.img_name)
|
||||
self.threads.append(thread)
|
||||
thread.start()
|
||||
|
||||
self.after(100, self.update_progress(self.total_frames, 0))
|
||||
self.stop_btn.config(text='Stop', state="normal")
|
||||
|
||||
def calculate_step_size(self, frame_count):
|
||||
"""This function calculates the the step size for the
|
||||
progressbar. The Progressbar does 0.02 steps normally."""
|
||||
percent = frame_count / 100 # find out how many frames are 1%
|
||||
percent = 1 / percent # find out how many % one frame is
|
||||
percent = percent * 0.5 # half that, because tkinter makes 0.02 with each step
|
||||
return percent
|
||||
|
||||
def align(self):
|
||||
self.enable_keybinding()
|
||||
# output placing
|
||||
self.input_entry.place(
|
||||
relx=0.10,
|
||||
rely=0.3,
|
||||
anchor="w",
|
||||
relwidth=0.5,
|
||||
relheight=0.06
|
||||
) # Position the converter button to the left
|
||||
self.input_btn.place(
|
||||
relx=0.90,
|
||||
rely=0.3,
|
||||
anchor="e",
|
||||
relwidth=0.2,
|
||||
relheight=0.06
|
||||
) # Position the converter button to the left
|
||||
|
||||
# output placing
|
||||
self.output_entry.place(
|
||||
relx=0.10,
|
||||
rely=0.4,
|
||||
anchor="w",
|
||||
relwidth=0.5,
|
||||
relheight=0.06
|
||||
) # Position the converter button to the left
|
||||
self.output_btn.place(
|
||||
relx=0.90,
|
||||
rely=0.4,
|
||||
anchor="e",
|
||||
relwidth=0.2,
|
||||
relheight=0.06
|
||||
) # Position the converter button to the left
|
||||
self.img_naming.place(
|
||||
relx=0.1,
|
||||
rely=0.5,
|
||||
anchor="w",
|
||||
relwidth=0.2,
|
||||
relheight=0.06
|
||||
) # Position the converter button to the left
|
||||
# button row
|
||||
self.start_btn.place(
|
||||
relx=0.325,
|
||||
rely=0.5,
|
||||
anchor="w",
|
||||
relwidth=0.15,
|
||||
relheight=0.06
|
||||
) # Position the converter button to the left
|
||||
self.stop_btn.place(
|
||||
relx=0.6,
|
||||
rely=0.5,
|
||||
anchor="e",
|
||||
relwidth=0.1,
|
||||
relheight=0.06
|
||||
) # Position the converter button to the left
|
||||
|
Loading…
Reference in New Issue