import datetime
import glob
import json
import os
from copy import deepcopy
from tkinter import *
from typing import List, Union
import cv2
import PIL
from PIL import ImageTk
from simba.mixins.pop_up_mixin import PopUpMixin
from simba.ui.tkinter_functions import (CreateLabelFrameWithIcon, Entry_Box,
SimbaButton, SimbaCheckbox,
SimBADropDown, SimBALabel,
SimBASeperator)
from simba.utils.checks import (check_ffmpeg_available,
check_file_exist_and_readable, check_float,
check_if_string_value_is_valid_video_timestamp,
check_int, check_nvidea_gpu_available,
check_that_hhmmss_start_is_before_end)
from simba.utils.enums import Formats, Keys, Links, Options
from simba.utils.errors import (FFMPEGCodecGPUError, FFMPEGNotFoundError,
NoFilesFoundError)
from simba.utils.lookups import (get_color_dict, get_ffmpeg_encoders,
get_fonts, get_icons_paths,
percent_to_crf_lookup,
video_quality_to_preset_lookup)
from simba.utils.printing import SimbaTimer, stdout_success
from simba.utils.read_write import (
check_if_hhmmss_timestamp_is_valid_part_of_video, get_fn_ext,
get_video_meta_data, read_frm_of_video, str_2_bool)
from simba.video_processors.batch_process_create_ffmpeg_commands import \
FFMPEGCommandCreator
from simba.video_processors.roi_selector import ROISelector
MENU_ICONS = get_icons_paths()
SETTINGS = {'codec': Formats.BATCH_CODEC.value,
'crop_color': 'Pink',
'font': 'Arial',
'crop_thickness': 10,
'txt_loc': 'bottom_middle',
'font_size': 25,
'clahe_clip_limit': 2,
'clahe_tile_size': 16}
[docs]class BatchProcessFrame(PopUpMixin):
"""
Interactive GUI that collect user-inputs for batch processing videos (e.g., cropping,
clipping etc.). User-selected output is stored in json file format within the user-defined `output_dir`
.. note::
`Batch pre-process tutorials <https://github.com/sgoldenlab/simba/blob/master/docs/tutorial_process_videos.md>`__.
:param str input_dir: Input folder path containing videos for bath processing.
:param str output_dir: Output folder path for where to store the processed videos.
:example:
>>> batch_preprocessor = BatchProcessFrame(input_dir=r'MyInputVideosDir', output_dir=r'MyOutputVideosDir')
>>> batch_preprocessor.create_main_window()
>>> batch_preprocessor.create_video_table_headings()
>>> batch_preprocessor.create_video_rows()
>>> batch_preprocessor.create_execute_btn()
>>> batch_preprocessor.main_frm.mainloop()
"""
def __init__(self,
input_dir: Union[str, os.PathLike],
output_dir: Union[str, os.PathLike]):
if not check_ffmpeg_available(raise_error=False):
raise FFMPEGNotFoundError(msg='Cannot perform batch video processing: FFMPEG not found', source=self.__class__.__name__)
self.input_dir, self.output_dir = input_dir, output_dir
if not os.path.exists(self.output_dir):
os.makedirs(self.output_dir)
self.videos_in_dir_dict, self.crop_dict = {}, {}
self.get_input_files()
if len(list(self.videos_in_dir_dict.keys())) == 0:
raise NoFilesFoundError(msg=f"The input directory {self.input_dir} contains ZERO video files in either .avi, .mp4, .mov, .flv, or m4v format", source=self.__class__.__name__)
PopUpMixin.__init__(self, title="BATCH PRE-PROCESS VIDEOS IN SIMBA", size=(2000, 600), icon='factory')
self.red_drop_img = ImageTk.PhotoImage(image=PIL.Image.open(MENU_ICONS["crop_red"]["icon_path"]))
self.green_drop_img = ImageTk.PhotoImage(image=PIL.Image.open(MENU_ICONS["crop_green"]["icon_path"]))
self.black_crop_img = ImageTk.PhotoImage(image=PIL.Image.open(MENU_ICONS["crop"]["icon_path"]))
self.percent_to_crf_lookup = percent_to_crf_lookup()
self.cpu_video_quality = list(range(10, 110, 10))
self.cpu_video_quality = [str(x) for x in self.cpu_video_quality]
self.settings = deepcopy(SETTINGS)
self.video_quality_to_preset_lookup = video_quality_to_preset_lookup()
self.clrs = get_color_dict()
self.gpu_available_state = NORMAL if check_nvidea_gpu_available() else DISABLED
self.max_char_vid_name = len(max(list(self.videos_in_dir_dict.keys()), key=len))
self.root.lift()
self.root.attributes("-topmost", True)
self.root.after(50, lambda: self.root.attributes("-topmost", False))
def _check_int_eb(self, entry_box: Entry_Box, valid_clr: str = 'white', invalid_clr: str = 'lightsalmon'):
value = entry_box.entry_get
valid_value = check_int(name='', value=value, allow_zero=False, allow_negative=False, raise_error=False)[0]
if not valid_value:
entry_box.set_bg_clr(clr=invalid_clr)
else:
entry_box.set_bg_clr(clr=valid_clr)
def _check_float_eb(self, entry_box: Entry_Box, valid_clr: str = 'white', invalid_clr: str = 'lightsalmon'):
value = entry_box.entry_get
valid_value = check_float(name='', value=value, allow_zero=False, allow_negative=False, raise_error=False)[0]
if not valid_value:
entry_box.set_bg_clr(clr=invalid_clr)
else:
entry_box.set_bg_clr(clr=valid_clr)
def _check_valid_hhmmss(self, entry_box: Entry_Box, valid_clr: str = 'white', invalid_clr: str = 'lightsalmon'):
value = entry_box.entry_get
valid_value = check_if_string_value_is_valid_video_timestamp(value=value, raise_error=False, name='')
if not valid_value:
entry_box.set_bg_clr(clr=invalid_clr)
else:
entry_box.set_bg_clr(clr=valid_clr)
[docs] def create_main_window(self):
self.quick_settings_frm = CreateLabelFrameWithIcon(parent=self.main_frm,header="QUICK SETTINGS",icon_name=Keys.DOCUMENTATION.value,icon_link=Links.BATCH_PREPROCESS.value)
self.clip_video_settings_frm = CreateLabelFrameWithIcon(parent=self.quick_settings_frm, header='CLIP VIDEOS SETTING', icon_name='clip', padx=5, pady=5)
self.quick_clip_start_entry_box = Entry_Box(parent=self.clip_video_settings_frm, fileDescription='START TIME: ', labelwidth=15, value="00:00:00", justify='center', img='play', entry_box_width=12, tooltip_key='BATCH_CLIP_START_TIME', trace=self._check_valid_hhmmss)
self.quick_clip_end_entry_box = Entry_Box(parent=self.clip_video_settings_frm, fileDescription='END TIME: ', labelwidth=15, value="00:00:00", justify='center', img='finish', entry_box_width=12, tooltip_key='BATCH_CLIP_END_TIME', trace=self._check_valid_hhmmss)
self.quick_clip_apply = SimbaButton(parent=self.clip_video_settings_frm, txt='APPLY', img='arrow_down_green_2', cmd=self.apply_trim_to_all)
self.quick_downsample_frm = CreateLabelFrameWithIcon(parent=self.quick_settings_frm, header='DOWNSAMPLE VIDEOS', icon_name='resize', padx=5, pady=5)
self.quick_downsample_width = Entry_Box(parent=self.quick_downsample_frm, fileDescription='WIDTH: ', labelwidth=15, value=400, justify='center', img='width', entry_box_width=12, tooltip_key='BATCH_DOWNSAMPLE_WIDTH', trace=self._check_int_eb)
self.quick_downsample_height = Entry_Box(parent=self.quick_downsample_frm, fileDescription='HEIGHT: ', labelwidth=15, value=600, justify='center', img='height', entry_box_width=12, tooltip_key='BATCH_DOWNSAMPLE_HEIGHT', trace=self._check_int_eb)
self.quick_downsample_apply = SimbaButton(parent=self.quick_downsample_frm, txt='APPLY', img='arrow_down_green_2', cmd=self.apply_resolution_to_all)
self.quick_set_fps = CreateLabelFrameWithIcon(parent=self.quick_settings_frm, header="CHANGE FPS", icon_name='camera', padx=5, pady=12)
self.quick_fps_entry_box = Entry_Box(parent=self.quick_set_fps, fileDescription='FPS: ', labelwidth=15, value=15.0, justify='center', img='camera', entry_box_width=12, tooltip_key='BATCH_FPS', trace=self._check_float_eb)
self.quick_fps_apply = SimbaButton(parent=self.quick_set_fps, txt='APPLY', img='arrow_down_green_2', cmd=self.apply_fps_to_all)
self.quick_set_quality = CreateLabelFrameWithIcon(parent=self.quick_settings_frm, header="OUTPUT VIDEO QUALITY", icon_name='star', padx=5, pady=12)
self.use_gpu_dropdown = SimBADropDown(parent=self.quick_set_quality, label="USE GPU", label_width=20, dropdown_options=['TRUE', 'FALSE'], value='FALSE', img='gpu_3', state=self.gpu_available_state, dropdown_width=15, tooltip_key='USE_GPU')
self.quick_set_quality_dropdown = SimBADropDown(parent=self.quick_set_quality, label='VIDEO QUALITY %', label_width=20, dropdown_options=self.cpu_video_quality, value=60, img='star', dropdown_width=15, tooltip_key='OUTPUT_VIDEO_QUALITY')
self.quick_set_quality_apply = SimbaButton(parent=self.quick_set_quality, txt='APPLY', img='arrow_down_green_2', cmd=self.apply_quality_to_all)
self.quick_settings_frm.grid(row=0, column=0, sticky=W, padx=10)
self.quick_clip_start_entry_box.grid(row=0, column=0, sticky=NW)
self.quick_clip_end_entry_box.grid(row=1, column=0, sticky=NW)
self.quick_clip_apply.grid(row=2, column=0, sticky=NW)
self.quick_downsample_width.grid(row=0, column=0, sticky=NW)
self.quick_downsample_height.grid(row=1, column=0, sticky=NW)
self.quick_downsample_apply.grid(row=2, column=0, sticky=NW)
self.quick_fps_entry_box.grid(row=0, column=0, sticky=NW)
self.quick_fps_apply.grid(row=2, column=0, sticky=NW)
self.use_gpu_dropdown.grid(row=0, column=0, sticky=NW)
self.quick_set_quality_dropdown.grid(row=1, column=0, sticky=NW)
self.quick_set_quality_apply.grid(row=2, column=0, sticky=W)
for col in range(4):
self.quick_settings_frm.grid_columnconfigure(col, weight=1, uniform="quick_settings")
for col, frm in enumerate([self.clip_video_settings_frm, self.quick_downsample_frm, self.quick_set_fps, self.quick_set_quality]):
frm.grid(row=0, column=col, sticky="nsew", padx=2, pady=2)
self.quick_settings_frm.update_idletasks()
max_width = max(frm.winfo_reqwidth() for frm in [self.clip_video_settings_frm, self.quick_downsample_frm, self.quick_set_fps, self.quick_set_quality])
max_height = max(frm.winfo_reqheight() for frm in [self.clip_video_settings_frm, self.quick_downsample_frm, self.quick_set_fps, self.quick_set_quality])
for frm in [self.clip_video_settings_frm, self.quick_downsample_frm, self.quick_set_fps, self.quick_set_quality]:
frm.config(width=max_width, height=max_height)
[docs] def inverse_all_cb_ticks(self, variable_name=None):
for video_name in self.videos.keys():
self.videos[video_name][variable_name].set(self.headings[variable_name].get())
for name, video_data in self.videos.items():
if variable_name == 'clip_cb_var':
entry_boxes = [self.videos[name]["start_entry"], self.videos[name]["end_entry"]]
cboxes = [self.videos[name]["clip_cb"]]
self._set_entry_boxes_bg_clr(state=self.headings[variable_name].get(), entry_boxes=entry_boxes, cboxes=cboxes)
elif variable_name == 'downsample_cb_var':
cboxes = [self.videos[name]["downsample_cb"]]
entry_boxes = [self.videos[name]["width_entry"], self.videos[name]["height_entry"]]
self._set_entry_boxes_bg_clr(state=self.headings[variable_name].get(), entry_boxes=entry_boxes, cboxes=cboxes)
elif variable_name == 'fps_cb_var':
cboxes = [self.videos[name]["fps_cb"]]
entry_boxes = [self.videos[name]["fps_entry"]]
self._set_entry_boxes_bg_clr(state=self.headings[variable_name].get(), entry_boxes=entry_boxes, cboxes=cboxes)
elif variable_name == 'grayscale_cb_var':
cboxes = [self.videos[name]["grayscale_cbox"]]
self._set_entry_boxes_bg_clr(state=self.headings[variable_name].get(), cboxes=cboxes)
elif variable_name == 'frame_cnt_cb_var':
cboxes = [self.videos[name]["frame_cnt_cbox"]]
self._set_entry_boxes_bg_clr(state=self.headings[variable_name].get(), cboxes=cboxes)
elif variable_name == 'apply_clahe_cb_var':
cboxes = [self.videos[name]["apply_clahe_cbox"]]
self._set_entry_boxes_bg_clr(state=self.headings[variable_name].get(), cboxes=cboxes)
[docs] def apply_resolution_to_all(self):
check_int(value=self.quick_downsample_width.entry_get, min_value=0, name=f"Quick set downsample WIDTH {self.quick_downsample_width.entry_get}",)
check_int(value=self.quick_downsample_height.entry_get, min_value=0, name=f"Quick set downsample HEIGHT {self.quick_downsample_height.entry_get}")
for video_name in self.videos.keys():
self.videos[video_name]["width_entry"].entry_set(self.quick_downsample_width.entry_get)
self.videos[video_name]["height_entry"].entry_set(self.quick_downsample_height.entry_get)
[docs] def apply_trim_to_all(self):
check_if_string_value_is_valid_video_timestamp(value=self.quick_clip_start_entry_box.entry_get, name=f"Quick set clip START time {self.quick_clip_start_entry_box.entry_get}",)
check_if_string_value_is_valid_video_timestamp(value=self.quick_clip_end_entry_box.entry_get, name=f"Quick set clip END time {self.quick_clip_start_entry_box.entry_get}")
check_that_hhmmss_start_is_before_end(start_time=self.quick_clip_start_entry_box.entry_get, end_time=self.quick_clip_end_entry_box.entry_get, name="Quick set START and END time")
for video_name in self.videos.keys():
self.videos[video_name]["start_entry"].entry_set(self.quick_clip_start_entry_box.entry_get)
self.videos[video_name]["end_entry"].entry_set(self.quick_clip_end_entry_box.entry_get)
[docs] def apply_fps_to_all(self):
check_float(value=self.quick_fps_entry_box.entry_get, min_value=0, name=f"Quick set FPS setting {self.quick_fps_entry_box.entry_get}")
for video_name in self.videos.keys():
self.videos[video_name]["fps_entry"].entry_set(self.quick_fps_entry_box.entry_get)
[docs] def apply_quality_to_all(self):
for video_name in self.videos.keys():
self.videos[video_name]["video_quality_dropdown"].setChoices(self.quick_set_quality_dropdown.getChoices())
[docs] def create_video_table_headings(self):
self.headings = {}
self.videos_frm = CreateLabelFrameWithIcon(parent=self.main_frm, header='VIDEOS', font=Formats.FONT_HEADER.value, pady=5, padx=15, icon_name='stack')
self.headings["video_name_col_head"] = SimBALabel(parent=self.videos_frm, txt='VIDEO NAME', font=Formats.FONT_REGULAR_BOLD.value, width=self.max_char_vid_name, justify='center')
self.headings["crop_video_col_head"] = SimBALabel(parent=self.videos_frm, txt='CROP \n VIDEO', font=Formats.FONT_REGULAR_BOLD.value, justify='center', img='crop', padx=12)
self.headings["start_time_col_head"] = SimBALabel(parent=self.videos_frm, txt='START \n TIME', font=Formats.FONT_REGULAR_BOLD.value, justify='center', padx=12, img='play')
self.headings["end_time_col_head"] = SimBALabel(parent=self.videos_frm, txt='END \n TIME', font=Formats.FONT_REGULAR_BOLD.value, justify='center', img='stop', padx=12)
self.headings["video_quality_head"] = SimBALabel(parent=self.videos_frm, txt='QUALITY', font=Formats.FONT_REGULAR_BOLD.value, justify='center', padx=12, img='star')
self.headings["shorten_all_videos_cbox"], self.headings["clip_cb_var"] = SimbaCheckbox(parent=self.videos_frm, txt='APPLY \n CLIP', txt_img='clip', cmd=lambda: self.inverse_all_cb_ticks(variable_name="clip_cb_var"), tooltip_key='BATCH_APPLY_CLIP')
self.headings["video_width_col_head"] = SimBALabel(parent=self.videos_frm, txt='WIDTH', img='width', font=Formats.FONT_REGULAR_BOLD.value, justify='center', padx=12)
self.headings["video_height_col_head"] = SimBALabel(parent=self.videos_frm, txt='HEIGHT', img='height', font=Formats.FONT_REGULAR_BOLD.value, justify='center', padx=12)
self.headings["downsample_all_videos_cbox"], self.headings["downsample_cb_var"] = SimbaCheckbox(parent=self.videos_frm, txt='APPLY \n DOWNSAMPLE', cmd=lambda: self.inverse_all_cb_ticks(variable_name="downsample_cb_var"), tooltip_key='BATCH_APPLY_DOWNSAMPLE')
self.headings["fps_col_head"] = SimBALabel(parent=self.videos_frm, txt='FPS', img='camera', font=Formats.FONT_REGULAR_BOLD.value, justify='center', padx=12)
self.headings["change_fps_all_videos_cbox"], self.headings["fps_cb_var"] = SimbaCheckbox(parent=self.videos_frm, txt='APPLY \n VIDEO FPS', cmd=lambda: self.inverse_all_cb_ticks(variable_name="fps_cb_var"), tooltip_key='BATCH_APPLY_FPS')
self.headings["grayscale_cbox"], self.headings["grayscale_cb_var"] = SimbaCheckbox(parent=self.videos_frm, txt='APPLY \n GREYSCALE', cmd=lambda: self.inverse_all_cb_ticks(variable_name="grayscale_cb_var"), tooltip_key='BATCH_APPLY_GREYSCALE')
self.headings["frame_cnt_cbox"], self.headings["frame_cnt_cb_var"] = SimbaCheckbox(parent=self.videos_frm, txt='APPLY \n FRAME COUNT', cmd=lambda: self.inverse_all_cb_ticks(variable_name="frame_cnt_cb_var"), tooltip_key='BATCH_APPLY_FRAME_COUNT')
self.headings["apply_clahe_cbox"], self.headings["apply_clahe_cb_var"] = SimbaCheckbox(parent=self.videos_frm, txt='APPLY \n CLAHE', cmd=lambda: self.inverse_all_cb_ticks(variable_name="apply_clahe_cb_var"), tooltip_key='BATCH_APPLY_CLAHE')
self.videos_frm.grid(row=1, column=0, sticky=W, padx=5, pady=15)
self.headings["video_name_col_head"].grid(row=0, column=0, sticky=NW)
self.headings["crop_video_col_head"].grid(row=0, column=2, sticky=NW)
self.headings["start_time_col_head"].grid(row=0, column=3, sticky=NW)
self.headings["end_time_col_head"].grid(row=0, column=4, sticky=W, padx=5)
self.headings["shorten_all_videos_cbox"].grid(row=0, column=5, sticky=W, padx=5)
self.headings["video_width_col_head"].grid(row=0, column=6, sticky=W, padx=5)
self.headings["video_height_col_head"].grid(row=0, column=7, sticky=W, padx=5)
self.headings["downsample_all_videos_cbox"].grid(row=0, column=8, sticky=W, padx=5)
self.headings["fps_col_head"].grid(row=0, column=9, sticky=W, padx=5)
self.headings["change_fps_all_videos_cbox"].grid(row=0, column=10, sticky=W, padx=5)
self.headings["grayscale_cbox"].grid(row=0, column=11, sticky=W, padx=5)
self.headings["frame_cnt_cbox"].grid(row=0, column=12, sticky=W, padx=5)
self.headings["apply_clahe_cbox"].grid(row=0, column=13, sticky=W, padx=5)
self.headings["video_quality_head"].grid(row=0, column=14, sticky=NW)
seperator = SimBASeperator(parent=self.videos_frm, color=None, orient='horizontal', borderwidth=1)
seperator.grid(row=1, column=0, columnspan=15, rowspan=1, sticky="ew")
seperator = SimBASeperator(parent=self.videos_frm, orient='vertical', borderwidth=1)
seperator.grid(row=0, column=1, rowspan=len(self.videos_in_dir_dict.keys()) + 400, sticky="ns")
def _set_entry_boxes_bg_clr(self, state: bool, entry_boxes: List[Entry_Box] = (), cboxes: List[SimbaCheckbox] = ()):
clr = 'white' if not state else 'lightgreen'
for entry_box in entry_boxes:
entry_box.set_bg_clr(clr=clr)
for cbox in cboxes:
cbox.configure(selectcolor=clr)
[docs] def create_video_rows(self):
self.videos = {}
for w in self.videos_frm.grid_slaves():
if int(w.grid_info()["row"]) > 1:
w.destroy()
for video_cnt, (name, data) in enumerate(self.videos_in_dir_dict.items()):
self.videos[name] = {}
row = video_cnt * 2 + 2
row_color = '#f8f8f8' if video_cnt % 2 == 0 else '#e5e5e5'
img = read_frm_of_video(video_path=data['file_path'], frame_index=0, raise_error=False, size=(420, 280), keep_aspect_ratio=True)
self.videos[name]["video_name_lbl"] = SimBALabel(parent=self.videos_frm, txt=name, font=Formats.FONT_REGULAR_BOLD.value, width=self.max_char_vid_name, justify='center', bg_clr=row_color, hover_img=img)
self.videos[name]["crop_btn"] = SimbaButton(parent=self.videos_frm, txt='CROP', txt_clr='black', cmd=lambda k=self.videos[name]["video_name_lbl"]["text"]: self.batch_process_crop_function(k), img='crop_2')
self.videos[name]["start_entry"] = Entry_Box(parent=self.videos_frm, fileDescription='', value="00:00:00", justify='center', entry_font=Formats.FONT_REGULAR_BOLD.value, entry_box_width=12)
self.videos[name]["end_entry"] = Entry_Box(parent=self.videos_frm, fileDescription='', value=data["video_length"], justify='center', entry_font=Formats.FONT_REGULAR_BOLD.value, entry_box_width=12)
self.videos[name]["clip_cb"], self.videos[name]["clip_cb_var"] = SimbaCheckbox(parent=self.videos_frm, txt='', cmd=lambda _name=name: self._set_entry_boxes_bg_clr(entry_boxes=[self.videos[_name]["start_entry"], self.videos[_name]["end_entry"]], state=self.videos[_name]["clip_cb_var"].get(), cboxes=[self.videos[_name]["clip_cb"]]))
self.videos[name]["width_entry"] = Entry_Box(parent=self.videos_frm, fileDescription='', value=data["width"], justify='center', entry_font=Formats.FONT_REGULAR_BOLD.value, entry_box_width=10)
self.videos[name]["height_entry"] = Entry_Box(parent=self.videos_frm, fileDescription='', value=data["height"], justify='center', entry_font=Formats.FONT_REGULAR_BOLD.value, entry_box_width=10)
self.videos[name]["downsample_cb"], self.videos[name]["downsample_cb_var"] = SimbaCheckbox(parent=self.videos_frm, txt='', cmd=lambda _name=name: self._set_entry_boxes_bg_clr(entry_boxes=[self.videos[_name]["width_entry"], self.videos[_name]["height_entry"]], state=self.videos[_name]["downsample_cb_var"].get(), cboxes=[self.videos[_name]["downsample_cb"]]))
self.videos[name]["fps_entry"] = Entry_Box(parent=self.videos_frm, fileDescription='', value=round(data["fps"], 4), justify='center', entry_font=Formats.FONT_REGULAR_BOLD.value, entry_box_width=8)
self.videos[name]["fps_cb"], self.videos[name]["fps_cb_var"] = SimbaCheckbox(parent=self.videos_frm, txt='', cmd=lambda _name=name: self._set_entry_boxes_bg_clr(entry_boxes=[self.videos[_name]["fps_entry"]], state=self.videos[_name]["fps_cb_var"].get(), cboxes=[self.videos[_name]["fps_cb"]]))
self.videos[name]["grayscale_cbox"], self.videos[name]["grayscale_cb_var"] = SimbaCheckbox(parent=self.videos_frm, txt='', cmd=lambda _name=name: self._set_entry_boxes_bg_clr(state=self.videos[_name]["grayscale_cb_var"].get(), cboxes=[self.videos[_name]["grayscale_cbox"]]))
self.videos[name]["frame_cnt_cbox"], self.videos[name]["frame_cnt_cb_var"] = SimbaCheckbox(parent=self.videos_frm, txt='', cmd=lambda _name=name: self._set_entry_boxes_bg_clr(state=self.videos[_name]["frame_cnt_cb_var"].get(), cboxes=[self.videos[_name]["frame_cnt_cbox"]]))
self.videos[name]["apply_clahe_cbox"], self.videos[name]["apply_clahe_cb_var"] = SimbaCheckbox(parent=self.videos_frm, txt='', cmd=lambda _name=name: self._set_entry_boxes_bg_clr(state=self.videos[_name]["apply_clahe_cb_var"].get(), cboxes=[self.videos[_name]["apply_clahe_cbox"]]))
self.videos[name]["video_quality_dropdown"] = SimBADropDown(parent=self.videos_frm, label="", dropdown_options=self.cpu_video_quality, value=60, dropdown_width=10)
self.videos[name]["video_name_lbl"].grid(row=row, column=0, sticky=W, pady=(3, 3))
self.videos[name]["crop_btn"].grid(row=row, column=2, pady=(3, 3))
self.videos[name]["start_entry"].grid(row=row, column=3, sticky=W, pady=(3, 3))
self.videos[name]["end_entry"].grid(row=row, column=4, padx=5, pady=(3, 3))
self.videos[name]["clip_cb"].grid(row=row, column=5, sticky=W, padx=5, pady=(3, 3))
self.videos[name]["width_entry"].grid(row=row, column=6, padx=5, pady=(3, 3))
self.videos[name]["height_entry"].grid(row=row, column=7, padx=5, pady=(3, 3))
self.videos[name]["downsample_cb"].grid(row=row, column=8, sticky=W, padx=5, pady=(3, 3))
self.videos[name]["fps_entry"].grid(row=row, column=9, padx=5, pady=(3, 3))
self.videos[name]["fps_cb"].grid(row=row, column=10, sticky=W, padx=5, pady=(3, 3))
self.videos[name]["grayscale_cbox"].grid(row=row, column=11, sticky=W, padx=5, pady=(3, 3))
self.videos[name]["frame_cnt_cbox"].grid(row=row, column=12, sticky=W, padx=5, pady=(3, 3))
self.videos[name]["apply_clahe_cbox"].grid(row=row, column=13, sticky=W, padx=5, pady=(3, 3))
try:
self.videos[name]["video_quality_dropdown"].grid_remove(row=row, column=14, sticky=W)
except:
pass
self.videos[name]["video_quality_dropdown"].grid(row=row, column=14, sticky=W)
if video_cnt != len(self.videos_in_dir_dict.keys()) -1:
sep = SimBASeperator(parent=self.videos_frm, orient='horizontal', height=1, color="#ccc")
sep.grid(row=row + 1, column=0, columnspan=15, sticky="ew")
[docs] def create_execute_btn(self):
self.execute_frm = CreateLabelFrameWithIcon(parent=self.main_frm, header="EXECUTE", icon_name='rocket', pady=5, padx=5, font=Formats.FONT_HEADER.value)
self.reset_all_btn = SimbaButton(parent=self.execute_frm, txt='RESET ALL', txt_clr='red', img='trash', cmd=self.create_video_rows)
self.reset_crop_btn = SimbaButton(parent=self.execute_frm, txt='RESET CROP', txt_clr='darkorange', img='trash', cmd=self.reset_crop)
self.execute_btn = SimbaButton(parent=self.execute_frm, txt='EXECUTE', txt_clr='blue', img='rocket', cmd=self.execute)
self.execute_frm.grid(row=2, column=0, sticky=W, padx=5, pady=30)
self.reset_all_btn.grid(row=0, column=0, sticky=W, padx=5)
self.reset_crop_btn.grid(row=0, column=1, sticky=W, padx=5)
self.execute_btn.grid(row=0, column=2, sticky=W, padx=5)
self.get_file_menu()
[docs] def reset_crop(self):
self.crop_dict = {}
for video_name, video_data in self.videos_in_dir_dict.items():
self.videos[video_name]["crop_btn"].configure(fg="black")
self.videos[video_name]["crop_btn"].configure(image=self.black_crop_img, compound='left')
[docs] def batch_process_crop_function(self, video_name):
check_file_exist_and_readable(self.videos_in_dir_dict[video_name]["file_path"])
roi_selector = ROISelector(path=self.videos_in_dir_dict[video_name]["file_path"], title=f"CROP {video_name} - Press ESC when ROI drawn", clr=self.clrs[self.settings['crop_color']], thickness=self.settings['crop_thickness'])
roi_selector.run()
self.crop_dict[video_name] = {}
self.crop_dict[video_name]["top_left_x"] = roi_selector.top_left[0]
self.crop_dict[video_name]["top_left_y"] = roi_selector.top_left[1]
self.crop_dict[video_name]["width"] = roi_selector.width
self.crop_dict[video_name]["height"] = roi_selector.height
self.crop_dict[video_name]["bottom_right_x"] = roi_selector.bottom_right[0]
self.crop_dict[video_name]["bottom_right_y"] = roi_selector.bottom_right[1]
k = cv2.waitKey(20) & 0xFF
cv2.destroyAllWindows()
self.videos[video_name]["crop_btn"].configure(fg="darkgreen", font=('Arial', 14, 'bold'), bg='darkgrey')
self.videos[video_name]["crop_btn"].configure(image=self.green_drop_img, compound='left')
[docs] def preferences_pop_up(self):
if hasattr(self, 'preferences_frm'):
self.preferences_frm.destroy()
self.preferences_frm = Toplevel()
self.preferences_frm.minsize(400, 300)
self.preferences_frm.wm_title("PREFERENCES")
self.preferences_frm.iconphoto(False, self.menu_icons['settings']["img"])
codecs = get_ffmpeg_encoders(alphabetically_sorted=True)
fonts = list(get_fonts().keys())
pref_frm = CreateLabelFrameWithIcon(parent=self.preferences_frm, header='SETTINGS', icon_name='settings')
self.codec_dropdown = SimBADropDown(parent=pref_frm, dropdown_options=codecs, label='CODEC:', label_width=25, value=self.settings['codec'], dropdown_width=30, searchable=True)
self.font_dropdown = SimBADropDown(parent=pref_frm, dropdown_options=fonts, label='FONT:', label_width=25, value=self.settings['font'], dropdown_width=30)
self.font_size_dropdown = SimBADropDown(parent=pref_frm, dropdown_options=list(range(1, 61)), label='FONT SIZE:', label_width=25, value=self.settings['font_size'], dropdown_width=30)
self.text_loc_dropdown = SimBADropDown(parent=pref_frm, dropdown_options=Formats.TXT_LOCATIONS.value, label='TEXT LOCATION:', label_width=25, value=self.settings['txt_loc'], dropdown_width=30)
self.crop_thickness_dropdown = SimBADropDown(parent=pref_frm, dropdown_options=list(range(1, 31)), label='CROP THICKNESS:', label_width=25, value=self.settings['crop_thickness'], dropdown_width=30)
self.crop_color_dropdown = SimBADropDown(parent=pref_frm, dropdown_options=list(self.clrs.keys()), label='CROP COLOR:', label_width=25, value=self.settings['crop_color'], dropdown_width=30)
self.clahe_clip_limit_dropdown = SimBADropDown(parent=pref_frm, dropdown_options=list(range(1, 31)), label='CLAHE CLIP LIMIT:', label_width=25, value=self.settings['clahe_clip_limit'], dropdown_width=30)
self.clahe_tile_size_dropdown = SimBADropDown(parent=pref_frm, dropdown_options=list(range(1, 31)), label='CLAHE TILE SIZE:', label_width=25, value=self.settings['clahe_tile_size'], dropdown_width=30)
self.save_pref_btn = SimbaButton(parent=pref_frm, txt='SAVE', img='rocket', cmd=lambda: self._save_preferences())
pref_frm.grid(row=0, column=0, sticky=NW)
self.codec_dropdown.grid(row=0, column=0, sticky=NW)
self.font_dropdown.grid(row=1, column=0, sticky=NW)
self.font_size_dropdown.grid(row=2, column=0, sticky=NW)
self.text_loc_dropdown.grid(row=3, column=0, sticky=NW)
self.crop_thickness_dropdown.grid(row=4, column=0, sticky=NW)
self.crop_color_dropdown.grid(row=5, column=0, sticky=NW)
self.clahe_clip_limit_dropdown.grid(row=6, column=0, sticky=NW)
self.clahe_tile_size_dropdown.grid(row=7, column=0, sticky=NW)
self.save_pref_btn.grid(row=8, column=0, sticky=NW)
def _save_preferences(self):
self.settings['codec'] = self.codec_dropdown.get_value()
self.settings['crop_color'] = self.crop_color_dropdown.get_value()
self.settings['font'] = self.font_dropdown.get_value()
self.settings['crop_thickness'] = int(self.crop_thickness_dropdown.get_value())
self.settings['font_size'] = int(self.font_size_dropdown.get_value())
self.settings['txt_loc'] = self.text_loc_dropdown.get_value()
stdout_success(msg='Preference settings updated.', source=self.__class__.__name__)
[docs] def execute(self):
out_video_dict = {}
out_video_dict["meta_data"] = {}
out_video_dict["video_data"] = {}
out_video_dict["meta_data"]["in_dir"] = self.input_dir
out_video_dict["meta_data"]["out_dir"] = self.output_dir
out_video_dict["meta_data"]["gpu"] = str_2_bool(self.use_gpu_dropdown.get_value())
if str_2_bool(self.use_gpu_dropdown.get_value()) and not check_nvidea_gpu_available():
raise FFMPEGCodecGPUError(msg="No GPU found (as evaluated by nvidea-smi returning None)", source=self.__class__.__name__)
for video_cnt, (name, data) in enumerate(self.videos_in_dir_dict.items()):
out_video_dict["video_data"][name] = {}
out_video_dict["video_data"][name]["video_info"] = self.videos_in_dir_dict[name]
out_video_dict["video_data"][name]["output_quality"] = self.percent_to_crf_lookup[self.videos[name]["video_quality_dropdown"].getChoices()]
if name in self.crop_dict.keys():
out_video_dict["video_data"][name]["crop"] = True
out_video_dict["video_data"][name]["crop_settings"] = self.crop_dict[name]
else:
out_video_dict["video_data"][name]["crop"] = False
out_video_dict["video_data"][name]["crop_settings"] = None
if self.videos[name]["clip_cb_var"].get():
out_video_dict["video_data"][name]["clip"] = True
out_video_dict["video_data"][name]["clip_settings"] = {"start": self.videos[name]["start_entry"].entry_get, "stop": self.videos[name]["end_entry"].entry_get}
else:
out_video_dict["video_data"][name]["clip"] = False
out_video_dict["video_data"][name]["clip_settings"] = None
if self.videos[name]["downsample_cb_var"].get():
out_video_dict["video_data"][name]["downsample"] = True
width, height = self.videos[name]["width_entry"].entry_get, self.videos[name]["height_entry"].entry_get
check_int(name=f'{name} width', value=width, min_value=0, raise_error=True)
check_int(name=f'{name} height', value=height, min_value=0, raise_error=True)
width, height = int(width) + int(width) % 2, int(height) + int(height) % 2
out_video_dict["video_data"][name]["downsample_settings"] = {"width": str(width), "height": str(height)}
else:
out_video_dict["video_data"][name]["downsample"] = False
out_video_dict["video_data"][name]["downsample_settings"] = None
if self.videos[name]["fps_cb_var"].get():
out_video_dict["video_data"][name]["fps"] = True
out_video_dict["video_data"][name]["fps_settings"] = {"fps": self.videos[name]["fps_entry"].entry_get}
else:
out_video_dict["video_data"][name]["fps"] = False
out_video_dict["video_data"][name]["fps_settings"] = None
if self.videos[name]["grayscale_cb_var"].get():
out_video_dict["video_data"][name]["grayscale"] = True
out_video_dict["video_data"][name]["grayscale_settings"] = None
else:
out_video_dict["video_data"][name]["grayscale"] = False
out_video_dict["video_data"][name]["grayscale_settings"] = None
if self.videos[name]["frame_cnt_cb_var"].get():
out_video_dict["video_data"][name]["frame_cnt"] = True
out_video_dict["video_data"][name]["frame_cnt_settings"] = None
else:
out_video_dict["video_data"][name]["frame_cnt"] = False
out_video_dict["video_data"][name]["frame_cnt_settings"] = None
if self.videos[name]["apply_clahe_cb_var"].get():
out_video_dict["video_data"][name]["clahe"] = True
out_video_dict["video_data"][name]["clahe_settings"] = None
else:
out_video_dict["video_data"][name]["clahe"] = False
out_video_dict["video_data"][name]["clahe_settings"] = None
out_video_dict["video_data"][name]["last_operation"] = None
for operation in ["clahe", "frame_cnt", "grayscale", "fps", "downsample", "clip", "crop"]:
if out_video_dict["video_data"][name][operation]:
out_video_dict["video_data"][name]["last_operation"] = operation
self.save_path = os.path.join(self.output_dir, "batch_process_log.json")
with open(self.save_path, "w") as fp:
json.dump(out_video_dict, fp)
self.perform_unit_tests(out_video_dict["video_data"])
# test = BatchProcessFrame(input_dir=r'D:\troubleshooting\maplight_ri\project_folder\blob\videos', output_dir=r"D:\troubleshooting\maplight_ri\project_folder\blob\batch_out_6")
# test.create_main_window()
# test.create_video_table_headings()
# test.create_video_rows()
# test.create_execute_btn()
# test.main_frm.mainloop()
#
# test = BatchProcessFrame(input_dir=r'D:\troubleshooting\batch_fps', output_dir=r"D:\troubleshooting\batch_fps\out")
# test.create_main_window()
# test.create_video_table_headings()
# test.create_video_rows()
# test.create_execute_btn()
# test.main_frm.mainloop()