Source code for simba.video_processors.batch_process_menus

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))
[docs] def get_input_files(self): for file_path in glob.glob(self.input_dir + "/*"): lower_str_name = file_path.lower() if lower_str_name.endswith(Options.ALL_VIDEO_FORMAT_OPTIONS.value): _, video_name, ext = get_fn_ext(file_path) self.videos_in_dir_dict[video_name] = get_video_meta_data( video_path=file_path, fps_as_int=False ) self.videos_in_dir_dict[video_name]["extension"] = ext self.videos_in_dir_dict[video_name]["video_length"] = str( datetime.timedelta( seconds=int( self.videos_in_dir_dict[video_name]["frame_count"] / self.videos_in_dir_dict[video_name]["fps"] ) ) ) self.videos_in_dir_dict[video_name]["video_length"] = ( "0" + self.videos_in_dir_dict[video_name]["video_length"] ) self.videos_in_dir_dict[video_name]["file_path"] = file_path
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")
[docs] def get_file_menu(self): menu = Menu(self.root) file_menu = Menu(menu) menu.add_cascade(label="File...", menu=file_menu, compound="left") file_menu.add_command(label="Preferences...", compound="left", image=self.menu_icons["settings"]["img"], command=lambda: self.preferences_pop_up()) self.root.config(menu=menu)
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"])
[docs] def perform_unit_tests(self, out_video_dict): timer = SimbaTimer(start=True) for video_name, video_data in out_video_dict.items(): if video_data["crop"]: check_int( value=video_data["crop_settings"]["width"], min_value=1, name=f"Crop width for video {video_name}", ) check_int( value=video_data["crop_settings"]["height"], min_value=1, name=f"Crop height for video {video_name}", ) if video_data["clip"]: for variable in ["start", "stop"]: check_if_string_value_is_valid_video_timestamp( value=video_data["clip_settings"][variable], name=f"Clip {variable} time for video {video_name}", ) check_that_hhmmss_start_is_before_end( start_time=video_data["clip_settings"]["start"], end_time=video_data["clip_settings"]["stop"], name=f"Clip time for video {video_name}", ) for variable in ["start", "stop"]: check_if_hhmmss_timestamp_is_valid_part_of_video( timestamp=video_data["clip_settings"][variable], video_path=video_data["video_info"]["file_path"], ) if video_data["downsample"]: check_int( value=video_data["downsample_settings"]["width"], min_value=1, name=f"Downsample width for video {video_name}", ) check_int( value=video_data["downsample_settings"]["height"], min_value=1, name=f"Downsample height for video {video_name}", ) if video_data["fps"]: check_float(value=video_data["fps_settings"]["fps"], min_value=0, name=f"FPS settings for video {video_name}") ffmpeg_runner = FFMPEGCommandCreator(json_path=self.save_path, codec=self.settings['codec']) ffmpeg_runner.crop_videos() ffmpeg_runner.clip_videos() ffmpeg_runner.downsample_videos() ffmpeg_runner.apply_fps() ffmpeg_runner.apply_grayscale() ffmpeg_runner.apply_frame_count(font_size=int(self.settings['font_size']), font=self.settings['font'], loc=self.settings['txt_loc']) ffmpeg_runner.apply_clahe(clip_limit=int(self.settings['clahe_clip_limit']), tile_size=(int(self.settings['clahe_tile_size']), int(self.settings['clahe_tile_size']))) ffmpeg_runner.move_all_processed_files_to_output_folder() timer.stop_timer() stdout_success(msg=f"SimBA batch pre-process JSON saved at {self.save_path}", source=self.__class__.__name__) stdout_success(msg=f"Video batch pre-processing complete, new videos stored in {self.output_dir}", elapsed_time=timer.elapsed_time_str, source=self.__class__.__name__)
# 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()