simba-uw-tf-dev 4.7.4__py3-none-any.whl → 4.7.6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of simba-uw-tf-dev might be problematic. Click here for more details.
- simba/SimBA.py +1180 -1178
- simba/assets/.recent_projects.txt +1 -0
- simba/mixins/feature_extraction_mixin.py +0 -2
- simba/mixins/geometry_mixin.py +357 -302
- simba/outlier_tools/skip_outlier_correction.py +2 -2
- simba/plotting/heat_mapper_clf_mp.py +45 -23
- simba/plotting/plot_clf_results.py +2 -1
- simba/plotting/plot_clf_results_mp.py +456 -455
- simba/sandbox/av1.py +5 -0
- simba/sandbox/denoise_hqdn3d.py +266 -0
- simba/sandbox/extract_random_frames.py +126 -0
- simba/sandbox/remove_end_of_video.py +80 -80
- simba/ui/import_pose_frame.py +13 -13
- simba/ui/pop_ups/clf_plot_pop_up.py +1 -1
- simba/ui/pop_ups/video_processing_pop_up.py +11 -10
- simba/ui/video_timelaps.py +158 -36
- simba/video_processors/video_processing.py +20 -13
- {simba_uw_tf_dev-4.7.4.dist-info → simba_uw_tf_dev-4.7.6.dist-info}/METADATA +1 -1
- {simba_uw_tf_dev-4.7.4.dist-info → simba_uw_tf_dev-4.7.6.dist-info}/RECORD +23 -20
- {simba_uw_tf_dev-4.7.4.dist-info → simba_uw_tf_dev-4.7.6.dist-info}/LICENSE +0 -0
- {simba_uw_tf_dev-4.7.4.dist-info → simba_uw_tf_dev-4.7.6.dist-info}/WHEEL +0 -0
- {simba_uw_tf_dev-4.7.4.dist-info → simba_uw_tf_dev-4.7.6.dist-info}/entry_points.txt +0 -0
- {simba_uw_tf_dev-4.7.4.dist-info → simba_uw_tf_dev-4.7.6.dist-info}/top_level.txt +0 -0
simba/ui/import_pose_frame.py
CHANGED
|
@@ -82,7 +82,7 @@ class ImportPoseFrame(ConfigReader, PopUpMixin):
|
|
|
82
82
|
Label(self.import_tracking_frm, text="Please CREATE PROJECT CONFIG before importing tracking data \n", font=Formats.FONT_REGULAR.value).grid(row=0, column=0, sticky=NW)
|
|
83
83
|
else:
|
|
84
84
|
ConfigReader.__init__(self, config_path=config_path, read_video_info=False)
|
|
85
|
-
self.data_type_dropdown = SimBADropDown(parent=self.import_tracking_frm, dropdown_options=Options.IMPORT_TYPE_OPTIONS.value, label="DATA TYPE: ", label_width=25, command=self.create_import_menu, dropdown_width=25, value=Options.IMPORT_TYPE_OPTIONS.value[0])
|
|
85
|
+
self.data_type_dropdown = SimBADropDown(parent=self.import_tracking_frm, dropdown_options=Options.IMPORT_TYPE_OPTIONS.value, label="DATA TYPE: ", label_width=25, command=self.create_import_menu, dropdown_width=25, value=Options.IMPORT_TYPE_OPTIONS.value[0], img='file_type')
|
|
86
86
|
self.data_type_dropdown.grid(row=0, column=0, sticky=NW)
|
|
87
87
|
self.create_import_menu(data_type_choice=Options.IMPORT_TYPE_OPTIONS.value[0])
|
|
88
88
|
self.import_tracking_frm.grid(row=idx_row, column=idx_column, sticky=NW)
|
|
@@ -91,7 +91,7 @@ class ImportPoseFrame(ConfigReader, PopUpMixin):
|
|
|
91
91
|
|
|
92
92
|
def __show_smoothing_entry_box_from_dropdown(self, choice: str):
|
|
93
93
|
if (choice == GAUSSIAN) or (choice == SAVITZKY_GOLAY):
|
|
94
|
-
self.smoothing_time_eb.grid(row=
|
|
94
|
+
self.smoothing_time_eb.grid(row=1, column=0, sticky=E)
|
|
95
95
|
else:
|
|
96
96
|
self.smoothing_time_eb.grid_forget()
|
|
97
97
|
|
|
@@ -265,21 +265,21 @@ class ImportPoseFrame(ConfigReader, PopUpMixin):
|
|
|
265
265
|
self.animal_name_entry_boxes = None
|
|
266
266
|
|
|
267
267
|
self.interpolation_frm = CreateLabelFrameWithIcon(parent=self.choice_frm, header="INTERPOLATION METHOD", pady=5, padx=5,font=Formats.FONT_HEADER.value, icon_name='fill', relief='groove')
|
|
268
|
-
self.interpolation_dropdown = SimBADropDown(parent=self.interpolation_frm, dropdown_options=Options.INTERPOLATION_OPTIONS_W_NONE.value, label='INTERPOLATION METHOD: ', label_width=25, dropdown_width=35, value=Options.INTERPOLATION_OPTIONS_W_NONE.value[0])
|
|
268
|
+
self.interpolation_dropdown = SimBADropDown(parent=self.interpolation_frm, dropdown_options=Options.INTERPOLATION_OPTIONS_W_NONE.value, label='INTERPOLATION METHOD: ', label_width=25, dropdown_width=35, value=Options.INTERPOLATION_OPTIONS_W_NONE.value[0], img='fill')
|
|
269
269
|
self.interpolation_frm.grid(row=0, column=0, sticky=NW)
|
|
270
270
|
self.interpolation_dropdown.grid(row=0, column=0, sticky=NW)
|
|
271
271
|
|
|
272
272
|
self.smoothing_frm = CreateLabelFrameWithIcon(parent=self.choice_frm, header="SMOOTHING METHOD", pady=5, padx=5, font=Formats.FONT_HEADER.value, icon_name='smooth', relief='groove')
|
|
273
|
-
self.smoothing_dropdown = SimBADropDown(parent=self.smoothing_frm, dropdown_options=Options.SMOOTHING_OPTIONS_W_NONE.value, label='SMOOTHING: ', label_width=25, dropdown_width=35, value=Options.SMOOTHING_OPTIONS_W_NONE.value[0], command=self.__show_smoothing_entry_box_from_dropdown)
|
|
274
|
-
self.smoothing_time_eb = Entry_Box(self.smoothing_frm, "SMOOTHING PERIOD (MS):", labelwidth=
|
|
273
|
+
self.smoothing_dropdown = SimBADropDown(parent=self.smoothing_frm, dropdown_options=Options.SMOOTHING_OPTIONS_W_NONE.value, label='SMOOTHING: ', label_width=25, dropdown_width=35, value=Options.SMOOTHING_OPTIONS_W_NONE.value[0], command=self.__show_smoothing_entry_box_from_dropdown, img='smooth')
|
|
274
|
+
self.smoothing_time_eb = Entry_Box(self.smoothing_frm, "SMOOTHING PERIOD (MS):", labelwidth=25, validation="numeric", entry_box_width=35, img='timer_2')
|
|
275
275
|
self.smoothing_frm.grid(row=1, column=0, sticky=NW)
|
|
276
276
|
self.smoothing_dropdown.grid(row=0, column=0, sticky=NW)
|
|
277
277
|
|
|
278
278
|
if data_type_choice in ["CSV (DLC/DeepPoseKit)", "MAT (DANNCE 3D)", "JSON (BENTO)", "CSV (SimBA BLOB)", 'H5 (FaceMap)', 'CSV (SimBA YOLO)']: # DATA TYPES WHERE NO TRACKS HAVE TO BE SPECIFIED
|
|
279
279
|
self.import_directory_frm = LabelFrame(self.choice_frm, text=FRAME_DIR_IMPORT_TITLES[data_type_choice], pady=5, padx=5, font=Formats.FONT_HEADER.value,)
|
|
280
|
-
self.import_directory_select = FolderSelect(self.import_directory_frm, "Input data DIRECTORY:", lblwidth=25, initialdir=self.project_path)
|
|
280
|
+
self.import_directory_select = FolderSelect(self.import_directory_frm, "Input data DIRECTORY:", lblwidth=25, initialdir=self.project_path, lbl_icon='folder')
|
|
281
281
|
self.import_single_frm = LabelFrame(self.choice_frm, text=FRAME_FILE_IMPORT_TITLES[data_type_choice], pady=5, padx=5, font=Formats.FONT_HEADER.value,)
|
|
282
|
-
self.import_file_select = FileSelect(self.import_single_frm, "Input data FILE:", lblwidth=25, file_types=[("Pose data file", FILE_TYPES[data_type_choice])])
|
|
282
|
+
self.import_file_select = FileSelect(self.import_single_frm, "Input data FILE:", lblwidth=25, file_types=[("Pose data file", FILE_TYPES[data_type_choice])], lbl_icon='file_type')
|
|
283
283
|
|
|
284
284
|
if data_type_choice == "CSV (DLC/DeepPoseKit)":
|
|
285
285
|
self.import_dir_btn = Button(self.import_directory_frm, fg="blue", font=Formats.FONT_REGULAR.value, text="Import DLC CSV DIRECTORY to SimBA project", command=lambda: self.__import_dlc_csv_data(interpolation_settings=self.interpolation_dropdown.getChoices(),
|
|
@@ -342,7 +342,7 @@ class ImportPoseFrame(ConfigReader, PopUpMixin):
|
|
|
342
342
|
self.dlc_data_type_option_dropdown.setChoices(Options.MULTI_DLC_TYPE_IMPORT_OPTION.value[1])
|
|
343
343
|
self.tracking_type_frm.grid(row=5, column=0, sticky=NW)
|
|
344
344
|
self.dlc_data_type_option_dropdown.grid(row=0, column=0, sticky=NW)
|
|
345
|
-
self.data_dir_select = FolderSelect(self.data_dir_frm, "H5 DLC DIRECTORY: ", lblwidth=25)
|
|
345
|
+
self.data_dir_select = FolderSelect(self.data_dir_frm, "H5 DLC DIRECTORY: ", lblwidth=25, lbl_icon='folder')
|
|
346
346
|
self.instructions_lbl = Label(self.data_dir_frm, text="Please import videos BEFORE importing the \n multi animal DLC tracking data", font=Formats.FONT_REGULAR.value)
|
|
347
347
|
self.run_btn = Button(self.import_frm, text="IMPORT DLC .H5", fg="blue", command=lambda: self.__multi_animal_run_call(pose_estimation_tool=data_type_choice,
|
|
348
348
|
interpolation_settings=self.interpolation_dropdown.getChoices(),
|
|
@@ -352,7 +352,7 @@ class ImportPoseFrame(ConfigReader, PopUpMixin):
|
|
|
352
352
|
data_path=self.data_dir_select.folder_path,
|
|
353
353
|
tracking_data_type=self.dlc_data_type_option_dropdown.getChoices()))
|
|
354
354
|
elif data_type_choice == "SLP (SLEAP)":
|
|
355
|
-
self.data_dir_select = FolderSelect(self.data_dir_frm, "SLP SLEAP DIRECTORY: ", lblwidth=25)
|
|
355
|
+
self.data_dir_select = FolderSelect(self.data_dir_frm, "SLP SLEAP DIRECTORY: ", lblwidth=25, lbl_icon='folder')
|
|
356
356
|
self.instructions_lbl = Label(self.data_dir_frm, font=Formats.FONT_REGULAR.value, text="Please import videos before importing the \n multi animal SLEAP tracking data if you are tracking more than ONE animal")
|
|
357
357
|
self.run_btn = Button(self.import_frm, text="IMPORT SLEAP .SLP", fg="blue", font=Formats.FONT_REGULAR.value, command=lambda: self.__multi_animal_run_call(pose_estimation_tool=data_type_choice,
|
|
358
358
|
interpolation_settings=self.interpolation_dropdown.getChoices(),
|
|
@@ -362,7 +362,7 @@ class ImportPoseFrame(ConfigReader, PopUpMixin):
|
|
|
362
362
|
data_path=self.data_dir_select.folder_path))
|
|
363
363
|
|
|
364
364
|
elif data_type_choice == "TRK (multi-animal APT)":
|
|
365
|
-
self.data_dir_select = FolderSelect(self.data_dir_frm, "TRK APT DIRECTORY: ", lblwidth=25)
|
|
365
|
+
self.data_dir_select = FolderSelect(self.data_dir_frm, "TRK APT DIRECTORY: ", lblwidth=25, lbl_icon='folder')
|
|
366
366
|
self.instructions_lbl = Label(self.data_dir_frm, text="Please import videos before importing the \n multi animal TRK tracking data", font=Formats.FONT_REGULAR.value,)
|
|
367
367
|
self.run_btn = Button(self.import_frm, text="IMPORT APT .TRK", font=Formats.FONT_REGULAR.value, fg="blue", command=lambda: self.__multi_animal_run_call(pose_estimation_tool=data_type_choice,
|
|
368
368
|
interpolation_settings=self.interpolation_dropdown.getChoices(),
|
|
@@ -372,7 +372,7 @@ class ImportPoseFrame(ConfigReader, PopUpMixin):
|
|
|
372
372
|
data_path=self.data_dir_select.folder_path))
|
|
373
373
|
|
|
374
374
|
elif data_type_choice == "CSV (SLEAP)":
|
|
375
|
-
self.data_dir_select = FolderSelect(self.data_dir_frm, "CSV SLEAP DIRECTORY:", lblwidth=25)
|
|
375
|
+
self.data_dir_select = FolderSelect(self.data_dir_frm, "CSV SLEAP DIRECTORY:", lblwidth=25, lbl_icon='folder')
|
|
376
376
|
self.instructions_lbl = Label(self.data_dir_frm, font=Formats.FONT_REGULAR.value, text="Please import videos before importing the SLEAP tracking data \n IF you are tracking more than ONE animal")
|
|
377
377
|
self.run_btn = Button(self.import_frm, text="IMPORT SLEAP .CSV", fg="blue", font=Formats.FONT_REGULAR.value, command=lambda: self.__multi_animal_run_call(pose_estimation_tool=data_type_choice,
|
|
378
378
|
interpolation_settings=self.interpolation_dropdown.getChoices(),
|
|
@@ -382,7 +382,7 @@ class ImportPoseFrame(ConfigReader, PopUpMixin):
|
|
|
382
382
|
data_path=self.data_dir_select.folder_path))
|
|
383
383
|
|
|
384
384
|
elif data_type_choice == "H5 (SLEAP)":
|
|
385
|
-
self.data_dir_select = FolderSelect(self.data_dir_frm, "H5 SLEAP DIRECTORY", lblwidth=25)
|
|
385
|
+
self.data_dir_select = FolderSelect(self.data_dir_frm, "H5 SLEAP DIRECTORY", lblwidth=25, lbl_icon='folder')
|
|
386
386
|
self.instructions_lbl = Label(self.data_dir_frm, font=Formats.FONT_REGULAR.value, text="Please import videos before importing the SLEAP H5 tracking data \n IF you are tracking more than ONE animal")
|
|
387
387
|
self.run_btn = Button(self.import_frm, text="IMPORT SLEAP H5", font=Formats.FONT_REGULAR.value, fg="blue", command=lambda: self.__multi_animal_run_call(pose_estimation_tool=data_type_choice,
|
|
388
388
|
interpolation_settings=self.interpolation_dropdown.getChoices(),
|
|
@@ -391,7 +391,7 @@ class ImportPoseFrame(ConfigReader, PopUpMixin):
|
|
|
391
391
|
animal_names=self.animal_name_entry_boxes,
|
|
392
392
|
data_path=self.data_dir_select.folder_path))
|
|
393
393
|
elif data_type_choice == "H5 (SuperAnimal-TopView)":
|
|
394
|
-
self.data_dir_select = FolderSelect(self.data_dir_frm, "H5 SuperAnimal DIRECTORY:", lblwidth=25)
|
|
394
|
+
self.data_dir_select = FolderSelect(self.data_dir_frm, "H5 SuperAnimal DIRECTORY:", lblwidth=25, lbl_icon='folder')
|
|
395
395
|
self.instructions_lbl = Label(self.data_dir_frm, font=Formats.FONT_REGULAR.value, text="Please import videos before importing the H5 SuperAnimal tracking data \n IF you are tracking more than ONE animal")
|
|
396
396
|
self.run_btn = Button(self.import_frm, text="IMPORT SuperAnimal-Mouse-TopView H5", fg="blue", font=Formats.FONT_REGULAR.value, command=lambda: self.__multi_animal_run_call(pose_estimation_tool=data_type_choice,
|
|
397
397
|
interpolation_settings=self.interpolation_dropdown.getChoices(),
|
|
@@ -57,7 +57,7 @@ class SklearnVisualizationPopUp(PopUpMixin, ConfigReader):
|
|
|
57
57
|
self.bp_threshold_lbl.grid(row=0, column=0, sticky=NW)
|
|
58
58
|
self.bp_threshold_entry.grid(row=1, column=0, sticky=NW)
|
|
59
59
|
|
|
60
|
-
self.style_settings_frm = CreateLabelFrameWithIcon(parent=self.main_frm, header="SETTINGS", icon_name='style', icon_link=Links.SKLEARN_PLOTS.value, padx=5, pady=5, relief='solid')
|
|
60
|
+
self.style_settings_frm = CreateLabelFrameWithIcon(parent=self.main_frm, header="TEXT SETTINGS", icon_name='style', icon_link=Links.SKLEARN_PLOTS.value, padx=5, pady=5, relief='solid')
|
|
61
61
|
self.text_size_dropdown = SimBADropDown(parent=self.style_settings_frm, dropdown_options=TEXT_SIZE_OPTIONS, label='TEXT SIZE: ', label_width=40, dropdown_width=15, value='AUTO', img='text')
|
|
62
62
|
self.text_spacing_dropdown = SimBADropDown(parent=self.style_settings_frm, dropdown_options=TEXT_SIZE_OPTIONS, label='TEXT SPACING: ', label_width=40, dropdown_width=15, value='AUTO', img='text_spacing')
|
|
63
63
|
self.text_thickness_dropdown = SimBADropDown(parent=self.style_settings_frm, dropdown_options=TEXT_SIZE_OPTIONS, label='TEXT THICKNESS: ', label_width=40, dropdown_width=15, value='AUTO', img='bold')
|
|
@@ -69,13 +69,14 @@ from simba.video_processors.video_processing import (
|
|
|
69
69
|
crop_single_video, crop_single_video_circle, crop_single_video_polygon,
|
|
70
70
|
crossfade_two_videos, downsample_video, extract_frame_range,
|
|
71
71
|
extract_frames_single_video, flip_videos, frames_to_movie, gif_creator,
|
|
72
|
-
multi_split_video, remove_beginning_of_video,
|
|
73
|
-
resize_videos_by_width, reverse_videos,
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
72
|
+
multi_split_video, remove_beginning_of_video, remove_end_of_video,
|
|
73
|
+
resize_videos_by_height, resize_videos_by_width, reverse_videos,
|
|
74
|
+
roi_blurbox, rotate_video, superimpose_elapsed_time,
|
|
75
|
+
superimpose_frame_count, superimpose_freetext, superimpose_overlay_video,
|
|
76
|
+
superimpose_video_names, superimpose_video_progressbar,
|
|
77
|
+
temporal_concatenation, upsample_fps, video_bg_subtraction,
|
|
78
|
+
video_bg_subtraction_mp, video_concatenator, video_to_bw,
|
|
79
|
+
video_to_greyscale, watermark_video)
|
|
79
80
|
|
|
80
81
|
sys.setrecursionlimit(10**7)
|
|
81
82
|
|
|
@@ -1548,7 +1549,7 @@ class ClipSingleVideoByFrameNumbers(PopUpMixin):
|
|
|
1548
1549
|
if interactive_ui.img_window.winfo_exists(): interactive_ui.close()
|
|
1549
1550
|
self.click_event.set(True)
|
|
1550
1551
|
|
|
1551
|
-
interactive_ui = TimelapseSlider(video_path=self.selected_video.file_path)
|
|
1552
|
+
interactive_ui = TimelapseSlider(video_path=self.selected_video.file_path, use_timestamps=False)
|
|
1552
1553
|
interactive_ui.run()
|
|
1553
1554
|
self.click_event = BooleanVar(value=False)
|
|
1554
1555
|
interactive_ui.img_window.protocol("WM_DELETE_WINDOW", window_closed)
|
|
@@ -1647,7 +1648,7 @@ class ClipMultipleVideosByFrameNumbersPopUp(PopUpMixin):
|
|
|
1647
1648
|
if interactive_ui.img_window.winfo_exists(): interactive_ui.close()
|
|
1648
1649
|
self.click_event.set(True)
|
|
1649
1650
|
|
|
1650
|
-
interactive_ui = TimelapseSlider(video_path=kwargs['video_path'], frame_cnt=kwargs['frame_cnt'], crop_ratio=kwargs['crop_ratio'])
|
|
1651
|
+
interactive_ui = TimelapseSlider(video_path=kwargs['video_path'], frame_cnt=kwargs['frame_cnt'], crop_ratio=kwargs['crop_ratio'], use_timestamps=False)
|
|
1651
1652
|
interactive_ui.run()
|
|
1652
1653
|
self.click_event = BooleanVar(value=False)
|
|
1653
1654
|
interactive_ui.img_window.protocol("WM_DELETE_WINDOW", window_closed)
|
|
@@ -2494,7 +2495,7 @@ class Convert2WEBMPopUp(PopUpMixin):
|
|
|
2494
2495
|
def __init__(self):
|
|
2495
2496
|
super().__init__(title="CONVERT VIDEOS TO WEBM", icon='webm')
|
|
2496
2497
|
settings_frm = CreateLabelFrameWithIcon(parent=self.main_frm, header="SETTINGS", icon_name=Keys.DOCUMENTATION.value, icon_link=Links.VIDEO_TOOLS.value)
|
|
2497
|
-
self.WEBM_CODEC_LK = {'VP8': 'vp8', 'VP9': 'vp9'}
|
|
2498
|
+
self.WEBM_CODEC_LK = {'VP8': 'vp8', 'VP9': 'vp9', 'AV1': 'av1'}
|
|
2498
2499
|
|
|
2499
2500
|
self.quality_dropdown = SimBADropDown(parent=settings_frm, label="OUTPUT VIDEO QUALITY:", dropdown_options=list(range(10, 110, 10)), label_width=25, value=60, img='pct', dropdown_width=30)
|
|
2500
2501
|
self.codec_dropdown = SimBADropDown(parent=settings_frm, label="COMPRESSION CODEC:", dropdown_options=list(self.WEBM_CODEC_LK.keys()), label_width=25, value='VP9', img='file_type', dropdown_width=30)
|
simba/ui/video_timelaps.py
CHANGED
|
@@ -37,6 +37,7 @@ class TimelapseSlider():
|
|
|
37
37
|
:param int ruler_divisions: Number of major divisions on time ruler. Default 6.
|
|
38
38
|
:param bool show_ruler: If True, display time ruler below timelapse. Default True.
|
|
39
39
|
:param int ruler_height: Height of ruler in pixels. Default 60.
|
|
40
|
+
:param bool use_timestamps: If True, display timestamps (HH:MM:SS) in labels and ruler. If False, display frame numbers. Default True.
|
|
40
41
|
|
|
41
42
|
:example:
|
|
42
43
|
>>> slider = TimelapseSlider(video_path='path/to/video.mp4', frame_cnt=25, crop_ratio=75)
|
|
@@ -61,7 +62,8 @@ class TimelapseSlider():
|
|
|
61
62
|
ruler_height: Optional[int] = None,
|
|
62
63
|
ruler_width: Optional[int] = None,
|
|
63
64
|
img_width: Optional[int] = None,
|
|
64
|
-
img_height: Optional[int] = None
|
|
65
|
+
img_height: Optional[int] = None,
|
|
66
|
+
use_timestamps: bool = True):
|
|
65
67
|
|
|
66
68
|
check_file_exist_and_readable(file_path=video_path)
|
|
67
69
|
check_int(name='frame_cnt', value=frame_cnt, min_value=1, raise_error=True)
|
|
@@ -80,9 +82,11 @@ class TimelapseSlider():
|
|
|
80
82
|
check_valid_boolean(value=show_ruler, source=f'{self.__class__.__name__} show_ruler', raise_error=True)
|
|
81
83
|
self.video_meta = get_video_meta_data(video_path=video_path, raise_error=True)
|
|
82
84
|
if show_ruler: check_int(name='ruler_divisions', value=ruler_divisions, min_value=1, raise_error=True)
|
|
85
|
+
check_valid_boolean(value=use_timestamps, source=f'{self.__class__.__name__} use_timestamps', raise_error=True)
|
|
83
86
|
self.size, self.padding, self.crop_ratio, self.frame_cnt = ruler_width, padding, crop_ratio, frame_cnt
|
|
84
87
|
self.ruler_height, self.video_path, self.show_ruler, self.ruler_divisions = ruler_height, video_path, show_ruler, ruler_divisions
|
|
85
88
|
self.img_width, self.img_height = img_width, img_height
|
|
89
|
+
self.use_timestamps = use_timestamps
|
|
86
90
|
self.frm_name = f'{self.video_meta["video_name"]} - TIMELAPSE VIEWER - hit "X" or ESC to close'
|
|
87
91
|
self.video_capture, self._pending_frame_update, self._frame_debounce_ms = None, None, 50
|
|
88
92
|
|
|
@@ -94,16 +98,26 @@ class TimelapseSlider():
|
|
|
94
98
|
lbl.image = self.tk_image
|
|
95
99
|
|
|
96
100
|
def _update_selection(self, slider_type: str):
|
|
97
|
-
start_sec =
|
|
98
|
-
end_sec =
|
|
101
|
+
start_sec = self.start_scale.get_value()
|
|
102
|
+
end_sec = self.end_scale.get_value()
|
|
99
103
|
max_sec = int(self.video_meta['video_length_s'])
|
|
104
|
+
|
|
105
|
+
# Convert to int for timestamp mode, keep float for frame mode to preserve precision
|
|
106
|
+
if self.use_timestamps:
|
|
107
|
+
start_sec = int(start_sec)
|
|
108
|
+
end_sec = int(end_sec)
|
|
109
|
+
else:
|
|
110
|
+
# In frame mode, use float precision but ensure we're within bounds
|
|
111
|
+
start_sec = max(0.0, min(float(start_sec), float(max_sec)))
|
|
112
|
+
end_sec = max(0.0, min(float(end_sec), float(max_sec)))
|
|
113
|
+
|
|
100
114
|
if slider_type == 'start':
|
|
101
115
|
if start_sec >= end_sec:
|
|
102
|
-
end_sec = min(start_sec + 1, max_sec)
|
|
116
|
+
end_sec = min(start_sec + (1.0 if self.use_timestamps else 1.0/self.video_meta['fps']), max_sec)
|
|
103
117
|
self.end_scale.set_value(end_sec)
|
|
104
118
|
else:
|
|
105
119
|
if end_sec <= start_sec:
|
|
106
|
-
start_sec = max(end_sec - 1, 0)
|
|
120
|
+
start_sec = max(end_sec - (1.0 if self.use_timestamps else 1.0/self.video_meta['fps']), 0)
|
|
107
121
|
self.start_scale.set_value(start_sec)
|
|
108
122
|
|
|
109
123
|
self.selected_start[0] = start_sec
|
|
@@ -118,34 +132,118 @@ class TimelapseSlider():
|
|
|
118
132
|
self.selected_start_frame[0] = start_frame
|
|
119
133
|
self.selected_end_frame[0] = end_frame
|
|
120
134
|
|
|
121
|
-
self.
|
|
122
|
-
|
|
135
|
+
if self.use_timestamps:
|
|
136
|
+
self.start_time_label.config(text=seconds_to_timestamp(start_sec), fg='green')
|
|
137
|
+
self.end_time_label.config(text=seconds_to_timestamp(end_sec), fg='red')
|
|
138
|
+
else:
|
|
139
|
+
start_frame = int(start_sec * self.video_meta['fps'])
|
|
140
|
+
end_frame = int(end_sec * self.video_meta['fps'])
|
|
141
|
+
self.start_time_label.config(text=str(start_frame), fg='green')
|
|
142
|
+
self.end_time_label.config(text=str(end_frame), fg='red')
|
|
123
143
|
|
|
124
144
|
if self.video_meta['video_length_s'] > 0:
|
|
125
145
|
self._highlight_segment(start_sec, end_sec)
|
|
126
146
|
self._schedule_frame_update(slider_type=slider_type)
|
|
127
147
|
|
|
128
148
|
def _move_start_frame(self, direction: int):
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
149
|
+
# Temporarily unbind the callback to avoid conflicts
|
|
150
|
+
original_cmd = self.start_scale.scale.cget('command')
|
|
151
|
+
self.start_scale.scale.config(command='')
|
|
152
|
+
|
|
153
|
+
try:
|
|
154
|
+
if self.use_timestamps:
|
|
155
|
+
current_seconds = self.selected_start[0]
|
|
156
|
+
new_seconds = current_seconds + direction
|
|
157
|
+
# Ensure start doesn't exceed end
|
|
158
|
+
end_seconds = self.selected_end[0]
|
|
159
|
+
new_seconds = max(0, min(new_seconds, int(self.video_meta['video_length_s']), end_seconds - 1))
|
|
160
|
+
self.start_scale.set_value(int(new_seconds))
|
|
161
|
+
self.selected_start[0] = int(new_seconds)
|
|
162
|
+
# Recalculate frame from seconds
|
|
163
|
+
new_frame = int(new_seconds * self.video_meta['fps'])
|
|
164
|
+
new_frame = max(0, min(new_frame, self.video_meta['frame_count'] - 1))
|
|
165
|
+
self.selected_start_frame[0] = new_frame
|
|
166
|
+
else:
|
|
167
|
+
# Get current frame from the selected_start_frame
|
|
168
|
+
current_frame = self.selected_start_frame[0]
|
|
169
|
+
new_frame = current_frame + direction
|
|
170
|
+
# Ensure start doesn't exceed end
|
|
171
|
+
end_frame = self.selected_end_frame[0]
|
|
172
|
+
new_frame = max(0, min(new_frame, self.video_meta['frame_count'] - 1, end_frame - 1))
|
|
173
|
+
# Convert frame to seconds for the slider
|
|
174
|
+
new_seconds = new_frame / self.video_meta['fps']
|
|
175
|
+
# Update the slider value
|
|
176
|
+
self.start_scale.set_value(new_seconds)
|
|
177
|
+
# Directly update both frame and seconds
|
|
178
|
+
self.selected_start_frame[0] = new_frame
|
|
179
|
+
self.selected_start[0] = new_seconds
|
|
180
|
+
|
|
181
|
+
# Update the display labels and highlights
|
|
182
|
+
if self.use_timestamps:
|
|
183
|
+
self.start_time_label.config(text=seconds_to_timestamp(self.selected_start[0]), fg='green')
|
|
184
|
+
else:
|
|
185
|
+
self.start_time_label.config(text=str(self.selected_start_frame[0]), fg='green')
|
|
186
|
+
|
|
187
|
+
# Update highlights and frame preview
|
|
188
|
+
if self.video_meta['video_length_s'] > 0:
|
|
189
|
+
self._highlight_segment(self.selected_start[0], self.selected_end[0])
|
|
190
|
+
if self._pending_frame_update is not None:
|
|
191
|
+
if hasattr(self, 'img_window') and self.img_window.winfo_exists():
|
|
192
|
+
self.img_window.after_cancel(self._pending_frame_update)
|
|
193
|
+
self._update_frame_display(slider_type='start')
|
|
194
|
+
finally:
|
|
195
|
+
# Rebind the callback
|
|
196
|
+
self.start_scale.scale.config(command=original_cmd)
|
|
138
197
|
|
|
139
198
|
def _move_end_frame(self, direction: int):
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
199
|
+
# Temporarily unbind the callback to avoid conflicts
|
|
200
|
+
original_cmd = self.end_scale.scale.cget('command')
|
|
201
|
+
self.end_scale.scale.config(command='')
|
|
202
|
+
|
|
203
|
+
try:
|
|
204
|
+
if self.use_timestamps:
|
|
205
|
+
current_seconds = self.selected_end[0]
|
|
206
|
+
new_seconds = current_seconds + direction
|
|
207
|
+
# Ensure end doesn't go below start
|
|
208
|
+
start_seconds = self.selected_start[0]
|
|
209
|
+
new_seconds = max(start_seconds + 1, min(new_seconds, int(self.video_meta['video_length_s'])))
|
|
210
|
+
self.end_scale.set_value(int(new_seconds))
|
|
211
|
+
self.selected_end[0] = int(new_seconds)
|
|
212
|
+
# Recalculate frame from seconds
|
|
213
|
+
new_frame = int(new_seconds * self.video_meta['fps'])
|
|
214
|
+
new_frame = max(0, min(new_frame, self.video_meta['frame_count'] - 1))
|
|
215
|
+
self.selected_end_frame[0] = new_frame
|
|
216
|
+
else:
|
|
217
|
+
# Get current frame from the selected_end_frame
|
|
218
|
+
current_frame = self.selected_end_frame[0]
|
|
219
|
+
new_frame = current_frame + direction
|
|
220
|
+
# Ensure end doesn't go below start
|
|
221
|
+
start_frame = self.selected_start_frame[0]
|
|
222
|
+
new_frame = max(start_frame + 1, min(new_frame, self.video_meta['frame_count'] - 1))
|
|
223
|
+
# Convert frame to seconds for the slider
|
|
224
|
+
new_seconds = new_frame / self.video_meta['fps']
|
|
225
|
+
# Update the slider value
|
|
226
|
+
self.end_scale.set_value(new_seconds)
|
|
227
|
+
# Directly update both frame and seconds
|
|
228
|
+
self.selected_end_frame[0] = new_frame
|
|
229
|
+
self.selected_end[0] = new_seconds
|
|
230
|
+
|
|
231
|
+
# Update the display labels and highlights
|
|
232
|
+
if self.use_timestamps:
|
|
233
|
+
self.end_time_label.config(text=seconds_to_timestamp(self.selected_end[0]), fg='red')
|
|
234
|
+
else:
|
|
235
|
+
self.end_time_label.config(text=str(self.selected_end_frame[0]), fg='red')
|
|
236
|
+
|
|
237
|
+
# Update highlights and frame preview
|
|
238
|
+
if self.video_meta['video_length_s'] > 0:
|
|
239
|
+
self._highlight_segment(self.selected_start[0], self.selected_end[0])
|
|
240
|
+
if self._pending_frame_update is not None:
|
|
241
|
+
if hasattr(self, 'img_window') and self.img_window.winfo_exists():
|
|
242
|
+
self.img_window.after_cancel(self._pending_frame_update)
|
|
243
|
+
self._update_frame_display(slider_type='end')
|
|
244
|
+
finally:
|
|
245
|
+
# Rebind the callback
|
|
246
|
+
self.end_scale.scale.config(command=original_cmd)
|
|
149
247
|
|
|
150
248
|
def _schedule_frame_update(self, slider_type: str):
|
|
151
249
|
"""Schedule frame preview update with debouncing.
|
|
@@ -164,10 +262,20 @@ class TimelapseSlider():
|
|
|
164
262
|
def _update_frame_display(self, slider_type: str):
|
|
165
263
|
if slider_type == 'start':
|
|
166
264
|
seconds = self.selected_start[0]
|
|
167
|
-
self.
|
|
265
|
+
if self.use_timestamps:
|
|
266
|
+
label_text = f"Start Frame Preview ({seconds_to_timestamp(seconds)})"
|
|
267
|
+
else:
|
|
268
|
+
frame_num = int(seconds * self.video_meta['fps'])
|
|
269
|
+
label_text = f"Start Frame Preview (Frame {frame_num})"
|
|
270
|
+
self.frame_label.config(text=label_text, font=Formats.FONT_LARGE_BOLD.value, fg='green')
|
|
168
271
|
else:
|
|
169
272
|
seconds = self.selected_end[0]
|
|
170
|
-
self.
|
|
273
|
+
if self.use_timestamps:
|
|
274
|
+
label_text = f"End Frame Preview ({seconds_to_timestamp(seconds)})"
|
|
275
|
+
else:
|
|
276
|
+
frame_num = int(seconds * self.video_meta['fps'])
|
|
277
|
+
label_text = f"End Frame Preview (Frame {frame_num})"
|
|
278
|
+
self.frame_label.config(text=label_text, font=Formats.FONT_LARGE_BOLD.value, fg='red')
|
|
171
279
|
|
|
172
280
|
frame_index = int(seconds * self.video_meta['fps'])
|
|
173
281
|
if frame_index >= self.video_meta['frame_count']: frame_index = self.video_meta['frame_count'] - 1
|
|
@@ -207,7 +315,7 @@ class TimelapseSlider():
|
|
|
207
315
|
timelapse_height, timelapse_width = self.timelapse_img.shape[0], self.timelapse_img.shape[1]
|
|
208
316
|
padded_timelapse = np.zeros((timelapse_height, timelapse_width + (2 * self.padding), 3), dtype=np.uint8)
|
|
209
317
|
padded_timelapse[:, self.padding:self.padding + timelapse_width] = self.timelapse_img
|
|
210
|
-
ruler = ImageMixin.create_time_ruler(video_path=self.video_path, width=timelapse_width, height=self.ruler_height, num_divisions=self.ruler_divisions)
|
|
318
|
+
ruler = ImageMixin.create_time_ruler(video_path=self.video_path, width=timelapse_width, height=self.ruler_height, num_divisions=self.ruler_divisions, show_time=self.use_timestamps)
|
|
211
319
|
self.timelapse_img = cv2.vconcat([padded_timelapse, ruler])
|
|
212
320
|
|
|
213
321
|
self.original_timelapse = self.timelapse_img.copy()
|
|
@@ -241,24 +349,37 @@ class TimelapseSlider():
|
|
|
241
349
|
self.start_scale.grid(row=0, column=1, padx=5)
|
|
242
350
|
self.start_scale.scale.config(command=lambda x: self._update_selection(slider_type='start'))
|
|
243
351
|
|
|
244
|
-
|
|
352
|
+
initial_start_text = "00:00:00" if self.use_timestamps else "0"
|
|
353
|
+
self.start_time_label = SimBALabel(parent=self.slider_frame, txt=initial_start_text, font=Formats.FONT_LARGE_BOLD.value, width=10, txt_clr='green')
|
|
245
354
|
self.start_time_label.grid(row=0, column=2, padx=5)
|
|
246
355
|
|
|
247
|
-
|
|
356
|
+
start_btn_txt = "Previous second" if self.use_timestamps else "Previous frame"
|
|
357
|
+
start_btn_tooltip = "Move start time back by 1 second" if self.use_timestamps else "Move start frame back by 1 frame"
|
|
358
|
+
self.start_frame_left_btn = SimbaButton(parent=self.slider_frame, txt=start_btn_txt, tooltip_txt=start_btn_tooltip, cmd=self._move_start_frame, cmd_kwargs={'direction': -1}, font=Formats.FONT_REGULAR_BOLD.value, img='left_arrow_green')
|
|
248
359
|
self.start_frame_left_btn.grid(row=0, column=3, padx=2)
|
|
249
|
-
|
|
360
|
+
start_btn_txt_right = "Next second" if self.use_timestamps else "Next frame"
|
|
361
|
+
start_btn_tooltip_right = "Move start time forward by 1 second" if self.use_timestamps else "Move start frame forward by 1 frame"
|
|
362
|
+
self.start_frame_right_btn = SimbaButton(parent=self.slider_frame, txt=start_btn_txt_right, tooltip_txt=start_btn_tooltip_right, cmd=self._move_start_frame, cmd_kwargs={'direction': 1}, font=Formats.FONT_REGULAR_BOLD.value, img='right_arrow_green')
|
|
250
363
|
self.start_frame_right_btn.grid(row=0, column=4, padx=2)
|
|
251
364
|
|
|
252
365
|
self.end_scale = SimBAScaleBar(parent=self.slider_frame, label="END TIME:", from_=0, to=int(self.video_meta['video_length_s']), orient=HORIZONTAL, length=400, resolution=1, value=int(self.video_meta['video_length_s']), showvalue=False, label_width=15, sliderrelief='raised', troughcolor='white', activebackground='red', lbl_font=Formats.FONT_LARGE_BOLD.value)
|
|
253
366
|
self.end_scale.grid(row=1, column=1, padx=5)
|
|
254
367
|
self.end_scale.scale.config(command=lambda x: self._update_selection(slider_type='end'))
|
|
255
368
|
|
|
256
|
-
|
|
369
|
+
if self.use_timestamps:
|
|
370
|
+
initial_end_text = seconds_to_timestamp(int(self.video_meta['video_length_s']))
|
|
371
|
+
else:
|
|
372
|
+
initial_end_text = str(self.video_meta['frame_count'] - 1)
|
|
373
|
+
self.end_time_label = SimBALabel(parent=self.slider_frame, txt=initial_end_text, font=Formats.FONT_LARGE_BOLD.value, width=10, txt_clr='red')
|
|
257
374
|
self.end_time_label.grid(row=1, column=2, padx=5)
|
|
258
375
|
|
|
259
|
-
|
|
376
|
+
end_btn_txt = "Previous second" if self.use_timestamps else "Previous frame"
|
|
377
|
+
end_btn_tooltip = "Move end time back by 1 second" if self.use_timestamps else "Move end frame back by 1 frame"
|
|
378
|
+
self.end_frame_left_btn = SimbaButton(parent=self.slider_frame, txt=end_btn_txt, tooltip_txt=end_btn_tooltip, cmd=self._move_end_frame, cmd_kwargs={'direction': -1}, font=Formats.FONT_REGULAR_BOLD.value, img='left_arrow_red')
|
|
260
379
|
self.end_frame_left_btn.grid(row=1, column=3, padx=2)
|
|
261
|
-
|
|
380
|
+
end_btn_txt_right = "Next second" if self.use_timestamps else "Next frame"
|
|
381
|
+
end_btn_tooltip_right = "Move end time forward by 1 second" if self.use_timestamps else "Move end frame forward by 1 frame"
|
|
382
|
+
self.end_frame_right_btn = SimbaButton(parent=self.slider_frame, txt=end_btn_txt_right, tooltip_txt=end_btn_tooltip_right, cmd=self._move_end_frame, cmd_kwargs={'direction': 1}, font=Formats.FONT_REGULAR_BOLD.value, img='right_arrow_red')
|
|
262
383
|
self.end_frame_right_btn.grid(row=1, column=4, padx=2)
|
|
263
384
|
|
|
264
385
|
self.selected_start = [0]
|
|
@@ -325,8 +446,9 @@ class TimelapseSlider():
|
|
|
325
446
|
|
|
326
447
|
|
|
327
448
|
|
|
328
|
-
|
|
449
|
+
|
|
329
450
|
# x = TimelapseSlider(video_path=r"E:\troubleshooting\mitra_emergence\project_folder\clip_test\Box1_180mISOcontrol_Females_clipped_progress_bar.mp4",
|
|
330
451
|
# frame_cnt=25,
|
|
331
|
-
# crop_ratio=75
|
|
452
|
+
# crop_ratio=75,
|
|
453
|
+
# use_timestamps=False)
|
|
332
454
|
# x.run()
|
|
@@ -56,7 +56,7 @@ from simba.utils.lookups import (get_current_time, get_ffmpeg_codec,
|
|
|
56
56
|
get_ffmpeg_crossfade_methods, get_fonts,
|
|
57
57
|
get_named_colors, percent_to_crf_lookup,
|
|
58
58
|
percent_to_qv_lk, quality_pct_to_crf,
|
|
59
|
-
video_quality_to_preset_lookup)
|
|
59
|
+
video_quality_to_preset_lookup, get_ffmpeg_encoders)
|
|
60
60
|
from simba.utils.printing import SimbaTimer, stdout_information, stdout_success
|
|
61
61
|
from simba.utils.read_write import (
|
|
62
62
|
check_if_hhmmss_timestamp_is_valid_part_of_video,
|
|
@@ -440,10 +440,11 @@ def clahe_enhance_video(file_path: Union[str, os.PathLike],
|
|
|
440
440
|
dir, file_name, file_ext = get_fn_ext(filepath=file_path)
|
|
441
441
|
if out_path is None:
|
|
442
442
|
save_path = os.path.join(dir, f"CLAHE_{file_name}.avi")
|
|
443
|
+
fourcc = cv2.VideoWriter_fourcc(*Formats.AVI_CODEC.value)
|
|
443
444
|
else:
|
|
444
445
|
check_if_dir_exists(in_dir=os.path.dirname(out_path), source=f'{clahe_enhance_video.__name__} out_path')
|
|
446
|
+
fourcc = cv2.VideoWriter_fourcc(*Formats.MP4_CODEC.value)
|
|
445
447
|
save_path = out_path
|
|
446
|
-
fourcc = cv2.VideoWriter_fourcc(*Formats.AVI_CODEC.value)
|
|
447
448
|
if verbose: print(f"Applying CLAHE on video {file_name}, this might take awhile...")
|
|
448
449
|
cap = cv2.VideoCapture(file_path)
|
|
449
450
|
writer = cv2.VideoWriter( save_path, fourcc, video_meta_data["fps"], (video_meta_data["width"], video_meta_data["height"]), 0)
|
|
@@ -3265,6 +3266,9 @@ def convert_to_avi(path: Union[str, os.PathLike],
|
|
|
3265
3266
|
timer = SimbaTimer(start=True)
|
|
3266
3267
|
check_ffmpeg_available(raise_error=True)
|
|
3267
3268
|
check_str(name=f'{convert_to_avi.__name__} codec', value=codec, options=('xvid', 'divx', 'mjpeg'))
|
|
3269
|
+
CODEC_LK = {'mpeg4': 'mpeg4', 'divx': 'libxvid', 'mjpeg': 'mjpeg'}
|
|
3270
|
+
codec = CODEC_LK[codec]
|
|
3271
|
+
check_valid_codec(codec=codec, raise_error=True, source=f'{convert_to_avi.__name__} codec')
|
|
3268
3272
|
check_instance(source=f'{convert_to_avi.__name__} path', instance=path, accepted_types=(str,))
|
|
3269
3273
|
check_int(name=f'{convert_to_avi.__name__} quality', value=quality)
|
|
3270
3274
|
datetime_ = datetime.now().strftime("%Y%m%d%H%M%S")
|
|
@@ -3292,18 +3296,18 @@ def convert_to_avi(path: Union[str, os.PathLike],
|
|
|
3292
3296
|
_ = get_video_meta_data(video_path=file_path)
|
|
3293
3297
|
out_path = os.path.join(save_dir, f'{video_name}.avi')
|
|
3294
3298
|
if codec == 'divx':
|
|
3295
|
-
cmd = f'ffmpeg -i "{file_path}" -c:v
|
|
3299
|
+
cmd = f'ffmpeg -i "{file_path}" -c:v {codec} -crf {crf} -vtag DIVX "{out_path}" -loglevel error -stats -hide_banner -y'
|
|
3296
3300
|
elif codec == 'xvid':
|
|
3297
|
-
cmd = f'ffmpeg -i "{file_path}" -c:v
|
|
3301
|
+
cmd = f'ffmpeg -i "{file_path}" -c:v {codec} -q:v {qv} "{out_path}" -loglevel error -stats -hide_banner -y'
|
|
3298
3302
|
else:
|
|
3299
|
-
cmd = f'ffmpeg -i "{file_path}" -c:v
|
|
3303
|
+
cmd = f'ffmpeg -i "{file_path}" -c:v {codec} -q:v {qv} "{out_path}" -loglevel error -stats -hide_banner -y'
|
|
3300
3304
|
subprocess.call(cmd, shell=True, stdout=subprocess.PIPE)
|
|
3301
3305
|
timer.stop_timer()
|
|
3302
3306
|
stdout_success(msg=f"{len(file_paths)} video(s) converted to AVI and saved in {save_dir} directory.", elapsed_time=timer.elapsed_time_str, source=convert_to_avi.__name__,)
|
|
3303
3307
|
|
|
3304
3308
|
|
|
3305
3309
|
def convert_to_webm(path: Union[str, os.PathLike],
|
|
3306
|
-
codec: Literal['vp8', 'vp9'] = 'vp9',
|
|
3310
|
+
codec: Literal['vp8', 'vp9', 'av1'] = 'vp9',
|
|
3307
3311
|
save_dir: Optional[Union[str, os.PathLike]] = None,
|
|
3308
3312
|
quality: Optional[int] = 60) -> None:
|
|
3309
3313
|
|
|
@@ -3326,7 +3330,10 @@ def convert_to_webm(path: Union[str, os.PathLike],
|
|
|
3326
3330
|
|
|
3327
3331
|
timer = SimbaTimer(start=True)
|
|
3328
3332
|
check_ffmpeg_available(raise_error=True)
|
|
3329
|
-
check_str(name=f'{convert_to_webm.__name__} codec', value=codec, options=('vp8', 'vp9'))
|
|
3333
|
+
check_str(name=f'{convert_to_webm.__name__} codec', value=codec, options=('vp8', 'vp9', 'av1'))
|
|
3334
|
+
CODEC_LK = {'vp8': 'libvpx', 'vp9': 'libvpx-vp9', 'av1': 'libaom-av1'}
|
|
3335
|
+
codec = CODEC_LK[codec]
|
|
3336
|
+
check_valid_codec(codec=codec, raise_error=True, source=f'{convert_to_webm.__name__} codec')
|
|
3330
3337
|
check_instance(source=f'{convert_to_webm.__name__} path', instance=path, accepted_types=(str,))
|
|
3331
3338
|
check_int(name=f'{convert_to_webm.__name__} quality', value=quality)
|
|
3332
3339
|
datetime_ = datetime.now().strftime("%Y%m%d%H%M%S")
|
|
@@ -3346,17 +3353,13 @@ def convert_to_webm(path: Union[str, os.PathLike],
|
|
|
3346
3353
|
os.makedirs(save_dir)
|
|
3347
3354
|
else:
|
|
3348
3355
|
raise InvalidInputError(msg=f'Paths is not a valid file or directory path.', source=convert_to_webm.__name__)
|
|
3356
|
+
|
|
3349
3357
|
for file_cnt, file_path in enumerate(file_paths):
|
|
3350
3358
|
_, video_name, _ = get_fn_ext(filepath=file_path)
|
|
3351
3359
|
print(f'Converting video {video_name} to WEBM (Video {file_cnt+1}/{len(file_paths)})...')
|
|
3352
3360
|
_ = get_video_meta_data(video_path=file_path)
|
|
3353
3361
|
out_path = os.path.join(save_dir, f'{video_name}.webm')
|
|
3354
|
-
|
|
3355
|
-
cmd = f'ffmpeg -i "{file_path}" -c:v libvpx -crf {crf} "{out_path}" -loglevel error -stats -hide_banner -y'
|
|
3356
|
-
elif codec == 'vp9':
|
|
3357
|
-
cmd = f'ffmpeg -i "{file_path}" -c:v libvpx-vp9 -crf {crf} "{out_path}" -loglevel error -stats -hide_banner -y'
|
|
3358
|
-
else:
|
|
3359
|
-
cmd = f'ffmpeg -i "{file_path}" -c:v libaom-av1 -crf {crf} "{out_path}" -loglevel error -stats -hide_banner -y'
|
|
3362
|
+
cmd = f'ffmpeg -i "{file_path}" -c:v {codec} -crf {crf} "{out_path}" -loglevel error -stats -hide_banner -y'
|
|
3360
3363
|
subprocess.call(cmd, shell=True, stdout=subprocess.PIPE)
|
|
3361
3364
|
timer.stop_timer()
|
|
3362
3365
|
stdout_success(msg=f"{len(file_paths)} video(s) converted to WEBM and saved in {save_dir} directory.", elapsed_time=timer.elapsed_time_str, source=convert_to_webm.__name__,)
|
|
@@ -3381,6 +3384,10 @@ def convert_to_mov(path: Union[str, os.PathLike],
|
|
|
3381
3384
|
timer = SimbaTimer(start=True)
|
|
3382
3385
|
check_ffmpeg_available(raise_error=True)
|
|
3383
3386
|
check_str(name=f'{convert_to_mov.__name__} codec', value=codec, options=('prores', 'animation', 'cineform', 'dnxhd'))
|
|
3387
|
+
|
|
3388
|
+
|
|
3389
|
+
|
|
3390
|
+
|
|
3384
3391
|
check_instance(source=f'{convert_to_mov.__name__} path', instance=path, accepted_types=(str,))
|
|
3385
3392
|
check_int(name=f'{convert_to_mov.__name__} quality', value=quality)
|
|
3386
3393
|
datetime_ = datetime.now().strftime("%Y%m%d%H%M%S")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: simba-uw-tf-dev
|
|
3
|
-
Version: 4.7.
|
|
3
|
+
Version: 4.7.6
|
|
4
4
|
Summary: Toolkit for computer classification and analysis of behaviors in experimental animals
|
|
5
5
|
Home-page: https://github.com/sgoldenlab/simba
|
|
6
6
|
Author: Simon Nilsson, Jia Jie Choong, Sophia Hwang
|